Opened 4 years ago

Last modified 21 months ago

#16995 new Bug

ModelFormSet initial data from initial parameter uses "extra" forms

Reported by: p910221@… Owned by: nobody
Component: Documentation Version: master
Severity: Normal Keywords:
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

It seems that when you feed initial data into a ModelFromSet using the initial parameter, it uses the "extra" forms that you define with the extra parameter in the modelformset_factory. For example, if you feed a list of two dictionaries (with the model data) into the initial parameter of a FormSet, but the formset_factory that the FormSet was created from has an extra parameter of 1 (or None), then only the model data from the first dictionary in the list will show up in the FormSet.

Since "initial" data doen't seem like they should be in the "extra" forms, I think this is a bug?

Change History (3)

comment:1 Changed 4 years ago by carljm

  • Component changed from Forms to Documentation
  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Triage Stage changed from Unreviewed to Accepted
  • Version changed from 1.3 to SVN

I believe this is working as designed. I do, however, think the documentation is lacking in clarity and there's an inconsistency with how initial works in a regular FormSet.

With a FormSet, extra forms are those beyond the ones created by the data in initial, as documented here.

With a ModelFormSet, the data in the non-extra forms is determined by the existing model instances. It's sometimes useful to be able to pre-populate some of the extra forms with some initial data, and that's what the initial parameter is used for. If initial pre-populated the non-extra forms, how would the conflict with existing model instance data be resolved?

If this were being designed from scratch, you could perhaps make a case that a ModelFormSet should consist of first the forms representing existing instances, then forms representing the given initial data, then empty extra forms (i.e. initial would add more forms, not prepopulate the extra forms). This would perhaps be more consistent with FormSet, but I'm not sure it's actually better for real usage.

In any case, if it's an improvement at all, it's not enough of one to justify making a backwards-incompatible change. So I'm converting this into a documentation bug; the ModelFormSet docs should have an explanation of how initial and extra interact differently in a ModelFormSet than in a FormSet.

comment:2 Changed 3 years ago by supervacuo

I was about to report this as a separate bug (after asking in #django-dev); I see this is as a feature proposal along the lines of "Set extra to the length of initial in ModelFormSet if it is not set manually".

I'm using modelformset_factory to create a model formset class, then passing initial in the constructor when instantiating it. Line 91 of forms/formsets.py uses the value of extra to work out how many extra forms should be drawn, but I think this information could already be implied by the length of the initial dict, at least in the case where extra is not specified (for backwards compatibility), and having to specify both seems a little like Repeating Yourself.

The code I'm using which demonstrates the problem is here.

I can certainly try writing a patch (it shouldn't be a big change, and I imagine the tests would be pretty straightforward) if this seems like a good idea. If not, I agree that the "Making forms from models" docs should explain that extra needs to be set, even with initial, using something like

from django.forms.models import modelformset_factory

foos = some_function_that_returns_a_list_of_foo()

FooFormSet = modelformset_factory(Foo, extra=len(foos))

foo_formset = FooFormSet(initial=[f.__dict__ for f in foos])

comment:3 Changed 21 months ago by alex.isayko@…

The quick workaround on this is to use lambda function:

from django.forms.models import modelformset_factory

foos = some_function_that_returns_a_list_of_foo()

FooFormSet = lambda *args, **kwargs: modelformset_factory(Foo, extra=kwargs.pop('extra', 0))(*args, **kwargs)

foo_formset = FooFormSet(initial=[f.__dict__ for f in foos], extra=len(foos))
Note: See TracTickets for help on using tickets.
Back to Top