In the django docs, there's an example of using inlineformset_factory to edit already created objects
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#using-an-inline-formset-in-a-view
I changed the example to be this way:
def manage_books(request):
author = Author()
BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',))
if request.method == "POST":
formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
if formset.is_valid():
formset.save()
return HttpResponseRedirect(author.get_absolute_url())
else:
formset = BookInlineFormSet(instance=author)
return render_to_response("manage_books.html", {
"formset": formset,
})
With the above, it renders only the inline model without the parent model.
To create a new object, say Author, with multiple Books associated to, using inlineformset_factory, what's the approach?
An example using the above Author Book model from django docs will be helpful. The django docs only provided example of how to edit already created object using inlineformset_factory but not to create new one
I've done that using Django Class-Based Views.
Here's my approach:
models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
forms.py
from django.forms import ModelForm
from django.forms.models import inlineformset_factory
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Fieldset
from .models import Author, Book
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ('name', )
@property
def helper(self):
helper = FormHelper()
helper.form_tag = False # This is crucial.
helper.layout = Layout(
Fieldset('Create new author', 'name'),
)
return helper
class BookFormHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(BookFormHelper, self).__init__(*args, **kwargs)
self.form_tag = False
self.layout = Layout(
Fieldset("Add author's book", 'title'),
)
BookFormset = inlineformset_factory(
Author,
Book,
fields=('title', ),
extra=2,
can_delete=False,
)
views.py
from django.views.generic import CreateView
from django.http import HttpResponseRedirect
from .forms import AuthorForm, BookFormset, BookFormHelper
from .models import Book, Author
class AuthorCreateView(CreateView):
form_class = AuthorForm
template_name = 'library/manage_books.html'
model = Author
success_url = '/'
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
book_form = BookFormset()
book_formhelper = BookFormHelper()
return self.render_to_response(
self.get_context_data(form=form, book_form=book_form)
)
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
book_form = BookFormset(self.request.POST)
if (form.is_valid() and book_form.is_valid()):
return self.form_valid(form, book_form)
return self.form_invalid(form, book_form)
def form_valid(self, form, book_form):
"""
Called if all forms are valid. Creates a Author instance along
with associated books and then redirects to a success page.
"""
self.object = form.save()
book_form.instance = self.object
book_form.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, book_form):
"""
Called if whether a form is invalid. Re-renders the context
data with the data-filled forms and errors.
"""
return self.render_to_response(
self.get_context_data(form=form, book_form=book_form)
)
def get_context_data(self, **kwargs):
""" Add formset and formhelper to the context_data. """
ctx = super(AuthorCreateView, self).get_context_data(**kwargs)
book_formhelper = BookFormHelper()
if self.request.POST:
ctx['form'] = AuthorForm(self.request.POST)
ctx['book_form'] = BookFormset(self.request.POST)
ctx['book_formhelper'] = book_formhelper
else:
ctx['form'] = AuthorForm()
ctx['book_form'] = BookFormset()
ctx['book_formhelper'] = book_formhelper
return ctx
urls.py
from django.conf.urls import patterns, url
from django.views.generic import TemplateView
from library.views import AuthorCreateView
urlpatterns = patterns('',
url(r'^author/manage$', AuthorCreateView.as_view(), name='handle-books'),
url(r'^$', TemplateView.as_view(template_name='home.html'), name='home'),
)
manage_books.html
{% load crispy_forms_tags %}
<head>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
</head>
<div class='container'>
<form method='post'>
{% crispy form %}
{{ book_form.management_form }}
{{ book_form.non_form_errors }}
{% crispy book_form book_formhelper %}
<input class='btn btn-primary' type='submit' value='Save'>
</form>
<div>
Notice:
inlineformset_factory
feature and Django generic Class-Based Viewsdjango-crispy-forms
is installed, and it's properly
configured.I know it's more code that the showed solutions, but start to using Django Class-Based Views is great.