Working with Inline Models
To manage related models on the same page as a parent model in the Django admin, you use Inlines. This allows you to edit objects with a ForeignKey or ManyToManyField relationship directly within the parent's change form.
Basic Implementation
To add an inline, define a class inheriting from TabularInline or StackedInline and include it in the inlines attribute of your ModelAdmin.
from django.contrib import admin
from .models import Author, Book
# TabularInline displays related objects in a compact, table-like layout
class BookInline(admin.TabularInline):
model = Author.books.through
class AuthorAdmin(admin.ModelAdmin):
inlines = [
BookInline,
]
admin.site.register(Author, AuthorAdmin)
Layout Options
TabularInline: Displays fields in a horizontal table. Best for models with few fields.StackedInline: Displays fields in a vertical stack, similar to the mainModelAdmin. Best for models with many fields or complex layouts.
Customizing Inline Counts
You can control how many empty forms are displayed and the limits on the number of related objects using extra, min_num, and max_num.
class PhotoInline(admin.StackedInline):
model = Photo
extra = 2 # Number of empty forms to display
max_num = 10 # Maximum number of related objects allowed
min_num = 1 # Minimum number of forms that must be filled
Dynamic Counts
If you need to calculate these values based on the specific object being edited, override the get_extra, get_min_num, or get_max_num methods.
class BinaryTreeAdmin(admin.TabularInline):
model = BinaryTree
def get_extra(self, request, obj=None, **kwargs):
extra = 2
if obj:
# Reduce extra forms based on existing related objects
return extra - obj.binarytree_set.count()
return extra
def get_max_num(self, request, obj=None, **kwargs):
max_num = 3
if obj:
return max_num - obj.binarytree_set.count()
return max_num
Organizing Fields with Fieldsets
Inlines support fieldsets just like ModelAdmin, allowing you to group fields or make them collapsible.
class PhotoInline(admin.StackedInline):
model = Photo
fieldsets = [
(None, {
"fields": ["image", "title"],
"description": "Primary Information"
}),
("Details", {
"fields": ["description", "creation_date"],
"classes": ["collapse"], # Makes this section collapsible
"description": "Additional Metadata",
}),
]
Advanced Configuration
Handling Multiple Foreign Keys
If the related model has more than one ForeignKey to the parent model, you must explicitly specify which one to use with fk_name.
class SightingInline(admin.TabularInline):
model = Sighting
fk_name = "location" # Specify which FK points to the parent model
Custom Forms and Validation
You can use a custom ModelForm to add specific validation logic or custom widgets to your inlines.
from django import forms
from django.core.exceptions import ValidationError
class TitleForm(forms.ModelForm):
def clean(self):
cleaned_data = super().clean()
if cleaned_data.get("title1") != cleaned_data.get("title2"):
raise ValidationError("The two titles must be the same")
return cleaned_data
class TitleInline(admin.TabularInline):
model = Title
form = TitleForm
extra = 1
Controlling Permissions
You can restrict actions within the inline by overriding permission methods.
class ReadOnlyChapterInline(admin.TabularInline):
model = Chapter
def has_add_permission(self, request, obj=None):
return False # Prevent adding new chapters from this inline
def has_change_permission(self, request, obj=None):
return False # Make existing chapters read-only
def has_delete_permission(self, request, obj=None):
return False # Prevent deletion
Troubleshooting
Protected Deletions
The InlineModelAdmin uses a specialized DeleteProtectedModelForm (found in django/contrib/admin/options.py) to prevent the deletion of related objects that are protected by database constraints. If you attempt to delete an inline object that has protected relationships, the admin will raise a ValidationError listing the objects that prevent the deletion.
Missing "Add Another" Button
If the "Add another" button is missing, check:
- If
max_numhas been reached. - If the user has
addpermissions for the related model. - If
has_add_permissionhas been overridden to returnFalse. - If the parent model is an auto-created ManyToMany "through" model; in this case, permissions are derived from the parent model's
changepermission.