I was recently working on a project that required combining a Django model and another model related to it via a ForeignKey in a single form. At first I considered generating fields for the related model in the ModelForm's init, the more I thought about it though, the more I realized that would be messy, brittle and not easy to make changes to in the future.
The Solution
Turns out you can put a formset and a ModelForm (or regular form) inside the same html form in a template and have them be validated individually. Each one only cares about the POST data relevant to them. Something like:
from django.forms.models import modelformset_factory from django.shortcuts import render from forms import FormClass from models import RelatedModel def handle_form_and_formset(request): ''' Handles displaying, validating and saving a form and related model formset ''' form = FormClass() RelatedFormset = modelformset_factory(RelatedModel, extra=5) formset = RelatedFormset(queryset=RelatedModel.objects.none()) if request.method == "POST": form = FormClass(request.POST) formset = RelatedFormset(request.POST, queryset=RelatedModel.objects.none()) if form.is_valid() and formset.is_valid(): # do something with the form data here for f_form in formset: if f_form.is_valid() and f_form.has_changed(): # do something with the formset data return render(request, "some/template/name.html", {'form': form, 'formset': formset'})
The corresponding template looks something like this:
<form method="post"> {% csrf_token %} {{ form.as_p }} {% for f_form in formset %} {{ f_form.as_p }} {% endfor %} </form>
That's it! I didn't have to worry about form inits, or figuring out how to handle validation or anything like that, I just let the form do what it does best and the formset do what it does best. The astute reader may think this solution is obvious, but to me, it was a revelation.
Epilogue
As you might have guessed, this isn't limited to ModelForm's and the formset's of related models. This would work to combine any form and any formset (or even multiple forms and multiple formsets).
Gotchas
It took me awhile to figure out this line:
if f_form.is_valid() and f_form.has_changed():
Without adding the f_form.has_changed()
I kept running into an error
because I was trying to save one of the extras that had no data supplied.
credits:
tags
django, python, forms, formsets
Comments