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. inlineformset_factory
wraps 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 BaseInlineFormSet
.
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 can_delete=True
, while modelformset_factory
defaults to can_delete=False
.
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 None
.
If you want to create custom save behavior, you can override 2 methods in your BaseFormSet class: save_new
and 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.