38

These formsets are exhibiting exactly the opposite behavior that I want.

My view is set up like this:

def post(request): # TODO: handle vehicle formset
    VehicleFormSetFactory = formset_factory(VehicleForm, extra=1)
    if request.POST:
        vehicles_formset = VehicleFormSetFactory(request.POST)
    else:
        vehicles_formset = VehicleFormSetFactory()

And my template looks like this:

    <div id="vehicle_forms">
        {{ vehicles_formset.management_form }}
        {% for form in vehicles_formset.forms %}
            <h4>Vehicle {{forloop.counter}}</h4>
            <table>
                {% include "form.html" %}
            </table>
        {% endfor %}
    </div>

That way it initially generates only 1 form, like I want. But I want that one form to be required!

When I dynamically add blank forms with JavaScript and vehicles_formset.empty_form all those extra forms are required, which I don't want.

From the docs:

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

This is the behavior the first form is exhibiting (not what I want) but not the behavior that the extra forms are exhibiting (what I do want).

Is there some attribute I can can change to at least make one form required?

Serjik
  • 10,543
  • 8
  • 61
  • 70
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • @ mpen although I'm late for the party, I was wondering how to go about a slightly different issue that I'm facing my question here (https://stackoverflow.com/questions/44159559/need-to-have-a-required-and-optional-fields-in-django-formset) – dungu May 25 '17 at 08:40
  • @kedric You're asking the wrong guy. I haven't used Django since shortly after I posted this question. – mpen May 25 '17 at 18:20
  • ooh, Okay bro thanks for the response – dungu May 25 '17 at 18:44

4 Answers4

76

Found a better solution:

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

Then create your formset like this:

MyFormSet = formset_factory(MyForm, formset=RequiredFormSet)

I really don't know why this wasn't an option to begin with... but, whatever. It only took a few hours of my life to figure out.

This will make all the forms required. You could make just the first one required by setting self.forms[0].empty_permitted to False.

mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 2
    This is the solution to one of the most annoying parts of Django, it's inexplicable why this isn't part of the documentation – YPCrumble Mar 03 '14 at 06:24
  • For some reason, I get a keyError when trying to set a specific form to empty_permitted. For example "form[0].empty_permitted = False" gives an error. – Riot Goes Woof Jun 18 '14 at 15:24
  • @Zorpix Should be `self.forms[0]` I think, assuming you have at least one form. – mpen Jun 18 '14 at 17:40
29

New in Django 1.7: you can specify this behaviour with your formset_factory

https://docs.djangoproject.com/en/1.8/topics/forms/formsets/#validate-min

VehicleFormSetFactory = formset_factory(VehicleForm, min_num=1, validate_min=True, extra=1)
Anentropic
  • 32,188
  • 12
  • 99
  • 147
  • But, what if `extra=3` and the third form is filled and first two forms are left empty? This throws an error. How to implement this to be valid with atleast one form being valid one?? – Sreekanth Reddy Balne Sep 11 '18 at 15:51
  • @whitehathackersree well, if Django does not behave that way then you will have to work around it somehow. Comparing the source code (https://github.com/django/django/blob/1.7.11/django/forms/formsets.py#L314 vs https://github.com/django/django/blob/1.11.15/django/forms/formsets.py#L332) it looks like this may have been fixed in Django 1.11 – Anentropic Sep 11 '18 at 16:03
  • This still does not set the `required` attribute on the input fields, as in, it is still possible to submit the form (it will just return with an error since you didn't fill out the form correctly). – fgblomqvist Aug 14 '19 at 14:16
10

Well... this makes the first form required.

class RequiredFormSet(BaseFormSet):
    def clean(self):
        if any(self.errors):
            return
        if not self.forms[0].has_changed():
            raise forms.ValidationError('Please add at least one vehicle.') 

Only "problem" is that if there are 0 forms, then the clean method doesn't seem to get called at all, so I don't know how to check if there are 0. Really...this should never happen though (except that my JS has a bug in it, allowing you to remove all the forms).

mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 1
    If you use this form to edit an existing set of objects, you will always be forced to update the first one. This is only suitable for creation, not updating. – Daniel Palm Sep 09 '14 at 03:48
  • @Daniel Palm , do you have a solution ? I also have same problem. – Hùng Ng Vi May 15 '17 at 10:46
2

Oh I think I see. Try this:

from django.forms.formsets import BaseFormSet, formset_factory
class OneExtraRequiredFormSet(BaseFormSet):
    def initial_form_count(self):
        return max(super(OneExtraRequiredFormSet,self).initial_form_count() - 1,0)

VehicleFormSetFactory = formset_factory(VehicleForm, formset=OneExtraRequiredFormSet, extra=1)

== Original answer below ==

When you say "at least make one form required", I assume you mean "make only one extra form required, regardless of how many have been added via javascript".

You will need to have hidden input on your page which contains the number of forms that have been added via javascript, and then use that number, minus 1, as the value to pass in as the extra attribute to your formsets constructor.

Wogan
  • 70,277
  • 5
  • 35
  • 35
  • Not quite... ignoring the JavaScript for now, one and only one form is displayed (extra=1, no initial data). That *one* form is not required -- I want it to be. Any forms added via JS should *not* be required, ever. – mpen Mar 09 '10 at 04:34
  • That has some bizarre behavior. When I leave the form blank and submit it, it adds another one and puts errors on the new form, and still doesn't validate the first one. – mpen Mar 09 '10 at 05:00
  • Hmm ok try it without overriding the total_form_count method. – Wogan Mar 09 '10 at 05:05
  • (just fixed a typo, it should of course be max instead of min in the initial_form_count method) – Wogan Mar 10 '10 at 01:14
  • Nope. Don't think you can do it by playing with the counts. – mpen Mar 11 '10 at 19:22