Inlineformset_factory creates new objects and edits objects after creation

In django docs there is 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 modified the example like this:

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 only displays the inline model without the parent model.

To create a new object, say, Author, with several books related to using inlineformset_factory, which approach?

An example of using the above author book model from django docs would be useful. Django docs provided only an example of how to edit an already created object using inlineformset_factory, but not create a new one

+6
source share
4 answers

I did not read your question correctly. You also need to display the form for the parent model. I have not tested this, I take away what I did before and the previously related answer, but it should work.

UPDATE

If you use the view for both and edit, you must first check the author ID. If the identifier is missing, it will display both forms as a new instance, while with the identifier it will fill them with existing data. Then you can check if there was a POST request.

 def manage_books(request, id): if id: author = Author.objects.get(pk=author_id) # if this is an edit form, replace the author instance with the existing one else: author = Author() author_form = AuthorModelForm(instance=author) # setup a form for the parent BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',)) formset = BookInlineFormSet(instance=author) if request.method == "POST": author_form = AuthorModelForm(request.POST) if id: author_form = AuthorModelForm(request.POST, instance=author) formset = BookInlineFormSet(request.POST, request.FILES) if author_form.is_valid(): created_author = author_form.save(commit=False) formset = BookInlineFormSet(request.POST, request.FILES, instance=created_author) if formset.is_valid(): created_author.save() formset.save() return HttpResponseRedirect(created_author.get_absolute_url()) return render_to_response("manage_books.html", { "author_form": author_form, "formset": formset, }) 
+5
source

I did this using Django class representations.

Here is 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 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> 

Note:

  • This is a simple example that uses inlineformset_factory and general Django role representations.
  • I am installing django-crispy-forms and it is configured correctly.
  • The code repository is located at: https://bitbucket.org/slackmart/library_example

I know that there is more code that the solutions shown, but starting to use Django Class-Based Views is fine.

+8
source

I am posting my final decisions, as the extensive assistant provided by Onyeka said.

Below I publish Add and Edit solutions for using inlineformset_factory in Django using the Author and Book example found in Django Docs.

Firstly, the "Add Author" object with the addition of 3 additional Book objects.

Obviously this applies to views.py

 def add_author(request): '''This function creates a brand new Author object with related Book objects using inlineformset_factory''' author = Author() author_form = AuthorModelForm(instance=author) # setup a form for the parent BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',)) if request.method == "POST": author_form = AuthorModelForm(request.POST) formset = BookInlineFormSet(request.POST, request.FILES) if author_form.is_valid(): created_author = author_form.save(commit=False) formset = BookInlineFormSet(request.POST, request.FILES, instance=created_author) if formset.is_valid(): created_author.save() formset.save() return HttpResponseRedirect(created_author.get_absolute_url()) else: author_form = AuthorModelForm(instance=author) formset = BookInlineFormSet() return render(request, "add_author.html", { "author_form": author_form, "formset": formset, }) def edit_author(request, author_id): '''This function edits an Author object and its related Book objects using inlineformset_factory''' if id: author = Author.objects.get(pk=author_id) # if this is an edit form, replace the author instance with the existing one else: author = Author() author_form = AuthorModelForm(instance=author) # setup a form for the parent BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',)) formset = BookInlineFormSet(instance=author) if request.method == "POST": author_form = AuthorModelForm(request.POST) if id: author_form = AuthorModelForm(request.POST, instance=author) formset = BookInlineFormSet(request.POST, request.FILES) if author_form.is_valid(): created_author = author_form.save(commit=False) formset = BookInlineFormSet(request.POST, request.FILES, instance=created_author) if formset.is_valid(): created_author.save() formset.save() return HttpResponseRedirect(created_author.get_absolute_url()) return render(request, "edit_author.html", { "author_id": author_id, # This author_id is referenced # in template for constructing the posting url via {% url %} tag "author_form": author_form, "formset": formset, }) 

This part is included in your urls.py , assuming the views have been imported and URL patterns are already being generated.

 ... url(r'^add/book/$', views.add_author, name='add_author'), url(r'^edit/(?P<author_id>[\d]+)$', views.edit_author, name='edit_author'), ... 

Now to the part of the templates. The copyright editor object template (edit_author.html) looks like this (without applying a style)

 <form action="{% url 'edit_book' author_id %}" method="POST" > <!-- See above: We're using the author_id that was passed to template via views render of the edit_author(...) function --> {% csrf_token %} <!-- You're dealing with forms. csrf_token must come --> {{ author_form.as_p }} {{ formset.as_p }} <input type="submit" value="submit"> </form> 

To add a brand new Author object through a template (add_author.html):

 <form action="." method="POST" >{% csrf_token %} {{ author_form.as_p }} {{ formset.as_p }} <input type="submit" value="submit"> </form> 

Note:

Using action = '.' may seem like a cheap way to build a URL through which a form submits form data to the same page. In this example, using action = '.' for the edit_author.html template, they always got the form submitted in / edit / instead of / edit / 1 or / edit / 2

Building a URL using {% url 'edit_author' author_id%} ensures that the form is always placed in the correct URL. If you do not use {% url%}, I need many hours and problems.

Thanks a lot to Oneka.

+2
source

I did exactly what you are trying: https://github.com/yakoub/django_training/tree/master/article

you need to create a separate form using the prefix attribute. when saving, you need to iterate over all the books and associate them with the author you just created.

+1
source

Source: https://habr.com/ru/post/985554/


All Articles