Django and empty formset are valid
Asked Answered
P

5

12

I have a little problem with the formset.

I must display several formsets in a page, and each formset has several forms. So i did something like that :

#GET
for prod in products:
     ProductFormSet = modelformset_factory(Product,exclude=('date',),extra=prod.amount)
     formsset.append(ProductFormSet(prefix="prod_%d"%prod.pk))

#POST
for prod in products:
     ProductFormSet = modelformset_factory(Product,exclude=('date',),extra=prod.amount)
     formsset.append(ProductFormSet(request.POST,prefix="prod_%d"%prod.pk))

The problem is when I submit the page, the empties forms are 'automatically' valid (without check), but if I fill one field in one form, the check works on it.

I don't know why, so if anyone has an idea,

thanks.

Powel answered 19/12, 2010 at 2:49 Comment(0)
B
26

I ran into this question while researching another problem. While digging through the Django source in search of a solution for my problem, I found the answer to this question so I'll document it here:

When a form is allowed to have empty values (this applies for empty forms contained within a formset) and the submitted values haven't been changed from the initial ones, the validation is skipped. Check the full_clean() method in django/forms/forms.py (line 265 in Django 1.2):

# If the form is permitted to be empty, and none of the form data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
     return

I'm not sure what kind of solution you're looking for (also, this question is already somewhat dated) but maybe this will help someone in the future.

Bondwoman answered 22/3, 2011 at 20:54 Comment(1)
Thanks, I used your solution to solve my problem: Empty forms should NOT validate. See my example.Plum
P
17

@Jonas, thanks. I used your description to solve my problem. I needed the a form to NOT validate when empty. (Forms added with javascript)

class FacilityForm(forms.ModelForm):
    class Meta:
        model = Facility

    def __init__(self, *arg, **kwarg):
        super(FacilityForm, self).__init__(*arg, **kwarg)
        self.empty_permitted = False


 facility_formset = modelformset_factory(Facility, form=FacilityForm)(request.POST)

It will make sure any displayed forms must not be empty when submitted.

Plum answered 21/1, 2013 at 16:50 Comment(2)
Since empty_permitted is an argument in the init method, you could use form_kwargs in your call and avoid creating your init method in your subclass example. facility_formset = modelformset_factory(Facility, form=FacilityForm)(request.POST, form_kwargs={'empty_permitted': False})Pigeontoed
Note that form_kwargs is available in django version 1.9+Plum
C
3

The forms created based on the "extra" parameter of "formset_factory" have their "empty_permitted" property set to True. (see: formset.py line 123)

# Allow extra forms to be empty.
    if i >= self.initial_form_count():
        defaults['empty_permitted'] = True

So it seems the better way to use "initial" parameter of the FormSet and not the "extra" parameter of "formset_factory" for this use case.

Please find the description at using-initial-data-with-a-formset

Caldeira answered 6/4, 2012 at 6:33 Comment(0)
J
0

Simple is better. Due a Formset is a list of forms, you could iterate this set.

if FormSet(request.POST,).is_valid():
    for form in FormSet:
        # Check if value is empty using value().
        if form['field'].value():
            # this form's field is not empty. Create and save object.
            object = form.save()
Japanese answered 12/4, 2018 at 20:48 Comment(0)
B
0

I'm not sure what version of Django the OP was using, but this answer works for Django 4.1.

@Jonas is right; very little validation is performed on an empty formset by default. You could solve this by passing form_kwargs = {'empty_permitted': False}, but that can cause other problems (for example, this prevents the template engine from being able to create a blank form for use with javascript using the {{ formset.empty_form }} tag.

Depending on your use case, I think a better solution is to add a minimum form number validation. So OP's view would include:

#GET
for prod in products:
    ProductFormSet = modelformset_factory(
        Product,
        exclude=('date',),
        extra=prod.amount,
        min_num = 1, # This means that there must be at least 1 form.
        validate_min = True, # This tells the formset validation to check min_num.
    )
     formsset.append(ProductFormSet(prefix="prod_%d"%prod.pk))

#POST
for prod in products:
    ProductFormSet = modelformset_factory(
        Product,
        exclude=('date',),
        extra=prod.amount,
        min_num = 1, # This means that there must be at least 1 form.
        validate_min = True, # This tells the formset validation to check
    )
     formsset.append(ProductFormSet(request.POST,prefix="prod_%d"%prod.pk))

If there isn't at least one form that has changed, the form validation will fail as desired.

Bustup answered 4/9, 2022 at 18:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.