Django Admin 后台管理
2026/3/20大约 11 分钟
Django Admin 后台管理
一、Admin 基础
1.1 启用 Admin
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# ...
]
# urls.py
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
# 创建超级用户
python manage.py createsuperuser
1.2 注册模型
# blog/admin.py
from django.contrib import admin
from .models import Article, Category, Tag
# 方式一:简单注册
admin.site.register(Article)
admin.site.register(Category)
admin.site.register(Tag)
# 方式二:使用装饰器(推荐)
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
pass
# 方式三:分开定义
class ArticleAdmin(admin.ModelAdmin):
pass
admin.site.register(Article, ArticleAdmin)
1.3 自定义 Admin 站点
# admin.py
from django.contrib import admin
# 自定义站点信息
admin.site.site_header = 'MySite 管理后台'
admin.site.site_title = 'MySite'
admin.site.index_title = '欢迎使用 MySite 管理后台'
# 或创建自定义 AdminSite
class MyAdminSite(admin.AdminSite):
site_header = 'MySite 管理后台'
site_title = 'MySite'
index_title = '欢迎使用管理后台'
def get_app_list(self, request, app_label=None):
"""自定义应用排序"""
app_list = super().get_app_list(request, app_label)
# 自定义排序逻辑
return app_list
my_admin_site = MyAdminSite(name='myadmin')
# urls.py
from .admin import my_admin_site
urlpatterns = [
path('admin/', my_admin_site.urls),
]
二、ModelAdmin 配置
2.1 列表页配置
from django.contrib import admin
from django.utils.html import format_html
from .models import Article
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
# 列表显示的字段
list_display = [
'id', 'title', 'author', 'category',
'status', 'views', 'created_at', 'is_featured'
]
# 可点击进入编辑的字段
list_display_links = ['id', 'title']
# 可编辑的字段
list_editable = ['status', 'is_featured']
# 过滤器
list_filter = [
'status',
'category',
'is_featured',
('created_at', admin.DateFieldListFilter),
('author', admin.RelatedOnlyFieldListFilter),
]
# 搜索字段
search_fields = [
'title',
'content',
'author__username',
'author__email',
]
# 日期层级导航
date_hierarchy = 'created_at'
# 排序
ordering = ['-created_at', '-id']
# 每页显示数量
list_per_page = 20
list_max_show_all = 200
# 显示数量选择器
show_full_result_count = True
# 空值显示
empty_value_display = '-'
# 保留过滤器参数
preserve_filters = True
2.2 自定义列表字段
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'author_link', 'status_badge', 'cover_preview', 'word_count']
@admin.display(description='作者', ordering='author__username')
def author_link(self, obj):
"""带链接的作者名"""
from django.urls import reverse
url = reverse('admin:auth_user_change', args=[obj.author.id])
return format_html('<a href="{}">{}</a>', url, obj.author.username)
@admin.display(description='状态')
def status_badge(self, obj):
"""状态徽章"""
colors = {
'draft': 'gray',
'pending': 'orange',
'published': 'green',
}
color = colors.get(obj.status, 'gray')
return format_html(
'<span style="background:{}; color:white; padding:2px 8px; border-radius:4px;">{}</span>',
color,
obj.get_status_display()
)
@admin.display(description='封面')
def cover_preview(self, obj):
"""封面预览"""
if obj.cover:
return format_html(
'<img src="{}" style="width:50px; height:50px; object-fit:cover;"/>',
obj.cover.url
)
return '-'
@admin.display(description='字数')
def word_count(self, obj):
"""字数统计"""
return len(obj.content)
2.3 编辑页配置
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
# 编辑页显示的字段
fields = ['title', 'slug', 'content', 'category', 'tags', 'status']
# 或使用 exclude 排除字段
# exclude = ['created_at', 'updated_at']
# 只读字段
readonly_fields = ['created_at', 'updated_at', 'views', 'slug']
# 字段分组
fieldsets = [
('基本信息', {
'fields': ['title', 'slug', 'author'],
}),
('内容', {
'fields': ['content', 'excerpt'],
'classes': ['wide'], # wide, collapse, extrapretty
}),
('分类与标签', {
'fields': ['category', 'tags'],
}),
('发布设置', {
'fields': ['status', 'is_featured', 'published_at'],
'classes': ['collapse'],
}),
('SEO 设置', {
'fields': ['meta_title', 'meta_description', 'meta_keywords'],
'classes': ['collapse'],
'description': '搜索引擎优化相关设置',
}),
('统计信息', {
'fields': ['views', 'created_at', 'updated_at'],
'classes': ['collapse'],
}),
]
# 水平显示多选字段
filter_horizontal = ['tags']
# 或垂直显示
# filter_vertical = ['tags']
# 外键选择使用原始 ID 输入
raw_id_fields = ['author']
# 自动填充字段
prepopulated_fields = {'slug': ('title',)}
# 保存按钮位置
save_on_top = True
2.4 自定义表单
from django import forms
from django.contrib import admin
from .models import Article
class ArticleAdminForm(forms.ModelForm):
"""自定义 Admin 表单"""
content = forms.CharField(
widget=forms.Textarea(attrs={'class': 'vLargeTextField', 'rows': 20})
)
class Meta:
model = Article
fields = '__all__'
def clean_title(self):
title = self.cleaned_data.get('title')
if len(title) < 5:
raise forms.ValidationError('标题至少需要5个字符')
return title
def clean(self):
cleaned_data = super().clean()
status = cleaned_data.get('status')
content = cleaned_data.get('content')
if status == 'published' and len(content) < 100:
raise forms.ValidationError('发布的文章内容至少需要100个字符')
return cleaned_data
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
form = ArticleAdminForm
def get_form(self, request, obj=None, **kwargs):
"""动态修改表单"""
form = super().get_form(request, obj, **kwargs)
# 根据用户权限修改字段
if not request.user.is_superuser:
form.base_fields['status'].choices = [
('draft', '草稿'),
('pending', '待审核'),
]
return form
三、内联编辑(Inline)
3.1 基础内联
from django.contrib import admin
from .models import Author, Book, Chapter
class BookInline(admin.TabularInline): # 或 StackedInline
model = Book
extra = 1 # 额外的空表单数量
min_num = 0 # 最小数量
max_num = 10 # 最大数量
can_delete = True
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
inlines = [BookInline]
# 多对多关系的内联
class ArticleTagInline(admin.TabularInline):
model = Article.tags.through
extra = 1
3.2 高级内联配置
class ChapterInline(admin.TabularInline):
model = Chapter
extra = 0
fields = ['order', 'title', 'word_count', 'is_published']
readonly_fields = ['word_count']
ordering = ['order']
# 自定义表单
formfield_overrides = {
models.TextField: {'widget': forms.Textarea(attrs={'rows': 2})},
}
# 自定义查询集
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related('book')
# 是否显示添加链接
show_change_link = True
# 自定义权限
def has_add_permission(self, request, obj=None):
return request.user.has_perm('books.add_chapter')
def has_delete_permission(self, request, obj=None):
return request.user.is_superuser
class CommentInline(admin.StackedInline):
model = Comment
extra = 0
fields = ['author', 'content', 'created_at', 'is_approved']
readonly_fields = ['created_at']
# 折叠显示
classes = ['collapse']
# 详细模式模板
verbose_name = '评论'
verbose_name_plural = '评论列表'
3.3 嵌套内联
# 使用 django-nested-admin 实现嵌套内联
# pip install django-nested-admin
from nested_admin import NestedStackedInline, NestedModelAdmin
class SectionInline(NestedStackedInline):
model = Section
extra = 0
class ChapterInline(NestedStackedInline):
model = Chapter
extra = 0
inlines = [SectionInline]
@admin.register(Book)
class BookAdmin(NestedModelAdmin):
inlines = [ChapterInline]
四、Admin Actions
4.1 自定义操作
from django.contrib import admin, messages
from django.utils import timezone
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
actions = ['make_published', 'make_draft', 'export_as_csv']
@admin.action(description='将选中文章标记为已发布')
def make_published(self, request, queryset):
updated = queryset.update(
status='published',
published_at=timezone.now()
)
self.message_user(
request,
f'成功发布 {updated} 篇文章',
messages.SUCCESS
)
@admin.action(description='将选中文章标记为草稿')
def make_draft(self, request, queryset):
updated = queryset.update(status='draft')
self.message_user(request, f'已将 {updated} 篇文章设为草稿')
@admin.action(description='导出为 CSV')
def export_as_csv(self, request, queryset):
import csv
from django.http import HttpResponse
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="articles.csv"'
writer = csv.writer(response)
writer.writerow(['ID', '标题', '作者', '状态', '创建时间'])
for article in queryset:
writer.writerow([
article.id,
article.title,
article.author.username,
article.get_status_display(),
article.created_at.strftime('%Y-%m-%d %H:%M'),
])
return response
4.2 带确认页面的操作
from django.contrib.admin import helpers
from django.template.response import TemplateResponse
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
actions = ['delete_with_confirmation']
@admin.action(description='删除文章(带确认)')
def delete_with_confirmation(self, request, queryset):
if 'confirm' in request.POST:
# 执行删除
deleted = queryset.delete()[0]
self.message_user(request, f'已删除 {deleted} 篇文章')
return None
# 显示确认页面
context = {
'title': '确认删除',
'queryset': queryset,
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
}
return TemplateResponse(
request,
'admin/article_delete_confirmation.html',
context
)
<!-- templates/admin/article_delete_confirmation.html -->
{% extends "admin/base_site.html" %} {% block content %}
<h1>确认删除</h1>
<p>您确定要删除以下 {{ queryset.count }} 篇文章吗?</p>
<ul>
{% for article in queryset %}
<li>{{ article.title }}</li>
{% endfor %}
</ul>
<form method="post">
{% csrf_token %} {% for article in queryset %}
<input
type="hidden"
name="{{ action_checkbox_name }}"
value="{{ article.pk }}"
/>
{% endfor %}
<input type="hidden" name="action" value="delete_with_confirmation" />
<input type="hidden" name="confirm" value="1" />
<button type="submit" class="default">确认删除</button>
<a href="{% url 'admin:blog_article_changelist' %}">取消</a>
</form>
{% endblock %}
4.3 权限控制操作
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
def get_actions(self, request):
"""根据权限过滤操作"""
actions = super().get_actions(request)
if not request.user.has_perm('blog.publish_article'):
if 'make_published' in actions:
del actions['make_published']
if not request.user.is_superuser:
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
五、过滤器
5.1 自定义过滤器
from django.contrib.admin import SimpleListFilter
class StatusFilter(SimpleListFilter):
title = '状态'
parameter_name = 'status'
def lookups(self, request, model_admin):
return [
('published', '已发布'),
('draft', '草稿'),
('pending', '待审核'),
('archived', '已归档'),
]
def queryset(self, request, queryset):
if self.value():
return queryset.filter(status=self.value())
return queryset
class DateRangeFilter(SimpleListFilter):
title = '创建时间'
parameter_name = 'created'
def lookups(self, request, model_admin):
return [
('today', '今天'),
('week', '本周'),
('month', '本月'),
('year', '今年'),
]
def queryset(self, request, queryset):
from datetime import timedelta
from django.utils import timezone
now = timezone.now()
if self.value() == 'today':
return queryset.filter(created_at__date=now.date())
elif self.value() == 'week':
start = now - timedelta(days=7)
return queryset.filter(created_at__gte=start)
elif self.value() == 'month':
return queryset.filter(
created_at__year=now.year,
created_at__month=now.month
)
elif self.value() == 'year':
return queryset.filter(created_at__year=now.year)
return queryset
class HasCoverFilter(SimpleListFilter):
title = '封面图'
parameter_name = 'has_cover'
def lookups(self, request, model_admin):
return [
('yes', '有封面'),
('no', '无封面'),
]
def queryset(self, request, queryset):
if self.value() == 'yes':
return queryset.exclude(cover='').exclude(cover__isnull=True)
elif self.value() == 'no':
return queryset.filter(
models.Q(cover='') | models.Q(cover__isnull=True)
)
return queryset
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_filter = [StatusFilter, DateRangeFilter, HasCoverFilter, 'category']
5.2 关联模型过滤器
class AuthorFilter(admin.RelatedFieldListFilter):
"""只显示有文章的作者"""
def field_choices(self, field, request, model_admin):
from django.contrib.auth import get_user_model
User = get_user_model()
authors = User.objects.filter(
articles__isnull=False
).distinct()
return [(author.pk, str(author)) for author in authors]
class CategoryFilter(SimpleListFilter):
title = '分类'
parameter_name = 'category'
def lookups(self, request, model_admin):
# 只显示有文章的分类
categories = Category.objects.annotate(
article_count=Count('articles')
).filter(article_count__gt=0)
return [(c.pk, f'{c.name} ({c.article_count})') for c in categories]
def queryset(self, request, queryset):
if self.value():
return queryset.filter(category_id=self.value())
return queryset
六、权限管理
6.1 模型权限
class Article(models.Model):
# ...
class Meta:
permissions = [
('publish_article', '可以发布文章'),
('feature_article', '可以推荐文章'),
('view_statistics', '可以查看统计'),
]
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
return request.user.has_perm('blog.add_article')
def has_change_permission(self, request, obj=None):
if obj is None:
return request.user.has_perm('blog.change_article')
# 只能编辑自己的文章
return obj.author == request.user or request.user.is_superuser
def has_delete_permission(self, request, obj=None):
if obj is None:
return request.user.has_perm('blog.delete_article')
return obj.author == request.user or request.user.is_superuser
def has_view_permission(self, request, obj=None):
return True
def has_module_permission(self, request):
return request.user.is_staff
6.2 字段级权限
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
"""根据权限设置只读字段"""
readonly = list(super().get_readonly_fields(request, obj))
if not request.user.is_superuser:
readonly.extend(['author', 'created_at'])
if obj and obj.status == 'published':
readonly.append('slug')
return readonly
def get_fields(self, request, obj=None):
"""根据权限显示字段"""
fields = list(super().get_fields(request, obj))
if not request.user.has_perm('blog.view_statistics'):
fields = [f for f in fields if f not in ['views', 'likes']]
return fields
def get_fieldsets(self, request, obj=None):
"""根据权限显示字段集"""
fieldsets = [
('基本信息', {'fields': ['title', 'slug', 'content']}),
('分类', {'fields': ['category', 'tags']}),
]
if request.user.has_perm('blog.publish_article'):
fieldsets.append(
('发布设置', {'fields': ['status', 'published_at']})
)
if request.user.is_superuser:
fieldsets.append(
('高级设置', {'fields': ['author', 'is_featured']})
)
return fieldsets
6.3 查询集权限
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
def get_queryset(self, request):
"""根据用户过滤数据"""
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
if request.user.groups.filter(name='编辑').exists():
return qs
# 普通用户只能看自己的文章
return qs.filter(author=request.user)
def save_model(self, request, obj, form, change):
"""保存时设置作者"""
if not change: # 新建时
obj.author = request.user
super().save_model(request, obj, form, change)
七、自定义视图
7.1 添加自定义页面
from django.contrib import admin
from django.urls import path
from django.template.response import TemplateResponse
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path(
'statistics/',
self.admin_site.admin_view(self.statistics_view),
name='article-statistics'
),
path(
'<int:pk>/preview/',
self.admin_site.admin_view(self.preview_view),
name='article-preview'
),
]
return custom_urls + urls
def statistics_view(self, request):
"""统计页面"""
from django.db.models import Count, Sum
from django.db.models.functions import TruncMonth
# 统计数据
monthly_stats = Article.objects.annotate(
month=TruncMonth('created_at')
).values('month').annotate(
count=Count('id'),
total_views=Sum('views')
).order_by('month')
context = {
**self.admin_site.each_context(request),
'title': '文章统计',
'monthly_stats': monthly_stats,
'total_articles': Article.objects.count(),
'total_views': Article.objects.aggregate(Sum('views'))['views__sum'],
}
return TemplateResponse(
request,
'admin/blog/article/statistics.html',
context
)
def preview_view(self, request, pk):
"""预览页面"""
article = self.get_object(request, pk)
context = {
**self.admin_site.each_context(request),
'article': article,
}
return TemplateResponse(
request,
'admin/blog/article/preview.html',
context
)
def changelist_view(self, request, extra_context=None):
"""自定义列表视图"""
extra_context = extra_context or {}
extra_context['statistics_url'] = 'admin:article-statistics'
return super().changelist_view(request, extra_context)
7.2 自定义按钮
from django.utils.html import format_html
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'status', 'action_buttons']
change_list_template = 'admin/blog/article/change_list.html'
@admin.display(description='操作')
def action_buttons(self, obj):
return format_html(
'<a class="button" href="{}">预览</a> '
'<a class="button" href="{}">复制</a>',
f'{obj.pk}/preview/',
f'{obj.pk}/copy/',
)
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path(
'<int:pk>/copy/',
self.admin_site.admin_view(self.copy_article),
name='article-copy'
),
]
return custom_urls + urls
def copy_article(self, request, pk):
"""复制文章"""
original = self.get_object(request, pk)
new_article = Article.objects.create(
title=f'{original.title} (副本)',
content=original.content,
author=request.user,
category=original.category,
status='draft',
)
new_article.tags.set(original.tags.all())
self.message_user(request, '文章复制成功')
return redirect('admin:blog_article_change', new_article.pk)
<!-- templates/admin/blog/article/change_list.html -->
{% extends "admin/change_list.html" %} {% block object-tools-items %}
<li>
<a href="{% url 'admin:article-statistics' %}" class="button"> 查看统计 </a>
</li>
{{ block.super }} {% endblock %}
八、富文本编辑器
8.1 集成 CKEditor
# pip install django-ckeditor
# settings.py
INSTALLED_APPS = [
...
'ckeditor',
'ckeditor_uploader',
]
CKEDITOR_UPLOAD_PATH = 'uploads/'
CKEDITOR_CONFIGS = {
'default': {
'toolbar': 'full',
'height': 400,
'width': '100%',
'extraPlugins': ','.join([
'codesnippet',
'uploadimage',
]),
},
'simple': {
'toolbar': 'Basic',
'height': 200,
},
}
# urls.py
urlpatterns = [
path('ckeditor/', include('ckeditor_uploader.urls')),
]
# models.py
from ckeditor.fields import RichTextField
from ckeditor_uploader.fields import RichTextUploadingField
class Article(models.Model):
content = RichTextUploadingField('内容')
# admin.py
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
pass # 自动使用富文本编辑器
8.2 集成 TinyMCE
# pip install django-tinymce
# settings.py
INSTALLED_APPS = [
...
'tinymce',
]
TINYMCE_DEFAULT_CONFIG = {
'height': 400,
'width': '100%',
'menubar': True,
'plugins': 'advlist autolink lists link image charmap preview anchor '
'searchreplace visualblocks code fullscreen insertdatetime '
'media table paste code help wordcount',
'toolbar': 'undo redo | formatselect | bold italic backcolor | '
'alignleft aligncenter alignright alignjustify | '
'bullist numlist outdent indent | removeformat | help',
}
# admin.py
from django import forms
from tinymce.widgets import TinyMCE
class ArticleAdminForm(forms.ModelForm):
content = forms.CharField(widget=TinyMCE())
class Meta:
model = Article
fields = '__all__'
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
form = ArticleAdminForm
九、高级功能
9.1 自动完成
# 使用 django-autocomplete-light
# pip install django-autocomplete-light
from dal import autocomplete
class AuthorAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
from django.contrib.auth import get_user_model
User = get_user_model()
if not self.request.user.is_authenticated:
return User.objects.none()
qs = User.objects.all()
if self.q:
qs = qs.filter(username__icontains=self.q)
return qs
# urls.py
urlpatterns = [
path('author-autocomplete/', AuthorAutocomplete.as_view(), name='author-autocomplete'),
]
# admin.py
from dal import autocomplete
class ArticleAdminForm(forms.ModelForm):
class Meta:
model = Article
fields = '__all__'
widgets = {
'author': autocomplete.ModelSelect2(url='author-autocomplete'),
}
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
form = ArticleAdminForm
9.2 拖拽排序
# 使用 django-admin-sortable2
# pip install django-admin-sortable2
from adminsortable2.admin import SortableAdminMixin, SortableInlineAdminMixin
class MenuItem(models.Model):
order = models.PositiveIntegerField(default=0)
name = models.CharField(max_length=100)
class Meta:
ordering = ['order']
@admin.register(MenuItem)
class MenuItemAdmin(SortableAdminMixin, admin.ModelAdmin):
list_display = ['name', 'order']
# 内联排序
class ChapterInline(SortableInlineAdminMixin, admin.TabularInline):
model = Chapter
extra = 0
9.3 导入导出
# 使用 django-import-export
# pip install django-import-export
from import_export import resources
from import_export.admin import ImportExportModelAdmin
class ArticleResource(resources.ModelResource):
class Meta:
model = Article
fields = ['id', 'title', 'author__username', 'category__name', 'status', 'created_at']
export_order = fields
def before_import_row(self, row, **kwargs):
# 导入前处理
pass
def after_import_row(self, row, row_result, **kwargs):
# 导入后处理
pass
@admin.register(Article)
class ArticleAdmin(ImportExportModelAdmin):
resource_class = ArticleResource
list_display = ['title', 'author', 'status']
9.4 历史记录
# 使用 django-simple-history
# pip install django-simple-history
from simple_history.admin import SimpleHistoryAdmin
@admin.register(Article)
class ArticleAdmin(SimpleHistoryAdmin):
list_display = ['title', 'author', 'status']
history_list_display = ['status', 'changed_by']
十、性能优化
10.1 查询优化
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'category', 'tag_count']
list_select_related = ['author', 'category'] # 优化外键查询
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related(
'author', 'category'
).prefetch_related(
'tags'
).annotate(
tag_count=Count('tags')
)
@admin.display(description='标签数')
def tag_count(self, obj):
return obj.tag_count
10.2 列表页优化
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
# 禁用全部计数(大数据量时)
show_full_result_count = False
# 限制每页数量
list_per_page = 25
# 使用简单的过滤器
list_filter = ['status', 'category'] # 避免复杂过滤器
# 限制搜索字段
search_fields = ['title'] # 避免搜索大文本字段
10.3 编辑页优化
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
# 使用 raw_id_fields 替代下拉框
raw_id_fields = ['author']
# 自动完成(Django 4.0+)
autocomplete_fields = ['category']
# 限制内联数量
class ChapterInline(admin.TabularInline):
model = Chapter
extra = 0
max_num = 20
inlines = [ChapterInline]
十一、自定义主题
11.1 覆盖模板
templates/
└── admin/
├── base_site.html # 基础模板
├── index.html # 首页
├── login.html # 登录页
└── blog/
└── article/
├── change_list.html # 列表页
├── change_form.html # 编辑页
└── add_form.html # 添加页
<!-- templates/admin/base_site.html -->
{% extends "admin/base.html" %} {% block title %}{{ title }} | MySite 管理后台{%
endblock %} {% block branding %}
<h1 id="site-name">
<a href="{% url 'admin:index' %}">
<img
src="{% static 'images/logo.png' %}"
alt="Logo"
style="height: 40px;"
/>
MySite 管理后台
</a>
</h1>
{% endblock %} {% block extrastyle %}
<link rel="stylesheet" href="{% static 'admin/css/custom.css' %}" />
{% endblock %} {% block footer %}
<div id="footer">
<p>MySite 管理后台 v1.0 | © 2024</p>
</div>
{% endblock %}
11.2 自定义 CSS
/* static/admin/css/custom.css */
/* 主题色 */
:root {
--primary: #2c3e50;
--secondary: #3498db;
--accent: #e74c3c;
}
/* 头部 */
#header {
background: var(--primary);
}
#branding h1 a {
color: white;
}
/* 模块 */
.module h2 {
background: var(--secondary);
}
/* 按钮 */
.button,
input[type="submit"],
input[type="button"] {
background: var(--secondary);
border-radius: 4px;
}
/* 链接 */
a:link,
a:visited {
color: var(--secondary);
}
/* 表格 */
#result_list th {
background: var(--primary);
}
#result_list tr:nth-child(odd) {
background: #f9f9f9;
}
11.3 使用第三方主题
# django-jazzmin
# pip install django-jazzmin
INSTALLED_APPS = [
'jazzmin', # 必须在 admin 之前
'django.contrib.admin',
...
]
JAZZMIN_SETTINGS = {
'site_title': 'MySite Admin',
'site_header': 'MySite',
'site_brand': 'MySite',
'welcome_sign': '欢迎使用管理后台',
'copyright': 'MySite',
'topmenu_links': [
{'name': '首页', 'url': 'admin:index'},
{'name': '查看网站', 'url': '/', 'new_window': True},
],
'show_sidebar': True,
'navigation_expanded': True,
'icons': {
'auth': 'fas fa-users-cog',
'auth.user': 'fas fa-user',
'auth.Group': 'fas fa-users',
'blog.Article': 'fas fa-newspaper',
},
}