Skip to main content

Django Admin Forms

Django's admin interface provides a powerful and flexible way to manage your application's data. One of the most useful aspects of the Django admin is the ability to customize forms for creating and editing model instances. In this tutorial, we'll explore how to customize Django admin forms to create more user-friendly and powerful administrative interfaces.

Introduction to Django Admin Forms

When you register a model with the Django admin, it automatically generates forms for creating and editing model instances. These forms are based on your model's fields and their attributes. However, Django provides several ways to customize these forms to fit your specific needs.

The Django admin forms system allows you to:

  • Control which fields appear in the form
  • Customize the appearance and layout of fields
  • Add custom validation
  • Override form behavior
  • Create custom form fields and widgets

Basic Form Customization

Controlling Which Fields Appear

The simplest way to customize your admin forms is by specifying which fields should appear in the form using the fields attribute in your ModelAdmin class.

python
# admin.py
from django.contrib import admin
from .models import Book

class BookAdmin(admin.ModelAdmin):
fields = ('title', 'author', 'publication_date', 'price')

admin.site.register(Book, BookAdmin)

In this example, only the specified fields will appear in the form, and they'll appear in the order listed.

Field Exclusion

Alternatively, you can use the exclude attribute to specify fields that should be excluded from the form:

python
class BookAdmin(admin.ModelAdmin):
exclude = ('created_at', 'updated_at')

This is useful when you want to include most fields but exclude a few specific ones.

Advanced Form Layout with Fieldsets

For models with many fields, you can organize them into fieldsets using the fieldsets attribute:

python
class BookAdmin(admin.ModelAdmin):
fieldsets = (
('Basic Information', {
'fields': ('title', 'author', 'summary')
}),
('Publishing Details', {
'fields': ('publication_date', 'publisher', 'isbn')
}),
('Financial Information', {
'classes': ('collapse',), # This fieldset is collapsible
'fields': ('price', 'discount', 'tax_rate'),
'description': 'Financial details about the book'
}),
)

Each fieldset takes a title and a dictionary that specifies the fields to include. You can also add CSS classes and descriptions to fieldsets.

Customizing Form Fields with ModelForm

For more advanced customization, you can define a custom ModelForm and associate it with your ModelAdmin:

python
from django import forms
from .models import Book

class BookForm(forms.ModelForm):
# Add a custom field that isn't in the model
notes = forms.CharField(required=False, widget=forms.Textarea)

# Customize a model field
title = forms.CharField(help_text="Enter the book title")

class Meta:
model = Book
fields = '__all__' # Include all fields

class BookAdmin(admin.ModelAdmin):
form = BookForm

This approach gives you full control over the form fields, including adding fields that aren't in the model, customizing widgets, and adding help text.

Readonly Fields

You can make certain fields read-only in the admin interface using the readonly_fields attribute:

python
class BookAdmin(admin.ModelAdmin):
readonly_fields = ('created_at', 'isbn')

Inline Editing with Inlines

Django admin also allows you to edit related objects inline. For instance, if you have a Book model with related Chapter models, you can edit chapters directly in the book form:

python
from .models import Book, Chapter

class ChapterInline(admin.TabularInline): # You can also use StackedInline
model = Chapter
extra = 1 # Number of empty forms to display

class BookAdmin(admin.ModelAdmin):
inlines = [ChapterInline]

admin.site.register(Book, BookAdmin)

Custom Validation and Saving

You can add custom validation and saving behavior by overriding methods in your ModelAdmin:

python
class BookAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# Custom logic before saving
obj.last_edited_by = request.user.username
super().save_model(request, obj, form, change)

Real-World Example: Complete Book Management System

Let's put it all together with a comprehensive example of a book management system:

python
# models.py
from django.db import models

class Author(models.Model):
name = models.CharField(max_length=100)
bio = models.TextField(blank=True)

def __str__(self):
return self.name

class Publisher(models.Model):
name = models.CharField(max_length=100)
address = models.TextField()
website = models.URLField(blank=True)

def __str__(self):
return self.name

class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
publication_date = models.DateField()
isbn = models.CharField(max_length=13, unique=True)
pages = models.IntegerField()
price = models.DecimalField(max_digits=8, decimal_places=2)
discount = models.DecimalField(max_digits=4, decimal_places=2, default=0)
summary = models.TextField()
cover_image = models.ImageField(upload_to='book_covers/', blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return self.title

def discounted_price(self):
return self.price * (1 - self.discount)

class Chapter(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='chapters')
title = models.CharField(max_length=100)
number = models.PositiveIntegerField()
content = models.TextField()

class Meta:
ordering = ['number']

def __str__(self):
return f"{self.number}. {self.title}"
python
# admin.py
from django import forms
from django.contrib import admin
from django.utils.html import format_html
from .models import Author, Publisher, Book, Chapter

class AuthorAdmin(admin.ModelAdmin):
list_display = ('name',)
search_fields = ('name',)

class PublisherAdmin(admin.ModelAdmin):
list_display = ('name', 'website_link')
search_fields = ('name',)

def website_link(self, obj):
if obj.website:
return format_html('<a href="{0}" target="_blank">{0}</a>', obj.website)
return "-"
website_link.short_description = 'Website'

class ChapterInline(admin.TabularInline):
model = Chapter
extra = 1
fields = ('number', 'title')

class BookAdminForm(forms.ModelForm):
notes = forms.CharField(widget=forms.Textarea, required=False,
help_text="Administrative notes about this book")

class Meta:
model = Book
fields = '__all__'
widgets = {
'summary': forms.Textarea(attrs={'rows': 4}),
}
help_texts = {
'isbn': 'Enter the 13-digit ISBN without hyphens',
'discount': 'Enter decimal value (e.g., 0.15 for 15% discount)',
}

class BookAdmin(admin.ModelAdmin):
form = BookAdminForm
list_display = ('title', 'author', 'publication_date', 'price', 'display_discount_price')
list_filter = ('publication_date', 'publisher')
search_fields = ('title', 'author__name', 'isbn')
date_hierarchy = 'publication_date'

fieldsets = (
('Book Information', {
'fields': (('title', 'author'), 'summary', 'cover_image')
}),
('Publishing Details', {
'fields': ('publisher', 'publication_date', 'isbn', 'pages')
}),
('Pricing', {
'fields': (('price', 'discount'), 'notes'),
'classes': ('wide',)
}),
)

inlines = [ChapterInline]
readonly_fields = ('created_at', 'updated_at')

def display_discount_price(self, obj):
return f"${obj.discounted_price():.2f}"
display_discount_price.short_description = 'Discounted Price'

def save_model(self, request, obj, form, change):
if not change: # Only when creating new objects
obj.created_by = request.user.username
obj.last_updated_by = request.user.username
super().save_model(request, obj, form, change)

admin.site.register(Author, AuthorAdmin)
admin.site.register(Publisher, PublisherAdmin)
admin.site.register(Book, BookAdmin)

In this comprehensive example, we've implemented:

  1. Custom ModelForm with additional fields and widgets
  2. Organized fields into logical fieldsets
  3. Inline editing for related models
  4. Custom display methods for the list view
  5. Custom save logic to track who created/updated entries
  6. Help text and customized widgets for better user experience

Summary

Django admin forms provide a powerful way to customize the admin interface to match your application's needs. Key techniques include:

  • Controlling field display with fields and exclude
  • Organizing fields with fieldsets
  • Creating custom forms with ModelForm
  • Using readonly_fields for non-editable information
  • Implementing inline editing with InlineModelAdmin
  • Adding custom validation and save behavior

By mastering these techniques, you can create an admin interface that is not only functional but also intuitive and efficient for your team to use.

Additional Resources

Exercises

  1. Create a BlogPost model with related Comment models, and implement an admin interface that allows editing comments inline.
  2. Customize the admin form for a Product model to include multiple fieldsets for basic information, pricing, and inventory details.
  3. Implement a custom ModelForm that adds validation to ensure that a Task model's due date is not in the past.
  4. Create a read-only field in the admin that displays calculated information (e.g., total inventory value based on quantity and price).


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)