Difficulty: Intermediate
Inline models allow you to edit related objects on the same page as their parent object. Instead of navigating to a separate page to add or edit related items, you see them inline within the parent's edit form. This is especially useful for one-to-many relationships where the child objects are closely tied to the parent.
Django provides two inline classes: TabularInline displays related objects in a compact table format (one row per object), and StackedInline displays them in a stacked format (one block per object, similar to the standard edit form). TabularInline is better for objects with few fields, while StackedInline is better for objects with many fields.
To use inlines, create a class that inherits from admin.TabularInline or admin.StackedInline, set the 'model' attribute to the related model, and add the inline class to the parent ModelAdmin's 'inlines' list. The related model must have a ForeignKey pointing to the parent model.
The 'extra' attribute controls how many empty forms appear for adding new related objects (default is 3). Set extra=0 if you do not want empty forms by default. 'min_num' sets the minimum number of inline forms. 'max_num' limits the total number of inline forms. 'show_change_link' adds a link to the full change form for each inline object.
You can customize inline fields using the same attributes as ModelAdmin: fields, exclude, readonly_fields, and custom methods. You can also override get_queryset() to filter which related objects appear, and formfield_for_foreignkey() to customize dropdown fields.
from django.contrib import admin
from .models import Order, OrderItem
class OrderItemInline(admin.TabularInline):
model = OrderItem
extra = 1
min_num = 1
fields = ['product', 'quantity', 'unit_price', 'line_total']
readonly_fields = ['line_total']
@admin.display(description='Line Total')
def line_total(self, obj):
if obj.pk:
return f'${obj.quantity * obj.unit_price:.2f}'
return '-'
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ['id', 'customer', 'status', 'total', 'created_at']
inlines = [OrderItemInline]
TabularInline shows OrderItems as rows in a table within the Order edit page. extra=1 shows one empty row for adding items. readonly_fields and custom methods work like in ModelAdmin.
from django.contrib import admin
from .models import Article, Comment
class CommentInline(admin.StackedInline):
model = Comment
extra = 0 # No empty forms
fields = ['author', 'content', 'is_approved', 'created_at']
readonly_fields = ['created_at']
show_change_link = True
def get_queryset(self, request):
"""Show only recent comments."""
qs = super().get_queryset(request)
return qs.order_by('-created_at')[:20]
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'comment_count']
inlines = [CommentInline]
@admin.display(description='Comments')
def comment_count(self, obj):
return obj.comments.count()
StackedInline displays each comment as a block with full field layout. extra=0 means no empty forms appear. show_change_link adds a link to each comment's full edit page. get_queryset limits to recent comments.
from django.contrib import admin
from .models import Product, ProductImage, ProductReview, ProductVariant
class ProductImageInline(admin.TabularInline):
model = ProductImage
extra = 1
max_num = 10
class ProductVariantInline(admin.TabularInline):
model = ProductVariant
extra = 0
fields = ['size', 'color', 'sku', 'stock']
class ProductReviewInline(admin.StackedInline):
model = ProductReview
extra = 0
readonly_fields = ['author', 'rating', 'content', 'created_at']
can_delete = False # Prevent deleting reviews from product page
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['name', 'price', 'is_active']
inlines = [ProductImageInline, ProductVariantInline, ProductReviewInline]
A single parent ModelAdmin can have multiple inlines. Here, the Product edit page shows images (tabular), variants (tabular), and reviews (stacked). can_delete=False prevents deletion of reviews from the inline.
from django.contrib import admin
from django.contrib.contenttypes.admin import GenericTabularInline
from .models import Article, Product, Tag
class TagInline(GenericTabularInline):
model = Tag
extra = 1
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
inlines = [TagInline]
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
inlines = [TagInline] # Same inline reused for different models
GenericTabularInline works with models using Django's contenttypes framework for generic foreign keys. The same Tag inline can be reused across Article and Product admin pages.
TabularInline, StackedInline, inlines, extra, ForeignKey Inline