Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django formsets confusion (validation, required, empty_permitted)

I am really finding the django formsets confusing.

I am especially having problems with the following concepts which I don't really understand:

The formset is smart enough to ignore extra forms that were not changed.

Talking about code trying to be too smart. What is this supposed to mean exactly ? Why would I even want that ?

Then, trying to understand the previous concept, I see people

making forms in the formsets required.

This is another concept I can't get the hang of. What is a required form in a formset and why do I have to make a form required ? Again something not documented.

Then coming to my actual problem, which other people seem to have had, but they can't really explain why they've fixed it the way they've fixed it.

Why in the following example, the formset is valid, while an individual form with the same input will be invalid ?

import django
class MyForm(django.forms.Form):
    start = django.forms.DateField()
    end = django.forms.DateField()

data =  {
    'form-TOTAL_FORMS': '1',
    'form-MAX_NUM_FORMS': '',
    'form-INITIAL_FORMS': '0',
    'form-0-start': '',
    'form-0-end': '',
}

MyFormSet = formset_factory(MyForm)
formset = MyFormSet(data)
#fee_forms[0].empty_permitted = False

print formset.is_valid()
# --- returns True ---
print formset.errors

f = MyForm({'start': '', 'end': ''})
print f.is_valid()
# --- returns False ---
print f.errors

Setting empty_permitted to False seems to give the expected results for me (which is for the formset to be invalid due to missing 'start' and 'end'). This is another undocumented feature ...

Would anybody spare some time to explain ?

Thank you

like image 334
Cricri Avatar asked Dec 06 '12 14:12

Cricri


1 Answers

The formset is smart enough to ignore extra forms that were not changed.

Talking about code trying to be too smart. What is this supposed to mean exactly? Why would I even want that?

It seems to mean — as you worked out — that "extra" forms created by the formset (with extra=N in your example) have empty_permitted set to True. Have a glance at django/forms/formsets.py to see this happening.

formset[0].empty_permitted means that if formset[0].has_changed() == False, no further processing/validation is done. Again, you can see this in action in forms/forms.py.

To prevent this, a blog post suggests defining a custom formset to use in modelformset_factory (or inlineformset_factory) which sets empty_permitted = False:

class MyModelFormSet(BaseModelFormSet):
    def __init__(self, *args, **kwargs):
        super(MyModelFormSet, self).__init__(*args, **kwargs)
        for form in self.forms:
            form.empty_permitted = False

I haven't tested this, but it looks legit.

As to why anyone might want this, it makes using django-dynamic-formset much simpler — you can send data for form-0 and form-2 and, assuming form-1 was an extra form (i.e. not linked to model data), Django won't complain. If empty_permitted were False by default, you'd have to worry about skipping the blank form in your own code, or reindexing things in Javascript.

like image 167
supervacuo Avatar answered Sep 22 '22 11:09

supervacuo