Django 管理站点¶
Django 最强大的部分之一是自动管理界面。它从你的模型中读取元数据,提供一个快速的、以模型为中心的界面,受信任的用户可以管理你网站上的内容。管理的推荐使用范围仅限于一个组织的内部管理工具。它不打算用于围绕你的整个前端构建。
管理有很多用于定制的钩子,但要注意不要试图专门使用这些钩子。如果你需要提供一个更以流程为中心的接口,抽象掉数据库表和字段的实现细节,那么可能是时候编写自己的视图了。
在本文档中,我们将讨论如何激活、使用和定制 Django 的管理界面。
概况¶
在 startproject 使用的默认项目模板中启用了管理。
如果你没有使用默认的项目模板,这里是要求:
将
'django.contrib.admin'和它的依赖关系 ——django.contrib.auth、django.contrib.contenttypes、django.contrib.messages和django.contrib.session添加到你的INSTALLED_APPS配置中。在你的
TEMPLATES配置中设置一个在OPTIONS的'context_processors'选项中包含django.template.context_processors.request、django.contrib.auth.context_processors.auth和django.contrib.messages.context_processors.messages的DjangoTemplates后端:如果你自定义了
MIDDLEWARE设置,必须包含django.contrib.sessions.middleware.SessionMiddleware、django.contrib.auth.middleware.AuthenticationMiddleware和django.contrib.messages.middleware.MessageMiddleware。
在你采取了这些步骤之后,你就可以通过访问你挂接的 URL(默认是 /admin/)来使用管理站点。
如果需要创建一个用户来登录,请使用 createsuperuser 命令。默认情况下,登录管理需要用户的 is_staff 属性设置为 True。
最后,确定你的应用程序的哪些模型应该在管理界面中是可编辑的。对于这些模型中的每一个,按照 ModelAdmin 中的描述,在管理处注册它们。
其他主题¶
ModelAdmin 对象¶
- class ModelAdmin[source]¶
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)[source]¶
还有一个装饰器用于注册你的
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()。
- autodiscover()[source]¶
该函数试图在每个已安装的应用程序中导入一个
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_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设置为你的模型中DateField或DateTimeField的名称,变化列表页将包括一个基于日期的向下扩展。举例:
date_hierarchy = "pub_date"
你也可以使用
__查找来指定相关模型上的字段,例如:date_hierarchy = "author__pub_date"
这将根据可用的数据智能地填充自己,例如,如果所有的日期都在一个月内,它将只显示日级的向下扩展。
Note
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模型的表格只包括name和title字段,你可以指定fields或exclude,如:from django.contrib import admin class AuthorAdmin(admin.ModelAdmin): fields = ["name", "title"] class AuthorAdmin(admin.ModelAdmin): exclude = ["birth_date"]
由于 Author 模型只有三个字段,即
name、title和birth_date,因此上述声明所产生的表单将包含完全相同的字段。
- ModelAdmin.fields¶
使用
fields选项在 “添加” 和 “更改” 页面的表单中进行简单的布局修改,比如只显示可用字段的子集,修改它们的顺序,或者将它们分成几行。例如,你可以为django.contrib.flatpages.models.FlatPage模型定义一个更简单的管理表单版本,如下所示:class FlatPageAdmin(admin.ModelAdmin): fields = ["url", "title", "content"]
在上面的例子中,只有
url、title和content等字段会依次显示。fields可以包含ModelAdmin.readonly_fields中定义的值,以只读方式显示。对于更复杂的布局需求,请参阅
fieldets选项。fields选项接受与list_display相同类型的值,但不接受可调用对象和用于相关字段的__查找。模型和模型管理方法的名称只有在readonly_fields中列出时才会被使用。要在同一行显示多个字段,将这些字段包在自己的元组中。在这个例子中,
url和title字段将显示在同一行,content字段将显示在它们下面的一行:class FlatPageAdmin(admin.ModelAdmin): fields = [("url", "title"), "content"]
可能会与
ModelAdmin.fieldsets选项混淆这个
fields选项不应与fieldsets选项中的fields字典键混淆,下一节将介绍。如果
fields或fieldsets选项都不存在,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"], }, ), ]
这样一来,管理页面就变成了这样:
如果
fieldsets或fields选项都不存在,Django 将默认在一个单一的表单集中显示每个非AutoField且有editable=True的字段,顺序与模型中定义的字段相同。field_options字典可以有以下键:fields要在此字段集中显示的字段名称的列表或元组。这个键是必需的。
举例:
{ "fields": ["first_name", "last_name", "address", "city", "state"], }
与
fields选项一样,要在同一行显示多个字段,请将这些字段封装在自己的元组中。在这个例子中,first_name和last_name字段将显示在同一行:{ "fields": [("first_name", "last_name"), "address", "city", "state"], }
fields可以包含readonly_fields中定义的值,以只读方式显示。如果你将可调用的名称添加到
fields中,与fields选项的规则相同:可调用的名称必须列在readonly_fields中。
classes一个包含额外 CSS 类的列表或元组,用于应用到 fieldset。这可以包括项目中定义的任何自定义 CSS 类,以及 Django 提供的任何 CSS 类。在默认的管理站点 CSS 样式表中,定义了两个特别有用的类:
collapse和wide。举例:
{ "classes": ["wide", "collapse"], }
具有
wide样式的 fieldsets 将在管理界面中获得额外的水平空间。具有名称和collapse样式的 fieldsets 将初始折叠,使用一个可展开的小部件来切换其可见性。Changed in Django 5.1:使用
collapse类的fieldsets现在使用<details>和<summary>元素,前提是它们定义了name。
description一个可选的额外文本字符串,显示在每个 fieldset 的顶部,位于 fieldset 标题下方。
请注意,当这个值在管理界面显示时,它不是 HTML 转义后的。如果你愿意的话,这可以让你包含 HTML。或者你可以使用纯文本和
django.utils.html.escape()来转义任何 HTML 特殊字符。
TabularInline对fieldsets的支持有限。在
TabularInline中使用fieldsets功能有限。你可以通过在field_options字典中定义fields来指定哪些字段将显示以及它们在TabularInline布局中的顺序。所有其他功能均不支持。这包括使用
name来定义一组字段的标题。
- ModelAdmin.filter_horizontal¶
默认情况下,在管理网站中显示一个
ManyToManyField是<select multiple>。但是,多选框在选择很多项目时,会很难用。在这个列表中添加一个ManyToManyField,就可以改用一个不显眼的 JavaScript “过滤器” 界面,在选项中进行搜索。未选择和选择的选项并排出现在两个框中。参见filter_vertical来使用垂直界面。
- ModelAdmin.filter_vertical¶
与
filter_horizontal相同,但使用垂直显示过滤界面,未选择的选项框出现在选择选项框的上方。
- ModelAdmin.form¶
默认情况下,会为你的模型动态创建一个
ModelForm。它用于创建在添加/更改页面上显示的表单。你可以很容易地提供你自己的ModelForm来覆盖添加/更改页面上的任何默认表单行为。另外,你可以使用ModelAdmin.get_form()方法来定制默认的表单,而不是指定一个全新的表单。示例请参见 在管理中添加自定义验证 一节。
ModelAdmin.exclude优先级较高如果你的
ModelForm和ModelAdmin都定义了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。Warning
如果你想使用一个带有关系字段的自定义部件(例如
ForeignKey或ManyToManyField),确保你没有在raw_id_fields、radio_fields或autocomplete_fields中包含该字段的名称。formfield_overrides不会让你改变有raw_id_fields、radio_fields或autocomplete_fields设置的关系字段上的部件。这是因为raw_id_fields、radio_fields和autocomplete_fields意味着自己的自定义部件。
- ModelAdmin.inlines¶
参见下面的
InlineModelAdmin对象以及ModelAdmin.get_formsets_with_inlines()。
- ModelAdmin.list_display¶
设置
list_display来控制哪些字段显示在管理的变更列表页面。举例:
list_display = ["first_name", "last_name"]
如果你不设置
list_display,管理网站将显示一个单列,显示每个对象的__str__()表示。list_display中可以使用五种类型的值。除了最简单的类型外,其他类型都可以使用display()装饰器,该装饰器用于自定义字段的显示方式:模型字段的名称。例如:
class PersonAdmin(admin.ModelAdmin): list_display = ["first_name", "last_name"]
相关字段的名称,使用
__符号。例如: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:增加了对使用
__查找的支持,用于针对相关字段。关于
list_display要注意的几个特殊情况:如果字段是
ForeignKey,Django 会显示相关对象的__str__()。ManyToManyField字段不被支持,因为这需要为表中的每一行单独执行一条 SQL 语句。如果你还是想这样做,请给你的模型一个自定义方法,并将该方法的名称添加到list_display中。(更多关于list_display中的自定义方法,请参见下文)。如果字段是
BooleanField,Django 会显示一个漂亮的 “是”、“否” 或 “未知” 图标,而不是True、False或None。如果给定的字符串是模型的一个方法,
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或一个返回True、False或None的可调用对象,如果你用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的每个元素:模型中的字段或相关字段中的字段。
一个可调用对象。
一个代表
ModelAdmin属性的字符串。一个代表模型属性的字符串。
例如,如果你有
first_name作为一个模型字段和ModelAdmin属性,将使用模型字段。
- ModelAdmin.list_display_links¶
使用
list_display_links来控制list_display中的字段是否以及哪些字段应该被链接到对象的 “更改” 页面。默认情况下,更改列表页将把第一列 ——
list_display中指定的第一个字段 —— 链接到每个项目的更改页面。但是list_display_links让你改变这一点:将其设置为
None,则完全没有链接。将它设置为一个列表或元组字段(格式与
list_display相同),你希望将其列转换为链接。你可以指定一个或多个字段。只要字段出现在
list_display中,Django 就不会关心链接了多少(或多少)字段。唯一的要求是,如果你想以这种方式使用list_display_links,你必须定义list_display。
在这个例子中,
first_name和last_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中列出的字段将作为表单部件显示在变更列表页上,允许用户一次编辑和保存多行。Note
list_editable以特定方式与其他几个选项进行交互;你应该注意以下规则:list_editable中的任何字段都必须在list_display中。你不能编辑一个没有显示的字段!同一字段不能同时列在
list_editable和list_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()方法。Note
当
select_related()已经在变更列表的QuerySet上被调用时,ModelAdmin会忽略这个属性。
- ModelAdmin.ordering¶
设置
ordering来指定对象列表在 Django 管理视图中的排序方式。这应该是一个列表或元组,格式与模型的ordering参数相同。如果没有提供,Django 管理员将使用模型的默认排序。
如果你需要指定一个动态的顺序(例如取决于用户或语言),你可以实现一个
get_ordering()方法。排序和分类的性能考虑
为了确保结果的确定性排序,如果不能找到一个单一的或唯一的字段集,提供总的排序,那么变更表就会在排序中增加
pk。例如,如果默认的排序是按非唯一的
name字段排序,那么更改列表就按name和pk排序。如果你有很多行,而且在name和pk上没有索引,这可能会表现得很差。
- 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不接受DateTimeField、ForeignKey、OneToOneField和ManyToManyField字段。
- 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 将使用单选按钮接口代替。假设group是Person模型上的一个ForeignKey:class PersonAdmin(admin.ModelAdmin): radio_fields = {"group": admin.VERTICAL}
你可以在
django.contrib.admin模块中选择使用HORIZONTAL或VERTICAL。不要在
radio_fields中包含一个字段,除非它是ForeignKey或已设置choices。
- ModelAdmin.autocomplete_fields¶
autocomplete_fields是一个ForeignKey和/或ManyToManyField字段的列表,你想将其改为 Select2 自动完成输入。autocomplete_fields是一个ForeignKey和/或的列表,默认情况下,管理对这些字段使用选择框接口(<select>)。有时你不想产生选择所有相关实例在下拉中显示的开销。ManyToManyField字段你想改成 Select2 自动完成输入。Select2 输入看起来与默认输入类似,但自带搜索功能,异步加载选项。如果相关模型有很多实例,这样会更快、更方便用户使用。
你必须在相关对象的
ModelAdmin上定义search_fields,因为自动完成搜索使用它。为了避免未经授权的数据泄露,用户必须拥有相关对象的
view或change权限才能使用自动完成。结果的排序和分页由相关的
ModelAdmin的get_ordering()和get_paginator()方法控制。在下面的例子中,
ChoiceAdmin对Question有一个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是你想改变为ForeignKey或ManyToManyField的Input部件的字段列表:class ArticleAdmin(admin.ModelAdmin): raw_id_fields = ["newspaper"]
raw_id_fields的Input部件应该包含一个主键,如果该字段是`ForeignKey`。或者是一个逗号分隔的值列表,如果该字段是ManyToManyField。raw_id_fields部件在字段旁边显示一个放大镜按钮,允许用户搜索和选择一个值:
- ModelAdmin.readonly_fields¶
默认情况下,管理会将所有字段显示为可编辑。该选项中的任何字段(应该是
list或tuple)将按原样显示其数据,不可编辑;它们也被排除在用于创建和编辑的ModelForm中。请注意,当指定ModelAdmin.fields或ModelAdmin.fieldsets时,只读字段必须存在才能显示(否则将被忽略)。如果没有通过
ModelAdmin.fields或ModelAdmin.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_as为True,则 “保存并添加另一个” 将被 “另存为新” 按钮所取代,该按钮将创建一个新的对象(具有新的 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,在管理更改列表页面上启用搜索框。这应该被设置为字段名的列表,每当有人在该文本框中提交搜索查询时,就会被搜索到。这些字段应该是某种文本字段,如
CharField或TextField。你也可以对ForeignKey或ManyToManyField进行相关查询,并使用查找 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 会做相当于这个 SQLWHERE子句。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>。前缀
查找
^
=
@
None
如果你需要自定义搜索,你可以使用
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`、tuple或set)。一个空的集合会禁用所有列的排序。如果你需要动态地指定这个列表,可以实现一个
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 方法¶
Warning
当覆盖 ModelAdmin.save_model() 和 ModelAdmin.delete_model() 时,你的代码必须保存/删除对象。它们不是为了否决的目的,而是允许你执行额外的操作。
- ModelAdmin.save_model(request, obj, form, change)[source]¶
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)[source]¶
delete_model方法被赋予HttpRequest和一个模型实例。覆盖该方法可以进行删除前或删除后的操作。调用super().delete_model()使用Model.delete`()删除对象。
- ModelAdmin.delete_queryset(request, queryset)[source]¶
delete_queryset()方法是给定HttpRequest和一个QuerySet要删除的对象。重写该方法,可以自定义 “删除选定对象” 的删除过程 动作。
- ModelAdmin.save_formset(request, form, formset, change)[source]¶
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()
另见 在表单集中保存对象。
Warning
所有返回 ModelAdmin 属性的钩子都返回属性本身,而不是其值的副本。动态修改该值可能会导致意外的结果。
让我们以 ModelAdmin.get_readonly_fields() 为例:
class PersonAdmin(admin.ModelAdmin):
readonly_fields = ["name"]
def get_readonly_fields(self, request, obj=None):
readonly = super().get_readonly_fields(request, obj)
if not request.user.is_superuser:
readonly.append("age") # Edits the class attribute.
return readonly
这会导致 readonly_fields 变成 ["name", "age", "age", ...],即使对于超级用户也是如此,因为每次非超级用户访问页面时都会添加 "age"。
- ModelAdmin.get_ordering(request)¶
get_ordering方法以request为参数,并期望返回一个类似于ordering属性的list或tuple的排序。例如: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)[source]¶
get_search_results方法将显示的对象列表修改为符合所提供的搜索词的对象。它接受请求、应用当前过滤器的查询集和用户提供的搜索词。它返回一个元组,其中包含一个修改后实现搜索的查询集,以及一个布尔值,表示结果是否包含重复。默认的实现是搜索
ModelAdmin.search_fields中命名的字段。这个方法可以被你自己定制的搜索方法覆盖。例如,你可能希望按整数字段搜索,或使用外部工具如 Solr 或 Haystack。你必须确定你的搜索方法实施的查询集更改是否可能引入重复的结果,并在返回值的第二个元素中返回
True。例如,如果要按
name和age进行搜索,你可以使用: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,预计将返回一个list或tuple字段名,这些字段名将与自动完成部件一起显示,如上面ModelAdmin.autocomplete_fields部分所述。
- ModelAdmin.get_readonly_fields(request, obj=None)¶
get_readonly_fields方法是给定HttpRequest和被编辑的obj``(或者在添加表单中给定 ``None),并期望返回一个list或tuple的字段名,这些字段名将被显示为只读,如上面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)[source]¶
get_list_display方法被赋予HttpRequest,预计将返回一个list或tuple的字段名,这些字段名将显示在变更列表视图上,如上面ModelAdmin.list_display一节所述。
- ModelAdmin.get_list_display_links(request, list_display)[source]¶
get_list_display_links方法被赋予HttpRequest和由ModelAdmin.get_list_display()返回的list或tuple。如ModelAdmin.list_display_links一节所述,预计它将返回变化列表中的None或list或tuple字段名,这些字段名将被链接到变化视图。
- 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)[source]¶
get_list_filter方法被赋予HttpRequest,并期望返回与list_filter属性相同的序列类型。
get_list_select_related方法被赋予HttpRequest,应该像ModelAdmin.list_select_related那样返回一个布尔值或列表。
- ModelAdmin.get_search_fields(request)[source]¶
get_search_fields方法被赋予HttpRequest,并期望返回与search_fields属性相同的序列类型。
- ModelAdmin.get_sortable_by(request)¶
get_sortable_by()方法被传递给HttpRequest,并期望返回一个字段名的集合(例如list、tuple或set),这些字段名将在更改列表页中被排序。如果设置了,它的默认实现将返回
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)[source]¶
get_inline_instances方法是给定HttpRequest和被编辑的obj(或者在添加表单中给定None),并期望返回一个list或tuple的 :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()[source]¶
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 %}
Note
注意到
self.my_view函数被包装在self.admin_site.admin_view中。这很重要,因为它确保了两件事情:权限检查被运行,确保只有活跃的工作人员用户可以访问视图。
应用了
django.views.decorators.cache.never_cache()装饰器以防止缓存,确保返回的信息是最新的。
Note
请注意,自定义模式包含在常规的管理 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)[source]¶
返回一个
ModelForm类,用于管理员添加和更改视图,参见add_view()和change_view()。基本实现使用
modelform_factory()来子类form,通过fields和exclude等属性进行修改。所以,例如,如果你想为超级用户提供额外的字段,你可以像这样换一个不同的基本表单: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)[source]¶
产生 (
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_foreignkey和formfield_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)[source]¶
返回用于列表的
Changelist类。默认情况下,使用的是django.contrib.admin.views.main.ChangeList。通过继承这个类,你可以改变列表的行为。
- ModelAdmin.get_changelist_form(request, **kwargs)[source]¶
返回一个
ModelForm类,供变更列表页面中的Formset使用。要使用一个自定义表单,例如:from django import forms class MyForm(forms.ModelForm): pass class MyModelAdmin(admin.ModelAdmin): def get_changelist_form(self, request, **kwargs): return MyForm
- ModelAdmin.get_changelist_formset(request, **kwargs)[source]¶
如果使用了
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_to在raw_id_fields中正确运行所需的查找。重写这个方法来定制你的
ModelAdmin子类允许的查找。Changed in Django 5.0:已添加
request参数。
- ModelAdmin.has_view_permission(request, obj=None)¶
如果允许查看
obj,应返回True,否则返回False。如果 obj 是None,应返回True或False表示是否允许查看该类型的对象(例如,False将被解释为当前用户不允许查看该类型的任何对象)。如果用户有 “更改” 或 “查看” 权限,默认的实现将返回
True。
- ModelAdmin.has_add_permission(request)¶
如果允许添加对象,应返回
True,否则返回False。
- ModelAdmin.has_change_permission(request, obj=None)¶
如果允许编辑
obj,应返回True,否则应返回False。如果obj是None,应返回True或False表示是否允许编辑该类型对象(例如,False将被解释为当前用户不允许编辑该类型的任何对象)。
- ModelAdmin.has_delete_permission(request, obj=None)¶
如果允许删除
obj,应返回True,否则返回False。如果obj是None,应返回True或False,以表明是否允许删除该类型的对象(例如,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)[source]¶
使用
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)[source]¶
返回要用于该视图的分页器实例。默认情况下,实例化一个
paginator的实例。
- ModelAdmin.response_add(request, obj, post_url_continue=None)[source]¶
确定
add_view()阶段的HttpResponse。response_add在提交管理表单后,对象和所有相关实例被创建和保存后被调用。你可以覆盖它来改变对象创建后的默认行为。
- ModelAdmin.response_change(request, obj)[source]¶
确定
change_view()阶段的HttpResponse。response_change在管理表单提交后,对象和所有相关实例被保存后被调用。你可以覆盖它来改变对象被改变后的默认行为。
- ModelAdmin.response_delete(request, obj_display, obj_id)[source]¶
为
delete_view()阶段确定HttpResponse。response_delete在对象被删除后被调用。你可以覆盖它来改变对象被删除后的默认行为。obj_display是删除对象名称的字符串。obj_id是用于检索要删除的对象的序列化标识符。
- ModelAdmin.get_formset_kwargs(request, obj, inline, prefix)[source]¶
一个用于自定义传递给表单集构造函数的关键字参数的钩子。例如,将
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)[source]¶
一个钩子,用于管理更改表格的初始数据。默认情况下,字段的初始值来自
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)[source]¶
一个钩子,用于自定义
delete_view()和 “删除已选” 动作 的删除过程。objs参数是要删除的对象(一个QuerySet或模型实例列表)的等价可迭代对象,request是HttpRequest。这个方法必须返回一个四元元组
(delete_objects, model_count, perms_needed, protected)。deleted_objects是一个代表所有将被删除对象的字符串列表。如果有任何相关的对象要删除,则列表是嵌套的,包括这些相关对象。该列表在模板中使用unordered_list过滤器进行格式化。model_count是一个将每个模型的verbose_name_plural映射到将被删除的对象数量的字典。perms_needed是一组verbose_name的用户没有权限删除的模型。protected是一个字符串列表,代表所有不能删除的受保护相关对象。该列表显示在模板中。
其他方法¶
- ModelAdmin.change_view(request, object_id, form_url='', extra_context=None)[source]¶
模型实例编辑页面的 Django 视图。见下面的说明。
- ModelAdmin.delete_view(request, object_id, extra_context=None)[source]¶
模型实例删除确认页面的 Django 视图。参见下面的说明。
- ModelAdmin.history_view(request, object_id, extra_context=None)[source]¶
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 app 将 STATIC_URL (如果 STATIC_URL 是 None,则 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', …]。
jQuery 已从 3.6.4 升级到 3.7.1。
ModelAdmin 类默认需要 jQuery,所以除非你有特殊需要,否则不需要将 jQuery 添加到你的 ModelAdmin 的媒体资源列表中。例如,如果你要求 jQuery 库在全局命名空间中(例如在使用第三方 jQuery 插件时),或者你需要一个较新版本的 jQuery,你将不得不包含自己的副本。
Django 提供了 jQuery 的非压缩和 “最小化” 版本,分别为 jquery.js 和 jquery.min.js。
ModelAdmin 和 InlineModelAdmin 有一个 media 属性,它返回一个 Media 对象的列表,其中存储了表单和/或表单集的 JavaScript 文件的路径。如果 DEBUG 是 True,它将返回各种 JavaScript 文件的未压缩版本,包括 jquery.js;如果不是,它将返回 “最小化” 版本。
在管理中添加自定义验证¶
你也可以在管理中添加自定义的数据验证。自动管理界面重用 django.forms,ModelAdmin 类让你能够定义你自己的表单:
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 StackedInline[source]¶
管理界面可以在同一页面上与父模型编辑模型。这些被称为内联。假设你有这两个模型:
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 选项¶
InlineModelAdmin 与 ModelAdmin 共享许多相同的功能,并增加了一些自己的功能(共享功能实际上是在 BaseModelAdmin 超级类中定义的)。共享的功能有:
InlineModelAdmin 类添加或自定义:
- InlineModelAdmin.model¶
内联使用的模型。这是必须的。
- InlineModelAdmin.fk_name¶
模型上外键的名称。在大多数情况下会自动处理,但如果同一父模型有多个外键,则必须明确指定
fk_name。
- InlineModelAdmin.formset¶
默认为
BaseInlineFormSet。使用你自己的表单集可以给你提供很多定制的可能性。内联是围绕 模型表单集 建立的。
- InlineModelAdmin.form¶
form的值默认为ModelForm。当为这个内联创建表单集时,会通过inlineformset_factory()传递这个值。
Warning
当为 InlineModelAdmin 表单编写自定义验证时,要谨慎编写依赖于父模型特征的验证。如果父模型未能验证,它可能会处于不一致的状态,如 验证 ModelForm 中的警告所述。
- InlineModelAdmin.classes¶
一个包含额外 CSS 类的列表或元组,用于应用到为内联渲染的 fieldset。默认为
None。与在fieldsets中配置的 classes 一样,具有collapse类的内联将使用可展开的小部件初始折叠。Changed in Django 5.1:使用
collapse类的fieldsets现在使用<details>和<summary>元素,前提是它们定义了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是你想改变为ForeignKey或ManyToManyField的Input部件的字段列表: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。
- InlineModelAdmin.show_change_link¶
指定在管理中可以更改的内联对象是否有更改表单的链接。默认为
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,否则返回False。obj是被编辑的父对象,或者当添加一个新的父对象时返回None。
- InlineModelAdmin.has_change_permission(request, obj=None)¶
如果允许编辑内联对象,应返回
True,否则返回False。obj是被编辑的父对象。
- InlineModelAdmin.has_delete_permission(request, obj=None)¶
如果允许删除内联对象,应返回
True,否则返回False。obj是被编辑的父对象。
Note
传递给 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.through。through 属性是对管理多对多关系的模型的引用。当你定义一个多对多字段时,这个模型是由 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)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["person", "group"], name="unique_person_group"
)
]
在管理中显示这个中间模型的第一步是为 Membership 模型定义一个内联类:
class MembershipInline(admin.TabularInline):
model = Membership
extra = 1
这个例子对 Membership 模型使用默认的 InlineModelAdmin 值,并将额外的添加表单限制为一个。这可以使用 InlineModelAdmin 类的任何可用选项来定制。
现在为 Person 和 Group 模型创建管理视图:
class PersonAdmin(admin.ModelAdmin):
inlines = [MembershipInline]
class GroupAdmin(admin.ModelAdmin):
inlines = [MembershipInline]
最后,在管理网站上注册你的 Person 和 Group 模型:
admin.site.register(Person, PersonAdmin)
admin.site.register(Group, GroupAdmin)
现在你的管理网站已经设置好了,可以在 Person 或 Group 的详情页中在线编辑 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 提供的 GenericTabularInline 或 GenericStackedInline (都是 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.htmlapp_index.htmlchange_form.htmlchange_form_object_tools.htmlchange_list.htmlchange_list_object_tools.htmlchange_list_results.htmldate_hierarchy.htmldelete_confirmation.htmlobject_history.htmlpagination.htmlpopup_response.htmlprepopulated_fields_js.htmlsearch_form.htmlsubmit_line.html
对于那些不能以这种方式覆盖的模板,你仍然可以通过将新版本放在你的 templates/admin 目录下,为整个项目覆盖它们。这对于创建自定义 404 和 500 页面特别有用。
Note
一些管理模板,如 change_list_results.html 是用来呈现自定义包含标签的。这些可以被覆盖,但在这种情况下,你可能最好创建你自己版本的标签,并给它一个不同的名字。这样你就可以有选择地使用它。
根和登录模板¶
如果你想更改索引、登录或注销模板,你最好创建自己的 AdminSite 实例(见下文),并更改 AdminSite.index_template 、AdminSite.login_template 或 AdminSite.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')[source]¶
一个 Django 管理站点由
django.contrib.admin.sites.AdminSite的实例来表示;默认情况下,这个类的实例被创建为django.contrib.admin.site,你可以用它来注册你的模型和ModelAdmin实例。如果你想自定义默认的管理站点,你可以 覆盖它。
当构造一个
AdminSite的实例时,你可以使用构造函数的name参数提供一个唯一的实例名称。这个实例名是用来标识实例的,特别是在 反查管理 URL 时。如果没有提供实例名,将使用默认的实例名admin。参见 自定义 AdminSite 类,了解定制AdminSite类的例子。
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的例子。
一个布尔值,决定是否在大屏幕上显示导航侧栏。默认情况下,它被设置为
True。
- AdminSite.final_catch_all_view¶
一个布尔值,用于决定是否在管理员中添加一个最终的总括视图,将未认证的用户重定向到登录页面。默认情况下,它被设置为
True。Warning
不建议将此设置为
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)[source]¶
返回一个变量字典,将其放入管理站点中每个页面的模板上下文中。
默认情况下包括以下变量和值:
site_header:AdminSite.site_headersite_title:AdminSite.site_titlesite_url:AdminSite.site_urlhas_permission:AdminSite.has_permission()available_apps:当前用户可用的 application registry 中的应用程序列表。列表中的每个条目都是一个代表应用程序的字典,其键如下:app_label:应用程序标签app_url:管理中应用程序索引的 URLhas_module_perms:一个布尔值,表示是否允许当前用户显示和访问模块的索引页models:应用程序中可用模型的清单
每个模型都是一个带有以下键的字典
model` :模型类
object_name:模型类名name:模型的复数名perms:dict跟踪add、change、delete和view权限admin_url:模型的管理变更表 URLadd_url:添加新模型实例的管理网址
is_popup:当前页面是否在弹出窗口中显示。is_nav_sidebar_enabled:AdminSite.enable_nav_sidebarlog_entries:AdminSite.get_log_entries()
- AdminSite.get_app_list(request, app_label=None)[source]¶
返回当前用户可用的 应用程序注册表 中的应用程序列表。你可以选择性地传递一个
app_label参数来获取单个应用程序的详细信息。列表中的每个条目都是一个字典,表示一个应用程序,具有以下键:app_label:应用程序标签app_url:管理中应用程序索引的 URLhas_module_perms:一个布尔值,表示是否允许当前用户显示和访问模块的索引页models:应用程序中可用模型的清单name:应用程序的名称
每个模型都是一个带有以下键的字典:
model` :模型类
object_name:模型类名name:模型的复数名perms:dict跟踪add、change、delete和view权限admin_url:模型的管理变更表 URLadd_url:添加新模型实例的管理网址
应用程序和模型的列表按它们的名称按字母顺序排序。你可以覆盖这个方法来改变管理员首页的默认顺序。
- AdminSite.has_permission(request)[source]¶
如果给定的
HttpRequest的用户有权限在管理站点中查看至少一个页面,则返回True。默认要求User.is_active和User.is_staff都是True。
- AdminSite.register(model_or_iterable, admin_class=None, **options)[source]¶
用给定的
admin_class注册给定的模型类(或类的迭代)。admin_class默认为ModelAdmin(默认的管理选项)。如果给定了关键字参数 —— 例如list_display—— 它们将作为选项应用到管理类中。如果模型是抽象的,将引发
ImproperlyConfigured异常,如果模型已经注册,将引发django.contrib.admin.exceptions.AlreadyRegistered异常。
- AdminSite.unregister(model_or_iterable)[source]¶
取消注册给定的模型类(或类的可迭代对象)
如果模型尚未注册,将引发
django.contrib.admin.exceptions.NotRegistered异常。
将 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'。
覆盖默认的管理站点¶
你可以通过设置自定义 AppConfig 的 default_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_site 和 myproject.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() 方法来包含一个新视图的模式。
Note
任何使用管理模板或扩展基本管理模板的视图,都应该在渲染模板之前设置 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¶
记录的动作类型:
ADDITION、CHANGE、DELETION。例如,要获取所有通过管理完成的添加列表:
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_change_message()[source]¶
将
change_message格式化并翻译成当前用户语言。在 Django 1.10 之前创建的消息将始终以其登录时的语言显示。
反查管理 URL¶
当部署了一个 AdminSite 时,可以使用 Django 的 URL 反查系统 访问该网站提供的视图。
AdminSite 提供了以下命名的 URL 模式。
页面 |
URL 名称 |
参数 |
|---|---|---|
索引 |
|
|
登录 |
|
|
登出 |
|
|
密码更改 |
|
|
密码更改完成 |
|
|
i18n JavaScript |
|
|
应用索引页面 |
|
|
重定向到对象的页面 |
|
|
每个 ModelAdmin 实例都提供一组额外的命名 URL:
页面 |
URL 名称 |
参数 |
|---|---|---|
变更列表 |
|
|
增加 |
|
|
历史 |
|
|
删除 |
|
|
修改 |
|
|
UserAdmin 提供一个命名的 URL:
页面 |
URL 名称 |
参数 |
|---|---|---|
密码更改 |
|
|
这些命名的 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_label 和 model_name 属性的对象,通常由当前模型的管理视图提供。
display 装饰器¶
- display(*, boolean=None, ordering=None, description=None, empty_value=None)[source]¶
这个装饰器可以用来设置自定义显示函数的特定属性,可以用
list_display或readonly_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')[source]¶
这个装饰器被用于需要授权的管理视图。用这个函数装饰的视图将有以下行为:
如果用户已登录,是工作人员(
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): ...