Skip to content

第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配置提供了丰富的定制选项:

  1. ✅ 自定义列表显示和过滤器提升管理效率
  2. ✅ 内联编辑功能简化相关模型操作
  3. ✅ 自定义管理动作支持批量处理
  4. ✅ 权限控制确保操作安全性
  5. ✅ 搜索和筛选功能增强数据查找

掌握高级配置技巧能够构建专业级管理后台。

下一篇

我们将学习静态文件和媒体文件的处理。

11.1 静态文件管理 →

目录

返回课程目录

Released under the Apache 2.0 License.