Django admin 权威指南(一)

欢迎大家访问我的个人网站《刘江的博客和教程》:www.liujiangblog.com

主要分享Python 及Django教程以及相关的博客

版本: Django 1.10

此部分由官方文档《6.5.1 The Django admin site》翻译而来。

6.5.1.1 概览

默认情况下,使用startproject的时候,admin就被激活了。

如果需要手动启用,请按下面的需求和要点进行操作:

  1. 将'django.contrib.admin'加入 INSTALLED_APPS。
  2. admin依赖下面四个模块,请确保它们存在于INSTALLED_APPS。
    django.contrib.auth
    django.contrib.contenttypes
    django.contrib.messages
    and django.contrib.sessions
  3. 在你的settings文件中的TEMPLATES中的'context_processors'选项内添加django.contrib.auth.context_processors.auth和django.contrib.messages.context_processors.messages。
    同样,将django.contrib.auth.middleware.AuthenticationMiddleware和django.contrib.messages.middleware.MessageMiddleware添加到MIDDLEWARE内。默认情况下,这些都是配置好的。
  4. 决定哪些模型将在admin内进行管理。
  5. 对于每个模型,可以创建一个对应的ModelAdmin类,这个类将封装对模型的所有自定义设置。
  6. 实例化一个AdminSite,将模型对模型对应的ModelAdmin类传给它。
  7. 链接AdminSite和你的URLconf。

做完上面的步骤,你就可以在浏览器中访问admin站点了,默认地址是....../admin/。但是,想要登录进去,你首先必须使用python manage.py createsuperuser命令创建管理员账户。

一、自定义actions(Admin actions)

通常情况下,admin的工作模式是“选中目标,修改目标”,但在某些列入同时修改大量目标的时候,这种模式就变得重复、繁琐。

为此,admin提供了自定义功能函数的手段,可以批量对数据进行修改。admin内置了一个批量删除对象的操作,如下图所示(Django内置的django.contrib.auth应用的用户模型):
Django admin 权威指南(一)_第1张图片

警告:“delete selected objects”使用的是 QuerySet.delete()方法,它不会执行你自定义的模型的delete()方法,请注意。如果你想使用自己的delete()方法,那你必须重写这个动作,将它覆盖了。

下面以一个新闻应用的文章模型为例,介绍一个批量更新的自定义actions,它将选择的文章由“草稿”状态更新为“发布”状态:
首先是模型的代码:

from django.db import models

STATUS_CHOICES = (
    ('d', 'Draft'),
    ('p', 'Published'),
    ('w', 'Withdrawn'),
)

class Article(models.Model):
    title = models.CharField(max_length=100)
    body = models.TextField()
    status = models.CharField(max_length=1, choices=STATUS_CHOICES)

    def __str__(self):              # __unicode__ on Python 2
        return self.title

编写功能函数

函数必须携带三个参数:

  • 当前的ModelAdmin
  • 当前的HttpRequest对象(即request)
  • 被选择的对象(即QuerySet)

下面是一个例子:
在应用中的admin.py文件中写入:

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')

注意:这里我们使用了queryset的update方法,它能批量操作。但在多数情况下,你要自己遍历queryset的每个元素,并执行具体的操作。也就是:

for obj in queryset:
    do_something_with(obj)

我们还可以设置一个简单易懂的简短描述(可以使用中文),用于代替生硬的函数名:

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
# 注意缩进,下面这句不在函数体内。
make_published.short_description = "Mark selected stories as published"

将自定义操作添加到对应的ModelAdmin中

关键是其中的actions = [make_published]这句。

from django.contrib import admin
from myapp.models import Article

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"

class ArticleAdmin(admin.ModelAdmin):
    list_display = ['title', 'status']
    ordering = ['title']
    actions = [make_published]

admin.site.register(Article, ArticleAdmin)

然后,页面看起来是下面的样子(注意下拉框):
Django admin 权威指南(一)_第2张图片

处理错误
如果你能够预知在自定义的操作中可能产生的错误,请处理该错误,并通过django.contrib.admin.ModelAdmin.message_user()以友好的方式给予用户提示信息。

将自定义操作定义为ModelAdmin的方法

上面的make_published函数看起来已经不错了,但是我们一般会将它作为ModelAdmin的方法来使用。下面我们把它移到ArticleAdmin类中:

class ArticleAdmin(admin.ModelAdmin):
    ...

    actions = ['make_published']  # 请注意这里改成字符串引用了
    # 第一个参数变为self
    def make_published(self, request, queryset):
        queryset.update(status='p')
    make_published.short_description = "Mark selected stories as published"

这样做的好处是自定义方法可以直接访问类本身。例如下面使用self引用,为方法添加提示信息的功能:

class ArticleAdmin(admin.ModelAdmin):
    ...

    def make_published(self, request, queryset):
        rows_updated = queryset.update(status='p')
        if rows_updated == 1:
            message_bit = "1 story was"
        else:
            message_bit = "%s stories were" % rows_updated
        self.message_user(request, "%s successfully marked as published." % message_bit)

回到浏览器,再试试,你会看到如下图所示(注意顶部的绿色提示行):
Django admin 权威指南(一)_第3张图片

跳转到中间页面

默认情况下,执行完actions后,浏览器会返回先前的修改列表页面。但有时候,一些复杂的action需要返回中间页面,例如内置的删除方法,在执行删除动作之前,会弹出一个删除确认页面。

要实现这个功能,只需要在action方法中返回一个HttpResponse(或它的子类)。
例如下面是一个利用Django内置的序列化函数将一个对象保存为json格式的范例:

from django.http import HttpResponse
from django.core import serializers

def export_as_json(modeladmin, request, queryset):
    response = HttpResponse(content_type="application/json")
    serializers.serialize("json", queryset, stream=response)
    return response

多数情况下,我们会使用HttpResponseRedirect跳转到一个中间页面,并在GET方法的url中携带别选择的对象作为参数传递过去,然后在已个新的视图中接收这个参数,并编写具体的更加复杂的业务逻辑,如下面的代码所示:

from django.contrib import admin
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect

def export_selected_objects(modeladmin, request, queryset):
    # 获得被打钩的checkbox对应的对象
    selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
    # 获取对应的模型
    ct = ContentType.objects.get_for_model(queryset.model)
    # 构造访问的url,使用GET方法,跳转到相应的页面
    return HttpResponseRedirect("/export/?ct=%s&ids=%s" % (ct.pk, ",".join(selected)))

具体的业务views这里没有给出,作为练习,留给大家。

可用于整个admin站点的actions

前面创建的actions基本都是针对某个模型,某个类进行操作。实际上有时候,我们还需要可以对admin站点内所有模型都有效的acitons。上面写的export_selected_objects函数可以是一个很好的例子。要实现这一功能,你需要使用内置的AdminSite.add_action方法:

AdminSite.add_action(action, name=None)[source]

from django.contrib import admin

admin.site.add_action(export_selected_objects, 'verbose name')

禁用acitons

  • 禁用全站级别的acitons:AdminSite.disable_action(name)[source]

例如,禁用内置的删除方法:

admin.site.disable_action('delete_selected')
  • 全站禁用,但个别可用:在ModelAdmin.actions中显示的引用

例如:

# 全站禁用删除功能
admin.site.disable_action('delete_selected')

# 这个老老实实的被禁了
class SomeModelAdmin(admin.ModelAdmin):
    actions = ['some_other_action']
    ...

# 这个声明:我还要用
class AnotherModelAdmin(admin.ModelAdmin):
    actions = ['delete_selected', 'a_third_action']
    ...
  • 在指定模型中禁用所有actions:设置ModelAdmin.actions为None
    (这会连带全局actions一起禁用了。)
    例如:

    class MyModelAdmin(admin.ModelAdmin):
    actions = None

  • 根据条件自动启用或禁用:ModelAdmin.get_actions(request)[source]
    你还可以根据条件自动选择性的启动或禁用某些acitons,你只需要改写ModelAdmin.get_actions()方法。

该方法将返回一个包含actions的字典。字典的键是aciton的名字(也就是前面的'delete_selected', 'a_third_action'之类),值是一个元组,包含(函数、名字、别名)

例如,允许用户名以“J”开头的用户批量删除对象,但其它用户不行:

class MyModelAdmin(admin.ModelAdmin):
    ...

    def get_actions(self, request):
        actions = super(MyModelAdmin, self).get_actions(request)
        if request.user.username[0].upper() != 'J':
            if 'delete_selected' in actions:
                del actions['delete_selected']
        return actions

二、 admin文档生成器

Django的admindocs应用可以从模型、视图、模板标签等地方获得文档内容。

概览
要激活admindocs,请按下面的步骤操作:

  • 在INSTALLED_APPS内添加django.contrib.admindocs
  • 在urlpatterns内添加url(r'^admin/doc/',include('django.contrib.admindocs.urls'))。确保它处于 r'^admin/'条目之前,原因你懂的。
  • 安装Python的docutils模块(http://docutils.sf.net/)
  • 可选:想使用admindocs的书签小工具,需要安装django.contrib.admindocs.middleware.XViewMiddleware。

译者注:在命令行中,使用pip3 install wheel,然后pip3 install docutils就可以迅速安装好Python的这个包了。

如果上述步骤顺利完成,那么你可以从admin界面访问doc界面,也可以直接访问“.../admin/doc”,如下图:

image_1b3pdb2s81qpfl7f17vo14b41be89.png-12.2kB
它看起来是下面的样子:
Django admin 权威指南(一)_第4张图片

下面的这些特殊标记,可帮助你在文档字符串中,快速创建指向其它组件的链接:
Django admin 权威指南(一)_第5张图片

模型

在doc页面的模型部分,列出了所有的模型,点击可以查看具体的字段等细节信息。信息主要来自字段的help_txt部分和模型方法的docstring部分。如下面图中展示:

有用的帮助信息看起来是这个样子的:

class BlogEntry(models.Model):
    """
    Stores a single blog entry, related to :model:`blog.Blog` and
    :model:`auth.User`.
    """
    slug = models.SlugField(help_text="A short label, generally used in URLs.")
    author = models.ForeignKey(
        User,
        models.SET_NULL,
        blank=True, null=True,
    )
    blog = models.ForeignKey(Blog, models.CASCADE)
    ...

    def publish(self):
        """Makes the blog entry live on the site."""
        ...

Django admin 权威指南(一)_第6张图片
Django admin 权威指南(一)_第7张图片

视图
站点内的每个URL都会在doc内享有一个页面,点击某个URL将会展示对应的视图信息。主要包括下面这些信息,请尽量丰富它们:

  • 视图功能的简单描述
  • 上下文环境,或者视图模块里的变量列表
  • 视图内使用的模板

例如:

from django.shortcuts import render

from myapp.models import MyModel

def my_view(request, slug):
    """
    Display an individual :model:`myapp.MyModel`.

    **Context**

    ``mymodel``
        An instance of :model:`myapp.MyModel`.

    **Template:**

    :template:`myapp/my_template.html`
    """
    context = {'mymodel': MyModel.objects.get(slug=slug)}
    return render(request, 'myapp/my_template.html', context)

Django admin 权威指南(一)_第8张图片
模板标签和过滤器

所有Django内置的或者你自定义的或者第三方app提供的标签和过滤器都将在页面内展示:
Django admin 权威指南(一)_第9张图片
Django admin 权威指南(一)_第10张图片

6.5.1.2 ModelAdmin类

ModelAdmin类是一个模型在admin页面里的展示方法,如果你对默认的admin页面满意,那么你完全不需要定义这个类,直接使用最原始的样子就好了。通常,它们保存在app的admin.py文件里。下面是个简单的例子:

from django.contrib import admin
from myproject.myapp.models import Author

# 创建一个ModelAdmin的子类
class AuthorAdmin(admin.ModelAdmin):
    pass
    
# 注册的时候,将原模型和ModelAdmin耦合起来
admin.site.register(Author, AuthorAdmin)

装饰器注册

register(*models, site=django.admin.sites.site)[source]
除了常用的admin.site.register(Author, AuthorAdmin)方式,你还可以用装饰器的方式连接模型和ModelAdmin。如下所示:

from django.contrib import admin
from .models import Author

@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
    pass

这个装饰器可以接收一些模型类作为参数,以及一个可选的关键字参数site(如果你使用的不是默认的AdminSite)。

from django.contrib import admin
from .models import Author, Reader, Editor
from myproject.admin_site import custom_admin_site

@admin.register(Author, Reader, Editor, site=custom_admin_site)
class PersonAdmin(admin.ModelAdmin):
    pass

注意:在Python2中如果你在类的__init__()方法中引用了模型的admin类,比如super(PersonAdmin, self).__init__(*args, **kwargs),那么你必须使用admin.site.register()的方式,而不能使用装饰器的方式。但是,在Python3中,可以避免这个问题,通过使用super().__init__(*args, **kwargs)。

搜索admin文件

当你在 INSTALLED_APPS设置中添加了'django.contrib.admin'后,Django将自动在每个应用中搜索admin模块并导入它。

  • class apps.AdminConfig:admin默认的AppConfig类,当Django启动时自动调用autodiscover()方法
  • class apps.SimpleAdminConfig:和上面的类似,但不调用autodiscover()
  • autodiscover()[source]:自动搜索admin模块的方法。在使用自定义的site时,必须禁用这个方法,你应该在INSTALLED_APPS设置中用'django.contrib.admin.apps.SimpleAdminConfig'替代'django.contrib.admin'

ModelAdmin选项

ModelAdmin.actions

一个列表,包含自定义的actions,前面有叙述。


ModelAdmin.actions_on_top

是否在列表上方显示actions的下拉框,默认为True


ModelAdmin.actions_on_bottom

是否在列表下方显示actions的下拉框,默认为False。和上面的选项一样,设置布尔值,actions_on_top=Fasle或者actions_on_bottom = True这种,可同时在上下都显示下拉框。效果看下面的图片,没什么大用途。
Django admin 权威指南(一)_第11张图片


ModelAdmin.actions_selection_counter

是否在actions下拉框右侧显示选中的对象的数量,默认为True,可改为False。
image_1b3s34r4h1aj99b61vtn1ac256dm.png-6.6kB


ModelAdmin.date_hierarchy

根据你指定的日期相关的字段,为页面创建一个时间导航栏,可通过日期过滤对象。例如:
date_hierarchy = 'pub_date'
它的效果看起来是这样的:
Django admin 权威指南(一)_第12张图片


ModelAdmin.empty_value_display

指定空白显示的内容。如果你有些字段没有值(例如None,空字符串等等),默认情况下会显示破折号“-”。但这个选项可以让你自定义显示什么,如下例就显示个“-empty-”:

from django.contrib import admin

class AuthorAdmin(admin.ModelAdmin):
    empty_value_display = '-empty-'

你还可以为整个admin站点设置默认空白显示值,通过AdminSite.empty_value_display="xxxxxxx"。甚至为某个函数设置空白值,如下:

from django.contrib import admin

class AuthorAdmin(admin.ModelAdmin):
    fields = ('name', 'title', 'view_birth_date')

    def view_birth_date(self, obj):
        return obj.birth_date
    # 注意下面这句
    view_birth_date.empty_value_display = '???'

ModelAdmin.exclude

不显示指定的某些字段。如下例有这么个模型:

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    title = models.CharField(max_length=3)
    birth_date = models.DateField(blank=True, null=True)

如果你不希望在页面内显示birth_date字段,那么这么设置:

from django.contrib import admin

class AuthorAdmin(admin.ModelAdmin):
    fields = ('name', 'title')

和这么设置是一样的:

from django.contrib import admin

class AuthorAdmin(admin.ModelAdmin):
    # 一定注意了,值是元组!一个元素的时候,最后的逗号不能省略。
    exclude = ('birth_date',)

ModelAdmin.fields

按你希望的顺序,显示指定的字段。与exclude相对。但要注意与list_display区分。
这里有个小技巧,你可以通过组合元组的方式,让某些字段在同一行内显示,例如下面的做法“url”和“title”将在一行内,而“content”则在下一行。

class FlatPageAdmin(admin.ModelAdmin):
    fields = (('url', 'title'), 'content')

如果没有对field或fieldsets选项进行定义,那么Django将按照模型定义中的顺序,每一行显示一个字段的方式,逐个显示所有的非AutoField和editable=True的字段。(自动字段,如主键,不可编辑字段是不会出现在页面里的。)


ModelAdmin.fieldsets

这个功能其实就是根据字段对页面进行分组显示或布局了。fieldsets是一个二元元组的列表。每个二元元组代表一个

,代表整个form的一部分。

二元元组的格式为 (name,field_options),name是一个表示该filedset标题的字符串,field_options是一个包含在该filedset内的字段列表。

下面是一个例子,有助于你理解:

from django.contrib import admin

class FlatPageAdmin(admin.ModelAdmin):
    fieldsets = (
        (None, {
            'fields': ('url', 'title', 'content', 'sites')
        }),
        ('Advanced options', {
            'classes': ('collapse',),
            'fields': ('registration_required', 'template_name'),
        }),
    )

它的页面看起来像下面的样子:
Django admin 权威指南(一)_第13张图片

在filed_options字典内,可以使用下面这些关键字:
fields:一个必填的元组,包含要在fieldset中显示的字段。例如:

{
'fields': ('first_name', 'last_name', 'address', 'city', 'state'),
}

同样,它也可以像前面那样通过组合元组,实现多个字段在一行内的效果:

{
'fields': (('first_name', 'last_name'), 'address', 'city', 'state'),
}

fileds可以包含 readonly_fields的值,作为只读字段。

classes:一个包含额外的CSS类的元组,例如:

{
'classes': ('wide', 'extrapretty'),
}

两个比较有用的样式是collaspe和wide,前者将fieldsets折叠起来,后者让它具备更宽的水平空间。

description:一个可选的额外的说明文本,放置在每个fieldset的顶部。但是,这里并没有对HTML语法进行转义,因此可能有时候会造成一些莫名其妙的显示,要忽略HTML的影响,请使用 django.utils.html.escape()。


ModelAdmin.filter_horizontal

水平扩展多对多字段。默认情况下,ManyTOManyField在admin的页面中会显示为一个select框。在需要选择大量对象时,这会有点困难。将ManyTOManyField添加到这个属性列表里后,页面就会对字段进行扩展,并提供过滤功能。如下图:
Django admin 权威指南(一)_第14张图片


ModelAdmin.filter_vertical

与上面的类似,不过是改成垂直布置了。


ModelAdmin.form

默认情况下,系统会为你的模型动态的创建ModelForm,它用于创建你的添加/修改页面的表单。你可以轻易地提供你自己的ModelForm,在"添加/修改"页面覆盖默认的表单行为。或者,你还可以自定义默认的表单而不是指定一个全新的表单,通过使用ModelAdmin.get_form()方法。具体的例子,参考《Adding custom validation to the admin》。

注意:如果你在一个ModelForm上定义了Meta.model属性,你也必须同时定义Meta.fields属性(或者Meta.exclude属性)。然而,当admin定义有自己的fields,Meta.fields属性会被忽略。如果ModelForm仅仅被用于admin,最好的解决方案是忽略Meta.model属性,因为ModelAdmin会自动选择正确的模型。或者,你可以设置fields=[]在Meta类中,用于ModelForm的验证。

注意:如果你的ModelForm和ModelAdmin同时定义了exclude选项,那么ModelAdmin中的具有优先权,如下例所示,"age"字段将被排除,但是“name”字段将被显示:

from django import forms
from django.contrib import admin
from myapp.models import Person

class PersonForm(forms.ModelForm):

    class Meta:
        model = Person
        exclude = ['name']

class PersonAdmin(admin.ModelAdmin):
    exclude = ['age']
    form = PersonForm

ModelAdmin.formfield_overrides

这个属性比较难以理解,通过一个列子来解释可能会更好一点。设想一下我们自己写了个RichTextEditorWidget(富文本控件),然后想用它来代替传统的