/django-nested-inline-formsets-example

An example of how to use Django inline formsets, each of which contains its own inline formset

Primary LanguagePythonMIT LicenseMIT

Django nested inline formsets example

This Django project is purely to demonstrate an example of how to create a form that contains inline formsets that each contains its own inline formset.

It runs in Django 4.0 using Python 3.9.

I'm indebted to this blogpost by Ravi Kumar Gadila for helping me figure this out.

The situation

We have a model describing Publishers. Each Publisher can have a number of Books. Each Book can have a number of BookImages (e.g. its cover, back cover, illustrations, etc):

Publisher #1
  |-Book
  |   |-BookImage
  |   |-BookImage
  |
  |-Book
      |-BookImage

Publisher #2
  |-Book
  |
  |-Book

See these in models.py.

Using an inline formset we could display a single form that would let the user edit all of the Books belonging to a single Publisher.

Using another inline formset we could display another form that would let the user edit all of the BookImages belonging to a single Book.

It becomes trickier if we want to combine these two forms into one: displaying all of the Books for a Publisher, and for each Book, all of its BookImages.

Solution

You can see in forms.py how we construct an inline formset, BookImageFormset for editing the BookImages belonging to a single Book.

And then we create a custom BaseBooksWithImagesFormset that has a custom nested property. This contains our BookImageFormset. We add custom methods for is_valid() and save() to ensure the data in these nested formsets are validated and saved.

Finally we create our PublisherBooksWithImagesFormset which is for editing all the Books belonging to a Publisher... and we pass it this argument: formset=BaseBooksWithImagesFormset so it knows how to handle each of the Books' BookImages.

See views.py for how we use this in a class-based view to create the page. This expects the id of a Publisher. And see the books/publisher_books_update.html template for how the outer form, and its Book formsets, and their nested BookImage formsets, are rendered.

Here's an image showing how that page looks:

Set-up

If you want to get this project running to see how it works...

  1. Download or clone the repository.

  2. Install Django and Pillow (required for the ImageField). For example, using pip with the requirements.txt file:

    pip install -r requirements.txt
    

    Or using pipenv with the Pipfiles:

    pipenv install
    

    (If using pipenv, enter the virtual environment before running the following commands, by doing pipenv shell)

  3. Run the migrations:

    ./manage.py migrate
    
  4. Create a superuser if you want to use the Django Admin:

    ./manage.py createsuperuser
    
  5. Run the development server:

    ./manage.py runserver
    
  6. View the site at http://127.0.0.1:8000/ and add at least one Publisher.

  7. You can then click the link to add some Books to your Publisher. You'll then be on a page like http://127.0.0.1:8000/publishers/1/books/edit/ which is the form with its inline formsets.

Thanks