编写你的第一个 Django应用,第2部分

本教程上接 教程第1部分 。我们将继续开发网页投票应用,本教程将主要聚焦在Django自动生成的管理界面部分。

哲学

为你的员工或者客户生成“添加、修改和删除内容的管理站点是一件单调乏味、缺乏创造性的工作。因此,Django集成了按数据模型自动生成管理站点的功能。

Django是在为新闻工作室这样的环境开发而成的,明确分离了“内容发布者站点”和“公共站点”。网站管理员使用发布系统来添加新闻、事件、体育比赛等,这些内容都在公共站点中呈现。Django很好地解决了在给网站管理员创建一个统一的界面来编辑各个应用对应的内容时的各种问题。

管理界面是为网站管理员而建立,普通的浏览者不属于此列。

创建一个管理员账户

首先我们需要创建一个具有登录到管理站点权限的账户,运行下面的命令:

$ python manage.py createsuperuser

输入一个账户名,并按回车键:

Username: admin

然后按提示输入你的邮箱地址:

Email address: admin@example.com

最后一步是输入密码,将会要求输入2次,第2次输入是对第1次输入的确定。

Password: **********
Password (again): *********
Superuser created successfully.

启动开发服务器

默认情况下,Django管理站点是激活的。让我们启动开发服务器来体验一下。

还记得在教程第1部分是怎么启动开发服务器的吗:

$ python manage.py runserver

现在,打开浏览器,在URL的本地域名之后紧接着输入”/admin/” ,像这样: http://127.0.0.1:8000/admin/。你将看到管理站点的登录画面。

Django admin login screen

历来 翻译 在默认情况下都是开启的。

在登录窗口是否用你本地语言来显示,依赖于你浏览器的设置和Django有没有对此语言进行翻译。

你看到的并不是预期的结果?

如果在登录窗口你看到的是报错信息:

ImportError at /admin/
cannot import name patterns
...

那么你可能使用的Django版本号与本教程采用的版本号不一样,要么切换回旧的教程、要么更新你的Django版本。

进入管理站点

现在,使用超级管理员账户登录到管理站点,将呈现管理界面如下:

Django admin index page

你会看到一些可编辑内容:组和用户。它们由 django.contrib.auth 提供,而登录认证框架则是由Django直接提供。

调整poll投票应用在管理界面中为可编辑状态

默认情况下,在管理站点不会看到你的应用,当然也不能编辑修改了。

只须调整一个地方就能搞定: 让Djang的管理app知道我们的 Question 对象做了一个管理接口。实现起来就是打开 polls/admin.py 文件,添加如下内容:

polls/admin.py
from django.contrib import admin

from .models import Question

admin.site.register(Question)

探索免费的管理功能

现在我们已经注册了 Question, Django知道应该在管理页面中显示该对象的相关操作功能了:

Django admin index page, now with polls displayed

点击 “Questions”,进入到question的 ”修改列表“ 页面。该页面将显示储存在数据库中的所有question对象,你可以选择一个进行修改。下面我们选择一个在上一节教程中添加的question对象 “What’s up?”进行操作:

Polls change list page

点击 “What’s up?” 进入编辑状态:

Editing form for question object

需要说明一下:

  • 提交表单是由Django按照 Question 模型来自动生成的。
  • 模型的属性类型的区别(DateTimeField, CharField) 会反应到HTML的输入控件中。 属性的每一个类型都清楚在Django管理界面中怎么展现自己。
  • 每一个 DateTimeField 类型都有一个用JavaScript 方式生成的快捷工具。日期类型有一个 “今天” 快捷方式,会弹出日历框, 时间类型有一个 “现在” 快捷方式,会弹出时间选择框。

在页面底部有一组选项按钮:

  • 保存 – 保存所做的修改,然后返回到该类对象的修改列表页面。
  • 保存并继续编辑 – 保存所做的修改,继续停留在本页面,可继续修改操作。
  • 保存并添加另一个 – 保存所做的修改,然后加载一个该类对象的空白表单来新建对象。
  • 删除 – 显示一个确认删除的页面。

如果对象属性 “Date published”的值与你在教程1中创建时的时间值不一致,很可能是你忘记了在 TIME_ZONE 中设置正确的时区。如果是的话,立即修改,重新访问时就会看到一致的时间了。

点击 “Date published” 栏的 “今天(Today)” 和 “现在(Now)” 快捷按键,然后再点击 ”保存并继续编辑“。 之后再点击左上方的 “历史(History)“ 你会看到Dja管理站点记录了某个用户在某个时间点对某个对象进行了所有的修改操作。

History page for question object

定制管理站点中的表单

花几分钟时间来梳理一下你并没有编写过的那些代码: 在对 Question 模型进行注册 admin.site.register(Question) 后,Django就自动构建了一系列用于管理对象的默认的表单格式。在你想定制一下管理站点中的表单形式和它的工作流程时,需要在注册模型对象到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() 的第2个参数。– 不管什么时候,只要你修改了某个模型对象的管理对象,都需要这一步。

上面所做的操作,将属性 ”发布日期(Publication date)“显示位置放在了属性 ”属性名(Question)“之前:

Fields have been reordered

当然并不仅限于上面修改的2个属性,在管理表单中有数十项属性都可以修改,选择一个直观的次序来显示是一个非常适用的一个细节。

谈到表单中的数据十项属性,你也可能想将其分组展现到表单中:

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 中每个元组tuple的第一部分是显示的组标题,看起来像这样:

Form has fieldsets now

你可以为每一组分配一个可选的HTML类。Django提供了一个 "collapse" 类能显示一些格外的折叠样式来作为初始化样式。Django provides a "collapse" class that displays a particular fieldset initially collapsed. 在你表单包含了一组属性显得太长时,它的作用尤为明显(用这个来将相关项折叠起来)

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'], 'classes': ['collapse']}),
    ]

admin.site.register(Question, QuestionAdmin)
Fieldset is initially collapsed

添加关联对象

好的, 我们的Question管理页面弄好了。但是一个 question 可以关联多个 choices,但是管理页面并没有显示关联的choices。

这里有2种方法可以解决这个问题。第1种就是像刚才注册 Question 一样来对 Choice 注册到Django的管理应用中。方法很简单:

polls/admin.py
from django.contrib import admin

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

现在 “Choices” 就成为Django管理界面中一个有效的选项了,其界面如下:

Choice admin page

在表单中, “Question” 属性是作为一个选择框,选择范围则是包含于数据库中所有 question 对象, Django会按照模型中 ForeignKey 属于外键引用,而自动在管理界面中使用 <select> 选择框。在我们当前的例子中,仅仅创建了一个 question 对象。

另外,在 “Question” 旁边有一个名为 ”新添加一个” 按钮:当某个对象包含了一个通过外键引用的另一个对象(关联对象),在选择引用的关联对象旁边都会有一个快捷按键 “ 新添加一个”,用于快速添加关联对象。当你点击 “新添加一个” 按键时,会弹出一个 添加question 窗口,添加好新的question对象后,点击 “保存” ,新添加的对象就会保存到数据库中,并且自动被选作为 “Add choice” 页面中 Choice 对象的关联对象。

但是,用这种方法来添加对象,其效率真的很低。我推荐另一种更好的方式:在新建对象时,直接添加一组关联对象,看一下怎么来实现:

删除原来的对象注册代码,然后按以下方式,重新编码注册代码:

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)

意思是:被关联对象 choice 的新建表单内嵌到 Question 管理页面中了。默认情况下提供的是3个关联对象的新建表单。点击 “添加question” 进入页面中了解它是如何工作的:

This tells Django: “Choice objects are edited on the Question admin page. By default, provide enough fields for 3 choices.”

Load the “Add question” page to see how that looks:

Add question page now has choices on it

它是这样运转的:在 Question 页面中包含有1组扩展表单:同时新建3个关联对象 choice ,而且回到对象列表页面中,每一个历史对象都会伴随显示3个相关联对象。

It works like this: There are three slots for related Choices – as specified by extra – and each time you come back to the “Change” page for an already-created object, you get another three extra slots.

在包含新建3个关联对象表的下面,还有一个 新建另一个Choice 按钮,点击后,就会出现待新建的第4个关联对象表,点击该表右上方的X图标可以删除该新建关联对象表,但不能删除默认的前3个新建对象表。新建关联对象表显示如下:

Additional slot added dynamically

但是有一个小问题:上面新建关联对象表内嵌在新建对象表单中后,会占用屏幕上更多的显示区域,所以,Django还提供了另一种显示内嵌关联对象的方式,只需要修改代码ChoiceInline为:

the ChoiceInline declaration to read:

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

使用 TabularInline 代替 StackedInline 后,关联对象新建表将按表格方式,排列得更紧凑:

Add question page now has more compact choices

另外,点击下方的 “新建另一个” 按钮后,在该新添加表格行的最后一列 “删除” 栏会显示 “删除按钮X”,点击这个按钮即删除之前添加的关联对象行。

Note that there is an extra “Delete?” column that allows removing rows added using the “Add Another Choice” button and rows that have already been saved.

自定义管理页面中的修改列表

现在 Question管理页面看起来不错,下面我们将对 修改列表 页面作一些调整。当前系统中仅有一项 questions,其 修改列表 页面现在看起来如下:

Polls change list page

默认情况下,Django将显示每一个对象的 str()。不过显示对象的一些属性信息会显示更有意义。要实现这个,需要使用 list_display 选项,该选项是用一个元组,元组中每一项显示一个对象属性名。在修改列表页面中显示对象的属性栏如下:

By default, Django displays the str() of each object. But sometimes it’d be more helpful if we could display individual fields. To do that, use the list_display admin option, which is a tuple of field names to display, as columns, on the change list page for the object:

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

可以将教程1中自定义的方法 was_published_recently 添加到上述元组,达到更好的表现形式:

Just for good measure, let’s also include the was_published_recently custom method from Tutorial 1:

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

现在,question 修改列表页面看起来是下面这个样子:

Polls change list page, updated

可以点击标题栏将按该列表栏的值进行排序 – 但是 was_published_recently 栏除外,因为该自定义方法不支持按值排序。另外,该方法的标题栏是以方法名来显示,但默读情况下会用空格替换其中的下划线,每一行也是用该方法返回结果的字符串形式来显示。

You can click on the column headers to sort by those values – except in the case of the was_published_recently header, because sorting by the output of an arbitrary method is not supported. Also note that the column header for was_published_recently is, by default, the name of the method (with underscores replaced with spaces), and that each line contains the string representation of the output.

你也可以为该方法添加一些属性,以达到更好的展现效果:

polls/models.py
class Question(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
    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’ 来显示排序后的结果:

Polls change list page, updated

各种类型的过滤显示结果依赖于过滤的属性对象类型。因为 pub_dateDateTimeField 时间日期类型,所以Django能够提供的过滤选项有: “任意日期(Any date)”, “今天(Today)”, “过去7天(Past 7 days)”, “本月(THis month)”, “今年(This year)”。

进展正在顺利进行中。让我们继续添加使其偹搜索的能力:

search_fields = ['question_text']

这样就在修改列表的上方添加好了一个搜索框。当你转入一个搜索条件时,Django就按此条件来搜索 question_text 属性。你可以使用多个搜索条件 – 因为它使用的是数据库中的 LIKE 查询语句,限制搜索条件的数量为一个合理的数字将让数据库中的查询变得更加容易。

现在是将修改列表页面的分页功能进行说明的最好时机。默认情况该页面将显示100 条列表。 Change list pagination, search boxes, filters, date-hierarchies, and column-header-ordering 这些功能可以让你随意组合使用。

定制管理界面的外观

很显然,在每个页面顶部都做一个 “Django管理” 标签是很荒唐的,其实只是用一个文本占位符实现的。

Clearly, having “Django administration” at the top of each admin page is ridiculous. It’s just placeholder text.

通过Django的模板系统可以很方便的修改、定制页面外面。Django管理站点是包含在Django的,它的各种接口也是通过Django自己的模板系统来实现的。

That’s easy to change, though, using Django’s template system. The Django admin is powered by Django itself, and its interfaces use Django’s own template system.

定制项目模板

在项目目录下建立一个 templates 子目录(和manage.py文件处于同一级目录)。模板可以放在Django能访问到的任何文件系统下(Django可运行于与你其它服务器相同的环境下)。但是将模板存放在项目目录下是很好的一种方式:整洁、清晰、明了。

打开项目配置文件 mysite/settings.py 编辑 TEMPLATESDIRS 的配置如下:

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加载模板时就会按该列表中的路径来查找。

下面在模板目录 templates 中建立子目录 admin ,然后从Django管理站点的模板目录(django/contrib/admin/templates)中复制 admin/base_site.html 文件在新建的目录中。

Django源码文件的存放路径?

如果你很难找到Django源码文件的存放路径,下面的命令可以帮助你找到:

$ python -c "
import sys
sys.path = sys.path[1:]
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 属性来比较方便的进行定制。

We use this approach to teach you how to override templates. In an actual project, you would probably use the django.contrib.admin.AdminSite.site_header attribute to more easily make this particular customization.

模板文件中包含了很多像 {% block branding %}{{ title }}。其中 {%{{ 标签属于Django模板语言部分,当Django渲染 admin/base_site.html 时,该模板语言就会生成最终的HTML页面。不要担心你现在还不会弄模板 – 我们将在教程第3部分中详细讲解Django模板语言的使用。

记住在Django的默认管理站点中,模板任何部分都可以被覆盖。想要覆盖模板时,像刚才操作 base_site.html 时一样 – 从管理站点的模板目录中复制到你指定的目录下,然后修改它。

定制你的应用模板

聪明的读者会发现,在项目配置文件中 DIRS 默认是空的,但Django是如何查找到默认的管理站点模板文件的? 答案是:默认配置中 APP_DIRS 设置是 True,Django就会自动从每一个应用包下的子目录 templates/ 中查找模板文件,这即是Django的默认动作(不要忘记 django.contrib.admin 也属于一个应用)。

我们的投票应用非常简单,无需定制它的管理模板。不过当它的功能越来越复杂、默认的管理模板难以实现想要的功能时,也许就是修改应用模板的时机。在你把投票应用集成到任何一个新项目中时,就会发现应用模板的定制修改能够让你很省心、一劳永逸。

关于Django查找模板的内容请查看 模板加载文档

定制管理站点的首页

通过简单的对管理站点的首页进行定制修改,可以让你有个大概了解。

默认情况下,在 INSTALLED_APPS 中的应用都会注册到Django的管理应用中,是按字母顺序依次注册。你很可能想要对其界面外观作些大的调整,总之,首页是管理站点众多页面中最重要的一个,也应该方便易用。

定制首页要修改的是 admin/index.html。 和上一节中定制管理站点模板一样 – 从其默认目录中复制到指定目录下,编辑该文件:模板中有个变量 app_list 包含的是所有已安装的应用。现在用硬编码方式来修改,按你的喜好,修改其指向应用对应的管理页面。 再一次提醒你,别担心现在还不理解模板语言 – 我们在接下来的第3部分教程中就会讲解。

当你对管理站点比较了解后,请开始投票应用的 教程第3部分 吧。