自定义 Django 管理站点

DjangoDjangoBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

本教程从《添加样式表和图像》结束的地方开始。我们将继续Web投票应用程序,并专注于自定义Django自动生成的管理站点,我们在《设置数据库》中首次探讨了该站点。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL django(("Django")) -.-> django/DatabaseModelsandMigrationsGroup(["Database, Models, and Migrations"]) django(("Django")) -.-> django/UserInterfaceandInteractionGroup(["User Interface and Interaction"]) django(("Django")) -.-> django/DevelopmentandAdministrationToolsGroup(["Development and Administration Tools"]) django/DatabaseModelsandMigrationsGroup -.-> django/models("Models") django/DatabaseModelsandMigrationsGroup -.-> django/databases("Databases") django/DatabaseModelsandMigrationsGroup -.-> django/schemaeditor("SchemaEditor") django/UserInterfaceandInteractionGroup -.-> django/templates("Templates") django/DevelopmentandAdministrationToolsGroup -.-> django/django_admin("Django Admin") django/DevelopmentandAdministrationToolsGroup -.-> django/contrib_packages("Contrib Packages") subgraph Lab Skills django/models -.-> lab-153747{{"自定义 Django 管理站点"}} django/databases -.-> lab-153747{{"自定义 Django 管理站点"}} django/schemaeditor -.-> lab-153747{{"自定义 Django 管理站点"}} django/templates -.-> lab-153747{{"自定义 Django 管理站点"}} django/django_admin -.-> lab-153747{{"自定义 Django 管理站点"}} django/contrib_packages -.-> lab-153747{{"自定义 Django 管理站点"}} end

自定义管理表单

通过使用 admin.site.register(Question) 注册 Question 模型,Django 能够构建默认的表单表示形式。通常,你会希望自定义管理表单的外观和工作方式。你可以在注册对象时告诉 Django 你想要的选项来实现这一点。

让我们通过重新排列编辑表单上的字段来看看这是如何工作的。将 admin.site.register(Question) 这一行替换为:

编辑 ~/project/mysite/polls/admin.py 文件,使其如下所示:

from django.contrib import admin

from.models import Question


class QuestionAdmin(admin.ModelAdmin):
    fields = ["pub_date", "question_text"]


admin.site.register(Question, QuestionAdmin)

每当你需要更改模型的管理选项时,你都将遵循此模式 —— 创建一个模型管理类,然后将其作为第二个参数传递给 admin.site.register()

运行 Django 开发服务器:

cd ~/project/mysite
python manage.py runserver

在桌面环境的 Firefox 中打开 http://127.0.0.1:8000/admin/,然后点击 “Questions” 链接。你应该会看到一个如下所示的表单。

上面这个特定的更改使得 “发布日期” 字段出现在 “问题” 字段之前:

管理表单字段重新排序

对于只有两个字段的情况,这可能并不明显,但对于有几十个字段的管理表单来说,选择一个直观的顺序是一个重要的可用性细节。

说到有几十个字段的表单,你可能希望将表单拆分为多个字段集:

from django.contrib import admin

from.models import Question


class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {"fields": ["question_text"]}),
        ("日期信息", {"fields": ["pub_date"]}),
    ]


admin.site.register(Question, QuestionAdmin)

~django.contrib.admin.ModelAdmin.fieldsets 中每个元组的第一个元素是字段集的标题。这是我们现在的表单样子:

带有字段集的管理表单

添加关联对象

好的,我们已经有了问题管理页面,但是一个 Question 有多个 Choice,而管理页面并没有显示选项。

不过,马上就会显示了。

有两种方法可以解决这个问题。第一种方法是像注册 Question 一样向管理站点注册 Choice

from django.contrib import admin

from.models import Choice, Question

#...
admin.site.register(Choice)

现在,“选项” 在 Django 管理站点中是一个可用选项。“添加选项” 表单如下所示:

添加选项表单界面

在那个表单中,“问题” 字段是一个下拉框,包含数据库中的每个问题。Django 知道 ~django.db.models.ForeignKey 在管理站点中应该表示为一个 <select> 框。在我们的例子中,此时只有一个问题。

还要注意 “问题” 旁边的 “添加另一个问题” 链接。与另一个对象具有 ForeignKey 关系的每个对象都会自动获得这个链接。当你点击 “添加另一个问题” 时,会弹出一个带有 “添加问题” 表单的窗口。如果你在那个窗口中添加一个问题并点击 “保存”,Django 会将问题保存到数据库中,并在你正在查看的 “添加选项” 表单上动态地将其添加为所选选项。

但是,实际上,这是一种向系统中添加 Choice 对象的低效方式。如果能在创建 Question 对象时直接添加一堆选项,那就更好了。让我们来实现这一点。

删除对 Choice 模型的 register() 调用。然后,编辑 Question 的注册代码如下:

from django.contrib import admin

from.models import Choice, Question


class ChoiceInline(admin.StackedInline):
    model = Choice
    extra = 3


class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {"fields": ["question_text"]}),
        ("日期信息", {"fields": ["pub_date"], "classes": ["collapse"]}),
    ]
    inlines = [ChoiceInline]


admin.site.register(Question, QuestionAdmin)

这告诉 Django:“Choice 对象在 Question 管理页面上进行编辑。默认情况下,提供足够的字段用于 3 个选项。”

加载 “添加问题” 页面,看看效果如何:

带有选项的问题管理页面

它的工作原理如下:有三个用于关联选项的插槽 —— 由 extra 指定 —— 并且每次你回到已创建对象的 “更改” 页面时,都会再得到另外三个额外的插槽。

在当前三个插槽的末尾,你会找到一个 “添加另一个选项” 链接。如果你点击它,会添加一个新的插槽。如果你想删除添加的插槽,可以点击添加插槽右上角的 X。这张图片展示了一个添加的插槽:

动态添加的额外插槽

不过,有一个小问题。显示用于输入关联 Choice 对象的所有字段会占用大量屏幕空间。因此,Django 提供了一种以表格形式显示内联关联对象的方式。要使用它,将 ChoiceInline 声明改为:

class ChoiceInline(admin.TabularInline):
  ...

使用 TabularInline(而不是 StackedInline),关联对象将以更紧凑的基于表格的格式显示:

表格形式的内联选项显示

请注意,有一个额外的 “删除?” 列,允许删除使用 “添加另一个选项” 按钮添加的行以及已经保存的行。

自定义管理更改列表

既然问题管理页面看起来不错了,那我们来对“更改列表”页面做一些调整 —— 就是那个显示系统中所有问题的页面。

目前它是这样的:

投票应用更改列表页面

默认情况下,Django 显示每个对象的 str() 表示。但有时如果我们能显示单个字段会更有帮助。要做到这一点,可以使用 ~django.contrib.admin.ModelAdmin.list_display 管理选项,它是一个字段名元组,用于在对象的更改列表页面上作为列显示:

class QuestionAdmin(admin.ModelAdmin):
    #...
    list_display = ["question_text", "pub_date"]

为了保险起见,我们还把《设置数据库》中的 was_published_recently() 方法也加上:

class QuestionAdmin(admin.ModelAdmin):
    #...
    list_display = ["question_text", "pub_date", "was_published_recently"]

现在问题更改列表页面是这样的:

问题更改列表视图

你可以点击列标题按这些值进行排序 —— 除了 was_published_recently 标题,因为不支持按任意方法的输出进行排序。还要注意,was_published_recently 的列标题默认是方法名(下划线替换为空格),并且每行都包含输出的字符串表示。

你可以通过在该方法上使用 ~django.contrib.admin.display 装饰器(在 polls/models.py 中)来改进这一点,如下所示:

from django.contrib import admin


class Question(models.Model):
    #...
    @admin.display(
        boolean=True,
        ordering="pub_date",
        description="最近发布?",
    )
    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now

有关可通过装饰器配置的属性的更多信息,请参阅 ~django.contrib.admin.ModelAdmin.list_display

再次编辑你的 polls/admin.py 文件,并对 Question 更改列表页面进行改进:使用 ~django.contrib.admin.ModelAdmin.list_filter 进行过滤。在 QuestionAdmin 中添加以下行:

list_filter = ["pub_date"]

这会添加一个“过滤”侧边栏,让人们可以按 pub_date 字段过滤更改列表:

管理列表过滤侧边栏

显示的过滤器类型取决于你正在过滤的字段类型。因为 pub_date 是一个 ~django.db.models.DateTimeField,Django 知道要给出适当的过滤选项:“任何日期”、“今天”、“过去7天”、“本月”、“今年”。

这看起来很不错了。让我们添加一些搜索功能:

search_fields = ["question_text"]

这会在更改列表顶部添加一个搜索框。当有人输入搜索词时,Django 将在 question_text 字段中进行搜索。你可以使用任意多个字段 —— 不过因为它在幕后使用 LIKE 查询,将搜索字段数量限制在合理数量会让你的数据库更容易进行搜索。

现在也是时候注意一下更改列表会自动提供分页功能了。默认是每页显示100项。更改列表分页 <django.contrib.admin.ModelAdmin.list_per_page>搜索框 <django.contrib.admin.ModelAdmin.search_fields>过滤器 <django.contrib.admin.ModelAdmin.list_filter>日期层次结构 <django.contrib.admin.ModelAdmin.date_hierarchy>列标题排序 <django.contrib.admin.ModelAdmin.list_display> 都能像你期望的那样协同工作。

自定义管理界面的外观和感觉

显然,在每个管理页面顶部显示“Django 管理”是很荒谬的。这只是占位文本。

不过,你可以使用 Django 的模板系统来更改它。Django 管理界面由 Django 自身提供支持,其界面使用 Django 自己的模板系统。

自定义项目模板

在你的项目目录(即包含 manage.py 的目录)中创建一个 templates 目录。模板可以存放在 Django 能够访问的文件系统的任何位置。(Django 以服务器运行的用户身份运行。)然而,将模板保存在项目内部是一个值得遵循的良好惯例。

打开你的设置文件(记得是 mysite/settings.py),并在 TEMPLATES 设置中添加一个 DIRS <TEMPLATES-DIRS> 选项:

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

DIRS <TEMPLATES-DIRS> 是一个文件系统目录列表,在加载 Django 模板时会检查这些目录;它是一个搜索路径。

组织模板

就像静态文件一样,我们本可以把所有模板都放在一个大的 templates 目录中,这样也能完美运行。然而,属于特定应用的模板应该放在该应用的模板目录(例如 polls/templates)中,而不是项目的(templates)。我们将在《可复用应用教程》(/intro/reusable-apps)中更详细地讨论这样做的原因。

现在在 templates 目录内创建一个名为 admin 的目录,并将 Django 自身源代码中默认的 Django 管理模板目录(django/contrib/admin/templates)中的模板 admin/base_site.html 复制到该目录中。

Django 源文件在哪里?

如果你在系统上难以找到 Django 源文件的位置,可以运行以下命令:

python -c "import django; print(django.__path__)"
['/home/labex/.local/lib/python3.10/site-packages/django']

然后,编辑该文件,将 {{ site_header|default:_('Django administration') }}(包括花括号)替换为你认为合适的你自己网站的名称。你最终应该得到一段类似这样的代码:

{% block branding %}
<div id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a><div>
{% endblock %}

我们使用这种方法来教你如何覆盖模板。在实际项目中,你可能会使用 django.contrib.admin.AdminSite.site_header 属性来更轻松地进行这种特定的定制。

这个模板文件包含很多像 {% block branding %}{{ title }} 这样的文本。{%{{ 标签是 Django 模板语言的一部分。当 Django 渲染 admin/base_site.html 时,这种模板语言将被求值以生成最终的 HTML 页面,就像我们在《创建公共接口视图》中看到的那样。

请注意,Django 的任何默认管理模板都可以被覆盖。要覆盖一个模板,按照你对 base_site.html 所做的操作进行 —— 从默认目录复制到你的自定义目录,并进行更改。

自定义应用的模板

敏锐的读者可能会问:但是如果 DIRS <TEMPLATES-DIRS> 默认是空的,Django 是如何找到默认的管理模板的呢?答案是,由于 APP_DIRS <TEMPLATES-APP_DIRS> 被设置为 True,Django 会自动在每个应用包内查找一个 templates/ 子目录,用作备用(别忘了 django.contrib.admin 是一个应用)。

我们的投票应用不是很复杂,不需要自定义管理模板。但是如果它变得更复杂,并且需要为其某些功能修改 Django 的标准管理模板,那么修改应用的模板而不是项目的模板会更明智。这样,你可以将投票应用包含在任何新项目中,并确保它能找到所需的自定义模板。

有关 Django 如何查找其模板的更多信息,请参阅《模板加载文档》(template-loading)。

自定义管理索引页面

同样,你可能想要自定义 Django 管理索引页面的外观和感觉。

默认情况下,它会按字母顺序显示 INSTALLED_APPS 中已向管理应用注册的所有应用。你可能想要对布局进行重大更改。毕竟,索引页面可能是管理站点中最重要的页面,它应该易于使用。

要自定义的模板是 admin/index.html。(按照上一节中对 admin/base_site.html 所做的操作 —— 从默认目录复制到你的自定义模板目录)。编辑该文件,你会看到它使用了一个名为 app_list 的模板变量。该变量包含每个已安装的 Django 应用。你可以不使用它,而是以你认为最合适的方式硬编码到特定对象管理页面的链接。

总结

恭喜你!你已经完成了“自定义 Django 管理站点”实验。你可以在 LabEx 中练习更多实验来提升你的技能。