Django自带的admin管理后台简直是个bug级的存在, 几行代码就可以撸出一个功能齐全的管理后台来。小编我今天就带你看下Django自带的admin有哪些高级用法值得掌握。本文建议先收藏再阅读。

我们将以博客为例,创建一个名为blog的app, 再创建一个文章(Article)模型,如下所示:

我们先在blog目录下创建admin.py,添加如下代码。这些都是最常见设置,我这里就不详述了。

from django.contrib import admin
from .models import Article
from django.utils.html import format_html


class ArticleAdmin(admin.ModelAdmin):
    # Custom admin list view
    list_display = ('title', 'author', 'status', 'create_date', )
    # list_display_links = ('title', ) # default
    # sortable_by # a sub set of list_display. All fields are sortable by default.


    '''define which fields are editable on list view'''
    list_editable = ('status', )


    '''10 items per page'''
    list_per_page = 5


    '''Max 200 when clicking show all'''
    list_max_show_all = 200 #default


    '''Calling select related objects to reduce SQL queries'''
    list_select_related = ('author', )


    '''Render a search box at top. ^, =, @, None=icontains'''
    search_fields = ['title']


    '''Render date options at top. do not accept tuple'''
    date_hierarchy = 'create_date'


    '''Replacement value for empty field'''
    empty_value_display = 'NA'


    '''filter options'''
    list_filter = ('status', 'author__is_superuser', ) 


admin.site.register(Article, ArticleAdmin)

此时登录django后台访问Article模型你将看到如下基础显示效果。我们将从这里开始展示django自带admin后台更高级的用法。

1. 自定义list_display显示字段

目前文章列表中的文章创建日期是英文字段,不是我们想要的。我们可以自定义一个时间格式(比如以2020-11-09 15:00)展示,并以红色标注。

此时修改我们的admin.py, 在ArticleAdmin类中新增一个custom_date方法,把其加入list_display选项,如下所示:

# Register your models here.
class ArticleAdmin(admin.ModelAdmin):


    # Custom admin list view
    list_display = ('title', 'author', 'status', 'create_date', 'custom_date', )
    '''中间省略'''    
    '''custom field on list view'''
    def custom_date(self, obj):
        return format_html(
            '<span style="color: red;">{}</span>',
            obj.create_date.strftime("%Y-%m-%d %H:%M:%S")
        )


    custom_date.short_description = '定制格式及颜色'

新的展示效果如下所示:

2. 优化ForeignKey或多对多字段的选择

Django admin中添加或修改一个对象时,对ForeignKey字段默认使用下拉菜单显示。例如本例中文章模型的author是一个ForeignKey字段。当你在admin中创建新的文章时,你将看到如下界面。当作者很少的时候,你可以轻松选择文章作者。当用户非常多的时候,下拉菜单变得非常长,此时通过下拉菜单选择作者非常不方便,效率低下。

一个更好的方式是使用autocomplete_fields或者raw_id_fields。前者使用基于jquery select2带搜索框的下拉菜单,适合中等数量的选项。后者使用弹出窗口搜索对象,适合数量非常多的选项。

修改admin.py,添加autocomplete_fields

# Register your models here.
class ArticleAdmin(admin.ModelAdmin):


    # Custom admin list view
    list_display = ('title', 'author', 'status', 'create_date', 'custom_date', )
    '''中间省略'''
    autocomplete_fields = ['author'] # use select2 to select user    

autocomplete_fields的展示效果如下所示,效果好得惊人。

再修改admin.py, 使用raw_id_fields

# Register your models here.
class ArticleAdmin(admin.ModelAdmin):


    # Custom admin list view
    list_display = ('title', 'author', 'status', 'create_date', 'custom_date', )
    '''中间省略'''    
    raw_id_fields = ("author",) # use a pop-out search window for a foreign key   

raw_id_fields展示效果如下所示。ForeinKey字段旁多了个放大镜,展示效果也相当不错。

本例模型中不涉及多对多字段,但在实际应用中django admin对于多对多字段使用多选下拉菜单,也非常不方便,建议选择使用filter_horizontal或filter_vertical设置,其展示效果为双向穿梭选择器,如下所示:

3. 重写save_model方法

在前例中,我们在创建文章时手动选择作者是非常愚蠢的行为,我们完全可以在创建模型实例时将当前用户与文章作者相关联(哪个登录用户添加的文章哪个就是作者)。首先你可以通过fields或exclude选项或自定义form把author字段从添加和修改表单中移除,然后重写save_model方法将当前用户设为作者。在这里我们还使用了更高级的fieldsets选项,在定义fields时把author排除在外,还将status选项的样式设为collapse(折叠)。

再次修改admin.py

# Register your models here.
class ArticleAdmin(admin.ModelAdmin):


    # Custom admin list view
    list_display = ('title', 'author', 'status', 'create_date', 'custom_date', )
    
    '''中间省略'''    
    fieldsets = (
        (None, {
            'fields': ('title', 'body', ),
            'classes': ('extrapretty', ),
        }),
        ('Advanced options', {
            'classes': ('collapse',),
            'fields': ('status', ),
        }),
    )


    '''Custom model methods'''
    def save_model(self, request, obj, form, change):
        # If creating new article, associate request.user with author.
        if not change:
            obj.author = request.user
        super().save_model(request, obj, form, change)

展示效果如下所示。此时添加文章也无需在手动选择作者了,而且status选项默认折叠,要填的字段少多了。

如果你在admin中使用了formset, 你也可以重写save_formset方法,如下所示:

    def save_formset(self, request, form, formset, change):
        instances = formset.save(commit=False)
        for obj in formset.deleted_objects:
            obj.delete()
        for instance in instances:
            instance.user = request.user
            instance.save()
        formset.save_m2m()

4. 重写get_queryset方法

在实际工作应用中,我们肯定希望不用用户有不同的权限,比如超级用户可以看到所有的文章,而一般员工只能看到自己的文章,这时我们就可以重写get_queryset方法,如下所示:

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        if request.user.is_superuser:
            return qs
        return qs.filter(author=request.user)

之前admin可以看到所有用户的文章,现在依然可以看到。如果你不是superuser,而只是staff user,当你登录admin后你将只能看到自己的文章,如下所示:

注意:django admin中首次创建的staff user登录admin后是一片空白,也不能进行编辑,如下所示。这是因为超级用户还未给这个staff user分配权限。

本例中我们先创建了一个editors组,然后给其分配了对Article进行增删改查的权限,然后把普通staff user添加到这个组,这时staff user登录admin后就可以对文章进行增删改查了。如下所示:

与重写get_queryset方法类似的,你还可以重写get_ordering方法(超级用户可以多个排序选择)和get_form方法(超级用户可以使用高级表单),如下所示:

    def get_ordering(self, request):
        if request.user.is_superuser:
            return ['author__username', 'title']
        else:
            return ['title']


    # Override add/change form
    def get_form(self, request, obj=None, **kwargs):
        if request.user.is_superuser:
            kwargs['form'] = MySuperuserForm
        return super().get_form(request, obj, **kwargs)

其它可以重写的方法还包括:

def delete_model(self, request, obj)
def delete_queryset(self, request, queryset)
def get_search_results(self, request, queryset, search_term) # Use other search engines Haystack
def save_related(self, request, form, formsets, change)
def get_autocomplete_fields(self, request) #  return a list or tuple of field names
def get_readonly_fields(self, request, obj=None) return a list or tuple of field names
def get_list_display(self, request) # return a list or tuple of field names
def get_exclude(self, request, obj=None) #  return a list of fields to be excluded
def get_fields(self, request, obj=None) # return a list of fields to be included in add form
def get_list_filter(self, request) # return a list or tuple
def get_list_select_related(self, request) # return a list or tuple
def get_sortable_by(self, request) # return a list or tuple
def get_inline_instances(self, request, obj=None) # return a list or tuple of InlinesModelAdmin
def get_inlines(self, request, obj) # You can override this method to dynamically add inlines

5. 重写formfield_for_foreignkey方法

重写该方法可以修改表单里foreinkey字段对应的选项,这个方法非常有用。比如用户创建文章选择类别时只选择自己创建的类别或者选择作者时仅显示特定的作者。比如本例中创建文章选择作者时仅显示用户名以admin开头的作者。

  # Override form field for foreign key,
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "author":
            kwargs["queryset"] = User.objects.filter(username__istartswith='admin')
        return super().formfield_for_foreignkey(db_field, request, **kwargs)

展示效果如下所示:

类似可以重写而且非常有用的方法还有针对表单中manytomany字段的formfield_for_manytomany方法和针对表单中choice字段的formfield_for_choice_field方法。示例如下所示:

# Override form field for a many to many relationship
def formfield_for_manytomany(self, db_field, request, **kwargs):
    if db_field.name == "tags":
        kwargs["queryset"] = Tag.objects.filter(owner=request.user)
    return super().formfield_for_manytomany(db_field, request, **kwargs)


# Override form field for a choice field
def formfield_for_choice_field(self, db_field, request, **kwargs):
    if db_field.name == "status":
        kwargs['choices'] = (
            ('accepted', 'Accepted'),
            ('denied', 'Denied'),
        )
        if request.user.is_superuser:
            kwargs['choices'] += (('ready', 'Ready for deployment'),)
    return super().formfield_for_choice_field(db_field, request, **kwargs)

其它针对权限和视图函数可以重写的方法还包括:

# Permissions related
def has_add_permission(self, request) # Should return True if adding an object is permitted
def has_view_permission(self, request, obj=None) # Should return True if viewing obj is permitted, False otherwise
def has_change_permission(self, request, obj=None) # Should return True if editing obj is permitted, False otherwise
def has_delete_permission(self, request, obj=None)  # Should return True if editing obj is permitted, False otherwise
def has_module_permission(self, request)


# Other view methods
def add_view(self, request, form_url='', extra_context=None)
def change_view(self, request, object_id, form_url='', extra_context=None)
def changelist_view(self, request, extra_context=None)
def delete_view(self, request, object_id, extra_context=None)
def history_view(self, request, object_id, extra_context=None)

6. 使用inline表单

本例中author和article是单对多的关系,前面案例中文章都是逐一添加的。如果我们希望在创建或编辑用户的时候,就一次性添加多篇文章怎么操作呢? 答案是使用inline表单。

我们首先用Article模型创建两个Inline类,一个用于展示和编辑已创建的文章列表,一个用于一次性添加多篇文章。由于Article模型可能有多个ForeignKey(比如category或author), 我们这里必需通过fk_name选项指定在那个ForeinKey的模型里使用创建的inlines类。

from django.contrib.auth import get_user_model
User = get_user_model()


class ArticleListInline(admin.TabularInline):
    model = Article
    # It is sometimes possible to have more than one foreign key to the same model. Must specify fk when this happens.
    fk_name = "author"
    # readonly_fields = ('title', 'body', 'status')
    # can_delete = False
    max_num = 3


    verbose_name = _('Edit Articles')
    verbose_name_plural = _('Edit Articles')
    extra = 2 # This controls the number of extra forms the formset will display. Defaults to 3


    """ 不允许这个inline类增加记录 (当然也增加不了,readonly_fileds中定义的字段,在增加时无法输入内容) """
    def has_add_permission(self, request, obj=None):
        return False




class ArticleAddInline(admin.TabularInline):
    model = Article
    # It is sometimes possible to have more than one foreign key to the same model. Must specify fk when this happens.
    fk_name = "author"
    extra = 2
    can_delete = False


    verbose_name = _('Add Articles')
    verbose_name_plural = _('Add Articles')


    """ Override this method. Return blank queryset and only use it to add data, not display data """
    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        return queryset.none()


然后将创建的ArticleInline类与UserAdmin相关联。

class UserAdmin(admin.ModelAdmin):
    search_fields = ['username']


    inlines = [
        ArticleAddInline,
        ArticleListInline,
    ]


admin.site.unregister(User)
admin.site.register(User, UserAdmin)


此时当你创建或编辑用户时,你可以看到或编辑该用户已创建的文章。你还可以一次性为该用户添加多篇文章,如下所示:

小结

Django admin功能强大, 你要会用才行呀。本文的技巧和高级用法你都你学了吗? 喜欢就一键三连吧!

大江狗

2021.1.27

相关阅读

Django基础(19): Django Admin管理后台详解(上)

Django基础(20): Django admin管理后台详解(中)如何自定义list_display和list_filter

Django基础(21): Django admin管理后台详解(下)如何自定义actions, 表单和美化admin

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐