Formset Customization Examples

Overriding formset_kwargs and factory_kwargs at run time

If the values in formset_kwargs and factory_kwargs need to be modified at run time, they can be set by overloading the get_formset_kwargs() and get_factory_kwargs() methods on any formset view (model, inline or generic) and the InlineFormSetFactory classes:

class AddressFormSetView(FormSetView):
    ...

    def get_formset_kwargs(self):
        kwargs = super(AddressFormSetView, self).get_formset_kwargs()
        # modify kwargs here
        return kwargs

    def get_factory_kwargs(self):
        kwargs = super(AddressFormSetView, self).get_factory_kwargs()
        # modify kwargs here
        return kwargs

Overriding the the base formset class

The formset_class option should be used if you intend to override the formset methods of a view or a subclass of InlineFormSetFactory.

For example, imagine you’d like to add your custom clean method for an inline formset view. Then, define a custom formset class, a subclass of Django’s BaseInlineFormSet, like this:

from django.forms.models import BaseInlineFormSet

class ItemInlineFormSet(BaseInlineFormSet):

    def clean(self):
        # ...
        # Your custom clean logic goes here

Now, in your InlineFormSetView sub-class, use your formset class via formset_class setting, like this:

from extra_views import InlineFormSetView
from my_app.models import Item
from my_app.forms import ItemForm

class ItemInlineView(InlineFormSetView):
    model = Item
    form_class = ItemForm
    formset_class = ItemInlineFormSet     # enables our custom inline

This will enable clean method being executed on the formset used by ItemInlineView.

Initial data for ModelFormSet and InlineFormSet

Passing initial data into ModelFormSet and InlineFormSet works slightly differently to a regular FormSet. The data passed in from initial will be inserted into the extra forms of the formset. Only the data from get_queryset() will be inserted into the initial rows:

from extra_views import ModelFormSetView
from my_app.models import Item


class ItemFormSetView(ModelFormSetView):
    template_name = 'item_formset.html'
    model = Item
    factory_kwargs = {'extra': 10}
    initial = [{'name': 'example1'}, {'name': 'example2'}]

The above will result in a formset containing a form for each instance of Item in the database, followed by 2 forms containing the extra initial data, followed by 8 empty forms.

Altenatively, initial data can be determined at run time and passed in by overloading get_initial():

...
class ItemFormSetView(ModelFormSetView):
    model = Item
    template_name = 'item_formset.html'
    ...

    def get_initial(self):
        # Get a list of initial values for the formset here
        initial = [...]
        return initial

Passing arguments to the form constructor

In order to change the arguments which are passed into each form within the formset, this can be done by the ‘form_kwargs’ argument passed in to the FormSet constructor. For example, to give every form an initial value of ‘example’ in the ‘name’ field:

from extra_views import InlineFormSetFactory

class ItemInline(InlineFormSetFactory):
    model = Item
    formset_kwargs = {'form_kwargs': {'initial': {'name': 'example'}}}

If these need to be modified at run time, it can be done by get_formset_kwargs():

from extra_views import InlineFormSetFactory

class ItemInline(InlineFormSetFactory):
    model = Item

    def get_formset_kwargs(self):
        kwargs = super(ItemInline, self).get_formset_kwargs()
        initial = get_some_initial_values()
        kwargs['form_kwargs'].update({'initial': initial})
        return kwargs

Named formsets

If you want more control over the names of your formsets (as opposed to iterating over inlines), you can use NamedFormsetsMixin:

from extra_views import NamedFormsetsMixin

class CreateOrderView(NamedFormsetsMixin, CreateWithInlinesView):
    model = Order
    inlines = [ItemInline, TagInline]
    inlines_names = ['Items', 'Tags']
    fields = '__all__'

Then use the appropriate names to render them in the html template:

...
{{ Tags }}
...
{{ Items }}
...

Success messages

When using Django’s messages framework, mixins are available to send success messages in a similar way to django.contrib.messages.views.SuccessMessageMixin. Ensure that 'django.contrib.messages.middleware.MessageMiddleware' is included in the MIDDLEWARE section of settings.py.

extra_views.SuccessMessageMixin is for use with views with multiple inline formsets. It is used in an identical manner to Django’s SuccessMessageMixin, making form.cleaned_data available for string interpolation using the %(field_name)s syntax:

from extra_views import CreateWithInlinesView, SuccessMessageMixin
...

class CreateOrderView(SuccessMessageMixin, CreateWithInlinesView):
    model = Order
    inlines = [ItemInline, ContactInline]
    success_message = 'Order %(name)s successfully created!'
    ...

    # or instead, set at runtime:
    def get_success_message(self, cleaned_data, inlines):
        return 'Order with id {} successfully created'.format(self.object.pk)

Note that the success message mixins should be placed ahead of the main view in order of class inheritance.

extra_views.FormSetSuccessMessageMixin is for use with views which handle a single formset. In order to parse any data from the formset, you should override the get_success_message method as below:

from extra_views import FormSetView, FormSetSuccessMessageMixin
from my_app.forms import AddressForm


class AddressFormSetView(FormSetView):
    form_class = AddressForm
    success_url = 'success/'
    ...
    success_message = 'Addresses Updated!'

# or instead, set at runtime
def get_success_message(self, formset)
    # Here you can use the formset in the message if required
    return '{} addresses were updated.'.format(len(formset.forms))