Django 管理站点

Django 最强大的部分之一是自动管理界面。它从你的模型中读取元数据,提供一个快速的、以模型为中心的界面,受信任的用户可以管理你网站上的内容。管理的推荐使用范围仅限于一个组织的内部管理工具。它不打算用于围绕你的整个前端构建。

管理有很多用于定制的钩子,但要注意不要试图专门使用这些钩子。如果你需要提供一个更以流程为中心的接口,抽象掉数据库表和字段的实现细节,那么可能是时候编写自己的视图了。

在本文档中,我们将讨论如何激活、使用和定制 Django 的管理界面。

概况

startproject 使用的默认项目模板中启用了管理。

如果你没有使用默认的项目模板,这里是要求:

  1. 'django.contrib.admin' 和它的依赖关系 —— django.contrib.authdjango.contrib.contenttypesdjango.contrib.messagesdjango.contrib.session 添加到你的 INSTALLED_APPS 配置中。
  2. 在你的 TEMPLATES 配置中设置一个在 OPTIONS'context_processors' 选项中包含 django.template.context_processors.requestdjango.contrib.auth.context_processors.authdjango.contrib.messages.context_processors.messagesDjangoTemplates 后端:
  3. If you've customized the MIDDLEWARE setting, django.contrib.sessions.middleware.SessionMiddleware, django.contrib.auth.middleware.AuthenticationMiddleware, and django.contrib.messages.middleware.MessageMiddleware must be included.
  4. 把管理的网址挂到你的 URLconf 里

在你采取了这些步骤之后,你就可以通过访问你挂接的 URL(默认是 /admin/)来使用管理站点。

如果需要创建一个用户来登录,请使用 createsuperuser 命令。默认情况下,登录管理需要用户的 is_staff 属性设置为 True

最后,确定你的应用程序的哪些模型应该在管理界面中是可编辑的。对于这些模型中的每一个,按照 ModelAdmin 中的描述,在管理处注册它们。

其他主题

参见

关于在生产中服务与管理相关的静态文件(图像、JavaScript 和 CSS)的信息,请参见 提供文件服务

有问题? 试试 FAQ:管理

ModelAdmin 对象

class ModelAdmin[源代码]

ModelAdmin 类是管理界面中模型的表示。通常,这些都存储在你的应用程序中一个名为 admin.py 的文件中。让我们来看看 ModelAdmin 的一个例子:

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


class AuthorAdmin(admin.ModelAdmin):
    pass


admin.site.register(Author, AuthorAdmin)

你是否需要一个 ModelAdmin 对象?

在前面的例子中,ModelAdmin 类没有定义任何自定义值(还没有)。因此,将提供默认的管理界面。如果你对默认的管理界面满意,你根本不需要定义一个 ModelAdmin 对象 —— 你可以不提供 ModelAdmin 描述而注册模型类。前面的例子可以简化为:

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

admin.site.register(Author)

register 装饰器

register(*models, site=django.contrib.admin.sites.site)[源代码]

还有一个装饰器用于注册你的 ModelAdmin 类:

from django.contrib import admin
from .models import Author


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

它接受一个或多个模型类,让它在 ModelAdmin 注册。如果你使用的是自定义的 AdminSite,请使用 site 关键字参数传递它:

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


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

如果你必须在你的模型管理类的 __init__() 方法中引用你的模型管理类,例如 super(PersonAdmin, self).__init__(*args, **kwargs),你就不能使用这个装饰器。你可以使用 super().__init__(*args, **kwargs)

发现管理文件

当你在 INSTALLED_APPS 配置中放入 'django.contrib.admin' 时,Django 会自动在每个应用程序中寻找 admin 模块并导入它。

class apps.AdminConfig

这是管理员默认的 AppConfig 类。当 Django 启动时,它会调用 autodiscover()

class apps.SimpleAdminConfig

这个类的工作原理和 AdminConfig 一样,只是没有调用 autodiscover()

default_site

一个点分隔的导入路径,用于导入默认的管理站点类,或者导入一个返回站点实例的可调用类。默认为 'django.contrib.admin.sites.AdminSite'。使用方法参见 覆盖默认的管理站点

autodiscover()[源代码]

该函数试图在每个已安装的应用程序中导入一个 admin 模块。这些模块将向管理注册模型。

通常情况下,你不需要直接调用这个函数,因为 AdminConfig 会在 Django 启动时调用它。

如果你使用的是自定义的 AdminSite,通常会将所有的 ModelAdmin 子类导入到你的代码中,并将它们注册到自定义的 AdminSite。在这种情况下,为了禁止自动发现,你应该在你的 INSTALLED_APPS 配置中加入 'django.contrib.admin.apps.SimpleAdminConfig' 而不是 'django.contrib.admin'

ModelAdmin 选项

ModelAdmin 非常灵活。它有几个选项用于处理自定义界面。所有选项都在 ModelAdmin 子类中定义:

from django.contrib import admin


class AuthorAdmin(admin.ModelAdmin):
    date_hierarchy = "pub_date"
ModelAdmin.actions

要在变更列表页上提供的动作列表。详见 管理动作

ModelAdmin.actions_on_top
ModelAdmin.actions_on_bottom

控制动作栏在页面的哪个位置出现。默认情况下,管理员更改列表在页面顶部显示动作(actions_on_top = True; actions_on_bottom = False)。

ModelAdmin.actions_selection_counter

控制是否在动作下拉菜单旁边显示选择计数器。默认情况下,管理员变更列表会显示它(actions_selection_counter = True)。

ModelAdmin.date_hierarchy

date_hierarchy 设置为你的模型中 DateFieldDateTimeField 的名称,变化列表页将包括一个基于日期的向下扩展。

举例:

date_hierarchy = "pub_date"

你也可以使用 __ 查找来指定相关模型上的字段,例如:

date_hierarchy = "author__pub_date"

这将根据可用的数据智能地填充自己,例如,如果所有的日期都在一个月内,它将只显示日级的向下扩展。

备注

date_hierarchy 内部使用 QuerySet.datetimes()。当启用时区支持时,请参考它的文档中的一些注意事项(USE_TZ = True)。

ModelAdmin.empty_value_display

该属性覆盖记录字段为空(None、空字符串等)的默认显示值。默认值是 - (破折号)。例如:

from django.contrib import admin


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

你也可以用 AdminSite.empty_value_display 覆盖所有管理页面的 empty_value_display,或者像这样覆盖特定字段:

from django.contrib import admin


class AuthorAdmin(admin.ModelAdmin):
    list_display = ["name", "title", "view_birth_date"]

    @admin.display(empty_value="???")
    def view_birth_date(self, obj):
        return obj.birth_date
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)

如果你想让 Author 模型的表格只包括 nametitle 字段,你可以指定 fieldsexclude ,如:

from django.contrib import admin


class AuthorAdmin(admin.ModelAdmin):
    fields = ["name", "title"]


class AuthorAdmin(admin.ModelAdmin):
    exclude = ["birth_date"]

由于 Author 模型只有三个字段,即 nametitlebirth_date,因此上述声明所产生的表单将包含完全相同的字段。

ModelAdmin.fields

使用 fields 选项在 “添加” 和 “更改” 页面的表单中进行简单的布局修改,比如只显示可用字段的子集,修改它们的顺序,或者将它们分成几行。例如,你可以为 django.contrib.flatpages.models.FlatPage 模型定义一个更简单的管理表单版本,如下所示:

class FlatPageAdmin(admin.ModelAdmin):
    fields = ["url", "title", "content"]

在上面的例子中,只有 urltitlecontent 等字段会依次显示。fields 可以包含 ModelAdmin.readonly_fields 中定义的值,以只读方式显示。

对于更复杂的布局需求,请参阅 fieldets 选项。

The fields option accepts the same types of values as list_display, except that callables and __ lookups for related fields aren't accepted. Names of model and model admin methods will only be used if they're listed in readonly_fields.

要在同一行显示多个字段,将这些字段包在自己的元组中。在这个例子中,urltitle 字段将显示在同一行,content 字段将显示在它们下面的一行:

class FlatPageAdmin(admin.ModelAdmin):
    fields = [("url", "title"), "content"]

可能会与 ModelAdmin.fieldsets 选项混淆

这个 fields 选项不应与 fieldsets 选项中的 fields 字典键混淆,下一节将介绍。

如果 fieldsfieldsets 选项都不存在,Django 将默认在一个单一的字段集中显示每个非 AutoField 且有 editable=True 的字段,顺序与模型中定义的字段相同。

ModelAdmin.fieldsets

设置 fieldsets 来控制管理员 “添加” 和 “更改” 页面的布局。

fieldsets 是一个包含多个 2-元组的列表,每个 2-元组表示管理员表单页面上的一个 <fieldset><fieldset> 是表单的一个 "section")。

这些 2-元组的格式是 (name, field_options),其中 name 是表示 fieldset 标题的字符串,而 field_options 是关于 fieldset 的信息的字典,包括要在其中显示的字段列表。

一个完整的例子,取自 django.contrib.flatpages.models.FlatPage 模型:

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"],
            },
        ),
    ]

这样一来,管理页面就变成了这样:

../../../_images/fieldsets.png

如果 fieldsetsfields 选项都不存在,Django 将默认在一个单一的表单集中显示每个非 AutoField 且有 editable=True 的字段,顺序与模型中定义的字段相同。

field_options 字典可以有以下键:

  • fields

    要在此字段集中显示的字段名称的列表或元组。这个键是必需的。

    举例:

    {
        "fields": ["first_name", "last_name", "address", "city", "state"],
    }
    

    fields 选项一样,要在同一行显示多个字段,请将这些字段封装在自己的元组中。在这个例子中,first_namelast_name 字段将显示在同一行:

    {
        "fields": [("first_name", "last_name"), "address", "city", "state"],
    }
    

    fields 可以包含 readonly_fields 中定义的值,以只读方式显示。

    如果你将可调用的名称添加到 fields 中,与 fields 选项的规则相同:可调用的名称必须列在 readonly_fields 中。

  • classes

    A list or tuple containing extra CSS classes to apply to the fieldset. This can include any custom CSS class defined in the project, as well as any of the CSS classes provided by Django. Within the default admin site CSS stylesheet, two particularly useful classes are defined: collapse and wide.

    举例:

    {
        "classes": ["wide", "collapse"],
    }
    

    Fieldsets with the wide style will be given extra horizontal space in the admin interface. Fieldsets with a name and the collapse style will be initially collapsed, using an expandable widget with a toggle for switching their visibility.

    Changed in Django 5.1:

    fieldsets using the collapse class now use <details> and <summary> elements, provided they define a name.

  • description

    A string of optional extra text to be displayed at the top of each fieldset, under the heading of the fieldset.

    请注意,当这个值在管理界面显示时,它不是 HTML 转义后的。如果你愿意的话,这可以让你包含 HTML。或者你可以使用纯文本和 django.utils.html.escape() 来转义任何 HTML 特殊字符。

TabularInline has limited support for fieldsets

Using fieldsets with TabularInline has limited functionality. You can specify which fields will be displayed and their order within the TabularInline layout by defining fields in the field_options dictionary.

All other features are not supported. This includes the use of name to define a title for a group of fields.

ModelAdmin.filter_horizontal

默认情况下,在管理网站中显示一个 ManyToManyField<select multiple>。但是,多选框在选择很多项目时,会很难用。在这个列表中添加一个 ManyToManyField,就可以改用一个不显眼的 JavaScript “过滤器” 界面,在选项中进行搜索。未选择和选择的选项并排出现在两个框中。参见 filter_vertical 来使用垂直界面。

ModelAdmin.filter_vertical

filter_horizontal 相同,但使用垂直显示过滤界面,未选择的选项框出现在选择选项框的上方。

ModelAdmin.form

默认情况下,会为你的模型动态创建一个 ModelForm。它用于创建在添加/更改页面上显示的表单。你可以很容易地提供你自己的 ModelForm 来覆盖添加/更改页面上的任何默认表单行为。另外,你可以使用 ModelAdmin.get_form() 方法来定制默认的表单,而不是指定一个全新的表单。

示例请参见 在管理中添加自定义验证 一节。

省略 Meta.model 属性

如果你在 ModelForm 上定义了 Meta.model 属性,你也必须定义 Meta.fields 属性(或 Meta.exclude 属性)。但是,由于管理有自己定义字段的方式,所以 Meta.field 属性将被忽略。

如果 ModelForm 只用于管理,最简单的解决办法是省略 Meta.model 属性,因为 ModelAdmin 将提供正确的模型。或者,你可以在 Meta 类中设置 fields = [] 以满足 ModelForm 的验证。

ModelAdmin.exclude 优先级较高

如果你的 ModelFormModelAdmin 都定义了 exclude 选项,那么 ModelAdmin 优先:

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

这提供了一个快速而简单的方法来覆盖一些 Field 选项,以便在管理中使用。formfield_overrides 是一个字典,它将字段类映射成一个参数的字典,以便在构造时传递给字段。

由于这有点抽象,我们来看一个具体的例子。formfield_overrides 最常见的用法是为某一类型的字段添加一个自定义部件。所以,想象一下,我们写了一个 RichTextEditorWidget,我们想用于大文本字段,而不是默认的 <textarea>。下面是我们要做的:

from django.contrib import admin
from django.db import models

# Import our custom widget and our model from where they're defined
from myapp.models import MyModel
from myapp.widgets import RichTextEditorWidget


class MyModelAdmin(admin.ModelAdmin):
    formfield_overrides = {
        models.TextField: {"widget": RichTextEditorWidget},
    }

注意,字典中的键是实际的字段类,不是 字符串。值是另一个字典;这些参数将被传递给表单字段的 __init__() 方法。详见 表单 API

警告

如果你想使用一个带有关系字段的自定义部件(例如 ForeignKeyManyToManyField),确保你没有在 raw_id_fieldsradio_fieldsautocomplete_fields 中包含该字段的名称。

formfield_overrides 不会让你改变有 raw_id_fieldsradio_fieldsautocomplete_fields 设置的关系字段上的部件。这是因为 raw_id_fieldsradio_fieldsautocomplete_fields 意味着自己的自定义部件。

ModelAdmin.inlines

参见下面的 InlineModelAdmin 对象以及 ModelAdmin.get_formsets_with_inlines()

ModelAdmin.list_display

设置 list_display 来控制哪些字段显示在管理的变更列表页面。

举例:

list_display = ["first_name", "last_name"]

如果你不设置 list_display,管理网站将显示一个单列,显示每个对象的 __str__() 表示。

There are five types of values that can be used in list_display. All but the simplest may use the display() decorator, which is used to customize how the field is presented:

  • 模型字段的名称。例如:

    class PersonAdmin(admin.ModelAdmin):
        list_display = ["first_name", "last_name"]
    
  • The name of a related field, using the __ notation. For example:

    class PersonAdmin(admin.ModelAdmin):
        list_display = ["city__name"]
    
  • 一个接受一个参数的可调用对象,即模型实例。例如:

    @admin.display(description="Name")
    def upper_case_name(obj):
        return f"{obj.first_name} {obj.last_name}".upper()
    
    
    class PersonAdmin(admin.ModelAdmin):
        list_display = [upper_case_name]
    
  • 表示 ModelAdmin 方法的字符串,该方法接受一个参数,即模型实例。例如:

    class PersonAdmin(admin.ModelAdmin):
        list_display = ["upper_case_name"]
    
        @admin.display(description="Name")
        def upper_case_name(self, obj):
            return f"{obj.first_name} {obj.last_name}".upper()
    
  • 代表模型属性或方法的字符串(没有任何必要的参数)。例如:

    from django.contrib import admin
    from django.db import models
    
    
    class Person(models.Model):
        name = models.CharField(max_length=50)
        birthday = models.DateField()
    
        @admin.display(description="Birth decade")
        def decade_born_in(self):
            decade = self.birthday.year // 10 * 10
            return f"{decade}’s"
    
    
    class PersonAdmin(admin.ModelAdmin):
        list_display = ["name", "decade_born_in"]
    
Changed in Django 5.1:

Support for using __ lookups was added, when targeting related fields.

关于 list_display 要注意的几个特殊情况:

  • 如果字段是 ForeignKey,Django 会显示相关对象的 __str__()

  • ManyToManyField 字段不被支持,因为这需要为表中的每一行单独执行一条 SQL 语句。如果你还是想这样做,请给你的模型一个自定义方法,并将该方法的名称添加到 list_display 中。(更多关于 list_display 中的自定义方法,请参见下文)。

  • 如果字段是 BooleanField,Django 会显示一个漂亮的 “是”、“否” 或 “未知” 图标,而不是 TrueFalseNone

  • 如果给定的字符串是模型的一个方法,ModelAdmin 或者是一个可调用的方法,Django 默认会对输出进行 HTML 转义。如果要转义用户的输入,并允许你自己使用未转义的标签,可以使用 format_html()

    下面是一个完整的示例模型:

    from django.contrib import admin
    from django.db import models
    from django.utils.html import format_html
    
    
    class Person(models.Model):
        first_name = models.CharField(max_length=50)
        last_name = models.CharField(max_length=50)
        color_code = models.CharField(max_length=6)
    
        @admin.display
        def colored_name(self):
            return format_html(
                '<span style="color: #{};">{} {}</span>',
                self.color_code,
                self.first_name,
                self.last_name,
            )
    
    
    class PersonAdmin(admin.ModelAdmin):
        list_display = ["first_name", "last_name", "colored_name"]
    
  • 正如一些例子已经证明的那样,当使用一个可调用对象、一个模型方法或一个 ModelAdmin 方法时,你可以通过用 display() 装饰器包装可调用对象,并传递 description 参数来自定义列的标题。

  • 如果一个字段的值是 None,一个空字符串,或者一个没有元素的可迭代字段,Django 将显示 - (破折号)。你可以用 AdminSite.empty_value_display 来覆盖这一点:

    from django.contrib import admin
    
    admin.site.empty_value_display = "(None)"
    

    你也可以使用 ModelAdmin.empty_value_display

    class PersonAdmin(admin.ModelAdmin):
        empty_value_display = "unknown"
    

    或在字段级别:

    class PersonAdmin(admin.ModelAdmin):
        list_display = ["name", "birth_date_view"]
    
        @admin.display(empty_value="unknown")
        def birth_date_view(self, obj):
            return obj.birth_date
    
  • 如果给定的字符串是模型的一个方法,ModelAdmin 或一个返回 TrueFalseNone 的可调用对象,如果你用 display() 装饰器包装该方法,传递 boolean 参数,并将其值设置为 True

    from django.contrib import admin
    from django.db import models
    
    
    class Person(models.Model):
        first_name = models.CharField(max_length=50)
        birthday = models.DateField()
    
        @admin.display(boolean=True)
        def born_in_fifties(self):
            return 1950 <= self.birthday.year < 1960
    
    
    class PersonAdmin(admin.ModelAdmin):
        list_display = ["name", "born_in_fifties"]
    
  • __str__() 方法在 list_display 中和其他模型方法一样有效,所以完全可以这样做:

    list_display = ["__str__", "some_other_field"]
    
  • 通常情况下,list_display 的元素如果不是实际的数据库字段,就不能用于排序(因为 Django 在数据库层面进行了所有的排序)。

    但是,如果 list_display 的元素代表某个数据库字段,你可以通过在方法上使用 display() 装饰器,传递 ordering 参数来表明这个事实:

    from django.contrib import admin
    from django.db import models
    from django.utils.html import format_html
    
    
    class Person(models.Model):
        first_name = models.CharField(max_length=50)
        color_code = models.CharField(max_length=6)
    
        @admin.display(ordering="first_name")
        def colored_first_name(self):
            return format_html(
                '<span style="color: #{};">{}</span>',
                self.color_code,
                self.first_name,
            )
    
    
    class PersonAdmin(admin.ModelAdmin):
        list_display = ["first_name", "colored_first_name"]
    

    上面会告诉 Django 在管理中按 colored_first_name 排序时,按 first_name 字段排序。

    要用 ordering 参数表示降序,可以在字段名上使用连字符前缀。使用上面的例子,它看起来像:

    @admin.display(ordering="-first_name")
    def colored_first_name(self): ...
    

    ordering 参数支持查询查找,在相关模型上按值排序。这个例子在列表显示中包含了一个 “作者的姓” 列,并允许按姓排序:

    class Blog(models.Model):
        title = models.CharField(max_length=255)
        author = models.ForeignKey(Person, on_delete=models.CASCADE)
    
    
    class BlogAdmin(admin.ModelAdmin):
        list_display = ["title", "author", "author_first_name"]
    
        @admin.display(ordering="author__first_name")
        def author_first_name(self, obj):
            return obj.author.first_name
    

    查询表达式 可与 ordering 参数一起使用:

    from django.db.models import Value
    from django.db.models.functions import Concat
    
    
    class Person(models.Model):
        first_name = models.CharField(max_length=50)
        last_name = models.CharField(max_length=50)
    
        @admin.display(ordering=Concat("first_name", Value(" "), "last_name"))
        def full_name(self):
            return self.first_name + " " + self.last_name
    
  • list_display 的元素也可以是属性:

    class Person(models.Model):
        first_name = models.CharField(max_length=50)
        last_name = models.CharField(max_length=50)
    
        @property
        @admin.display(
            ordering="last_name",
            description="Full name of the person",
            boolean=False,
        )
        def full_name(self):
            return self.first_name + " " + self.last_name
    
    
    class PersonAdmin(admin.ModelAdmin):
        list_display = ["full_name"]
    

    注意 @property 必须在 @display 之上。如果你使用老方法 —— 直接设置与 display 相关的属性,而不是使用 display() 装饰器 —— 请注意,必须使用 property() 函数,而 不是 @property 装饰器:

    def my_property(self):
        return self.first_name + " " + self.last_name
    
    
    my_property.short_description = "Full name of the person"
    my_property.admin_order_field = "last_name"
    my_property.boolean = False
    
    full_name = property(my_property)
    
    Changed in Django 5.0:

    支持在属性上使用 boolean 属性已经添加。

  • list_display 中的字段名也会以 CSS 类的形式出现在 HTML 输出中,在每个 <th> 元素上以 column-<field_name> 的形式出现。例如,这可以用来在 CSS 文件中设置列宽。

  • Django 会尝试按照这个顺序解释 list_display 的每个元素:

    • A field of the model or from a related field.
    • 一个可调用对象。
    • 一个代表 ModelAdmin 属性的字符串。
    • 一个代表模型属性的字符串。

    例如,如果你有 first_name 作为一个模型字段和 ModelAdmin 属性,将使用模型字段。

使用 list_display_links 来控制 list_display 中的字段是否以及哪些字段应该被链接到对象的 “更改” 页面。

默认情况下,更改列表页将把第一列 —— list_display 中指定的第一个字段 —— 链接到每个项目的更改页面。但是 list_display_links 让你改变这一点:

  • 将其设置为 None,则完全没有链接。

  • 将它设置为一个列表或元组字段(格式与 list_display 相同),你希望将其列转换为链接。

    你可以指定一个或多个字段。只要字段出现在 list_display 中,Django 就不会关心链接了多少(或多少)字段。唯一的要求是,如果你想以这种方式使用 list_display_links,你必须定义 list_display

在这个例子中,first_namelast_name 字段将在更改列表页面上被链接:

class PersonAdmin(admin.ModelAdmin):
    list_display = ["first_name", "last_name", "birthday"]
    list_display_links = ["first_name", "last_name"]

在这个例子中,更改列表页网格将没有链接:

class AuditEntryAdmin(admin.ModelAdmin):
    list_display = ["timestamp", "message"]
    list_display_links = None
ModelAdmin.list_editable

list_editable 设置为模型上允许在更改列表页上编辑的字段名称列表。也就是说,在 list_editable 中列出的字段将作为表单部件显示在变更列表页上,允许用户一次编辑和保存多行。

备注

list_editable 以特定方式与其他几个选项进行交互;你应该注意以下规则:

  • list_editable 中的任何字段都必须在 list_display 中。你不能编辑一个没有显示的字段!
  • 同一字段不能同时列在 list_editablelist_display_links 中 —— 一个字段不能既是表单又是链接。

如果这些规则中的任何一条被破坏,你会得到一个验证错误。

ModelAdmin.list_filter

设置 list_filter 以在管理员的更改列表页面右侧边栏上激活过滤器。

在最简单的情况下,list_filter 接受一个字段名称的列表或元组以激活过滤,但还有更多高级选项可供选择。详细信息请参阅 ModelAdmin 列表过滤器

ModelAdmin.list_max_show_all

设置 list_max_show_all 来控制 “全部显示” 的管理员更改列表页面上可以出现多少个项目。只有当总结果数小于或等于此配置时,管理才会在更改列表中显示 “全部显示” 链接。默认情况下,这个配置为 200

ModelAdmin.list_per_page

设置 list_per_page 来控制每个分页的管理变更列表页面上出现多少个项目。默认情况下,设置为 100

设置 list_select_related 告诉 Django 在检索管理变更列表页的对象列表时使用 select_related()。这样可以省去一堆数据库查询。

该值应是布尔值、列表或元组。默认值是 False

当值为 True 时,select_related() 总是会被调用。当值设置为 False 时,Django 将查看 list_display,如果有 ForeignKey,则调用 select_related()

如果你需要更精细的控制,可以使用元组(或列表)作为 list_select_related 的值。空元组将阻止 Django 调用 select_related。任何其他元组将直接传递给 select_related 作为参数。例如:

class ArticleAdmin(admin.ModelAdmin):
    list_select_related = ["author", "category"]

将调用 select_related('author', 'category')

如果需要根据请求指定一个动态值,可以实现一个 get_list_select_related() 方法。

备注

select_related() 已经在变更列表的 QuerySet 上被调用时,ModelAdmin 会忽略这个属性。

ModelAdmin.ordering

设置 ordering 来指定对象列表在 Django 管理视图中的排序方式。这应该是一个列表或元组,格式与模型的 ordering 参数相同。

如果没有提供,Django 管理员将使用模型的默认排序。

如果你需要指定一个动态的顺序(例如取决于用户或语言),你可以实现一个 get_ordering() 方法。

排序和分类的性能考虑

为了确保结果的确定性排序,如果不能找到一个单一的或唯一的字段集,提供总的排序,那么变更表就会在排序中增加 pk

例如,如果默认的排序是按非唯一的 name 字段排序,那么更改列表就按 namepk 排序。如果你有很多行,而且在 namepk 上没有索引,这可能会表现得很差。

ModelAdmin.paginator

用于分页的分页器类。默认情况下,使用 django.core.paginator.Paginator。如果自定义的分页器类没有和 django.core.paginator.Paginator 一样的构造函数接口,你还需要为 ModelAdmin.get_paginator() 提供一个实现。

ModelAdmin.prepopulated_fields

prepopulated_fields 设置为一个字典,将字段名称映射到它应该预先填充的字段:

class ArticleAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ["title"]}

当设置时,给定的字段将使用一点 JavaScript 从分配的字段中填充。这个功能的主要用途是从一个或多个其他字段自动生成 SlugField 字段的值。生成的值是通过连接源字段的值,然后将结果转化为有效的 slug(例如,用破折号代替空格,使用 ASCII 字母的小写字母)。

预填充的字段在保存值后不会被 JavaScript 修改。通常情况下,不希望 slug 发生变化(如果对象中使用了 slug,会导致对象的 URL 发生变化)。

prepopulated_fields 不接受 DateTimeFieldForeignKeyOneToOneFieldManyToManyField 字段。

ModelAdmin.preserve_filters

默认情况下,在创建、编辑或删除对象后,应用的过滤器会被保存在列表视图中。您可以通过将此属性设置为 False 来清除过滤器。

ModelAdmin.show_facets
New in Django 5.0.

控制是否在管理列表中显示筛选器的 facet 计数。默认值为 ShowFacets.ALLOW

当显示时,facet 计数会根据当前应用的筛选器而更新。

class ShowFacets
New in Django 5.0.

对于 ModelAdmin.show_facets 的允许值的枚举。

ALWAYS

始终显示 facet 计数。

ALLOW

当提供 _facets 查询字符串参数时显示 facet 计数。

NEVER

从不显示 facet 计数。

show_facets 设置为所需的 ShowFacets 值。例如,要始终显示 facet 计数而无需提供查询参数:

from django.contrib import admin


class MyModelAdmin(admin.ModelAdmin):
    ...
    # Have facets always shown for this model admin.
    show_facets = admin.ShowFacets.ALWAYS

性能考虑与 facets

启用 facet 过滤器将增加与筛选器数量相匹配的管理列表页面上的查询数量。这些查询可能会引发性能问题,特别是对于大型数据集。在这些情况下,将 show_facets 设置为 ShowFacets.NEVER 可以完全禁用 faceting。

ModelAdmin.radio_fields

默认情况下,Django 的管理对于 ForeignKey 或设置了 choices 的字段使用选择框界面(<select>)。如果字段存在于 radio_fields 中,Django 将使用单选按钮接口代替。假设 groupPerson 模型上的一个 ForeignKey

class PersonAdmin(admin.ModelAdmin):
    radio_fields = {"group": admin.VERTICAL}

你可以在 django.contrib.admin 模块中选择使用 HORIZONTALVERTICAL

不要在 radio_fields 中包含一个字段,除非它是 ForeignKey 或已设置 choices

ModelAdmin.autocomplete_fields

autocomplete_fields 是一个 ForeignKey 和/或 ManyToManyField 字段的列表,你想将其改为 Select2 自动完成输入。

autocomplete_fields 是一个 ForeignKey 和/或的列表,默认情况下,管理对这些字段使用选择框接口(<select>)。有时你不想产生选择所有相关实例在下拉中显示的开销。ManyToManyField 字段你想改成 Select2 自动完成输入。

Select2 输入看起来与默认输入类似,但自带搜索功能,异步加载选项。如果相关模型有很多实例,这样会更快、更方便用户使用。

你必须在相关对象的 ModelAdmin 上定义 search_fields,因为自动完成搜索使用它。

为了避免未经授权的数据泄露,用户必须拥有相关对象的 viewchange 权限才能使用自动完成。

结果的排序和分页由相关的 ModelAdminget_ordering()get_paginator() 方法控制。

在下面的例子中,ChoiceAdminQuestion 有一个 ForeignKey 的自动完成字段。结果由 question_text 字段过滤,并由 date_created 字段排序:

class QuestionAdmin(admin.ModelAdmin):
    ordering = ["date_created"]
    search_fields = ["question_text"]


class ChoiceAdmin(admin.ModelAdmin):
    autocomplete_fields = ["question"]

大型数据集的性能考虑

使用 ModelAdmin.ordering 排序可能会导致性能问题,因为在一个大的查询集上排序会很慢。

此外,如果你的搜索字段包括没有被数据库索引的字段,你可能会在极大的表上遇到性能不佳的情况。

对于这些情况,最好是使用全文索引搜索来编写自己的 ModelAdmin.get_search_results() 实现。

你可能还想改变非常大的表的 Paginator,因为默认的分页器总是执行 count() 查询。例如,你可以覆盖 Paginator.count 属性的默认实现。

ModelAdmin.raw_id_fields

默认情况下,Django 的管理员对 ForeignKey 的字段使用选择框接口(<select>)。有时候,你不想产生必须选择所有相关的实例来显示在下拉框中的开销。

raw_id_fields 是你想改变为 ForeignKeyManyToManyFieldInput 部件的字段列表:

class ArticleAdmin(admin.ModelAdmin):
    raw_id_fields = ["newspaper"]

raw_id_fieldsInput 部件应该包含一个主键,如果该字段是 `ForeignKey`。或者是一个逗号分隔的值列表,如果该字段是 ManyToManyFieldraw_id_fields 部件在字段旁边显示一个放大镜按钮,允许用户搜索和选择一个值:

../../../_images/raw_id_fields.png
ModelAdmin.readonly_fields

默认情况下,管理会将所有字段显示为可编辑。该选项中的任何字段(应该是 listtuple)将按原样显示其数据,不可编辑;它们也被排除在用于创建和编辑的 ModelForm 中。请注意,当指定 ModelAdmin.fieldsModelAdmin.fieldsets 时,只读字段必须存在才能显示(否则将被忽略)。

如果没有通过 ModelAdmin.fieldsModelAdmin.fielets 定义明确的顺序就使用 readonly_fields,它们将在所有可编辑字段之后最后添加。

一个只读字段不仅可以显示模型字段的数据,还可以显示模型的方法或 ModelAdmin 类本身的方法的输出。这与 ModelAdmin.list_display 的行为方式非常相似。这提供了一种方法来使用管理员接口来提供被编辑对象的状态反馈,例如:

from django.contrib import admin
from django.utils.html import format_html_join
from django.utils.safestring import mark_safe


class PersonAdmin(admin.ModelAdmin):
    readonly_fields = ["address_report"]

    # description functions like a model field's verbose_name
    @admin.display(description="Address")
    def address_report(self, instance):
        # assuming get_full_address() returns a list of strings
        # for each line of the address and you want to separate each
        # line by a linebreak
        return format_html_join(
            mark_safe("<br>"),
            "{}",
            ((line,) for line in instance.get_full_address()),
        ) or mark_safe("<span class='errors'>I can't determine this address.</span>")
ModelAdmin.save_as

设置 save_as,在管理更改表格时启用 “另存为新” 功能。

通常情况下,对象有三个保存选项。“保存”、“保存并继续编辑” 和 “保存并添加另一个”。如果 save_asTrue,则 “保存并添加另一个” 将被 “另存为新” 按钮所取代,该按钮将创建一个新的对象(具有新的 ID),而不是更新现有的对象。

默认情况下,save_as 被设置为 False

ModelAdmin.save_as_continue

save_as=True 时,保存新对象后默认重定向到该对象的变更视图。如果设置 save_as_continue=False,则重定向到变更列表视图。

默认情况下,save_as_continue 被设置为 True

ModelAdmin.save_on_top

设置 save_on_top 来在你的管理更改表格的顶部添加保存按钮。

通常情况下,保存按钮只出现在表格的底部。如果设置 save_on_top,按钮将同时出现在顶部和底部。

默认情况下,save_on_top 被设置为 False

ModelAdmin.search_fields

设置 search_fields,在管理更改列表页面上启用搜索框。这应该被设置为字段名的列表,每当有人在该文本框中提交搜索查询时,就会被搜索到。

这些字段应该是某种文本字段,如 CharFieldTextField。你也可以对 ForeignKeyManyToManyField 进行相关查询,并使用查找 API “follow” 符号:

search_fields = ["foreign_key__related_fieldname"]

例如,如果你有一个有作者的博客条目,下面的定义将可以通过作者的电子邮件地址搜索博客条目:

search_fields = ["user__email"]

当有人在管理搜索框中进行搜索时,Django 会将搜索查询拆分成多个词,并返回所有包含这些词的对象,不区分大小写(使用 icontains 查找),其中每个词必须在 search_fields 中至少有一个。例如,如果 search_fields 设置为 ['first_name', 'last_name'],用户搜索 john lennon',Django 会做相当于这个 SQL WHERE 子句。

WHERE (first_name ILIKE '%john%' OR last_name ILIKE '%john%')
AND (first_name ILIKE '%lennon%' OR last_name ILIKE '%lennon%')

搜索查询可以包含带空格的引号短语。例如,如果用户搜索 "john winston"'john winston',Django 会做相当于这个 SQL 的 WHERE 子句:

WHERE (first_name ILIKE '%john winston%' OR last_name ILIKE '%john winston%')

如果你不想使用 icontains 作为查找,你可以通过附加字段来使用任何查找。例如,你可以通过设置 search_fields['first_name__exact'] 来使用 exact

还可以使用一些(较老的)快捷方式来指定字段查找。你可以在 search_fields 中的字段前加上以下字符,相当于在字段中加上 __<lookup>

前缀 查找
^ istartswith
= iexact
@ search
None icontains

如果你需要自定义搜索,你可以使用 ModelAdmin.get_search_results() 来提供额外的或替代的搜索行为。

ModelAdmin.search_help_text

设置 search_help_text,为搜索框指定一个描述性文本,显示在它的下面。

ModelAdmin.show_full_result_count

设置 show_full_result_count 来控制是否应该在过滤后的管理页面上显示全部对象的数量(例如: 99 results (103 total))。如果这个选项被设置为 False,则会显示 99 results (Show all) 这样的文字。

默认的 show_full_result_count=True 会生成一个对表进行完整计数的查询,如果表包含大量的行,那么这个查询可能会很昂贵。

ModelAdmin.sortable_by

默认情况下,变更列表页面允许按 list_display 中指定的所有模型字段(以及使用 display() 装饰器的 ordering 参数或具有 admin_order_field 属性的可调用对象)进行排序。

如果你想禁止对某些列进行排序,请将 sortable_by 设置为你想排序的 list_display 子集的一个集合(例如 list`tupleset)。一个空的集合会禁用所有列的排序。

如果你需要动态地指定这个列表,可以实现一个 get_sortable_by() 方法来代替。

ModelAdmin.view_on_site

设置 view_on_site 来控制是否显示 “在站点上查看” 链接。这个链接应该把你带到一个可以显示保存对象的 URL。

这个值可以是一个布尔标志,也可以是一个可调用对象。如果 True (默认),对象的 get_absolute_url() 方法将被用来生成网址。

如果你的模型有一个 get_absolute_url() 方法,但你不想让 “在站点上查看” 按钮出现,你只需要将 view_on_site 设置为 False

from django.contrib import admin


class PersonAdmin(admin.ModelAdmin):
    view_on_site = False

如果它是一个可调用对象,它接受模型实例作为参数。例如:

from django.contrib import admin
from django.urls import reverse


class PersonAdmin(admin.ModelAdmin):
    def view_on_site(self, obj):
        url = reverse("person-detail", kwargs={"slug": obj.slug})
        return "https://example.com" + url

自定义模板选项

覆盖管理模板 部分描述了如何覆盖或扩展默认的管理模板。 使用以下选项来覆盖 ModelAdmin 视图使用的默认模板。

ModelAdmin.add_form_template

自定义模板的路径,由 add_view() 使用。

ModelAdmin.change_form_template

自定义模板的路径,由 change_view() 使用。

ModelAdmin.change_list_template

自定义模板的路径,由 changelist_view() 使用。

ModelAdmin.delete_confirmation_template

自定义模板的路径,由 delete_view() 用于在删除一个或多个对象时显示确认页面。

ModelAdmin.delete_selected_confirmation_template

自定义模板的路径,由 delete_selected 动作方法使用,在删除一个或多个对象时显示确认页面。参见 动作文档

ModelAdmin.object_history_template

自定义模板的路径,由 history_view() 使用。

ModelAdmin.popup_response_template

response_add()response_change()response_delete() 使用的自定义模板的路径。

ModelAdmin 方法

警告

当覆盖 ModelAdmin.save_model()ModelAdmin.delete_model() 时,你的代码必须保存/删除对象。它们不是为了否决的目的,而是允许你执行额外的操作。

ModelAdmin.save_model(request, obj, form, change)[源代码]

save_model 方法被赋予 HttpRequest、一个模型实例、一个 ModelForm 实例和一个基于是否添加或更改对象的布尔值。覆盖这个方法可以进行保存前或保存后的操作。调用 super().save_model() 使用 Model.save() 保存对象。

例如,在保存之前将 request.user 附加到对象上:

from django.contrib import admin


class ArticleAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        obj.user = request.user
        super().save_model(request, obj, form, change)
ModelAdmin.delete_model(request, obj)[源代码]

delete_model 方法被赋予 HttpRequest 和一个模型实例。覆盖该方法可以进行删除前或删除后的操作。调用 super().delete_model() 使用 Model.delete`() 删除对象。

ModelAdmin.delete_queryset(request, queryset)[源代码]

delete_queryset() 方法是给定 HttpRequest 和一个 QuerySet 要删除的对象。重写该方法,可以自定义 “删除选定对象” 的删除过程 动作

ModelAdmin.save_formset(request, form, formset, change)[源代码]

save_formset 方法被赋予 HttpRequest、父 ModelForm 实例和一个基于是否添加或更改父对象的布尔值。

例如,将 request.user 附加到每一个改变了的表单集模型实例:

class ArticleAdmin(admin.ModelAdmin):
    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()

另见 在表单集中保存对象

ModelAdmin.get_ordering(request)

get_ordering 方法以 request 为参数,并期望返回一个类似于 ordering 属性的 listtuple 的排序。例如:

class PersonAdmin(admin.ModelAdmin):
    def get_ordering(self, request):
        if request.user.is_superuser:
            return ["name", "rank"]
        else:
            return ["name"]
ModelAdmin.get_search_results(request, queryset, search_term)[源代码]

get_search_results 方法将显示的对象列表修改为符合所提供的搜索词的对象。它接受请求、应用当前过滤器的查询集和用户提供的搜索词。它返回一个元组,其中包含一个修改后实现搜索的查询集,以及一个布尔值,表示结果是否包含重复。

默认的实现是搜索 ModelAdmin.search_fields 中命名的字段。

这个方法可以被你自己定制的搜索方法覆盖。例如,你可能希望按整数字段搜索,或使用外部工具如 SolrHaystack。你必须确定你的搜索方法实施的查询集更改是否可能引入重复的结果,并在返回值的第二个元素中返回 True

例如,如果要按 nameage 进行搜索,你可以使用:

class PersonAdmin(admin.ModelAdmin):
    list_display = ["name", "age"]
    search_fields = ["name"]

    def get_search_results(self, request, queryset, search_term):
        queryset, may_have_duplicates = super().get_search_results(
            request,
            queryset,
            search_term,
        )
        try:
            search_term_as_int = int(search_term)
        except ValueError:
            pass
        else:
            queryset |= self.model.objects.filter(age=search_term_as_int)
        return queryset, may_have_duplicates

这个实现比 search_fields = ('name', '=age') 更有效,因为后者的结果是对数字字段进行字符串比较,例如 ... OR UPPER("polls_choice"."votes"::text) = UPPER('4') 在 PostgreSQL 上。

save_related 方法被赋予 HttpRequest、父 ModelForm 实例、内联表单集列表和一个基于父对象是否被添加或更改的布尔值。在这里可以对父对象相关的对象进行任何保存前或保存后的操作。请注意,此时父对象及其窗体已经被保存。

ModelAdmin.get_autocomplete_fields(request)

get_autocomplete_fields() 方法被赋予 HttpRequest,预计将返回一个 listtuple 字段名,这些字段名将与自动完成部件一起显示,如上面 ModelAdmin.autocomplete_fields 部分所述。

ModelAdmin.get_readonly_fields(request, obj=None)

get_readonly_fields 方法是给定 HttpRequest 和被编辑的 obj``(或者在添加表单中给定 ``None),并期望返回一个 listtuple 的字段名,这些字段名将被显示为只读,如上面 ModelAdmin.readonly_fields 部分所述。

ModelAdmin.get_prepopulated_fields(request, obj=None)

get_prepopulated_fields 方法是给定 HttpRequest 和被编辑的 obj (或者在添加表单中给定 None),并期望返回一个 dictionary,如上面 ModelAdmin.prepopulated_fields 一节所述。

ModelAdmin.get_list_display(request)[源代码]

get_list_display 方法被赋予 HttpRequest,预计将返回一个 listtuple 的字段名,这些字段名将显示在变更列表视图上,如上面 ModelAdmin.list_display 一节所述。

get_list_display_links 方法被赋予 HttpRequest 和由 ModelAdmin.get_list_display() 返回的 listtuple。如 ModelAdmin.list_display_links 一节所述,预计它将返回变化列表中的 Nonelisttuple 字段名,这些字段名将被链接到变化视图。

ModelAdmin.get_exclude(request, obj=None)

get_exclude 方法是给定 HttpRequest 和被编辑的 obj (或者在添加表单中给定 None),并期望返回一个字段列表,如 ModelAdmin.exclude 中所述。

ModelAdmin.get_fields(request, obj=None)

get_fields 方法是给定 HttpRequest 和被编辑的 obj (或者在添加表单中给定 None),并期望返回一个字段列表,如上面 ModelAdmin.fields 一节所述。

ModelAdmin.get_fieldsets(request, obj=None)

get_fieldsets 方法接收 HttpRequest 和正在编辑的 obj (或在添加表单上为 None),并期望返回一个包含多个 2-元组的列表,其中每个 2-元组表示管理员表单页面上的一个 <fieldset>,如上述的 ModelAdmin.fieldsets 部分所描述的那样。

ModelAdmin.get_list_filter(request)[源代码]

get_list_filter 方法被赋予 HttpRequest,并期望返回与 list_filter 属性相同的序列类型。

get_list_select_related 方法被赋予 HttpRequest,应该像 ModelAdmin.list_select_related 那样返回一个布尔值或列表。

ModelAdmin.get_search_fields(request)[源代码]

get_search_fields 方法被赋予 HttpRequest,并期望返回与 search_fields 属性相同的序列类型。

ModelAdmin.get_sortable_by(request)

get_sortable_by() 方法被传递给 HttpRequest,并期望返回一个字段名的集合(例如 listtupleset),这些字段名将在更改列表页中被排序。

如果设置了,它的默认实现将返回 sortable_by,否则将服从 get_list_display()

例如,要防止一列或多列无法排序:

class PersonAdmin(admin.ModelAdmin):
    def get_sortable_by(self, request):
        return {*self.get_list_display(request)} - {"rank"}
ModelAdmin.get_inline_instances(request, obj=None)[源代码]

get_inline_instances 方法是给定 HttpRequest 和被编辑的 obj (或者在添加表单中给定 None),并期望返回一个 listtuple 的 :class:``~django.contrib.admin.InlineModelAdmin` 对象,如下文 InlineModelAdmin 部分所述。例如,以下内容将返回没有基于添加、更改、删除和查看权限的默认过滤的内联:

class MyModelAdmin(admin.ModelAdmin):
    inlines = [MyInline]

    def get_inline_instances(self, request, obj=None):
        return [inline(self.model, self.admin_site) for inline in self.inlines]

如果你覆盖了这个方法,请确保返回的内联是 inlines 中定义的类的实例,否则在添加相关对象时可能会遇到 “Bad Request” 错误。

ModelAdmin.get_inlines(request, obj)

get_inlines 方法是给定 HttpRequest 和被编辑的 obj (或在添加表单中给定 None),并期望返回一个可迭代对象的内联。你可以覆盖这个方法,根据请求或模型实例动态添加内联,而不是在 ModelAdmin.inlines 中指定它们。

ModelAdmin.get_urls()[源代码]

ModelAdmin 上的 get_urls 方法返回用于该模型管理器的 URL,就像 URL 配置一样。因此,你可以像在 URL调度器 中记录的那样扩展它们,使用 AdminSite.admin_view() 包装器来处理你的视图:

from django.contrib import admin
from django.template.response import TemplateResponse
from django.urls import path


class MyModelAdmin(admin.ModelAdmin):
    def get_urls(self):
        urls = super().get_urls()
        my_urls = [path("my_view/", self.admin_site.admin_view(self.my_view))]
        return my_urls + urls

    def my_view(self, request):
        # ...
        context = dict(
            # Include common variables for rendering the admin template.
            self.admin_site.each_context(request),
            # Anything else you want in the context...
            key=value,
        )
        return TemplateResponse(request, "sometemplate.html", context)

如果你想使用管理布局,从 admin/base_site.html 扩展:

{% extends "admin/base_site.html" %}
{% block content %}
...
{% endblock %}

备注

注意到 self.my_view 函数被包装在 self.admin_site.admin_view 中。这很重要,因为它确保了两件事情:

  1. 权限检查被运行,确保只有活跃的工作人员用户可以访问视图。
  2. 应用了 django.views.decorators.cache.never_cache() 装饰器以防止缓存,确保返回的信息是最新的。

备注

请注意,自定义模式包含在常规的管理 URL 之前:管理 URL 模式是非常宽松的,几乎可以匹配任何东西,所以你通常会希望将你的自定义 URL 添加到内置的 URL 中。

在这个例子中,my_view 将在 /admin/myapp/mymodel/my_view/ 中被访问(假设在 /admin/ 中包含了管理的 URL)。

如果页面是可缓存的,但你仍然希望进行权限检查,你可以传递一个 cacheable=True 参数到 AdminSite.admin_view()

path("my_view/", self.admin_site.admin_view(self.my_view, cacheable=True))

ModelAdmin 视图有 model_admin 属性。其他 AdminSite 视图有 admin_site 属性。

ModelAdmin.get_form(request, obj=None, **kwargs)[源代码]

返回一个 ModelForm 类,用于管理员添加和更改视图,参见 add_view()change_view()

基本实现使用 modelform_factory() 来子类 form,通过 fieldsexclude 等属性进行修改。所以,例如,如果你想为超级用户提供额外的字段,你可以像这样换一个不同的基本表单:

class MyModelAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        if request.user.is_superuser:
            kwargs["form"] = MySuperuserForm
        return super().get_form(request, obj, **kwargs)

你也可以直接返回一个自定义的 ModelForm 类。

ModelAdmin.get_formsets_with_inlines(request, obj=None)[源代码]

产生 (FormSet, InlineModelAdmin) 对,用于管理添加和更改视图。

例如,如果你想只在变化视图中显示一个特定的内联,你可以覆盖 get_formsets_with_inlines 如下:

class MyModelAdmin(admin.ModelAdmin):
    inlines = [MyInline, SomeOtherInline]

    def get_formsets_with_inlines(self, request, obj=None):
        for inline in self.get_inline_instances(request, obj):
            # hide MyInline in the add view
            if not isinstance(inline, MyInline) or obj is not None:
                yield inline.get_formset(request, obj), inline
ModelAdmin.formfield_for_foreignkey(db_field, request, **kwargs)

ModelAdmin 上的 formfield_for_foreignkey 方法允许你覆盖外键字段的默认 formfield。例如,要根据用户返回这个外键字段的对象子集:

class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "car":
            kwargs["queryset"] = Car.objects.filter(owner=request.user)
        return super().formfield_for_foreignkey(db_field, request, **kwargs)

这使用 HttpRequest 实例过滤 Car 外键字段,只显示 User 实例拥有的汽车。

对于更复杂的过滤器,你可以使用 ModelForm.__init__() 方法来基于你的模型的 instance 进行过滤(参见 处理关系的字段)。例如:

class CountryAdminForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["capital"].queryset = self.instance.cities.all()


class CountryAdmin(admin.ModelAdmin):
    form = CountryAdminForm
ModelAdmin.formfield_for_manytomany(db_field, request, **kwargs)

formfield_for_foreignkey 方法一样,formfield_for_manytomany 方法也可以被重写,以改变多对多字段的默认字段。例如,如果一个车主可以拥有多辆汽车,而汽车可以属于多个车主 —— 多对多关系 —— 你可以过滤 Car 外键字段,只显示 User

class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_manytomany(self, db_field, request, **kwargs):
        if db_field.name == "cars":
            kwargs["queryset"] = Car.objects.filter(owner=request.user)
        return super().formfield_for_manytomany(db_field, request, **kwargs)
ModelAdmin.formfield_for_choice_field(db_field, request, **kwargs)

formfield_for_foreignkeyformfield_for_manytomany 方法一样,formfield_for_choice_field 方法可以被重写,以改变已声明选择的字段的默认字段。例如,如果超级用户的选择与普通员工的选择不同,你可以按以下步骤进行:

class MyModelAdmin(admin.ModelAdmin):
    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"].append(("ready", "Ready for deployment"))
        return super().formfield_for_choice_field(db_field, request, **kwargs)

choices 限制

任何在表单字段上设置的 choices 属性将只限于表单字段。如果模型上对应的字段设置了选择,那么提供给表单的选择必须是这些选择的有效子集,否则在保存前对模型本身进行验证时,表单提交将以一个 ValidationError 失败。

ModelAdmin.get_changelist(request, **kwargs)[源代码]

返回用于列表的 Changelist 类。默认情况下,使用的是 django.contrib.admin.views.main.ChangeList。通过继承这个类,你可以改变列表的行为。

ModelAdmin.get_changelist_form(request, **kwargs)[源代码]

返回一个 ModelForm 类,供变更列表页面中的 Formset 使用。要使用一个自定义表单,例如:

from django import forms


class MyForm(forms.ModelForm):
    pass


class MyModelAdmin(admin.ModelAdmin):
    def get_changelist_form(self, request, **kwargs):
        return MyForm

省略 Meta.model 属性

如果你在 ModelForm 上定义了 Meta.model 属性,你也必须定义 Meta.fields 属性(或 Meta.exclude 属性)。然而,ModelAdmin 会忽略这个值,用 ModelAdmin.list_editable 属性来覆盖它。最简单的解决办法是省略 Meta.model 属性,因为 ModelAdmin 将提供正确的模型使用。

ModelAdmin.get_changelist_formset(request, **kwargs)[源代码]

如果使用了 list_editable,则返回一个 ModelFormSet 类,供变更列表页面使用。要使用自定义表单集,例如:

from django.forms import BaseModelFormSet


class MyAdminFormSet(BaseModelFormSet):
    pass


class MyModelAdmin(admin.ModelAdmin):
    def get_changelist_formset(self, request, **kwargs):
        kwargs["formset"] = MyAdminFormSet
        return super().get_changelist_formset(request, **kwargs)
ModelAdmin.lookup_allowed(lookup, value, request)

变更列表页面中的对象可以通过 URL 的查询字符串进行过滤。例如 list_filter 就是这样工作的。这些查询类似于 QuerySet.filter() (例如 user__email=user@example.com)。由于用户可以对查询字符串中的查找进行操作,因此必须对它们进行处理,以防止未经授权的数据暴露。

lookup_allowed() 方法接收来自查询字符串的查找路径(例如,'user__email'),相应的值(例如,'user@example.com')和请求,并返回一个布尔值,指示是否允许使用这些参数来过滤 changelist 的 QuerySet。如果 lookup_allowed() 返回 False,将引发 DisallowedModelAdminLookup (是 SuspiciousOperation 的子类)。

默认情况下,lookup_allowed() 允许访问模型的本地字段、在 list_filter 中使用的字段路径(但不包括 get_list_filter())中使用的字段路径,以及 limit_choices_toraw_id_fields 中正确运行所需的查找。

重写这个方法来定制你的 ModelAdmin 子类允许的查找。

Changed in Django 5.0:

已添加 request 参数。

ModelAdmin.has_view_permission(request, obj=None)

如果允许查看 obj,应返回 True,否则返回 False。如果 obj 是 None,应返回 TrueFalse 表示是否允许查看该类型的对象(例如,False 将被解释为当前用户不允许查看该类型的任何对象)。

如果用户有 “更改” 或 “查看” 权限,默认的实现将返回 True

ModelAdmin.has_add_permission(request)

如果允许添加对象,应返回 True,否则返回 False

ModelAdmin.has_change_permission(request, obj=None)

如果允许编辑 obj,应返回 True,否则应返回 False。如果 objNone,应返回 TrueFalse 表示是否允许编辑该类型对象(例如,False 将被解释为当前用户不允许编辑该类型的任何对象)。

ModelAdmin.has_delete_permission(request, obj=None)

如果允许删除 obj,应返回 True,否则返回 False。如果 objNone,应返回 TrueFalse,以表明是否允许删除该类型的对象(例如,False 将被解释为当前用户不允许删除该类型的任何对象)。

ModelAdmin.has_module_permission(request)

如果允许在管理员索引页上显示模块和访问模块的索引页,应该返回 True,否则返回 False。默认使用 User.has_module_perms()。覆盖它并不限制对视图的访问,添加、更改或删除视图, has_view_permission()has_add_permission()has_change_permission()has_delete_permission() 应该用于此。

ModelAdmin.get_queryset(request)

ModelAdmin 上的 get_queryset 方法返回一个 QuerySet 的所有模型实例,这些实例可以被管理网站编辑。覆盖该方法的一个用例是显示登录用户拥有的对象:

class MyModelAdmin(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)
ModelAdmin.message_user(request, message, level=messages.INFO, extra_tags='', fail_silently=False)[源代码]

使用 django.contrib.messages 后台向用户发送消息。 参见 自定义 ModelAdmin 示例

关键字参数允许你改变消息的级别,添加额外的 CSS 标签,或者在没有安装 contrib.messages 框架的情况下无声地失败。这些关键字参数与 django.contrib.messages.add_message() 的参数一致,更多细节请参见该函数的文档。一个不同的地方是,除了整数/常量之外,级别还可以作为字符串标签传递。

ModelAdmin.get_paginator(request, queryset, per_page, orphans=0, allow_empty_first_page=True)[源代码]

返回要用于该视图的分页器实例。默认情况下,实例化一个 paginator 的实例。

ModelAdmin.response_add(request, obj, post_url_continue=None)[源代码]

确定 add_view() 阶段的 HttpResponse

response_add 在提交管理表单后,对象和所有相关实例被创建和保存后被调用。你可以覆盖它来改变对象创建后的默认行为。

ModelAdmin.response_change(request, obj)[源代码]

确定 change_view() 阶段的 HttpResponse

response_change 在管理表单提交后,对象和所有相关实例被保存后被调用。你可以覆盖它来改变对象被改变后的默认行为。

ModelAdmin.response_delete(request, obj_display, obj_id)[源代码]

delete_view() 阶段确定 HttpResponse

response_delete 在对象被删除后被调用。你可以覆盖它来改变对象被删除后的默认行为。

obj_display 是删除对象名称的字符串。

obj_id 是用于检索要删除的对象的序列化标识符。

ModelAdmin.get_formset_kwargs(request, obj, inline, prefix)[源代码]

一个用于自定义传递给表单集构造函数的关键字参数的钩子。例如,将 request 传递给表单集表单:

class MyModelAdmin(admin.ModelAdmin):
    def get_formset_kwargs(self, request, obj, inline, prefix):
        return {
            **super().get_formset_kwargs(request, obj, inline, prefix),
            "form_kwargs": {"request": request},
        }

你还可以使用它来为表单集表单设置 initial

ModelAdmin.get_changeform_initial_data(request)[源代码]

一个钩子,用于管理更改表格的初始数据。默认情况下,字段的初始值来自 GET 参数。例如,?name=initial_value 将把 name 字段的初始值设置为 initial_value

这个方法应该返回一个形式为 {'fieldname': 'fieldval'} 的字典:

def get_changeform_initial_data(self, request):
    return {"name": "custom_initial_value"}
ModelAdmin.get_deleted_objects(objs, request)[源代码]

一个钩子,用于自定义 delete_view() 和 “删除已选” 动作 的删除过程。

objs 参数是要删除的对象(一个 QuerySet 或模型实例列表)的等价可迭代对象,requestHttpRequest

这个方法必须返回一个四元元组 (delete_objects, model_count, perms_needed, protected)

deleted_objects 是一个代表所有将被删除对象的字符串列表。如果有任何相关的对象要删除,则列表是嵌套的,包括这些相关对象。该列表在模板中使用 unordered_list 过滤器进行格式化。

model_count 是一个将每个模型的 verbose_name_plural 映射到将被删除的对象数量的字典。

perms_needed 是一组 verbose_name 的用户没有权限删除的模型。

protected 是一个字符串列表,代表所有不能删除的受保护相关对象。该列表显示在模板中。

其他方法

ModelAdmin.add_view(request, form_url='', extra_context=None)[源代码]

模型实例添加页面的 Django 视图。见下面的说明。

ModelAdmin.change_view(request, object_id, form_url='', extra_context=None)[源代码]

模型实例编辑页面的 Django 视图。见下面的说明。

ModelAdmin.changelist_view(request, extra_context=None)[源代码]

模型实例变更列表/动作页面的 Django 视图。见下面的说明。

ModelAdmin.delete_view(request, object_id, extra_context=None)[源代码]

模型实例删除确认页面的 Django 视图。参见下面的说明。

ModelAdmin.history_view(request, object_id, extra_context=None)[源代码]

Django 视图用于显示给定模型实例的修改历史的页面。

与上一节中详细介绍的钩子类型的 ModelAdmin 方法不同,这五个方法实际上是被设计成作为 Django 视图从管理应用的 URL 调度处理程序中调用,以渲染处理模型实例 CRUD 操作的页面。因此,完全覆盖这些方法将显著改变管理员应用程序的行为。

覆盖这些方法的一个常见原因是为了增强提供给渲染视图的模板的上下文数据。在下面的例子中,更改视图被覆盖,以便为渲染模板提供一些额外的映射数据,否则这些数据将无法使用:

class MyModelAdmin(admin.ModelAdmin):
    # A template for a very customized change view:
    change_form_template = "admin/myapp/extras/openstreetmap_change_form.html"

    def get_osm_info(self):
        # ...
        pass

    def change_view(self, request, object_id, form_url="", extra_context=None):
        extra_context = extra_context or {}
        extra_context["osm_data"] = self.get_osm_info()
        return super().change_view(
            request,
            object_id,
            form_url,
            extra_context=extra_context,
        )

这些视图返回 TemplateResponse 实例,允许你在渲染前轻松定制响应数据。更多细节,请看 TemplateResponse 文档

ModelAdmin 静态资源定义

有时你会想在添加/更改视图时添加一点 CSS 和/或 JavaScript。这可以通过在你的 ModelAdmin 上使用 Media 内类来实现:

class ArticleAdmin(admin.ModelAdmin):
    class Media:
        css = {
            "all": ["my_styles.css"],
        }
        js = ["my_code.js"]

staticfiles appSTATIC_URL (如果 STATIC_URLNone,则 MEDIA_URL)预先加入任何资产路径。同样的规则适用于:ref:表单上定义的静态资源 <form-asset-paths>。

jQuery

Django 管理 JavaScript 使用了 jQuery 库。

为了避免与用户提供的脚本或库冲突,Django 的 jQuery(版本3.7.1)被命名为 django.jQuery。如果你想在自己的管理 JavaScript 中使用 jQuery,而不需要包含第二个副本,你可以在 changelist 和 add/edit 视图上使用 django.jQuery 对象。此外,依赖于 django.jQuery 的自己的管理表单或小部件在 声明表单媒体资产 时必须指定 js=['admin/js/jquery.init.js', …]

Changed in Django 5.0:

jQuery 已从 3.6.4 升级到 3.7.1。

ModelAdmin 类默认需要 jQuery,所以除非你有特殊需要,否则不需要将 jQuery 添加到你的 ModelAdmin 的媒体资源列表中。例如,如果你要求 jQuery 库在全局命名空间中(例如在使用第三方 jQuery 插件时),或者你需要一个较新版本的 jQuery,你将不得不包含自己的副本。

Django 提供了 jQuery 的非压缩和 “最小化” 版本,分别为 jquery.jsjquery.min.js

ModelAdminInlineModelAdmin 有一个 media 属性,它返回一个 Media 对象的列表,其中存储了表单和/或表单集的 JavaScript 文件的路径。如果 DEBUGTrue,它将返回各种 JavaScript 文件的未压缩版本,包括 jquery.js;如果不是,它将返回 “最小化” 版本。

在管理中添加自定义验证

你也可以在管理中添加自定义的数据验证。自动管理界面重用 django.formsModelAdmin 类让你能够定义你自己的表单:

class ArticleAdmin(admin.ModelAdmin):
    form = MyArticleAdminForm

MyArticleAdminForm 可以在任何地方定义,只要你在需要的地方导入。现在,在你的表单中,你可以为任何字段添加自己的自定义验证:

class MyArticleAdminForm(forms.ModelForm):
    def clean_name(self):
        # do something that validates your data
        return self.cleaned_data["name"]

在这里使用 ModelForm 是很重要的,否则可能会出问题。更多信息请参见 表单 文档中的 自定义验证器验证器返回的模型注解

InlineModelAdmin 对象

class InlineModelAdmin
class TabularInline[源代码]
class StackedInline[源代码]

管理界面可以在同一页面上与父模型编辑模型。这些被称为内联。假设你有这两个模型:

from django.db import models


class Author(models.Model):
    name = models.CharField(max_length=100)


class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)

你可以在作者页面上编辑作者撰写的书籍。你可以通过在 ModelAdmin.inlines 中指定内联来添加到模型中:

from django.contrib import admin


class BookInline(admin.TabularInline):
    model = Book


class AuthorAdmin(admin.ModelAdmin):
    inlines = [
        BookInline,
    ]

Django 提供了两个 InlineModelAdmin 的子类,它们是:

这两者之间的区别仅仅是用于呈现它们的模板。

InlineModelAdmin 选项

InlineModelAdminModelAdmin 共享许多相同的功能,并增加了一些自己的功能(共享功能实际上是在 BaseModelAdmin 超级类中定义的)。共享的功能有:

InlineModelAdmin 类添加或自定义:

InlineModelAdmin.model

内联使用的模型。这是必须的。

InlineModelAdmin.fk_name

模型上外键的名称。在大多数情况下会自动处理,但如果同一父模型有多个外键,则必须明确指定 fk_name

InlineModelAdmin.formset

默认为 BaseInlineFormSet。使用你自己的表单集可以给你提供很多定制的可能性。内联是围绕 模型表单集 建立的。

InlineModelAdmin.form

form 的值默认为 ModelForm。当为这个内联创建表单集时,会通过 inlineformset_factory() 传递这个值。

警告

当为 InlineModelAdmin 表单编写自定义验证时,要谨慎编写依赖于父模型特征的验证。如果父模型未能验证,它可能会处于不一致的状态,如 验证 ModelForm 中的警告所述。

InlineModelAdmin.classes

A list or tuple containing extra CSS classes to apply to the fieldset that is rendered for the inlines. Defaults to None. As with classes configured in fieldsets, inlines with a collapse class will be initially collapsed using an expandable widget.

Changed in Django 5.1:

fieldsets using the collapse class now use <details> and <summary> elements, provided they define a name.

InlineModelAdmin.extra

控制表单集在初始表格之外显示的额外表单数量,默认为 3 个。更多信息请参见 表单集文档

对于使用支持 JavaScript 的浏览器的用户,提供了一个 “添加另一个” 的链接,以便在 extra 参数提供的内联行之外,再添加任意数量的内联行。

如果当前显示的表单数量超过 max_num,或者用户没有启用 JavaScript,动态链接将不会出现。

InlineModelAdmin.get_extra() 还允许你自定义额外表格的数量。

InlineModelAdmin.max_num

这控制了内联中显示表单的最大数量。这并不直接与对象的数量相关,但如果该值足够小,则可以。更多信息请参见 model-formets-max-num

InlineModelAdmin.get_max_num() 还允许你自定义额外表单的最大数量。

InlineModelAdmin.min_num

这控制了内联中要显示的表单的最少数量。更多信息请参见 modelformset_factory()

InlineModelAdmin.get_min_num() 还允许你自定义显示的最小表单数量。

InlineModelAdmin.raw_id_fields

默认情况下,Django 的管理员对 ForeignKey 的字段使用选择框接口(<select>)。有时候,你不想产生必须选择所有相关的实例来显示在下拉框中的开销。

raw_id_fields 是你想改变为 ForeignKeyManyToManyFieldInput 部件的字段列表:

class BookInline(admin.TabularInline):
    model = Book
    raw_id_fields = ["pages"]
InlineModelAdmin.template

用于在页面上呈现内联的模板。

InlineModelAdmin.verbose_name

对模型内部 Meta 类中的 verbose_name 的重写。

InlineModelAdmin.verbose_name_plural

verbose_name_plural 的重写,来自模型内部的 Meta 类。如果这个没有给出,而 InlineModelAdmin.verbose_name 被定义,Django 将使用 InlineModelAdmin.verbose_name + 's'

InlineModelAdmin.can_delete

指定是否可以在内联中删除内联对象。默认为 True

指定在管理中可以更改的内联对象是否有更改表单的链接。默认为 False

InlineModelAdmin.get_formset(request, obj=None, **kwargs)

返回一个 BaseInlineFormSet 类,用于管理添加/更改视图。obj 是被编辑的父对象,或者当添加一个新的父对象时,返回 None。参见 ModelAdmin.get_formsets_with_inlines 的例子。

InlineModelAdmin.get_extra(request, obj=None, **kwargs)

返回要使用的额外内联表单的数量。默认情况下,返回 InlineModelAdmin.extra 属性。

重写此方法,以编程方式确定额外内联表格的数量。例如,可以根据模型实例(作为关键字参数 obj 传递):

class BinaryTreeAdmin(admin.TabularInline):
    model = BinaryTree

    def get_extra(self, request, obj=None, **kwargs):
        extra = 2
        if obj:
            return extra - obj.binarytree_set.count()
        return extra
InlineModelAdmin.get_max_num(request, obj=None, **kwargs)

返回要使用的额外内联表单的最大数量。默认情况下,返回 InlineModelAdmin.max_num 属性。

重写此方法,以编程方式确定内联表格的最大数量。例如,可以根据模型实例(作为关键字参数 obj 传递):

class BinaryTreeAdmin(admin.TabularInline):
    model = BinaryTree

    def get_max_num(self, request, obj=None, **kwargs):
        max_num = 10
        if obj and obj.parent:
            return max_num - 5
        return max_num
InlineModelAdmin.get_min_num(request, obj=None, **kwargs)

返回要使用的内联表单的最小数量。默认情况下,返回 InlineModelAdmin.min_num 属性。

重写此方法,以编程方式确定内联表格的最少数量。例如,这可以基于模型实例(作为关键字参数 obj 传递)。

InlineModelAdmin.has_add_permission(request, obj)

如果允许添加内联对象,应返回 True,否则返回 Falseobj 是被编辑的父对象,或者当添加一个新的父对象时返回 None

InlineModelAdmin.has_change_permission(request, obj=None)

如果允许编辑内联对象,应返回 True,否则返回 Falseobj 是被编辑的父对象。

InlineModelAdmin.has_delete_permission(request, obj=None)

如果允许删除内联对象,应返回 True,否则返回 Falseobj 是被编辑的父对象。

备注

传递给 InlineModelAdmin 方法的 obj 参数是被编辑的父对象,或者在添加新父对象时是 None

与有两个或更多外键到同一父模型的模型一起工作

有时同一个模型可以有多个外键。以这个模型为例:

from django.db import models


class Friendship(models.Model):
    to_person = models.ForeignKey(
        Person, on_delete=models.CASCADE, related_name="friends"
    )
    from_person = models.ForeignKey(
        Person, on_delete=models.CASCADE, related_name="from_friends"
    )

如果你想在 Person 管理添加/更改页面上显示内联,你需要明确定义外键,因为它不能自动这样做:

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


class FriendshipInline(admin.TabularInline):
    model = Friendship
    fk_name = "to_person"


class PersonAdmin(admin.ModelAdmin):
    inlines = [
        FriendshipInline,
    ]

与多对多模型一起工作

默认情况下,多对多关系的管理部件将显示在哪个模型上,包含对 ManyToManyField 的实际引用。根据你的 ModelAdmin 定义,你模型中的每个多对多字段将由一个标准的 HTML <select multiple>,一个水平或垂直过滤器,或一个 raw_id_fields 小部件来表示。然而,也可以用内联来代替这些部件。

假设我们有以下模型:

from django.db import models


class Person(models.Model):
    name = models.CharField(max_length=128)


class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, related_name="groups")

如果你想使用内联显示多对多关系,你可以通过为关系定义一个 InlineModelAdmin 对象来实现:

from django.contrib import admin


class MembershipInline(admin.TabularInline):
    model = Group.members.through


class PersonAdmin(admin.ModelAdmin):
    inlines = [
        MembershipInline,
    ]


class GroupAdmin(admin.ModelAdmin):
    inlines = [
        MembershipInline,
    ]
    exclude = ["members"]

在这个例子中,有两个特点值得注意。

首先 MembershipInline 类引用 Group.members.throughthrough 属性是对管理多对多关系的模型的引用。当你定义一个多对多字段时,这个模型是由 Django 自动创建的。

其次,GroupAdmin 必须手动排除 members 字段。Django 会在定义关系的模型上显示一个多对多字段的管理部件(在本例中是 Group)。如果你想使用一个内联模型来表示多对多关系,你必须告诉 Django 的管理 不要 显示这个部件,否则你将在你的管理页面上有两个部件来管理这个关系。

请注意,当使用这种技术时, m2m_changed 信号并没有被触发。这是因为对于管理来说,through 只是一个有两个外键字段的模型,而不是一个多对多的关系。

在所有其他方面,InlineModelAdmin 和其他的完全一样。你可以使用任何正常的 ModelAdmin 属性自定义外观。

与多对多中间模型一起工作

当你使用 ManyToManyField`的``through 参数指定一个中介模型时,管理默认不会显示一个部件。这是因为该中介模型的每一个实例所需要的信息比单个部件所能显示的信息要多,而且多个部件所需要的布局会根据中介模型的不同而不同。

然而,我们仍然希望能够在线编辑这些信息。幸运的是,我们可以通过内联管理模型来实现这一点。假设我们有以下模型:

from django.db import models


class Person(models.Model):
    name = models.CharField(max_length=128)


class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through="Membership")


class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

在管理中显示这个中间模型的第一步是为 Membership 模型定义一个内联类:

class MembershipInline(admin.TabularInline):
    model = Membership
    extra = 1

这个例子对 Membership 模型使用默认的 InlineModelAdmin 值,并将额外的添加表单限制为一个。这可以使用 InlineModelAdmin 类的任何可用选项来定制。

现在为 PersonGroup 模型创建管理视图:

class PersonAdmin(admin.ModelAdmin):
    inlines = [MembershipInline]


class GroupAdmin(admin.ModelAdmin):
    inlines = [MembershipInline]

最后,在管理网站上注册你的 PersonGroup 模型:

admin.site.register(Person, PersonAdmin)
admin.site.register(Group, GroupAdmin)

现在你的管理网站已经设置好了,可以在 PersonGroup 的详情页中在线编辑 Membership 对象。

使用通用关系作为内联

可以用一个内联与通用相关的对象。假设你有以下模型:

from django.contrib.contenttypes.fields import GenericForeignKey
from django.db import models


class Image(models.Model):
    image = models.ImageField(upload_to="images")
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey("content_type", "object_id")


class Product(models.Model):
    name = models.CharField(max_length=100)

如果你想允许在 Product``上编辑和创建一个 ``Image 实例,添加/更改视图,你可以使用由 admin 提供的 GenericTabularInlineGenericStackedInline (都是 GenericInlineModelAdmin 的子类)。它们分别为代表内联对象的表单实现了表格式和堆栈式的可视化布局,就像它们的非通用对应物一样。它们的行为就像其他内联一样。在你的 admin.py 中,对于这个示例应用程序:

from django.contrib import admin
from django.contrib.contenttypes.admin import GenericTabularInline

from myapp.models import Image, Product


class ImageInline(GenericTabularInline):
    model = Image


class ProductAdmin(admin.ModelAdmin):
    inlines = [
        ImageInline,
    ]


admin.site.register(Product, ProductAdmin)

更多具体信息请参见 contenttypes 文档

覆盖管理模板

你可以覆盖许多模板,管理模块用于生成管理网站的各种页面。你甚至可以为特定的应用程序或特定的模型覆盖其中的一些模板。

设置你的项目管理模板目录

管理员模板文件位于 django/contrib/admin/templates/admin 目录中。

为了覆盖其中的一个或多个目录,首先在你的项目的 templates 目录下创建一个 admin 目录。这个目录可以是你在 DjangoTemplates 后台的 DIRS 选项中指定的任何一个目录。如果你自定义了 'loaders' 选项,请确保 'django.template.loaders.filesystem.Loader' 出现在 'django.template.loaders.app_directories.Loader' 之前,这样你的自定义模板就会被模板加载系统发现,而不是 django.contrib.admin 所包含的模板。

在这个 admin 目录中,创建以你的应用程序命名的子目录。在这些应用程序子目录中,创建以你的模型命名的子目录。注意,管理员应用程序在查找目录时,会将模型名称小写,所以如果你要在大小写敏感的文件系统上运行应用程序,请确保你将目录命名为所有小写。

要为特定的应用程序覆盖管理员模板,请从 django/contrib/admin/templates/admin 目录中复制并编辑模板,然后将其保存到刚刚创建的目录之一。

例如,如果我们想在名为 my_app 的应用程序中的所有模型的变更列表视图中添加一个工具,我们会将 contrib/admin/templates/admin/change_list.html 复制到我们项目的 templates/admin/my_app/ 目录下,并进行任何必要的更改。

如果我们想只为名为 Page 的特定模型在变更列表视图中添加一个工具,我们会将同样的文件复制到项目的 templates/admin/my_app/page 目录下。

覆盖 vs. 替换管理模板

由于管理模板的模块化设计,通常没有必要也不建议更换整个模板。最好是只覆盖你需要更改的模板部分。

继续上面的例子,我们想在 History 工具旁边为 Page 模型添加一个新的链接。在看了 change_form.html 后,我们确定我们只需要覆盖 object-tools-items 块。因此这里是我们新的 change_form.html

{% extends "admin/change_form.html" %}
{% load i18n admin_urls %}
{% block object-tools-items %}
    <li>
        <a href="{% url opts|admin_urlname:'history' original.pk|admin_urlquote %}" class="historylink">{% translate "History" %}</a>
    </li>
    <li>
        <a href="mylink/" class="historylink">My Link</a>
    </li>
    {% if has_absolute_url %}
        <li>
            <a href="{% url 'admin:view_on_site' content_type_id original.pk %}" class="viewsitelink">{% translate "View on site" %}</a>
        </li>
    {% endif %}
{% endblock %}

这就是了!如果我们把这个文件放在 templates/admin/my_app 目录下,我们的链接就会出现在 my_app 内所有模型的更改表单中。

每个应用或模型可覆盖的模板

contrib/admin/templates/admin 中的模板并非每个应用程序或每个模型都可以被覆盖。以下情况可以:

  • actions.html
  • app_index.html
  • change_form.html
  • change_form_object_tools.html
  • change_list.html
  • change_list_object_tools.html
  • change_list_results.html
  • date_hierarchy.html
  • delete_confirmation.html
  • object_history.html
  • pagination.html
  • popup_response.html
  • prepopulated_fields_js.html
  • search_form.html
  • submit_line.html

对于那些不能以这种方式覆盖的模板,你仍然可以通过将新版本放在你的 templates/admin 目录下,为整个项目覆盖它们。这对于创建自定义 404 和 500 页面特别有用。

备注

一些管理模板,如 change_list_results.html 是用来呈现自定义包含标签的。这些可以被覆盖,但在这种情况下,你可能最好创建你自己版本的标签,并给它一个不同的名字。这样你就可以有选择地使用它。

根和登录模板

如果你想更改索引、登录或注销模板,你最好创建自己的 AdminSite 实例(见下文),并更改 AdminSite.index_templateAdminSite.login_templateAdminSite.logout_template 属性。

主题化支持

管理员使用 CSS 变量来定义颜色和字体。这允许在不必覆盖许多单独的 CSS 规则的情况下更改主题。例如,如果你喜欢紫色而不是蓝色,你可以在你的项目中添加一个 admin/base.html 模板覆盖:

{% extends 'admin/base.html' %}

{% block extrastyle %}{{ block.super }}
<style>
html[data-theme="light"], :root {
  --primary: #9774d5;
  --secondary: #785cab;
  --link-fg: #7c449b;
  --link-selected-fg: #8f5bb2;
}
</style>
{% endblock %}

CSS 变量的列表在 django/contrib/admin/static/admin/css/base.css 中定义。

深色模式的变量,遵循 prefers-color-scheme 媒体查询,定义在 django/contrib/admin/static/admin/css/dark_mode.css 中。这与模板中的 {% block dark-mode-vars %} 相关联。

AdminSite 对象

class AdminSite(name='admin')[源代码]

一个 Django 管理站点由 django.contrib.admin.sites.AdminSite 的实例来表示;默认情况下,这个类的实例被创建为 django.contrib.admin.site,你可以用它来注册你的模型和 ModelAdmin 实例。

如果你想自定义默认的管理站点,你可以 覆盖它

当构造一个 AdminSite 的实例时,你可以使用构造函数的 name 参数提供一个唯一的实例名称。这个实例名是用来标识实例的,特别是在 反查管理 URL 时。如果没有提供实例名,将使用默认的实例名 admin。参见 自定义 AdminSite 类,了解定制 AdminSite 类的例子。

django.contrib.admin.sites.all_sites

一个 WeakSet 包含所有的管理员站点实例。

AdminSite 属性

模板可以覆盖或扩展基本的管理模板,如 覆盖管理模板 中所述。

AdminSite.site_header

每个管理页面顶部的文本,作为一个 <div> (一个字符串)。默认情况下,这是 "Django administration"。

Changed in Django 5.0:

在较早的版本中,site_header 使用了一个 <h1> 标签。

AdminSite.site_title

在每个管理页面的 <title> (字符串)末尾放置的文字。默认情况下,是 “Django 站点管理”。

AdminSite.site_url

每个管理页面顶部的 “查看网站” 链接的 URL。默认情况下,site_url/。将其设置为 None 以删除该链接。

对于运行在子路径上的站点, each_context() 方法会检查当前请求是否设置了 request.META['SCRIPT_NAME'],如果 site_url 没有设置为 / 以外的内容,则使用该值。

AdminSite.index_title

放在管理索引页顶部的文字(一个字符串)。默认情况下,是 “网站管理”。

AdminSite.index_template

管理网站主索引视图将使用的自定义模板的路径。

AdminSite.app_index_template

管理网站应用索引视图将使用的自定义模板的路径。

AdminSite.empty_value_display

用于在管理站点的更改列表中显示空值的字符串。默认值为破折号。该值也可以在每个 ModelAdmin 的基础上被覆盖,也可以在 ModelAdmin 内的自定义字段上设置 empty_value_display 属性。参见 ModelAdmin.empty_value_display 的例子。

AdminSite.enable_nav_sidebar

一个布尔值,决定是否在大屏幕上显示导航侧栏。默认情况下,它被设置为 True

AdminSite.final_catch_all_view

一个布尔值,用于决定是否在管理员中添加一个最终的总括视图,将未认证的用户重定向到登录页面。默认情况下,它被设置为 True

警告

不建议将此设置为 False,因为该视图可以保护潜在的模型枚举隐私问题。

AdminSite.login_template

管理网站登录视图将使用的自定义模板的路径。

AdminSite.login_form

AuthenticationForm 的子类,将被管理网站登录视图使用。

AdminSite.logout_template

管理网站注销视图将使用的自定义模板的路径。

AdminSite.password_change_template

管理网站密码修改视图将使用的自定义模板的路径。

AdminSite.password_change_done_template

自定义模板的路径,该模板将被管理员网站密码修改完成后的视图使用。

AdminSite 方法

AdminSite.each_context(request)[源代码]

返回一个变量字典,将其放入管理站点中每个页面的模板上下文中。

默认情况下包括以下变量和值:

  • site_headerAdminSite.site_header

  • site_titleAdminSite.site_title

  • site_urlAdminSite.site_url

  • has_permissionAdminSite.has_permission()

  • available_apps :当前用户可用的 application registry 中的应用程序列表。列表中的每个条目都是一个代表应用程序的字典,其键如下:

    • app_label :应用程序标签
    • app_url :管理中应用程序索引的 URL
    • has_module_perms :一个布尔值,表示是否允许当前用户显示和访问模块的索引页
    • models :应用程序中可用模型的清单

    每个模型都是一个带有以下键的字典

    • model` :模型类
    • object_name :模型类名
    • name :模型的复数名
    • permsdict 跟踪 addchangedeleteview 权限
    • admin_url :模型的管理变更表 URL
    • add_url :添加新模型实例的管理网址
  • is_popup:当前页面是否在弹出窗口中显示。

  • is_nav_sidebar_enabled: AdminSite.enable_nav_sidebar

  • log_entries: AdminSite.get_log_entries()

AdminSite.get_app_list(request, app_label=None)[源代码]

返回当前用户可用的 应用程序注册表 中的应用程序列表。你可以选择性地传递一个 app_label 参数来获取单个应用程序的详细信息。列表中的每个条目都是一个字典,表示一个应用程序,具有以下键:

  • app_label :应用程序标签
  • app_url :管理中应用程序索引的 URL
  • has_module_perms :一个布尔值,表示是否允许当前用户显示和访问模块的索引页
  • models :应用程序中可用模型的清单
  • name:应用程序的名称

每个模型都是一个带有以下键的字典:

  • model` :模型类
  • object_name :模型类名
  • name :模型的复数名
  • permsdict 跟踪 addchangedeleteview 权限
  • admin_url :模型的管理变更表 URL
  • add_url :添加新模型实例的管理网址

应用程序和模型的列表按它们的名称按字母顺序排序。你可以覆盖这个方法来改变管理员首页的默认顺序。

AdminSite.has_permission(request)[源代码]

如果给定的 HttpRequest 的用户有权限在管理站点中查看至少一个页面,则返回 True。默认要求 User.is_activeUser.is_staff 都是 True

AdminSite.register(model_or_iterable, admin_class=None, **options)[源代码]

用给定的 admin_class 注册给定的模型类(或类的迭代)。admin_class 默认为 ModelAdmin (默认的管理选项)。如果给定了关键字参数 —— 例如 list_display —— 它们将作为选项应用到管理类中。

如果模型是抽象的,将引发 ImproperlyConfigured 异常,如果模型已经注册,将引发 django.contrib.admin.exceptions.AlreadyRegistered 异常。

AdminSite.unregister(model_or_iterable)[源代码]

取消注册给定的模型类(或类的可迭代对象)

如果模型尚未注册,将引发 django.contrib.admin.exceptions.NotRegistered 异常。

AdminSite.get_model_admin(model)[源代码]
New in Django 5.0.

返回给定模型类的管理类。如果模型未注册,将引发 django.contrib.admin.exceptions.NotRegistered 异常。

AdminSite.get_log_entries(request)[源代码]
New in Django 5.0.

返回相关的 LogEntry 实例的查询集,显示在站点索引页面上。可以覆盖此方法以根据其他条件过滤日志条目。

AdminSite 实例挂到你的 URLconf 中

设置 Django 管理的最后一步是将你的 AdminSite 实例挂到你的 URLconf 中。通过将一个给定的 URL 指向 AdminSite.urls 方法来实现。不需要使用 include()

在这个例子中,我们将默认的 AdminSite 实例 django.contrib.admin.site 注册到 URL /admin/

# urls.py
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path("admin/", admin.site.urls),
]

自定义 AdminSite

如果你想用自定义行为设置你自己的管理站点,你可以自由地将 AdminSite 子类化,并覆盖或添加任何你喜欢的内容。然后,创建一个你的 AdminSite 子类的实例(与你实例化任何其他 Python 类的方式相同),并用它注册你的模型和 ModelAdmin 子类,而不是用默认站点。最后,更新 myproject/urls.py 来引用你的 AdminSite 子类。

myapp/admin.py
from django.contrib import admin

from .models import MyModel


class MyAdminSite(admin.AdminSite):
    site_header = "Monty Python administration"


admin_site = MyAdminSite(name="myadmin")
admin_site.register(MyModel)
myproject/urls.py
from django.urls import path

from myapp.admin import admin_site

urlpatterns = [
    path("myadmin/", admin_site.urls),
]

请注意,当你使用自己的 AdminSite 实例时,你可能不希望自动发现 admin 模块,因为你可能会在你的 myproject.admin 模块中导入所有应用的 admin 模块。这意味着你需要在你的 INSTALLED_APPS 配置中加入 'django.contrib.admin.apps.SimpleAdminConfig' 而不是 'django.contrib.admin'

覆盖默认的管理站点

你可以通过设置自定义 AppConfigdefault_site` 属性来覆盖默认的 django.contrib.admin.site,将其设置为一个 AdminSite 子类或一个返回站点实例的可调用的点分隔导入路径。

myproject/admin.py
from django.contrib import admin


class MyAdminSite(admin.AdminSite): ...
myproject/apps.py
from django.contrib.admin.apps import AdminConfig


class MyAdminConfig(AdminConfig):
    default_site = "myproject.admin.MyAdminSite"
myproject/settings.py
INSTALLED_APPS = [
    # ...
    "myproject.apps.MyAdminConfig",  # replaces 'django.contrib.admin'
    # ...
]

同一个 URLconf 中的多个管理站点

你可以在同一个 Django 驱动的网站上创建多个管理站点的实例。创建多个 AdminSite 的实例,并将每个实例放置在不同的 URL 中。

在这个例子中,URL /basic-admin//advanced-admin/ 分别使用 AdminSite 实例 myproject.admin.basic_sitemyproject.admin.advanced_site

# urls.py
from django.urls import path
from myproject.admin import advanced_site, basic_site

urlpatterns = [
    path("basic-admin/", basic_site.urls),
    path("advanced-admin/", advanced_site.urls),
]

AdminSite 实例的构造函数只有一个参数,即名称,可以是任何你喜欢的名称。这个参数成为 URL 名称的前缀,以便 反查它们。只有当你使用一个以上的 AdminSite 时才需要这样做。

在管理站点上添加视图

就像 ModelAdmin 一样, AdminSite 也提供了一个 get_urls() 方法,它可以被重写来定义站点的其他视图。要添加一个新的视图到你的管理站点,扩展基本的 get_urls() 方法来包含一个新视图的模式。

备注

任何使用管理模板或扩展基本管理模板的视图,都应该在渲染模板之前设置 request.current_app。如果你的视图是在 AdminSite 上,它应该设置为 self.name;如果你的视图是在 ModelAdmin 上,它应该设置为 self.admin_site.name

增加密码重置功能

你可以在你的 URLconf 中添加几行字,就可以给管理网站添加密码重置功能。具体来说,添加以下四种模式:

from django.contrib import admin
from django.contrib.auth import views as auth_views

path(
    "admin/password_reset/",
    auth_views.PasswordResetView.as_view(
        extra_context={"site_header": admin.site.site_header}
    ),
    name="admin_password_reset",
),
path(
    "admin/password_reset/done/",
    auth_views.PasswordResetDoneView.as_view(
        extra_context={"site_header": admin.site.site_header}
    ),
    name="password_reset_done",
),
path(
    "reset/<uidb64>/<token>/",
    auth_views.PasswordResetConfirmView.as_view(
        extra_context={"site_header": admin.site.site_header}
    ),
    name="password_reset_confirm",
),
path(
    "reset/done/",
    auth_views.PasswordResetCompleteView.as_view(
        extra_context={"site_header": admin.site.site_header}
    ),
    name="password_reset_complete",
),

(这假定你已经在 admin/ 添加了管理员,并且要求你把以 ^admin/ 为开头的 URL 放在包含管理程序本身的行之前)。

admin_password_reset 命名的 URL 的存在,会使默认的管理登录页面的密码框下出现 “忘记密码了?” 的链接。

LogEntry 对象

class models.LogEntry

LogEntry 类可以跟踪通过管理界面完成的对象的添加、更改和删除。

LogEntry 属性

LogEntry.action_time

动作的日期和时间。

LogEntry.user

执行操作的用户(一个 AUTH_USER_MODEL 实例)。

LogEntry.content_type

修改对象的 ContentType

LogEntry.object_id

修改对象主键的文字表示。

LogEntry.object_repr

修改后的对象 repr()

LogEntry.action_flag

记录的动作类型: ADDITIONCHANGEDELETION

例如,要获取所有通过管理完成的添加列表:

from django.contrib.admin.models import ADDITION, LogEntry

LogEntry.objects.filter(action_flag=ADDITION)
LogEntry.change_message

对修改的详细描述。例如,在编辑的情况下,消息中包含了被编辑的字段列表。Django 管理网站将这些内容格式化为 JSON 结构,这样 get_change_message() 就可以重新组成一个用当前用户语言翻译的消息。不过自定义代码可能会将其设置为纯字符串。建议你使用 get_change_message() 方法来检索这个值,而不是直接访问它。

LogEntry 方法

LogEntry.get_edited_object()[源代码]

返回被引用对象的快捷方式。

LogEntry.get_change_message()[源代码]

change_message 格式化并翻译成当前用户语言。在 Django 1.10 之前创建的消息将始终以其登录时的语言显示。

反查管理 URL

当部署了一个 AdminSite 时,可以使用 Django 的 URL 反查系统 访问该网站提供的视图。

AdminSite 提供了以下命名的 URL 模式。

页面 URL 名称 参数
索引 index  
登录 login  
登出 logout  
密码更改 password_change  
密码更改完成 password_change_done  
i18n JavaScript jsi18n  
应用索引页面 app_list app_label
重定向到对象的页面 view_on_site content_type_id, object_id

每个 ModelAdmin 实例都提供一组额外的命名 URL:

页面 URL 名称 参数
变更列表 {{ app_label }}_{{ model_name }}_changelist  
增加 {{ app_label }}_{{ model_name }}_add  
历史 {{ app_label }}_{{ model_name }}_history object_id
删除 {{ app_label }}_{{ model_name }}_delete object_id
修改 {{ app_label }}_{{ model_name }}_change object_id

UserAdmin 提供一个命名的 URL:

页面 URL 名称 参数
密码更改 auth_user_password_change user_id

这些命名的 URL 在应用程序命名空间 admin 和与 Site 实例名称相对应的实例命名空间中注册。

所以,如果你想要在默认管理员中获取对特定 Choice 对象(来自 polls 应用程序)的更改视图的引用,你可以调用:

>>> from django.urls import reverse
>>> c = Choice.objects.get(...)
>>> change_url = reverse("admin:polls_choice_change", args=(c.id,))

这将找到管理应用程序的第一个注册实例(无论实例名称如何),并解析到该实例中改变 poll.Choice 实例的视图。

如果你想要在特定的管理员实例中查找一个 URL,请在反向调用中提供该实例的名称作为 current_app 提示。例如,如果你特别想要来自名为 custom 的管理员实例的管理员视图,你需要调用:

>>> change_url = reverse("admin:polls_choice_change", args=(c.id,), current_app="custom")

更多细节,请参见 反查命名空间的 URL 的文档。

为了让模板中的管理网址更容易反查,Django 提供了一个 admin_urlname 过滤器,它的参数是一个动作:

{% load admin_urls %}
<a href="{% url opts|admin_urlname:'add' %}">Add user</a>
<a href="{% url opts|admin_urlname:'delete' user.pk %}">Delete this user</a>

上面例子中的操作与上面描述的 ModelAdmin 实例的 URL 名称的最后一部分相匹配。opts 变量可以是任何具有 app_labelmodel_name 属性的对象,通常由当前模型的管理视图提供。

display 装饰器

display(*, boolean=None, ordering=None, description=None, empty_value=None)[源代码]

这个装饰器可以用来设置自定义显示函数的特定属性,可以用 list_displayreadonly_fields

@admin.display(
    boolean=True,
    ordering="-publish_date",
    description="Is Published?",
)
def is_published(self, obj):
    return obj.publish_date is not None

这就相当于直接在函数上设置一些属性(用原来的、较长的名字):

def is_published(self, obj):
    return obj.publish_date is not None


is_published.boolean = True
is_published.admin_order_field = "-publish_date"
is_published.short_description = "Is Published?"

还请注意,empty_value 装饰符参数映射到直接分配给函数的 empty_value_display 属性。它不能与 boolean 一起使用 —— 它们是相互排斥的。

使用这个装饰器并不是制作一个显示函数的必经之路,但在你的源码中使用它而不使用参数作为标记来识别函数的目的是很有用的:

@admin.display
def published_year(self, obj):
    return obj.publish_date.year

在这种情况下,它将不会给函数添加任何属性。

staff_member_required 装饰器

staff_member_required(redirect_field_name='next', login_url='admin:login')[源代码]

这个装饰器被用于需要授权的管理视图。用这个函数装饰的视图将有以下行为:

  • 如果用户已登录,是工作人员(User.is_staff=True),并且是活动的(User.is_active=True),则正常执行视图。
  • 否则,请求将被重定向到由 login_url 参数指定的 URL,并在由 redirect_field_name 指定的查询字符串变量中包含最初请求的路径。例如: /admin/login/?next=/admin/polls/question/3/

用法示例:

from django.contrib.admin.views.decorators import staff_member_required


@staff_member_required
def my_view(request): ...
Back to Top