Inline formset with ManyToMany field

Hello there,
Im relatively new to Django and i want to create a form with an inline formset, in particular an Author view, with a list for Books. I only found documentation about the ForeignKey case but no the M2M case. In the model Book, if i have a FK relation to Author it works, however when i have a M2M i get : ValueError: 'Book' has no foreign key to 'Author'. when starting my server.

Probably I’m missing something. My code is the following if it helps.

# models.py
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    authors = models.ManyToManyField(Author)
# forms.py
from django import forms

from .models import Author, Book

class BookForm(forms.ModelForm):
    class Meta:
        model = Book
        fields = ['title', 'authors']

BookFormSet = forms.inlineformset_factory(Author, Book, form=BookForm, exclude=())

Im using Python 3.12 and Django 5.1.6 if it helps.

Thanks in advance.
Pablo

Hey Pablo,

You’re running into this error because inlineformset_factory is designed for ForeignKey relationships, not ManyToManyField. Since Book doesn’t have a direct ForeignKey to Author, Django doesn’t know how to create the inline formset.

For handling ManyToManyField in forms, you can do something like this:

  1. Use a regular ModelForm with a ModelMultipleChoiceField for selecting multiple authors.
  2. If you need inline-like behavior, consider using a through model (an intermediate model for the M2M relation) and then create an inline formset for that model.

A quick example using a through model:

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    authors = models.ManyToManyField(Author, through='BookAuthor')

class BookAuthor(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    book = models.ForeignKey(Book, on_delete=models.CASCADE)