Admin 后台
Django Admin 是 Django 最强大的功能之一,它可以根据模型自动生成功能完备的管理后台,让你可以快速管理网站数据。
基本配置
创建管理员账户
python manage.py createsuperuser
按提示输入用户名、邮箱和密码。
访问管理后台
启动开发服务器后,访问 /admin/ 即可进入管理后台。
注册模型
# admin.py
from django.contrib import admin
from .models import Post, Category, Tag
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
pass
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
pass
# 或使用简单注册
admin.site.register(Tag)
ModelAdmin 选项
列表显示
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
# 列表显示字段
list_display = ['title', 'author', 'category', 'status', 'publish', 'views']
# 列表可点击排序字段
list_display_links = ['title']
# 列表可编辑字段
list_editable = ['status']
# 列表过滤
list_filter = ['status', 'category', 'author', 'publish']
# 搜索字段
search_fields = ['title', 'content', 'author__username']
# 日期层级导航
date_hierarchy = 'publish'
# 排序
ordering = ['-publish']
# 每页显示数量
list_per_page = 20
# 显示全部链接的最大数量
list_max_show_all = 100
# 外键自动完成
autocomplete_fields = ['author', 'category']
# 多对多水平显示
filter_horizontal = ['tags']
# 多对多垂直显示
filter_vertical = ['tags']
# 原始 ID 输入(适合大量数据的外键)
raw_id_fields = ['author']
编辑表单
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
# 表单字段
fields = ['title', 'slug', 'content', 'status', 'category', 'tags']
# 字段分组
fieldsets = (
('基本信息', {
'fields': ('title', 'slug', 'content')
}),
('分类和标签', {
'fields': ('category', 'tags'),
'classes': ('collapse',), # 可折叠
}),
('发布设置', {
'fields': ('status', 'author'),
'description': '设置文章的发布状态', # 描述文本
}),
)
# 排除字段
exclude = ['views']
# 只读字段
readonly_fields = ['views', 'publish', 'created', 'updated']
# 保存按钮在顶部
save_on_top = True
# 保存为新的
save_as = True
# 保存为新的继续编辑
save_as_continue = True
# 预填充字段
prepopulated_fields = {'slug': ('title',)}
自定义列表显示
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'category', 'status', 'publish', 'view_count']
def view_count(self, obj):
"""自定义列显示"""
return f'{obj.views:,} 次阅读'
view_count.short_description = '阅读量'
view_count.admin_order_field = 'views' # 可排序
def get_queryset(self, request):
"""自定义查询集"""
queryset = super().get_queryset(request)
return queryset.select_related('author', 'category')
def status_colored(self, obj):
"""带颜色的状态显示"""
from django.utils.html import format_html
colors = {
'draft': 'gray',
'published': 'green',
}
return format_html(
'<span style="color: {};">{}</span>',
colors.get(obj.status, 'black'),
obj.get_status_display()
)
status_colored.short_description = '状态'
内联编辑
内联编辑允许在父模型的编辑页面中编辑相关模型:
TabularInline
表格形式:
class CommentInline(admin.TabularInline):
model = Comment
extra = 1 # 额外空白行数
readonly_fields = ['author', 'content', 'created']
show_change_link = True # 显示编辑链接
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
inlines = [CommentInline]
StackedInline
堆叠形式:
class CommentInline(admin.StackedInline):
model = Comment
extra = 0
fields = ['author', 'content', 'active']
verbose_name = '评论'
verbose_name_plural = '评论列表'
def has_add_permission(self, request, obj=None):
"""禁止添加"""
return False
def has_delete_permission(self, request, obj=None):
"""禁止删除"""
return False
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
inlines = [CommentInline]
自定义操作
批量操作
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
actions = ['make_published', 'make_draft', 'export_as_csv']
def make_published(self, request, queryset):
"""批量发布"""
count = queryset.update(status='published')
self.message_user(request, f'成功发布 {count} 篇文章')
make_published.short_description = '发布所选文章'
def make_draft(self, request, queryset):
"""批量设为草稿"""
count = queryset.update(status='draft')
self.message_user(request, f'成功将 {count} 篇文章设为草稿')
make_draft.short_description = '设为草稿'
def export_as_csv(self, request, queryset):
"""导出为 CSV"""
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(['标题', '作者', '状态', '发布时间'])
for post in queryset:
writer.writerow([
post.title,
post.author.username,
post.get_status_display(),
post.publish.strftime('%Y-%m-%d %H:%M'),
])
return response
export_as_csv.short_description = '导出为 CSV'
自定义操作页面
from django.contrib import admin
from django.shortcuts import render
from django.http import HttpResponseRedirect
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
actions = ['change_status']
def change_status(self, request, queryset):
"""自定义操作页面"""
if 'apply' in request.POST:
status = request.POST.get('status')
count = queryset.update(status=status)
self.message_user(request, f'成功更新 {count} 篇文章状态')
return HttpResponseRedirect(request.get_full_path())
return render(request, 'admin/change_status.html', {
'posts': queryset,
'status_choices': Post.STATUS_CHOICES,
})
change_status.short_description = '更改文章状态'
自定义管理界面
自定义管理模板
在 templates/admin/ 目录下创建自定义模板:
templates/
└── admin/
├── base_site.html # 覆盖基础模板
├── index.html # 首页
├── change_list.html # 列表页
└── change_form.html # 编辑页
自定义站点标题:
<!-- templates/admin/base_site.html -->
{% extends "admin/base_site.html" %}
{% block title %}{{ title }} | 我的网站管理{% endblock %}
{% block branding %}
<h1 id="site-name">
<a href="{% url 'admin:index' %}">我的网站管理后台</a>
</h1>
{% endblock %}
自定义 CSS 和 JS
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
class Media:
css = {
'all': ('css/admin/custom.css',)
}
js = ('js/admin/custom.js',)
自定义站点
# admin.py
from django.contrib import admin
class MyAdminSite(admin.AdminSite):
site_header = '我的网站管理'
site_title = '网站管理'
index_title = '欢迎来到管理后台'
site_url = '/blog/'
my_admin_site = MyAdminSite(name='myadmin')
# urls.py
from django.urls import path
from myapp.admin import my_admin_site
urlpatterns = [
path('myadmin/', my_admin_site.urls),
]
权限控制
基于模型的权限
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
# 查看权限
def has_view_permission(self, request, obj=None):
return True
# 添加权限
def has_add_permission(self, request):
return request.user.is_staff
# 修改权限
def has_change_permission(self, request, obj=None):
if obj is None:
return True
return obj.author == request.user or request.user.is_superuser
# 删除权限
def has_delete_permission(self, request, obj=None):
if obj is None:
return True
return obj.author == request.user or request.user.is_superuser
# 模块权限
def has_module_permission(self, request):
return request.user.is_staff
限制数据访问
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
def get_queryset(self, request):
"""只显示用户自己的文章"""
qs = super().get_queryset(request)
if request.user.is_superuser:
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)
def get_form(self, request, obj=None, **kwargs):
"""根据用户权限修改表单"""
form = super().get_form(request, obj, **kwargs)
if not request.user.is_superuser:
form.base_fields['author'].disabled = True
return form
第三方扩展
django-ckeditor
富文本编辑器:
pip install django-ckeditor
# settings.py
INSTALLED_APPS = [
# ...
'ckeditor',
]
# models.py
from ckeditor.fields import RichTextField
class Post(models.Model):
content = RichTextField()
# admin.py
from ckeditor.widgets import CKEditorWidget
class PostAdmin(admin.ModelAdmin):
formfield_overrides = {
models.TextField: {'widget': CKEditorWidget},
}
django-import-export
数据导入导出:
pip install django-import-export
# admin.py
from import_export.admin import ImportExportModelAdmin
from import_export import resources
class PostResource(resources.ModelResource):
class Meta:
model = Post
fields = ['id', 'title', 'author__username', 'status', 'publish']
@admin.register(Post)
class PostAdmin(ImportExportModelAdmin):
resource_class = PostResource
list_display = ['title', 'author', 'status', 'publish']
django-simple-history
记录数据变更历史:
pip install django-simple-history
# models.py
from simple_history.models import HistoricalRecords
class Post(models.Model):
title = models.CharField(max_length=200)
history = HistoricalRecords()
# admin.py
from simple_history.admin import SimpleHistoryAdmin
@admin.register(Post)
class PostAdmin(SimpleHistoryAdmin):
list_display = ['title', 'status']
history_list_display = ['status']
完整示例
# admin.py
from django.contrib import admin
from django.utils.html import format_html
from django.urls import reverse
from .models import Post, Category, Tag, Comment
class CommentInline(admin.TabularInline):
model = Comment
extra = 0
readonly_fields = ['author', 'content', 'created']
show_change_link = True
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'post_count']
prepopulated_fields = {'slug': ('name',)}
search_fields = ['name']
def post_count(self, obj):
return obj.posts.count()
post_count.short_description = '文章数量'
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ['name', 'slug']
prepopulated_fields = {'slug': ('name',)}
search_fields = ['name']
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = [
'title', 'author_link', 'category_link',
'status_badge', 'publish', 'view_count'
]
list_filter = ['status', 'category', 'author', 'publish']
search_fields = ['title', 'content', 'author__username']
date_hierarchy = 'publish'
ordering = ['-publish']
list_per_page = 20
fieldsets = (
('文章内容', {
'fields': ('title', 'slug', 'content', 'featured_image')
}),
('分类与标签', {
'fields': ('category', 'tags'),
'classes': ('collapse',),
}),
('发布设置', {
'fields': ('status', 'author'),
}),
)
readonly_fields = ['views', 'publish', 'created', 'updated']
prepopulated_fields = {'slug': ('title',)}
filter_horizontal = ['tags']
inlines = [CommentInline]
actions = ['make_published', 'make_draft']
def author_link(self, obj):
url = reverse('admin:auth_user_change', args=[obj.author.pk])
return format_html('<a href="{}">{}</a>', url, obj.author.username)
author_link.short_description = '作者'
author_link.admin_order_field = 'author__username'
def category_link(self, obj):
if obj.category:
url = reverse('admin:blog_category_change', args=[obj.category.pk])
return format_html('<a href="{}">{}</a>', url, obj.category.name)
return '-'
category_link.short_description = '分类'
def status_badge(self, obj):
colors = {'draft': 'secondary', 'published': 'success'}
return format_html(
'<span class="badge bg-{}">{}</span>',
colors.get(obj.status, 'secondary'),
obj.get_status_display()
)
status_badge.short_description = '状态'
status_badge.admin_order_field = 'status'
def view_count(self, obj):
return format_html('{} 次', obj.views)
view_count.short_description = '阅读量'
view_count.admin_order_field = 'views'
def make_published(self, request, queryset):
count = queryset.update(status='published')
self.message_user(request, f'成功发布 {count} 篇文章')
make_published.short_description = '发布所选文章'
def make_draft(self, request, queryset):
count = queryset.update(status='draft')
self.message_user(request, f'成功将 {count} 篇文章设为草稿')
make_draft.short_description = '设为草稿'
def get_queryset(self, request):
return super().get_queryset(request).select_related('author', 'category')
def save_model(self, request, obj, form, change):
if not change:
obj.author = request.user
super().save_model(request, obj, form, change)
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ['author', 'post_link', 'content_preview', 'active', 'created']
list_filter = ['active', 'created']
search_fields = ['author__username', 'content', 'post__title']
actions = ['approve_comments', 'disapprove_comments']
def post_link(self, obj):
url = reverse('admin:blog_post_change', args=[obj.post.pk])
return format_html('<a href="{}">{}</a>', url, obj.post.title)
post_link.short_description = '文章'
def content_preview(self, obj):
return obj.content[:50] + '...' if len(obj.content) > 50 else obj.content
content_preview.short_description = '内容'
def approve_comments(self, request, queryset):
count = queryset.update(active=True)
self.message_user(request, f'成功批准 {count} 条评论')
approve_comments.short_description = '批准所选评论'
def disapprove_comments(self, request, queryset):
count = queryset.update(active=False)
self.message_user(request, f'成功拒绝 {count} 条评论')
disapprove_comments.short_description = '拒绝所选评论'