Admin
Quick reference for Django's powerful admin interface customization and usage.
🏛️ Basic Admin Setup
Register Models
# admin.py
from django.contrib import admin
from .models import Post, Category, Author
# Simple registration
admin.site.register(Post)
# Custom admin class
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'created_at']
prepopulated_fields = {'slug': ('name',)}
# Alternative registration
class AuthorAdmin(admin.ModelAdmin):
list_display = ['name', 'email', 'post_count']
admin.site.register(Author, AuthorAdmin)
Create Superuser
# Command line
python manage.py createsuperuser
# Programmatically
from django.contrib.auth.models import User
User.objects.create_superuser('admin', 'admin@example.com', 'password')
📋 List Display Customization
List Display Options
class PostAdmin(admin.ModelAdmin):
list_display = [
'title',
'author',
'category',
'published_date',
'is_published',
'view_count'
]
list_display_links = ['title'] # Clickable fields
list_editable = ['is_published'] # Inline editable
list_filter = ['category', 'published_date', 'is_published']
search_fields = ['title', 'content', 'author__name']
date_hierarchy = 'published_date'
ordering = ['-published_date']
list_per_page = 25
Custom List Display Methods
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'colored_status', 'short_content', 'post_count']
def colored_status(self, obj):
if obj.is_published:
return format_html(
'<span style="color: green;">Published</span>'
)
return format_html(
'<span style="color: red;">Draft</span>'
)
colored_status.short_description = 'Status'
def short_content(self, obj):
return obj.content[:50] + '...' if len(obj.content) > 50 else obj.content
short_content.short_description = 'Content Preview'
def post_count(self, obj):
return obj.author.post_set.count()
post_count.short_description = 'Posts by Author'
📝 Form Customization
Field Organization
class PostAdmin(admin.ModelAdmin):
fields = ['title', 'slug', 'author', 'category', 'content', 'is_published']
# Or use fieldsets for better organization
fieldsets = [
('Basic Information', {
'fields': ['title', 'slug', 'author']
}),
('Content', {
'fields': ['content', 'excerpt'],
'classes': ['wide']
}),
('Publishing', {
'fields': ['category', 'tags', 'is_published', 'published_date'],
'classes': ['collapse'] # Collapsible section
}),
('Advanced', {
'fields': ['meta_title', 'meta_description'],
'classes': ['collapse']
})
]
prepopulated_fields = {'slug': ('title',)}
autocomplete_fields = ['author', 'category']
filter_horizontal = ['tags'] # For ManyToMany fields
raw_id_fields = ['author'] # For ForeignKey with many objects
Custom Form Widgets
from django import forms
from django.contrib import admin
class PostAdminForm(forms.ModelForm):
content = forms.CharField(widget=forms.Textarea(attrs={'rows': 20, 'cols': 80}))
class Meta:
model = Post
fields = '__all__'
class PostAdmin(admin.ModelAdmin):
form = PostAdminForm
def formfield_for_dbfield(self, db_field, request, **kwargs):
if db_field.name == 'content':
kwargs['widget'] = forms.Textarea(attrs={'rows': 20})
return super().formfield_for_dbfield(db_field, request, **kwargs)
🔗 Inline Administration
Tabular Inlines
class CommentInline(admin.TabularInline):
model = Comment
extra = 1 # Number of empty forms
max_num = 10
fields = ['author', 'content', 'is_approved']
readonly_fields = ['created_at']
class PostAdmin(admin.ModelAdmin):
inlines = [CommentInline]
def get_inline_instances(self, request, obj=None):
# Conditionally show inlines
if obj and obj.allow_comments:
return super().get_inline_instances(request, obj)
return []
Stacked Inlines
class AuthorProfileInline(admin.StackedInline):
model = AuthorProfile
extra = 0
fieldsets = [
('Personal Info', {
'fields': ['bio', 'website', 'location']
}),
('Social Media', {
'fields': ['twitter', 'linkedin', 'github'],
'classes': ['collapse']
})
]
class AuthorAdmin(admin.ModelAdmin):
inlines = [AuthorProfileInline]
🎨 Advanced Customization
Custom Actions
def make_published(modeladmin, request, queryset):
updated = queryset.update(is_published=True)
modeladmin.message_user(
request,
f'{updated} posts were successfully marked as published.'
)
make_published.short_description = "Mark selected posts as published"
def export_as_csv(modeladmin, request, queryset):
import csv
from django.http import HttpResponse
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="posts.csv"'
writer = csv.writer(response)
writer.writerow(['Title', 'Author', 'Published Date'])
for post in queryset:
writer.writerow([post.title, post.author.name, post.published_date])
return response
export_as_csv.short_description = "Export selected posts as CSV"
class PostAdmin(admin.ModelAdmin):
actions = [make_published, export_as_csv]
def get_actions(self, request):
actions = super().get_actions(request)
# Remove default delete action
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
Custom Views
from django.urls import path
from django.shortcuts import render
from django.http import JsonResponse
class PostAdmin(admin.ModelAdmin):
change_list_template = 'admin/post_change_list.html'
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path('stats/', self.admin_site.admin_view(self.stats_view), name='post_stats'),
path('bulk-update/', self.admin_site.admin_view(self.bulk_update), name='post_bulk_update'),
]
return custom_urls + urls
def stats_view(self, request):
context = {
'total_posts': Post.objects.count(),
'published_posts': Post.objects.filter(is_published=True).count(),
'draft_posts': Post.objects.filter(is_published=False).count(),
}
return render(request, 'admin/post_stats.html', context)
def bulk_update(self, request):
if request.method == 'POST':
# Handle bulk update logic
return JsonResponse({'status': 'success'})
return render(request, 'admin/bulk_update.html')
Custom Templates
<!-- templates/admin/post_change_list.html -->
{% extends "admin/change_list.html" %} {% block content_title %}
<h1>Post Management</h1>
<div class="stats-bar">
<a href="{% url 'admin:post_stats' %}" class="button">View Statistics</a>
<a href="{% url 'admin:post_bulk_update' %}" class="button">Bulk Update</a>
</div>
{% endblock %} {% block result_list %}
<div class="custom-filters">
<!-- Custom filter UI -->
</div>
{{ block.super }} {% endblock %}
🔐 Permissions & Security
Permission Control
class PostAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
# Only staff can add posts
return request.user.is_staff
def has_change_permission(self, request, obj=None):
# Authors can only edit their own posts
if obj and request.user != obj.author:
return False
return super().has_change_permission(request, obj)
def has_delete_permission(self, request, obj=None):
# Only superusers can delete
return request.user.is_superuser
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
# Regular users see only their posts
return qs.filter(author=request.user)
Custom User Admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
class UserAdmin(BaseUserAdmin):
list_display = ['username', 'email', 'first_name', 'last_name', 'is_staff', 'last_login']
list_filter = ['is_staff', 'is_superuser', 'is_active', 'date_joined']
search_fields = ['username', 'first_name', 'last_name', 'email']
ordering = ['-date_joined']
fieldsets = BaseUserAdmin.fieldsets + (
('Custom Fields', {'fields': ('profile_picture', 'phone')}),
)
# Re-register User with custom admin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
📊 Media and File Handling
File Upload Admin
class DocumentAdmin(admin.ModelAdmin):
list_display = ['title', 'file_link', 'file_size', 'uploaded_at']
readonly_fields = ['file_size', 'uploaded_at']
def file_link(self, obj):
if obj.file:
return format_html(
'<a href="{}" target="_blank">Download</a>',
obj.file.url
)
return "No file"
file_link.short_description = 'File'
def file_size(self, obj):
if obj.file:
size = obj.file.size
if size < 1024:
return f"{size} bytes"
elif size < 1024 * 1024:
return f"{size // 1024} KB"
else:
return f"{size // (1024 * 1024)} MB"
return "No file"
Image Preview
from django.utils.html import format_html
class ImageAdmin(admin.ModelAdmin):
list_display = ['title', 'image_preview', 'uploaded_at']
readonly_fields = ['image_preview']
def image_preview(self, obj):
if obj.image:
return format_html(
'<img src="{}" style="max-height: 50px; max-width: 100px;" />',
obj.image.url
)
return "No image"
image_preview.short_description = 'Preview'
🎛️ Site Customization
Admin Site Configuration
# admin.py
from django.contrib import admin
admin.site.site_header = "My Company Admin"
admin.site.site_title = "Admin Portal"
admin.site.index_title = "Welcome to Admin Portal"
# Custom admin site
class MyAdminSite(admin.AdminSite):
site_header = 'Custom Admin'
site_title = 'Custom Admin Portal'
index_title = 'Welcome to Custom Admin'
def index(self, request, extra_context=None):
extra_context = extra_context or {}
extra_context['custom_data'] = {
'total_users': User.objects.count(),
'total_posts': Post.objects.count(),
}
return super().index(request, extra_context)
custom_admin_site = MyAdminSite(name='custom_admin')
custom_admin_site.register(Post, PostAdmin)
Custom CSS and JavaScript
class PostAdmin(admin.ModelAdmin):
class Media:
css = {
'all': ('admin/css/custom.css',)
}
js = ('admin/js/custom.js',)
🔧 Utility Functions
Common Admin Helpers
def admin_url(obj):
"""Generate admin URL for an object"""
from django.urls import reverse
from django.contrib.contenttypes.models import ContentType
content_type = ContentType.objects.get_for_model(obj.__class__)
return reverse(
f"admin:{content_type.app_label}_{content_type.model}_change",
args=[obj.pk]
)
def truncate_chars(text, length=50):
"""Truncate text for admin display"""
if len(text) <= length:
return text
return text[:length-3] + '...'
def format_datetime(dt):
"""Format datetime for admin display"""
return dt.strftime('%Y-%m-%d %H:%M') if dt else '-'
Batch Operations
def bulk_status_update(modeladmin, request, queryset, status):
"""Helper for bulk status updates"""
count = queryset.update(status=status)
modeladmin.message_user(
request,
f'{count} items updated to {status}.'
)
class PostAdmin(admin.ModelAdmin):
def make_published(self, request, queryset):
bulk_status_update(self, request, queryset, 'published')
make_published.short_description = "Publish selected posts"
def make_draft(self, request, queryset):
bulk_status_update(self, request, queryset, 'draft')
make_draft.short_description = "Set as draft"
actions = ['make_published', 'make_draft']
This cheatsheet covers the essential Django admin interface customization techniques for building powerful content management systems.