Django offers several out-of-the-box class-based views, including CreateView and UpdateView, that allow you to quckly implement basic CRUD functionality in your web-application without much of boilerplate code. Under the hood CreateView and UpdateView classes create, validate and save a Django ModelForm instance tied to your model class. But sometimes you need to create/edit related model instances along with the main model on the same page. For example, you have Author
model that represents a book author and Book
model which has a foreign key to Author
, and you want to edit information about an author and their books in the same form. Django provides formsets and model formsets for that purpose. But out-of-the-box create and update class-based views deal only with single forms. The below mixin class allows you to add support for multiple formsets in your create and edit views. Working with formsets in Django templates is beyond the scope of this article.
from django.db import transaction
from .forms import BookFormSet
class FormsetMixin:
formset_classes = {
'book_formset': BookFormSet,
}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
for formset_name, formset_class in self.formset_classes.items():
if formset_name not in kwargs:
context[formset_name] = formset_class(instance=self.object)
return context
def formsets_valid(self, response):
return response
def formsets_invalid(self, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
def form_valid(self, form):
with transaction.atomic():
response = super().form_valid(form)
formset_instances = {}
all_formsets_valid = True
for formset_name, formset_class in self.formset_classes.items():
formset = formset_class(self.request.POST, instance=self.object)
formset_instances[formset_name] = formset
if not formset.is_valid():
all_formsets_valid = False
if all_formsets_valid:
for formset in formset_instances.values():
formset.save()
return self.formsets_valid(response)
transaction.set_rollback(True)
return self.formsets_invalid(form=form, **formset_instances)
This example assumes that you have Author
and Book
models and you have created BookFormSet
class using modelformset_factory
function but it can be easily extended to support multiple formsets. Formset validation is done in form_valid
method that is called after the main model (Author
) form has been successfully validated. The whole code is wrapped in a database transaction which ensures that nothing will be saved to the database in case if one or more forms in a formset are not valid. The formsets_valid
method simply returns a success response but it can be overridden in your view class to include additional logic, for example, logging successful model instances creation/update.