Search code examples
pythondjangoformsextend

Over-Ride __init__: Extending Forms (Python/Django)


Struggling to figure out how to Over-Ride the __init__() method in my Django Form to include additional values from the database. I have a group of photographers that I am trying to list as a form option for the user. Afterwards, the user's photographer selection will be added (along with other information) to the database as an instantiation of a new model.

This is a continuation, or elaboration, of my other Current Question. @Rob Osborne has given me some great advice helping me understand how to extend BaseForm, but I still cannot get my code to execute. The linked question lists my models, form, and views, if you are interested. While I understand that using ModelForm is easier and more documented, I must use BaseForm in this instance.

Here is what I have:

class AForm(BaseForm):
    def __init__(self, data=None, files=None, instance=None, auto_id='id_%s',
                 prefix=None, initial=None, error_class=ErrorList,
                 label_suffix=':', empty_permitted=False):

        self.instance = instance
        object_data = self.instance.fields_dict()
        self.declared_fields = SortedDict()
        self.base_fields = fields_for_a(self.instance)

        BaseForm.__init__(self, data, files, auto_id, prefix, object_data,
                      error_class, label_suffix, empty_permitted)
        self.fields['photographer'].queryset = Photographer.objects.all()

    def save(self, commit=True):
        if not commit:
            raise NotImplementedError("AForm.save must commit it's changes.")

        if self.errors:
            raise ValueError(_(u"The Form could not be updated because the data didn't validate."))

        cleaned_data = self.cleaned_data

        # save fieldvalues for self.instance
        fields = field_list(self.instance)

        for field in fields:
            if field.enable_wysiwyg:
                value = unicode(strip(cleaned_data[field.name]))
            else:
                value = unicode(cleaned_data[field.name])

Using the above code results in a KeyError at 'photographer'.

I appreciate any ideas / comments on how to resolve this KeyError so that I can get the photographer values into my form. Thank you!


EDIT:

Trying to use super, as recommended by @supervacuo, but still getting a KeyError at photographer as before:

class AForm(BaseForm):
    def __init__(self, data=None, files=None, instance=None, auto_id='id_%s',
             prefix=None, initial=None, error_class=ErrorList,
             label_suffix=':', empty_permitted=False):

        super(AForm, self).__init__(data, files, auto_id, prefix, object_data,                         error_class, label_suffix, empty_permitted)
        self.fields['photographer'].queryset = Photographer.objects.all()

What could I be missing that is generating the KeyError? Thanks for any advice.


EDIT 2: adding fields_dict()

from models.py

class A(models.Model):
    category = models.ForeignKey(Category)
    user = models.ForeignKey(User)

    def fields_dict(self):
        fields_dict = {}
        fields_dict['title'] = self.title

        for key, value in self.fields():
            fields_dict[key.name] = value.value

        return fields_dict

Thanks for any advice.


EDIT 3: (edited class AForm above in the initial question as well, to include more information)

def fields_for_a(instance):
    fields_dict = SortedDict() 
    fields = field_list(instance)

    for field in fields:
        if field.field_type == Field.BOOLEAN_FIELD:
            fields_dict[field.name] = forms.BooleanField(label=field.label, required=False, help_text=field.help_text)
        elif field.field_type == Field.CHAR_FIELD:
            widget = forms.TextInput
            fields_dict[field.name] = forms.CharField(label=field.label, required=field.required, max_length=field.max_length, help_text=field.help_text, widget=widget)

            fields_dict[field.name] = field_type(label=field.label,
                                             required=field.required,
                                             help_text=field.help_text,
                                             max_length=field.max_length,
                                             widget=widget)

    return fields_dict

EDIT 4: def fields(self). from models.py:

    def fields(self):
        fields_list = []
        fields = list(self.category.field_set.all())
        fields += list(Field.objects.filter(category=None))

        for field in fields:
            try:
                fields_list.append((field, field.fieldvalue_set.get(ad=self),))
            except FieldValue.DoesNotExist:
                pass  # If no value is associated with that field, skip it.

        return fields_list

    def field(self, name):
        if name == 'title':
            return self.title
        else:
            return FieldValue.objects.get(field__name=name, ad=self).value

Solution

  • That GitHub link should've been the first thing in your question.

    The django-classifieds application has an entire system of dynamic fields (based on the Field and FieldValue models) which is why you're having trouble. If you don't fully understand this aspect of django-classifieds, I recommend you base your project on something else instead.

    Looking down the list of FIELD_CHOICES in django-classified's models.py, you can't use this database-driven field system to define relationsips — so there's no dynamic per-category ForeignKey field!

    The alternative would be to add a photographer field on your A model (any particular reason you've renamed it from Ad?), as it seems you have done based on your other question. To go the rest of the distance, however, you'd need to edit the fields_dict() method like so:

    def fields_dict(self):
        fields_dict = {}
        fields_dict['title'] = self.title
        fields_dict['photographer'] = self.photographer
    
        for key, value in self.fields():
            fields_dict[key.name] = value.value
    
        return fields_dict