Creating a model and related models with Inline formsets
I'd actually like to propose a small adjustment to nbv4's solution:
Assume that you don't create the empty created_author outside of the if-else statement and thus need to nest the formset inside the author_form.is_valid() to avoid runtime errors when the author_form is not valid (and thus no created_author is instantiated).
Instead of:
if request.method == 'POST':
author_form = AuthorModelForm(request.POST)
if author_form.is_valid():
created_author = author_form.save()
formset = BookFormSet(request.POST, instance=created_author)
if formset.is_valid():
formset.save()
return HttpResponseRedirect(...)
else:
...
Do the following:
if request.method == 'POST':
author_form = AuthorModelForm(request.POST)
if author_form.is_valid():
created_author = author_form.save(commit=False)
formset = BookFormSet(request.POST, instance=created_author)
if formset.is_valid():
created_author.save()
formset.save()
return HttpResponseRedirect(...)
else:
...
This version avoids committing the created_author until the book_formset has had a chance to validate. The use case to correct for is that someone fills out a valid AuthorForm with an invalid BookFormSet and keeps resubmitting, creating multiple Author records with no Books associated with them. This seems to work for my project-tracker app (replace "Author" with "Project" and "Book" with "Role").
First, create a Author model form.
author_form = AuthorModelForm()
then create a dummy author object:
author = Author()
Then create a inline formset using the dummy author like so:
formset = BookFormSet(instance=author) #since author is empty, this formset will just be empty forms
Send that off to a template. After the data is returned back to the view, you create the Author:
author = AuthorModelForm(request.POST)
created_author = author.save() # in practice make sure it's valid first
Now hook the inline formset in with the newly created author and then save:
formset = BookFormSet(request.POST, instance=created_author)
formset.save() #again, make sure it's valid first
edit:
To have no checkboxes on new forms, do this is a template:
{% for form in formset.forms %}
<table>
{% for field in form %}
<tr><th>{{field.label_tag}}</th><td>{{field}}{{field.errors}}</td></tr>
{% endfor %}
{% if form.pk %} {# empty forms do not have a pk #}
<tr><th>Delete?</th><td>{{field.DELETE}}</td></tr>
{% endif %}
</table>
{% endfor %}
models.py (Contact)
class Contact(models.Model)
first = models.CharField(max_length=30)
middle = models.CharField('M.I.',max_length=30, blank=True)
last = models.CharField(max_length=30)
sort_order = models.PositiveIntegerField(default=99)
models.py (Link)
class Link(models.Model):
contact = models.ForeignKey(Contact)
link = models.URLField()
description = models.CharField(max_length=30)
access_date = models.DateField(blank=True,null=True)
forms.py
from django.forms import ModelForm
from contacts.models import Contact
class ContactAjaxForm(ModelForm):
class Meta:
model=Contact
views.py
def edit(request,object_id=False):
LinkFormSet = inlineformset_factory(Contact, Link, extra=1)
if object_id:
contact=Contact.objects.get(pk=object_id)
else:
contact=Contact()
if request.method == 'POST':
f=forms.ContactAjaxForm(request.POST, request.FILES, instance=contact)
fs = LinkFormSet(request.POST,instance=contact)
if fs.is_valid() and f.is_valid():
f.save()
fs.save()
return HttpResponse('success')
else:
f = forms.ContactAjaxForm(instance=contact)
fs = LinkFormSet(instance=contact)
return render_to_response(
'contacts/edit.html',
{'fs': fs, 'f': f, 'contact': contact}
)
This is not based on the example in the book, it's edited down from some code on my site. I haven't tested it so there might be some bugs but overall it should be solid. Using an empty instance of Contact isn't the suggested method but it saves a bit of logic and it works.
Edit: Added Link Model, switched to normal Foreign Key instead of Generic Foreign Key which is confusing