Django-nonrel form field for ListField
Asked Answered
M

4

9

I'm experimenting with django-nonrel on appengine and trying to use a djangotoolbox.fields.ListField to implement a many-to-many relation. As I read in the documentation a ListField is something that you can use to make a workaround for djamgo-nonrel not supporting many-to-many relations.

This is an excerpt from my model:

class MyClass(models.Model):
    field = ListField(models.ForeignKey(AnotherClass))

So if I am getting this right I am creating a list of foreign keys to another class to show a relationship with multiple instances of another class

With this approach everything works fine ... No Exceptions. I can create `MyClass' objects in code and views. But when I try to use the admin interface I get the following error

No form field implemented for <class 'djangotoolbox.fields.ListField'>

So I though I would try something that I haven't done before. Create my own field. Well actually my own form for editing MyClass instances in the admin interface. Here is what I did:

class MyClassForm(ModelForm):
    field = fields.MultipleChoiceField(choices=AnotherClass.objects.all(), widget=FilteredSelectMultiple("verbose_name", is_stacked=False))
    class Meta:
        model = MyClass

then I pass MyClassForm as the form to use to the admin interface

class MyClassAdmin(admin.ModelAdmin):
    form = MyClassForm

admin.site.register(MyClass, MyClassAdmin)

I though that this would work but It doesn't. When I go to the admin interface I get the same error as before. Can anyone tell what I am doing wrong here ... or if you have any other suggestions or success stories of using the ListField, SetField, etc. from djangotoolbox.fields in the admin interface it would be very much appreciated.

Mammiemammiferous answered 9/6, 2011 at 22:52 Comment(0)
M
11

OK, here is what I did to get this all working ... I'll start from the beginning

This is what what my model looked like

class MyClass(models.Model):
    field = ListField(models.ForeignKey(AnotherClass))

I wanted to be able to use the admin interface to create/edit instances of this model using a multiple select widget for the list field. Therefore, I created some custom classes as follows

class ModelListField(ListField):
    def formfield(self, **kwargs):
        return FormListField(**kwargs)

class ListFieldWidget(SelectMultiple):
    pass

class FormListField(MultipleChoiceField):
    """
    This is a custom form field that can display a ModelListField as a Multiple Select GUI element.
    """
    widget = ListFieldWidget

    def clean(self, value):
        #TODO: clean your data in whatever way is correct in your case and return cleaned data instead of just the value
        return value

These classes allow the listfield to be used in the admin. Then I created a form to use in the admin site

class MyClassForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(MyClasstForm,self).__init__(*args, **kwargs)
        self.fields['field'].widget.choices = [(i.pk, i) for i in AnotherClass.objects.all()]
        if self.instance.pk:
            self.fields['field'].initial = self.instance.field

    class Meta:
        model = MyClass

After having done this I created a admin model and registered it with the admin site

class MyClassAdmin(admin.ModelAdmin):
    form = MyClassForm

    def __init__(self, model, admin_site):
        super(MyClassAdmin,self).__init__(model, admin_site)

admin.site.register(MyClass, MyClassAdmin)

This is now working in my code. Keep in mind that this approach might not at all be well suited for google_appengine as I am not very adept at how it works and it might create inefficient queries an such.

Mammiemammiferous answered 5/7, 2011 at 21:56 Comment(1)
Rman, I am having issues implementing the above example, can you post your input at #7783035Windbreak
S
3

As far as I understand, you're trying to have a M2M relationship in django-nonrel, which is not an out-of-the-box functionality. For starters, if you want a quick hack, you can go with this simple class and use a CharField to enter foreign keys manually:

class ListFormField(forms.Field):
    """ A form field for being able to display a djangotoolbox.fields.ListField. """

    widget = ListWidget

    def clean(self, value):
        return [v.strip() for v in value.split(',') if len(v.strip()) > 0]

But if you want to have a multiple selection from a list of models normally you'd have to use ModelMultipleChoiceField, which is also not functional in django-nonrel. Here's what I've done to emulate a M2M relationship using a MultipleSelectField:

Let's say you have a M2M relationship between 2 classes, SomeClass and AnotherClass respectively. You want to select the relationship on the form for SomeClass. Also I assume you want to hold the references as a ListField in SomeClass. (Naturally you want to create M2M relationships as they're explained here, to prevent exploding indexes if you're working on App Engine).

So you have your models like:

class SomeClass(models.Model):
    another_class_ids = ListField(models.PositiveIntegerField(), null=True, blank=True)
    #fields go here

class AnotherClass(models.Model):
    #fields go here

And in your form:

class SomeClassForm(forms.ModelForm):

    #Empty field, will be populated after form is initialized
    #Otherwise selection list is not refreshed after new entities are created.
    another_class = forms.MultipleChoiceField(required=False)

def __init__(self, *args, **kwargs):
    super(SomeClassForm,self).__init__(*args, **kwargs)
    self.fields['another_class'].choices = [(item.pk,item) for item in AnotherClass.objects.all()]

    if self.instance.pk: #If class is saved, highlight the instances that are related
        self.fields['another_class'].initial = self.instance.another_class_ids

def save(self, *args, **kwargs):  
    self.instance.another_class_ids = self.cleaned_data['another_class']         
    return super(SomeClassForm, self).save()

class Meta:
    model = SomeClass

Hopefully this should get you going for the start, I implemented this functionality for normal forms, adjust it for admin panel shouldn't be that hard.

Spool answered 10/6, 2011 at 20:34 Comment(2)
Thanks for the sample code. It helped me figure out how to get to what I need. I can now display a ListField as a SelectMultiple in my own forms. I still have to do some extra work to get this working in the admin pagesMammiemammiferous
Could you explain what work needs to be done to get this working in the admin pages?Butterball
B
0

This could be unrelated but for the admin interface, be sure you have djangotoolbox listed after django.contrib.admin in the settings.. INSTALLED_APPS

Baste answered 10/6, 2011 at 15:55 Comment(0)
Z
0

You could avoid a custom form class for such usage by inquiring for the model object

class ModelListField(ListField):
  def __init__(self, embedded_model=None, *args, **kwargs):
  super(ModelListField, self).__init__(*args, **kwargs)
  self._model = embedded_model.embedded_model

  def formfield(self, **kwargs):
    return FormListField(model=self._model, **kwargs)

class ListFieldWidget(SelectMultiple):
  pass

class FormListField(MultipleChoiceField):
  widget = ListFieldWidget

  def __init__(self, model=None, *args, **kwargs):
    self._model = model
    super(FormListField, self).__init__(*args, **kwargs)
    self.widget.choices = [(unicode(i.pk), i) for i in self._model.objects.all()]

  def to_python(self, value):
    return [self._model.objects.get(pk=key) for key in value]

  def clean(self, value):
    return value
Zak answered 13/7, 2013 at 10:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.