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
后端:
django.template.context_processors.request
被添加为 'context_processors'
选项中的依赖,以支持新的 AdminSite.enable_nav_sidebar
。
如果你自定义了 MIDDLEWARE
设置,则必须包含 django.contrib.auth.middleware.AuthenticationMiddleware
和 django.contrib.messages.middleware.MessageMiddleware
。
在你采取了这些步骤之后,你就可以通过访问你挂接的 URL(默认是 /admin/
)来使用管理站点。
如果需要创建一个用户来登录,请使用 createsuperuser
命令。默认情况下,登录管理需要用户的 is_staff
属性设置为 True
。
最后,确定你的应用程序的哪些模型应该在管理界面中是可编辑的。对于这些模型中的每一个,按照 ModelAdmin
中的描述,在管理处注册它们。
ModelAdmin
对象¶ModelAdmin
¶ModelAdmin
类是管理界面中模型的表示。通常,这些都存储在你的应用程序中一个名为 admin.py
的文件中。让我们来看看 ModelAdmin
的一个例子:
from django.contrib import admin
from myproject.myapp.models import Author
class AuthorAdmin(admin.ModelAdmin):
pass
admin.site.register(Author, AuthorAdmin)
你是否需要一个 ModelAdmin
对象?
在前面的例子中,ModelAdmin
类没有定义任何自定义值(还没有)。因此,将提供默认的管理界面。如果你对默认的管理界面满意,你根本不需要定义一个 ModelAdmin
对象 —— 你可以不提供 ModelAdmin
描述而注册模型类。前面的例子可以简化为:
from django.contrib import admin
from myproject.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
模块并导入它。
apps.
AdminConfig
¶这是管理员默认的 AppConfig
类。当 Django 启动时,它会调用 autodiscover()
。
apps.
SimpleAdminConfig
¶这个类的工作原理和 AdminConfig
一样,只是没有调用 autodiscover()
。
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_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'
这将根据可用的数据智能地填充自己,例如,如果所有的日期都在一个月内,它将只显示日级的向下扩展。
注解
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):
fields = ('name', 'title', 'view_birth_date')
@admin.display(empty_value='???')
def view_birth_date(self, obj):
return obj.birth_date
display()
装饰器的 empty_value
参数相当于在以前的版本中直接在显示函数上设置 empty_value_display
属性。为了向后兼容,仍然支持直接设置该属性。
ModelAdmin.
exclude
¶如果给定了这个属性,那么这个属性应该是一个要从表单中排除的字段名列表。
例如,我们考虑以下模型:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3)
birth_date = models.DateField(blank=True, null=True)
如果你想让 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')
注解
这个 fields
选项不应与 fieldsets
选项中的 fields
字典键混淆,下一节将介绍。
如果 fields
或 fieldsets
选项都不存在,Django 将默认在一个单一的字段集中显示每个非 AutoField
且有 editable=True
的字段,顺序与模型中定义的字段相同。
ModelAdmin.
fieldsets
¶设置 fieldsets
来控制管理员 “添加” 和 “更改” 页面的布局。
fieldsets
是一个由两个元组组成的列表,其中每个元组代表管理表单页面上的一个 <fieldset>
。(一个 <fieldset>
是表单的一个 “部分”。)
这两个元组的格式是 (name, field_options)
,其中 name
是代表字段集标题的字符串,field_options
是关于字段集的信息字典,包括要在其中显示的字段列表。
一个完整的例子,取自 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 类的列表或元组,用于应用到字段集。
举例:
{
'classes': ('wide', 'extrapretty'),
}
默认的管理网站样式表定义的两个有用的类是 collapse
和 wide
。collapse
风格的字段集将在管理中被初始折叠,并被一个小的 “点击展开” 链接所取代;wide
风格的字段集将被赋予额外的水平空间。wide
风格的字段集将获得额外的水平空间。
description
一串可选的额外文本,显示在每个字段集的顶部,在字段集的标题下。这个字符串在 TabularInline
中由于其布局的原因而无法显示。
请注意,当这个值在管理界面显示时,它不是 HTML 转义后的。如果你愿意的话,这可以让你包含 HTML。或者你可以使用纯文本和 django.utils.html.escape()
来转义任何 HTML 特殊字符。
ModelAdmin.
filter_horizontal
¶默认情况下,在管理网站中显示一个 ManyToManyField
是 <select multiple>
。但是,多选框在选择很多项目时,会很难用。在这个列表中添加一个 ManyToManyField
,就可以改用一个不显眼的 JavaScript “过滤器” 界面,在选项中进行搜索。未选择和选择的选项并排出现在两个框中。参见 filter_vertical
来使用垂直界面。
ModelAdmin.
filter_vertical
¶与 filter_horizontal
相同,但使用垂直显示过滤界面,未选择的选项框出现在选择选项框的上方。
ModelAdmin.
form
¶默认情况下,会为你的模型动态创建一个 ModelForm
。它用于创建在添加/更改页面上显示的表单。你可以很容易地提供你自己的 ModelForm
来覆盖添加/更改页面上的任何默认表单行为。另外,你可以使用 ModelAdmin.get_form()
方法来定制默认的表单,而不是指定一个全新的表单。
示例请参见 在管理中添加自定义验证 一节。
注解
如果你在 ModelForm
上定义了 Meta.model
属性,你也必须定义 Meta.fields
属性(或 Meta.exclude
属性)。但是,由于管理有自己定义字段的方式,所以 Meta.field
属性将被忽略。
如果 ModelForm
只用于管理,最简单的解决办法是省略 Meta.model
属性,因为 ModelAdmin
将提供正确的模型。或者,你可以在 Meta
类中设置 fields = []
以满足 ModelForm
的验证。
注解
如果你的 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。
警告
如果你想使用一个带有关系字段的自定义部件(例如 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')
一个接受一个参数的可调用对象,即模型实例。例如:
@admin.display(description='Name')
def upper_case_name(obj):
return ("%s %s" % (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 ("%s %s" % (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):
return '%d’s' % (self.birthday.year // 10 * 10)
class PersonAdmin(admin.ModelAdmin):
list_display = ('name', 'decade_born_in')
关于 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
参数来自定义列的标题。
display()
装饰器的 description
参数相当于在以前的版本中直接在显示函数上设置 short_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
display()
装饰器的 empty_value
参数相当于在以前的版本中直接在显示函数上设置 empty_value_display
属性。为了向后兼容,仍然支持直接设置该属性。
如果给定的字符串是模型的一个方法,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')
display()
装饰器的 boolean
参数相当于在以前的版本中直接在显示函数上设置 boolean
属性。为了向后兼容,仍然支持直接设置该属性。
__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')
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
display()
装饰器的 ordering
参数相当于在以前的版本中直接在显示函数上设置 admin_order_field
属性。为了向后兼容,仍然支持直接设置属性。
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',
)
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'
full_name = property(my_property)
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
中列出的字段将作为表单部件显示在变更列表页上,允许用户一次编辑和保存多行。
注解
list_editable
以特定方式与其他几个选项进行交互;你应该注意以下规则:
list_editable
中的任何字段都必须在 list_display
中。你不能编辑一个没有显示的字段!list_editable
和 list_display_links
中 —— 一个字段不能既是表单又是链接。如果这些规则中的任何一条被破坏,你会得到一个验证错误。
ModelAdmin.
list_filter
¶设置 list_filter
来激活管理更改列表页面右侧侧栏的过滤器,如下截图所示:
list_filter
应是一个元素的列表或元组,其中每个元素应是下列类型之一:
一个字段名,其中指定的字段应该是 BooleanField`
、CharField`、DateField
、DateTimeField
、IntegerField
、ForeignKey
或 ManyToManyField
,例如:
class PersonAdmin(admin.ModelAdmin):
list_filter = ('is_staff', 'company')
list_filter
中的字段名也可以使用 __
查找来跨越关系,例如:
class PersonAdmin(admin.UserAdmin):
list_filter = ('company__name',)
继承自 django.contrib.admin.SimpleListFilter
的类,你需要为其提供 title
和 parameter_name
属性,并覆盖 lookups
和 queryset
方法,例如:
from datetime import date
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
class DecadeBornListFilter(admin.SimpleListFilter):
# Human-readable title which will be displayed in the
# right admin sidebar just above the filter options.
title = _('decade born')
# Parameter for the filter that will be used in the URL query.
parameter_name = 'decade'
def lookups(self, request, model_admin):
"""
Returns a list of tuples. The first element in each
tuple is the coded value for the option that will
appear in the URL query. The second element is the
human-readable name for the option that will appear
in the right sidebar.
"""
return (
('80s', _('in the eighties')),
('90s', _('in the nineties')),
)
def queryset(self, request, queryset):
"""
Returns the filtered queryset based on the value
provided in the query string and retrievable via
`self.value()`.
"""
# Compare the requested value (either '80s' or '90s')
# to decide how to filter the queryset.
if self.value() == '80s':
return queryset.filter(birthday__gte=date(1980, 1, 1),
birthday__lte=date(1989, 12, 31))
if self.value() == '90s':
return queryset.filter(birthday__gte=date(1990, 1, 1),
birthday__lte=date(1999, 12, 31))
class PersonAdmin(admin.ModelAdmin):
list_filter = (DecadeBornListFilter,)
注解
为方便起见,HttpRequest
对象被传递给 lookups
和 queryset
方法,例如:
class AuthDecadeBornListFilter(DecadeBornListFilter):
def lookups(self, request, model_admin):
if request.user.is_superuser:
return super().lookups(request, model_admin)
def queryset(self, request, queryset):
if request.user.is_superuser:
return super().queryset(request, queryset)
另外,为了方便起见,ModelAdmin
对象被传递给 lookups
方法,例如,如果你想根据现有数据进行查找:
class AdvancedDecadeBornListFilter(DecadeBornListFilter):
def lookups(self, request, model_admin):
"""
Only show the lookups if there actually is
anyone born in the corresponding decades.
"""
qs = model_admin.get_queryset(request)
if qs.filter(birthday__gte=date(1980, 1, 1),
birthday__lte=date(1989, 12, 31)).exists():
yield ('80s', _('in the eighties'))
if qs.filter(birthday__gte=date(1990, 1, 1),
birthday__lte=date(1999, 12, 31)).exists():
yield ('90s', _('in the nineties'))
一个元组,其中第一个元素是字段名,第二个元素是继承自 django.contrib.admin.FieldListFilter
的类,例如:
class PersonAdmin(admin.ModelAdmin):
list_filter = (
('is_staff', admin.BooleanFieldListFilter),
)
你可以使用 RelatedOnlyFieldListFilter
将相关模型的选择限制在该关系所涉及的对象上:
class BookAdmin(admin.ModelAdmin):
list_filter = (
('author', admin.RelatedOnlyFieldListFilter),
)
假设 author
是 ForeignKey
到 User
模型,这将会把 list_filter
的选择限制在写过书的用户,而不是列出所有用户。
你可以使用 EmptyFieldListFilter
来过滤空值,它既可以过滤空字符串也可以过滤空值,这取决于字段允许存储的内容:
class BookAdmin(admin.ModelAdmin):
list_filter = (
('title', admin.EmptyFieldListFilter),
)
注解
FieldListFilter
的 API 被认为是内部的,可能会被改变。
注解
不支持 GenericForeignKey
字段。
增加了 EmptyFieldListFilter
类。
列表过滤器通常只有在过滤器有多个选择时才会出现。过滤器的 has_output()
方法控制它是否出现。
可以指定一个自定义模板来呈现列表过滤器:
class FilterWithCustomTemplate(admin.SimpleListFilter):
template = "custom_template.html"
具体的例子请看 Django 提供的默认模板(admin/filter.html
)。
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
字段排序,那么更改列表就按 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.
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 会做相当于这个 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>
。
前缀 | 查找 |
---|---|
^ | startswith |
= | iexact |
@ | search |
None | icontains |
如果你需要自定义搜索,你可以使用 ModelAdmin.get_search_results()
来提供额外的或替代的搜索行为。
增加了对带空格的引号短语的搜索支持。
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
方法¶警告
当覆盖 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
属性的 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)¶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)¶get_list_display
方法被赋予 HttpRequest
,预计将返回一个 list
或 tuple
的字段名,这些字段名将显示在变更列表视图上,如上面 ModelAdmin.list_display
一节所述。
ModelAdmin.
get_list_display_links
(request, list_display)¶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
),预计将返回一个双元组列表,其中每个双元组代表管理表单页面上的 <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
,并期望返回一个字段名的集合(例如 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)¶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
()¶ModelAdmin
上的 get_urls
方法以与 URLconf 相同的方式返回用于该 ModelAdmin 的 URL。 因此,你可以按照 URL调度器 中的文档来扩展它们:
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.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 %}
注解
请注意,自定义模式包含在常规的管理 URL 之前:管理 URL 模式是非常宽松的,几乎可以匹配任何东西,所以你通常会希望将你的自定义 URL 添加到内置的 URL 中。
在这个例子中,my_view
将在 /admin/myapp/mymodel/my_view/
中被访问(假设在 /admin/
中包含了管理的 URL)。
然而,上面注册的 self.my_view
函数存在两个问题:
由于这通常不是你想要的,Django 提供了一个方便的包装器来检查权限并将视图标记为不可缓存。这个封装器是 AdminSite.admin_view()
(即 self.admin_site.admin_view
在一个 ModelAdmin
实例中);使用它就像这样:
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
请注意上面第五行的包装的视图:
path('my_view/', self.admin_site.admin_view(self.my_view))
这个封装将保护 self.my_view
不被未经授权的访问,并将应用 django.views.decorators.cache.never_cache()
装饰器来确保在缓存中间件处于活动状态时不被缓存。
如果页面是可缓存的,但你仍然希望进行权限检查,你可以传递一个 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
,通过 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)¶产生 (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'] += (('ready', 'Ready for deployment'),)
return super().formfield_for_choice_field(db_field, request, **kwargs)
注解
任何在表单字段上设置的 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
注解
如果你在 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)¶变更列表页面中的对象可以通过 URL 的查询字符串进行过滤。例如 list_filter
就是这样工作的。这些查询类似于 QuerySet.filter()
(例如 user__email=user@example.com
)。由于用户可以对查询字符串中的查找进行操作,因此必须对它们进行处理,以防止未经授权的数据暴露。
lookup_allowed()
方法从查询字符串(如 'user__email'
)和相应的值(如``'user@example.com')中得到一个查找路径,并返回一个布尔值,表示是否允许使用参数过滤变更列表的 ``QuerySet
。如果 lookup_allowed()
返回 False
,则会引发 DisallowedModelAdminLookup
(SuspiciousOperation
的子类)。
默认情况下,lookup_allowed()
允许访问模型的本地字段、在 list_filter
中使用的字段路径(但不包括 get_list_filter()
)中使用的字段路径,以及 limit_choices_to
在 raw_id_fields
中正确运行所需的查找。
重写这个方法来定制你的 ModelAdmin
子类允许的查找。
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)¶使用 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_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
或模型实例列表)的等价可迭代对象,request
是 HttpRequest
。
这个方法必须返回一个四元元组 (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 app 将 STATIC_URL
(如果 STATIC_URL
是 None
,则 MEDIA_URL
)预先加入任何资产路径。同样的规则适用于:ref:表单上定义的静态资源 <form-asset-paths>。
Django 管理 JavaScript 使用了 jQuery 库。
为了避免与用户提供的脚本或库发生冲突,Django 的 jQuery(3.5.1 版)被命名为 django.jQuery
。如果你想在自己的管理 JavaScript 中使用 jQuery,而不需要包含第二个副本,你可以在变更列表和添加/删除视图中使用 django.jQuery
对象。另外,你自己的管理表单或部件依赖 django.jQuery
必须在 声明表单媒体资产 时指定 js=['admin/js/jquery.init.js', ...]
。
jQuery 从 3.4.1 版本升级到 3.5.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
对象¶InlineModelAdmin
¶TabularInline
¶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
选项¶InlineModelAdmin
与 ModelAdmin
共享许多相同的功能,并增加了一些自己的功能(共享功能实际上是在 BaseModelAdmin
超级类中定义的)。共享的功能有:
form
fieldsets
fields
formfield_overrides
exclude
filter_horizontal
filter_vertical
ordering
prepopulated_fields
get_fieldsets()
get_queryset()
radio_fields
readonly_fields
raw_id_fields
formfield_for_choice_field()
formfield_for_foreignkey()
formfield_for_manytomany()
has_module_permission()
InlineModelAdmin
类添加或自定义:
InlineModelAdmin.
model
¶内联使用的模型。这是必须的。
InlineModelAdmin.
fk_name
¶模型上外键的名称。在大多数情况下会自动处理,但如果同一父模型有多个外键,则必须明确指定 fk_name
。
InlineModelAdmin.
formset
¶默认为 BaseInlineFormSet
。使用你自己的表单集可以给你提供很多定制的可能性。内联是围绕 模型表单集 建立的。
InlineModelAdmin.
form
¶form
的值默认为 ModelForm
。当为这个内联创建表单集时,会通过 inlineformset_factory()
传递这个值。
警告
当为 InlineModelAdmin
表单编写自定义验证时,要谨慎编写依赖于父模型特征的验证。如果父模型未能验证,它可能会处于不一致的状态,如 验证 ModelForm 中的警告所述。
InlineModelAdmin.
classes
¶一个包含额外 CSS 类的列表或元组,应用于为内联渲染的字段集。默认值为 None
。如同在 fieldsets
中配置的类一样,带有 collapse
类的内联将被初始折叠,并且它们的标题将有一个小的 “显示” 链接。
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
¶对模型内部 Meta
类中的 verbose_name_plural
的覆盖。
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
是被编辑的父对象。
注解
传递给 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,
]
By default, admin widgets for many-to-many relations will be displayed
on whichever model contains the actual reference to the
ManyToManyField
. Depending on your ModelAdmin
definition, each many-to-many field in your model will be represented by a
standard HTML <select multiple>
, a horizontal or vertical filter, or a
raw_id_fields
widget. However, it is also possible to replace these
widgets with inlines.
假设我们有以下模型:
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)
在管理中显示这个中间模型的第一步是为 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 myproject.myapp.models import Image, Product
class ImageInline(GenericTabularInline):
model = Image
class ProductAdmin(admin.ModelAdmin):
inlines = [
ImageInline,
]
admin.site.register(Product, ProductAdmin)
更多具体信息请参见 contenttypes 文档。
你可以覆盖许多模板,管理模块用于生成管理网站的各种页面。你甚至可以为特定的应用程序或特定的模型覆盖其中的一些模板。
管理模板文件位于 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
目录下。
由于管理模板的模块化设计,通常没有必要也不建议更换整个模板。最好是只覆盖你需要更改的模板部分。
继续上面的例子,我们想在 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_template
、AdminSite.login_template
或 AdminSite.logout_template
属性。
管理使用 CSS 变量来定义颜色。这允许改变颜色,而不必覆盖许多单独的 CSS 规则。例如,如果你喜欢紫色而不是蓝色,你可以添加一个 admin/base.html
模板覆盖到你的项目中。
{% extends 'admin/base.html' %}
{% block extrahead %}{{ block.super }}
<style>
:root {
--primary: #9774d5;
--secondary: #785cab;
--link-fg: #7c449b;
--link-selected-fg: #8f5bb2;
}
</style>
{% endblock %}
定义一个深色主题,并根据 prefers-color-scheme 媒体查询应用。
CSS 变量列表定义在 django/contrib/admin/static/admin/css/base.css
。
AdminSite
对象¶AdminSite
(name='admin')¶一个 Django 管理站点由 django.contrib.admin.sites.AdminSite
的实例来表示;默认情况下,这个类的实例被创建为 django.contrib.admin.site
,你可以用它来注册你的模型和 ModelAdmin
实例。
如果你想自定义默认的管理站点,你可以 覆盖它。
当构造一个 AdminSite
的实例时,你可以使用构造函数的 name
参数提供一个唯一的实例名称。这个实例名是用来标识实例的,特别是在 反查管理 URL 时。如果没有提供实例名,将使用默认的实例名 admin
。参见 自定义 AdminSite 类,了解定制 AdminSite
类的例子。
AdminSite
属性¶模板可以覆盖或扩展基本的管理模板,如 覆盖管理模板 中所述。
AdminSite.
site_header
¶要放在每个管理页面顶部的文字,作为 <h1>
(一个字符串)。默认情况下,是 “Django 管理”。
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
。
警告
不建议将此设置为 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_header
: AdminSite.site_header
site_title
:AdminSite.site_title
site_url
:AdminSite.site_url
has_permission
:AdminSite.has_permission()
available_apps
:当前用户可用的 application registry 中的应用程序列表。列表中的每个条目都是一个代表应用程序的字典,其键如下:
app_label
:应用程序标签app_url
:管理中应用程序索引的 URLhas_module_perms
:一个布尔值,表示是否允许当前用户显示和访问模块的索引页models
:应用程序中可用模型的清单每个模型都是一个带有以下键的字典
object_name
:模型类名name
:模型的复数名perms
:dict
跟踪 add
、change
、delete
和 view
权限admin_url
:模型的管理变更表 URLadd_url
:添加新模型实例的管理网址AdminSite.
has_permission
(request)¶如果给定的 HttpRequest
的用户有权限在管理站点中查看至少一个页面,则返回 True
。默认要求 User.is_active
和 User.is_staff
都是 True
。
AdminSite.
register
(model_or_iterable, admin_class=None, **options)¶用给定的 admin_class
注册给定的模型类(或类的迭代)。admin_class
默认为 ModelAdmin
(默认的管理选项)。如果给定了关键字参数 —— 例如 list_display
—— 它们将作为选项应用到管理类中。
如果一个模型是抽象的,会引发 ImproperlyConfigured
,如果一个模型已经注册,会引发 django.contrib.admin.sites.AlreadyRegistered
。
AdminSite.
unregister
(model_or_iterable)¶取消注册给定的模型类(或类的可迭代对象)
如果一个模型还没有注册,就会引发 django.contrib.admin.sites.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
子类。
from django.contrib.admin import AdminSite
from .models import MyModel
class MyAdminSite(AdminSite):
site_header = 'Monty Python administration'
admin_site = MyAdminSite(name='myadmin')
admin_site.register(MyModel)
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
子类或一个返回站点实例的可调用的点分隔导入路径。
from django.contrib import admin
class MyAdminSite(admin.AdminSite):
...
from django.contrib.admin.apps import AdminConfig
class MyAdminConfig(AdminConfig):
default_site = 'myproject.admin.MyAdminSite'
INSTALLED_APPS = [
...
'myproject.apps.MyAdminConfig', # replaces 'django.contrib.admin'
...
]
你可以在同一个 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()
方法来包含一个新视图的模式。
注解
任何使用管理模板或扩展基本管理模板的视图,都应该在渲染模板之前设置 request.current_app
。如果你的视图是在 AdminSite
上,它应该设置为 self.name
;如果你的视图是在 ModelAdmin
上,它应该设置为 self.admin_site.name
。
你可以在你的 URLconf 中添加几行字,就可以给管理网站添加密码重置功能。具体来说,添加以下四种模式:
from django.contrib.auth import views as auth_views
path(
'admin/password_reset/',
auth_views.PasswordResetView.as_view(),
name='admin_password_reset',
),
path(
'admin/password_reset/done/',
auth_views.PasswordResetDoneView.as_view(),
name='password_reset_done',
),
path(
'reset/<uidb64>/<token>/',
auth_views.PasswordResetConfirmView.as_view(),
name='password_reset_confirm',
),
path(
'reset/done/',
auth_views.PasswordResetCompleteView.as_view(),
name='password_reset_complete',
),
(这假定你已经在 admin/
添加了管理员,并且要求你把以 ^admin/
为开头的 URL 放在包含管理程序本身的行之前)。
admin_password_reset
命名的 URL 的存在,会使默认的管理登录页面的密码框下出现 “忘记密码了?” 的链接。
LogEntry
对象¶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_edited_object
()¶返回被引用对象的快捷方式。
LogEntry.
get_change_message
()¶将 change_message
格式化并翻译成当前用户语言。在 Django 1.10 之前创建的消息将始终以其登录时的语言显示。
当部署了一个 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
对象(来自民意调查应用程序)的变更视图的引用,你会调用:
>>> 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)¶这个装饰器可以用来设置自定义显示函数的特定属性,可以用 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')¶该装饰器用于需要授权的管理视图。使用该函数装饰的视图将具有以下行为:
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):
...
5月 26, 2021