第10章:Django管理后台
10.2 高级Admin配置
列表显示自定义
高级列表显示配置可以显著提高管理后台的可用性:
python
# admin.py
from django.contrib import admin
from django.utils.html import format_html
from django.urls import reverse
from django.utils.safestring import mark_safe
from .models import Article, Category, Tag
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
# 基本列表配置
list_display = [
'title', 'author', 'category', 'is_published',
'created_at', 'view_count', 'status_indicator'
]
# 列表每页显示数量
list_per_page = 25
# 列表可编辑字段
list_editable = ['is_published', 'category']
# 可排序字段
sortable_by = ['title', 'created_at', 'author']
# 自定义方法字段
def view_count(self, obj):
"""显示文章浏览量"""
return obj.view_count or 0
view_count.short_description = '浏览量'
view_count.admin_order_field = 'view_count'
def status_indicator(self, obj):
"""显示状态指示器"""
if obj.is_published:
color = '#28a745' # 绿色
text = '已发布'
else:
color = '#ffc107' # 黄色
text = '草稿'
return format_html(
'<span style="background-color: {}; color: white; '
'padding: 4px 8px; border-radius: 4px; font-size: 12px;">{}</span>',
color, text
)
status_indicator.short_description = '状态'
# 带链接的字段
def title_link(self, obj):
"""带编辑链接的标题"""
url = reverse('admin:myapp_article_change', args=[obj.pk])
return format_html('<a href="{}">{}</a>', url, obj.title)
title_link.short_description = '标题'
title_link.admin_order_field = 'title'
# 条件显示字段
def get_list_display(self, request):
"""根据用户权限动态调整显示字段"""
if request.user.is_superuser:
return self.list_display + ['edit_link']
return self.list_display
def edit_link(self, obj):
"""编辑链接"""
url = reverse('admin:myapp_article_change', args=[obj.pk])
return format_html('<a href="{}">编辑</a>', url)
edit_link.short_description = '操作'
# 自定义列表过滤器
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
class PublishedFilter(admin.SimpleListFilter):
title = _('发布状态')
parameter_name = 'published'
def lookups(self, request, model_admin):
return (
('published', _('已发布')),
('draft', _('草稿')),
('recent', _('最近发布')),
)
def queryset(self, request, queryset):
if self.value() == 'published':
return queryset.filter(is_published=True)
if self.value() == 'draft':
return queryset.filter(is_published=False)
if self.value() == 'recent':
from django.utils import timezone
from datetime import timedelta
recent_date = timezone.now() - timedelta(days=7)
return queryset.filter(
is_published=True,
created_at__gte=recent_date
)
return queryset
# 在ArticleAdmin中使用自定义过滤器
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_filter = [
'category', 'tags', 'is_published', 'created_at',
PublishedFilter, 'author'
]过滤器和搜索
配置高级过滤器和搜索功能:
python
# admin.py
from django.contrib import admin
from django.db import models
from django.forms import TextInput, Textarea
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
# 搜索字段
search_fields = [
'title', 'content', 'excerpt',
'author__username', 'author__first_name', 'author__last_name',
'category__name', 'tags__name'
]
# 搜索字段配置
search_help_text = '搜索标题、内容、作者、分类或标签'
# 列表过滤器
list_filter = [
'is_published',
'category',
'tags',
'created_at',
'author',
'updated_at'
]
# 日期层级过滤
date_hierarchy = 'created_at'
# 自定义搜索功能
def get_search_results(self, request, queryset, search_term):
"""自定义搜索逻辑"""
queryset, use_distinct = super().get_search_results(
request, queryset, search_term
)
# 添加自定义搜索逻辑
if search_term:
# 搜索相关标签的文章
queryset |= self.model.objects.filter(
tags__name__icontains=search_term
)
# 搜索作者姓名
queryset |= self.model.objects.filter(
author__first_name__icontains=search_term
) | self.model.objects.filter(
author__last_name__icontains=search_term
)
return queryset, True
# 自定义过滤器
class WordCountFilter(admin.SimpleListFilter):
title = '字数范围'
parameter_name = 'word_count'
def lookups(self, request, model_admin):
return (
('short', '短文章 (< 500字)'),
('medium', '中等文章 (500-2000字)'),
('long', '长文章 (> 2000字)'),
)
def queryset(self, request, queryset):
if self.value() == 'short':
return queryset.filter(content__length__lt=500)
if self.value() == 'medium':
return queryset.filter(
content__length__gte=500,
content__length__lt=2000
)
if self.value() == 'long':
return queryset.filter(content__length__gte=2000)
return queryset
# 在过滤器中使用
list_filter = [
'is_published',
'category',
WordCountFilter, # 自定义过滤器
'created_at'
]内联编辑
内联编辑允许在同一个页面编辑相关模型:
python
# models.py
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE)
author_name = models.CharField(max_length=100)
email = models.EmailField()
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
is_approved = models.BooleanField(default=False)
# admin.py
from django.contrib import admin
from .models import Article, Comment
# 基本内联编辑
class CommentInline(admin.TabularInline):
model = Comment
extra = 1 # 额外显示的空行数
fields = ['author_name', 'email', 'content', 'is_approved', 'created_at']
readonly_fields = ['created_at']
# 堆叠式内联编辑
class CommentStackedInline(admin.StackedInline):
model = Comment
extra = 0
fields = ['author_name', 'email', 'content', 'is_approved']
readonly_fields = ['created_at']
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
inlines = [CommentInline] # 使用内联编辑
list_display = ['title', 'author', 'comment_count']
def comment_count(self, obj):
return obj.comment_set.count()
comment_count.short_description = '评论数'
# 更复杂的内联编辑
class CommentInline(admin.TabularInline):
model = Comment
extra = 0
fields = [
'author_name', 'email', 'content', 'is_approved',
'created_at', 'approve_button'
]
readonly_fields = ['created_at', 'approve_button']
def approve_button(self, obj):
"""批准按钮"""
if obj.pk and not obj.is_approved:
url = reverse('admin:approve_comment', args=[obj.pk])
return format_html(
'<a class="button" href="{}">批准</a>', url
)
elif obj.is_approved:
return '已批准'
return ''
approve_button.short_description = '操作'
# 限制显示的内联对象数量
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related('article')
# 自定义内联编辑行为
class CommentInline(admin.TabularInline):
model = Comment
extra = 1
# 自定义表单
def get_formset(self, request, obj=None, **kwargs):
formset = super().get_formset(request, obj, **kwargs)
# 根据用户权限修改表单
if not request.user.is_superuser:
# 非超级用户不能修改已批准的评论
if 'is_approved' in formset.form.base_fields:
formset.form.base_fields['is_approved'].widget.attrs['disabled'] = True
return formset
# 自定义保存行为
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for instance in instances:
# 自动设置文章作者为当前用户(如果未设置)
if not instance.article.author_id:
instance.article.author = request.user
instance.article.save()
instance.save()
formset.save_m2m()自定义Admin动作
创建自定义管理动作来批量处理对象:
python
# admin.py
from django.contrib import admin
from django.contrib import messages
from django.utils.translation import gettext_lazy as _
from .models import Article
def make_published(modeladmin, request, queryset):
"""发布选中的文章"""
updated = queryset.update(is_published=True)
modeladmin.message_user(
request,
f'成功发布 {updated} 篇文章。',
messages.SUCCESS
)
make_published.short_description = "发布选中的文章"
def make_draft(modeladmin, request, queryset):
"""将选中的文章设为草稿"""
updated = queryset.update(is_published=False)
modeladmin.message_user(
request,
f'成功将 {updated} 篇文章设为草稿。',
messages.INFO
)
make_draft.short_description = "将选中的文章设为草稿"
def duplicate_articles(modeladmin, request, queryset):
"""复制选中的文章"""
for article in queryset:
# 创建文章副本
article.pk = None
article.title = f"{article.title} (副本)"
article.slug = f"{article.slug}-copy"
article.is_published = False
article.save()
modeladmin.message_user(
request,
f'成功复制 {queryset.count()} 篇文章。',
messages.SUCCESS
)
duplicate_articles.short_description = "复制选中的文章"
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'is_published', 'created_at']
actions = [make_published, make_draft, duplicate_articles]
# 自定义动作权限
def get_actions(self, request):
actions = super().get_actions(request)
# 非超级用户不能使用复制功能
if not request.user.is_superuser:
if 'duplicate_articles' in actions:
del actions['duplicate_articles']
return actions
# 条件动作
def get_action_choices(self, request, default_choices=[]):
"""根据条件动态调整动作选项"""
choices = super().get_action_choices(request, default_choices)
if not request.user.is_superuser:
# 移除某些动作
choices = [choice for choice in choices if choice[0] != 'duplicate_articles']
return choices
# 更复杂的自定义动作
def export_selected_articles(modeladmin, 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(['标题', '作者', '分类', '发布时间', '内容'])
for article in queryset:
writer.writerow([
article.title,
article.author.username,
article.category.name if article.category else '',
article.created_at.strftime('%Y-%m-%d %H:%M:%S'),
article.content[:100] + '...' if len(article.content) > 100 else article.content
])
return response
export_selected_articles.short_description = "导出选中的文章为CSV"
# 带确认步骤的动作
def delete_selected_articles(modeladmin, request, queryset):
"""删除选中的文章(带确认)"""
if 'apply' in request.POST:
# 执行删除
count = queryset.count()
queryset.delete()
modeladmin.message_user(
request,
f'成功删除 {count} 篇文章。',
messages.WARNING
)
return HttpResponseRedirect(request.get_full_path())
# 显示确认页面
context = {
'articles': queryset,
'action_checkbox_name': admin.ACTION_CHECKBOX_NAME,
}
return render(request, 'admin/confirm_delete_articles.html', context)
delete_selected_articles.short_description = "删除选中的文章"
# 在ArticleAdmin中使用
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'is_published', 'created_at']
actions = [
make_published,
make_draft,
duplicate_articles,
export_selected_articles,
delete_selected_articles
]通过这些高级配置,你可以创建一个功能强大、用户体验良好的Django管理后台。
小结
Django高级Admin配置提供了丰富的定制选项:
- ✅ 自定义列表显示和过滤器提升管理效率
- ✅ 内联编辑功能简化相关模型操作
- ✅ 自定义管理动作支持批量处理
- ✅ 权限控制确保操作安全性
- ✅ 搜索和筛选功能增强数据查找
掌握高级配置技巧能够构建专业级管理后台。
下一篇
我们将学习静态文件和媒体文件的处理。