编写您的第一个Django应用, 第7部分

本教程上接 教程 6。我们将继续完成网页投票应用,并着重讲解 如何用 Django 自动生成管理网站,我们曾在 教程 2 中初次探索过。

自定义管理表单

通过 admin.site.register(Question) 注册 Question 后,Django 可以自动构建一个 默认的表单。通常,您想自定义管理表单的外观和功能。您可以在注册时通过配置来实现。

现在先来试试排序表单上的字段。将 admin.site.register(Question) 所在行替换为:

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()。 而且这种操作在任何时候都可以进行。

经过上面更改,“Publication date”字段会在“Question”字段之前:

Fields have been reordered

目前的表单只有两个字段可能看不出什么,但是对于字段很多的表单,设计一个直观合理的排序方式非常重要。

并且在字段数据很多时,还可以将表单分割成多个字段的集合:

polls/admin.py
from django.contrib import admin

from .models import Question


class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question_text']}),
        ('Date information', {'fields': ['pub_date']}),
    ]

admin.site.register(Question, QuestionAdmin)

fieldsets 中的每个元组的第一个元素是字段集合的标题。 它让表单看起来像下面的样子:

Form has fieldsets now

添加关联对象

现在我们有了问题管理页面,但是一个 Question 有多个 Choice,而管理页面并没有显示。

暂时。

现在有两个方法可以解决这个问题:一是就像刚刚 Question 一样也将 Choice 注册到管理界面。 这简单:

polls/admin.py
from django.contrib import admin

from .models import Choice, Question
# ...
admin.site.register(Choice)

现在“Choice”也可以管理页面看到了,其中“Add choice”表单应该类似这样:

Choice admin page

在这个表单中,“Question”字段是一个选择框,包含了当前数据库中每个问题实例。Django 知道将 ForeignKey 展示为一个 <select> 框。在我们的例子中,目前 只有一个问题对象存在。

请注意“Add Another”连接到“Question”。每一个包含 外键(ForeignKey) 关系的对象都会有这个。 当您点击“Add Another”时,会弹出一个“Add question”的表单窗口。如果在窗口中添加了问题并点击“Save”, Django 会将问题保存在数据库中,并动态地把它添加为“Add choice”表单的选项。

但是,实话说,这种将 Choice 对象添加到系统的方式效率不高。如果在创建 Question 对象时 直接添加一些 Choice 则会更好。让我们实现它。

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

polls/admin.py
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']}),
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
    ]
    inlines = [ChoiceInline]

admin.site.register(Question, QuestionAdmin)

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

载入“Add question”页面来看变成了什么样:

Add question page now has choices on it

它的工作原理如下:有3个插槽用于相关的选择 —— 由 extra 指定 —— 每当您回到已创建的对象的 “Change” 页面,您会再获得三个额外的插槽。

在三个当前插槽的最后,您将找到一个“Add another Choice”的链接。如果您点击它,将会添加一个新插槽。 如果要删除添加的插槽,可以单击添加插槽右上方的 X。请注意,您不能删除原来的三个插槽。 此图片显示所添加的插槽:

Additional slot added dynamically

不过还有个小问题,它需要大量的屏幕空间来显示所有的用于输入相关 Choice 对象的字段。为此, Django 提供了一个显示内联相关对象的表格方式;您只需要改变 ChoiceInline 声明如下:

polls/admin.py
class ChoiceInline(admin.TabularInline):
    #...

使用 TabularInline (而不是 StackedInline ),相关的对象将以一种更紧凑的 表格形式显示出来:

现在添加问题页面有更紧凑的选择

请注意,有一个额外的“Delete?”列,允许删除使用“Add Another Choice”按钮添加的行和已保存的行。

自定义管理更改列表

现在 Question 的管理页面看起来已经差不多了,下面来看看“更改列表”页面,也就是显示系统中 所有问题的页面。

请看下图:

Polls change list page

Django 默认显示每个对象的 str() 内容。但如果我们能显示个别字段或许会更有帮助。要做到 这一点,可以使用 list_display 管理选项, 这是一个要在对象的更改列表页面上显示作为列的字段名称的元组:

polls/admin.py
class QuestionAdmin(admin.ModelAdmin):
    # ...
    list_display = ('question_text', 'pub_date')

为了有好的效果,我们还加入了 教程 2 中的 was_published_recently() 方法:

polls/admin.py
class QuestionAdmin(admin.ModelAdmin):
    # ...
    list_display = ('question_text', 'pub_date', 'was_published_recently')

现在问题更改列表页面看起来像这样:

投票更改页,更新

您可以点击列标题按照这些值进行排序,但是是 was_published_recently 标题不行,因为 不支持按任意方法的输出进行排序。另请注意, 默认情况下 was_published_recently 的列标题 是方法名称(用空格替换下划线),并且每一行都包含输出的字符串表示形式。

您可以通过给出该方法(在 polls/models.py 中)中的几个属性来改进,如下所示:

polls/models.py
class Question(models.Model):
    # ...
    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now
    was_published_recently.admin_order_field = 'pub_date'
    was_published_recently.boolean = True
    was_published_recently.short_description = 'Published recently?'

有关这些方法属性的更多信息,请参阅 list_display

再次编辑您的 polls/admin.py 文件,并为 Question 更改列表页面增加一个改进: 使用 list_filter 过滤器。将以下行添加到 QuestionAdmin:

list_filter = ['pub_date']

这会增加一个“过滤器”侧边栏,让人们通过 pub_date 字段过滤变更列表:

投票更改页,更新

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

这个态势不错。让我们添加一些搜索功能:

search_fields = ['question_text']

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

现在是个很好的时间去关注更改列表给你自由分页。默认是每页显示 100 个项目。 更改列表分页搜索框过滤器日期层次列标题排序 都能如你所愿地工作。

自定义管理外观和感觉

显然,每个管理页面顶部的“Django 管理”都是没用的。这只是占位符文本。

不过使用 Django 的模板系统就很容易改变。Django 管理由 Django 本身提供支持,其界面使用 Django 自己的模板系统。

自定义 项目的 模板

在项目目录(包含 manage.py 的目录)中创建一个 templates 目录。模板可以是在 Django 可以访问的文件系统的任何地方。(Django 可以以您的服务器运行的任何用户运行。) 但是,将模板保留在项目中是一个很好的惯例。

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

mysite/settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(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 是加载 Django 模板时要检查的文件系统目录的列表; 它是一个搜索路径。

组织模板

就像静态文件一样,我们 可以 将所有的模板放在一个大的模板目录中,并且工作得很好。 但是,属于特定应用程序的模板应该放在该应用程序的模板目录(例如 polls/templates )中, 而不是项目的 templates 中。我们将在 可复用应用教程 中探讨 为什么 我们这样做的更多细节。

现在在 templates 里创建一个名为 admin 的目录,并从 Django 自身源码的默认管理 模板目录中复制模板 admin/base_site.html 到那个目录。

Django源文件在哪里?

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

$ python -c "import django; print(django.__path__)"

然后,只需编辑该文件,并将 {{ site_header|default:_('Django administration') }} (包括花括号)替换为您的网站名称。最终您应该得到这样一份代码:

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

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

此模板文件包含许多文本,如 {% block branding %}{{ title }}{%{{ 标签是 Django 模板语言的一部分。当 Django 渲染 admin/base_site.html 时, 这个模板语言将被应用,以产生最终的 HTML 页面,就像我们在 教程 3 中看到的那样。

请注意,Django 的任何默认管理模板都可以被覆盖。要覆盖一个模板,只需要使用 base_site.html 做同样的事情 —— 将它从默认目录复制到您的自定义目录中并进行更改。

自定义 应用程序的 模板

聪明的读者会问:如果 DIRS 默认为空,Django 如何能找到 默认的管理模板?答案是,由于 APP_DIRS 设置为 True,Django 会自动在每个应用程序包中查找 templates/ 的子目录,作为备用(不要忘了不要忘了 django.contrib.admin 是一个应用程序)。

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

参见 template loading documentation 了解更多关于 Django 如何找到模板的信息。

自定义管理首页

在类似的情况下,您可能想要自定义 Django 管理首页的外观。

默认情况下,它会按照字母顺序显示所有 INSTALLED_APPS 中,已经在管理应用程序中注册的应用。您可能需要对布局进行重大更改。毕竟,首页可能是管理页面中最重要的,它应该要很容易被 使用。

要自定义的模板是 admin/index.html。(与上节中的 admin/base_site.html 相似 —— 将其从默认目录复制到您的自定义模板目录)。 编辑文件,你会看到它使用一个名为 app_list 的模板变量。该变量包含每个已安装的 Django 应用。您可以以任何您认为最佳的方式 将链接硬编码到特定于对象的管理页面,而不是使用它。

下一步?

初学者教程结束于此。在此期间,您可能需要在 接下来做什么 中查看 一些建议。

如果您熟悉 Python 打包,并有兴趣了解如何将投票转化为“可重用的应用”,请查看 高级教程:如何编写可重用的应用