Django Admin管理后台详解2(转)


转载:https://mp.weixin.qq.com/s?__biz=MjM5OTMyODA4Nw==&mid=2247484136&idx=1&sn=b3f983f56a8d6fe94b3c137db5b9a209&chksm=a73c62d0904bebc6709eacdcdff45bf5b1934036b9b884b8cbbe172c608c0f18336ae1e658a3&scene=178&cur_album_id=1351340083605061635#rd

今天我们来看下django admin的一些高级技巧,比如如何自定义list_display和list_filter, 以及如何重写django admin的save方法和get_queryset方法。

自定义list_display

 

前文中我们已经介绍过django admin的list_display选项不能用于显示多对多的字段(如tags)。如果需要通过list_display选项显示多对多的字段或模型中原本不存在的字段或方法,我们需要新增自定义的list_play方法。

我们现在ArticleAdmin里重新定义一个show_tags方法,该方法有2个变量self和obj。我们先调用obj.tags.all()获取某篇文章obj的所有tags,然后使用join方法将它们连接成字符串。由于show_tags这个名字作为表单的header读起来并不友好,我们可以通过设置该方法的short_description属性来设置表头显示为"标签".

#blog/admin.py

class ArticleAdmin(admin.ModelAdmin):

    '''设置列表可显示的字段'''
    list_display = ('title', 'author',  'status', 'mod_date', 'show_tags')

    '''展示tags'''
    def show_tags(self, obj):
        tag_list = []
        for tag in obj.tags.all():
            tag_list.append(tag.name)
        return ','.join(tag_list)

    show_tags.short_description = '标签'  # 设置表头

显示效果如下:

你注意到上面图片有个问题没有?当标签为空的时候,显示内容为空白。有的时候我们需要设置空白值(empty value)来更好地提示用户,有的时候我们还要以不同颜色显示提示信息。下面我们就要对admin.py做些改进,当文章没有标签时,我们以红色字体显示没有"没有标签"。在下面中我们使用了format_html方法对字符串添加了样式。

#blog/admin.py

from django.contrib import admin
from .models import Article, Category, Tag
from django.utils.html import format_html

# Register your models here.

class ArticleAdmin(admin.ModelAdmin):

    '''设置列表可显示的字段'''
    list_display = ('title', 'author',  'status', 'mod_date', 'show_tags')

    '''展示tags'''
    def show_tags(self, obj):
        tag_list = []
        tags = obj.tags.all()
        if tags:
            for tag in tags:
                tag_list.append(tag.name)
            return ','.join(tag_list)
        else:
            return format_html(
                '文章{}无标签',
                obj.id,)

    '''设置表头'''
    show_tags.short_description = '标签'  # 设置表头
    
admin.site.register(Article, ArticleAdmin)

改进的展示效果如下:

注意:

  • 尽管我们实现了在列表中显示多对多的字段,但在实际项目中我们并不建议这么做,因为这额外地增加了许多数据库查询的工作量。

  • 获取某篇文章obj所对应的tags正确方法是使用obj.tags.all()而不是obj.tags.all, 否则会出现tags is not iterable的错误。你需要了解,obj.tags.all方法仅限于在模板中使用,在admin或view中使用时,千万别忘了加括号。

在本例中我们手动编写了需要显示的empty value。Django实际上是允许我们给所有的空字段设置显示名字的,我们只需要使用empty_value_display选项即可。我们还可以通过设置admin_order_field选项设置需要排序的字段。设置了admin_order_field选项的字段表头会出现一个小三角按钮,用户可以点击三角按钮实现正序或逆序排列。使用例子如下所示:

empty_value_display = "空值"
admin_order_field = ('title', 'mod_date')

自定义list_filter

自定义list_filter也是一个非常有用的Django技术,可以让用户快速找到自己需要查看或编辑的对象。在之前的案例中,我们的list_filter已经实现了按文章状态和发布时间对文章进行过滤。现在我们需要自定义一个过滤器,按文章标题所含的关键词(比如python, django)对所有文章进行过滤, 并显示在右边的过滤条件栏目里。

完整代码如下。我们定义了一个TitleKeywordFilter类,该类继承了admin的SimpleListFilter类。我们设置了过滤器的标题和参数名(keyword), 并通过lookups方法定义了过滤参数元组,并调用queryset方法返回符合查询条件的查询数据集。

from django.contrib import admin
from .models import Article, Category, Tag

class TitleKeywordFilter(admin.SimpleListFilter):
    #  右侧栏人为可读的标题
   title = '标题关键词'

    # 在url中显示的参数名,如?keyword=xxx.
    parameter_name = 'keyword'

    """
    自定义需要筛选的参数元组. 
    """
    def lookups(self, request, model_admin):
        return (
            ('python', '含python文章'),
            ('django', '含django文章'),
             )

    def queryset(self, request, queryset):
        """
        调用self.value()获取url中的参数, 然后筛选所需的queryset.
        """
        if self.value() == 'python':
            return queryset.filter(title__icontains='python')
        if self.value() == 'django':
            return queryset.filter(title__icontains='django')


class ArticleAdmin(admin.ModelAdmin):

    '''设置过滤选项'''
    list_filter = ('status', TitleKeywordFilter, 'pub_date', )

展示效果如下所示, 是不是很帅?

注意:

  • 自定义的Filter类的代码必需放在ModelAdmin类的前面,否则无法使用。

  • 自定义的Filter参数名parameter_name不要使用q和next,这两个参数已作为django admin的默认参数使用了。

重写Django admin的save_model方法

很多时候,我们需要重写Django自带的save_model方法。比如在文章创建时我们希望在后台自动添加作者,而不是允许用户自己选择作者是谁,我们可以选择在创建文章的表单里把作者隐藏,而在后台添加作者。如下所示:

from django.contrib import admin

class ArticleAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        obj.author = request.user
        super().save_model(request, obj, form, change)

在我们世界那么大,我想去看看。Django仿制微信朋友圈九宫格相册(1)一文中我们也展示了save_model方法的重写,该方法作用是允许用户在创建Album对象时,还上传一个zip文件包。上传后对zip文件包进行解压存储,并与每个Image对象想关联。

# album/forms.py

from django import forms
from .models import Album


class AlbumForm(forms.ModelForm):
    class Meta:
        model = Album
        exclude = []

    zip = forms.FileField(required=False)

# album/admin.py

import os
import uuid
import zipfile
from django.contrib import admin
from django.core.files.base import ContentFile
from .models import Album, AlbumImage
from .forms import AlbumForm


@admin.register(Album)
class AlbumModelAdmin(admin.ModelAdmin):
    form = AlbumForm
    prepopulated_fields = {'slug': ('title',)}
    list_display = ('title', 'thumb')
    list_filter = ('create_date',)

    def save_model(self, request, obj, form, change):
        if form.is_valid():
            album = form.save()

            if form.cleaned_data['zip'] is not None:
                zip = zipfile.ZipFile(form.cleaned_data['zip'])
                for filename in sorted(zip.namelist()):

                    file_name = os.path.basename(filename)
                    if not file_name:
                        continue

                    data = zip.read(filename)
                    contentfile = ContentFile(data)

                    img = AlbumImage()
                    img.album = album
                    filename = '{0}{1}.jpg'.format(album.slug[:8], str(uuid.uuid4())[-13:])
                    img.alt = filename
                    img.image.save(filename, contentfile)

                    img.thumb.save('thumb-{0}'.format(filename), contentfile)
                    img.save()
                zip.close()
            super().save_model(request, obj, form, change)

还记得我们Django 2.0 项目实战: 扩展Django自带User模型,实现用户注册与登录中对django的User模型做的扩展吗?我们新建了一个UserProfile模型,其与User是一对一的关系。我们现在希望在admin中创建一个User对象时,也同时创建一个UserProfile对象,这时我们就需要用到save_model方法的重写了。代码如下所示:

#myaccount/admin.py

from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
from .models import UserProfile

admin.site.unregister(User)


class UserProfileInline(admin.StackedInline):
    model = UserProfile
    exclude = ["uid", "join_date", "mod_date"]


class UserAdmin(UserAdmin):
    inlines = [UserProfileInline, ]

    def save_model(self, request, obj, form, change):
        if form.is_valid():
            user = form.save()
            user_profile = UserProfile()
            user_profile.user = user
            user_profile.save()

        super().save_model(request, obj, form, change)


class UserProfileAdmin(admin.ModelAdmin):
    list_display = ('user', 'avatar', 'org', 'join_date')
    exclude = []
    ordering = ('-join_date',)


admin.site.register(User, UserAdmin)
admin.site.register(UserProfile, UserProfileAdmin)

展示效果如下。我们使用了Inlines使UserProfile与User展示在同一页面上。由于我们重写了save_model方法,这样可以自动在创建User时也创建UserProfile,避免了只创建User而未创建UserProfile的错误,这对1对1的关系非常重要。

重写Django admin的get_queryset方法

Django的admin默认会展示所有对象。通过重写get_queryset方法,我们可以控制所需要获取的对象。比如下例中,我们先对用户进行判定,如果用户是超级用户就展示所有文章,如果不是超级用户,我们仅展示用户自己所发表的文章。

class ArticleAdmin(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)

小结

本文总结了django admin的一些高级技巧,比如如何自定义list_display和list_filter, 以及如何重写django admin的save方法和get_queryset方法。这些方法和技巧都非常有用,要熟练掌握哦。下文将是django admin介绍的最后一篇,小编我将介绍下如何美化django-admin,欢迎关注。