Django model formsets provide a way to edit multiple model instances within a single form. This is especially useful for editing related models inline. Below is some knowledge I've collected on some of the lesser documented and undocumented features of Django's model formsets.
Model Formset Factory Methods
Django Model Formsets are generally created using a factory method. The default is
modelformset_factory, which wraps formset_factory to create Model Forms. You can also create inline formsets to edit related objects, using inlineformset_factory.
modelformset_factory to restrict the queryset and set the initial data to the instance's related objects.
Adding Fields to a Model Formset
Just like with a normal Django formset, you can add additional fields to a model formset by creating a base formset class with an
add_fields method, then passing it in to the factory method. The only difference is the class you inherit from. For
inlineformset_factory, you should inherit from
If you're using
modelformset_factory, then you should import and inherit from
BaseModelFormSet instead. Also remember that
form.instance may be used to set initial data for the fields you're adding. Just check to make sure
form.instance is not
None before you try to access any properties.
from django.forms.models import BaseInlineFormSet, inlineformset_factory class BaseFormSet(BaseInlineFormSet): def add_fields(self, form, index): super(BasePlanItemFormSet, self).add_fields(form, index) # add fields to the form FormSet = inlineformset_factory(MyModel, MyRelatedModel, formset=BaseFormSet)
Changing the Default Form Field
If you'd like to customize one or more of the form fields within your model formset, you can create a
formfield_callback function and pass it to the formset factory. For example, if you want to set
required=False on all fields, you can do the following.
def custom_field_callback(field): return field.formfield(required=False) FormSet = modelformset_factory(model, formfield_callback=custom_field_callback)
field.formfield() will create the default form field with whatever arguments you pass in. You can also create different fields, and use
field.name to do field specific customization. Here's a more advanced example.
def custom_field_callback(field): if field.name == 'optional': return field.formfield(required=False) elif field.name == 'text': return field.formfield(widget=Textarea) elif field.name == 'integer': return IntegerField() else: return field.formfield()
Deleting Models in a Formset
Pass can_delete=True to your factory method, and you'll be able to delete the models in your formsets. Note that
inlineformset_factory defaults to
modelformset_factory defaults to
Creating New Models with Extra Forms
As with normal formsets, you can pass an
extra argument to your formset factory to create extra empty forms. These empty forms can then be used to create new models. Note that when you have extra empty forms in the formset, you'll get an equal number of
None results when you call
formset.save(), so you may need to filter those out if you're doing any post-processing on the saved objects.
If you want to set an upper limit on the number of extra forms, you can use the
max_num argument to restrict the maximum number of forms. For example, if you want up to 6 forms in the formset, do the following:
MyFormSet = inlineformset_factory(MyModel, MyRelatedModel, extra=6, max_num=6)
Saving Django Model Formsets
Model formsets have a save method, just like with model forms, but in this case, you'll get a list of all modified instances instead of a single instance. Unmodified instances will not be returned. As mentioned above, if you have any extra empty forms, then those list elements will be
If you want to create custom save behavior, you can override 2 methods in your BaseFormSet class:
save_existing. These methods look like this:
from django.forms.models import BaseInlineFormSet class BaseFormSet(BaseInlineFormSet): def save_new(self, form, commit=True): # custom save behavior for new objects, form is a ModelForm return super(BaseFormSet, self).save_new(form, commit=commit) def save_existing(self, form, instance, commit=True): # custom save behavior for existing objects # instance is the existing object, and form has the updated data return super(BaseFormSet, self).save_existing(form, instance, commit=commit)
Inline Model Admin
Django's Admin Site includes the ability to specify InlineModelAdmin objects. Subclasses of
InlineModelAdmin can use all the arguments of
inlineformset_factory, plus some admin specific arguments. Everything mentioned above applies equally to
InlineModelAdmin arguments: you can specify the number of extra forms, the maximum number of inline forms, and even your own formset with custom save behavior.