Part7 定制Django的自动生成式管理站点
这篇教程从第6节结束的地方开始,我们继续开发Web投票应用,我们将注意力放在定制Django的自动生成式管理站点,我们在第2节的时候用过一次。
定制admin表单
通过使用 admin.site.register(Question)
来注册Question
,Django可以组织一个默认的表格展示形式。很常见的情况是,你希望定制admin表单看起来的样子以及它的工作方式。你可以通过在注册对象的时候通过告知Django相关的选项来完成这样的定制。
让我们来看一下是怎么做的,通过重新调整编辑表单上的字段顺序。使用下面的代码替换admin.site.register(Question)
:
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”字段的前面,像下面这样:
image.png
只有两个字段的话不是特别明显,但是对于由很多字段的管理表单的话,选择一个直观的顺序是一个非常重要且有用的细节。
说到有很多字段的表单,你可能会想将表单切割成字段集合:
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)
在字段集合里的每一个元组的第一个元素是字段集合的标题,下面是我们的表单看起来的样子。
image.png添加关联对象
好了,我们现在以及有了我们的Question管理页面,但是一个Question有多个Choice,并且管理页面并没有显示Choice。
有两种方式来解决这种问题,第一种是使用admin来注册Choice,就像我们刚才注册Question一样,这非常简单:
from django.contrib import admin
from .models import Choice, Question
# ...
admin.site.register(Choice)
然后再Django管理页面就能看到Choice选项了,"Add choice"表单看起来像下面这样:
image.png
在这个表单里,"Question"字段是一个选择框,里面包含了数据库里的所有Question。Django知道在管理页面将一个ForeignKey当作一个选择框。在我们的例子里,只有一个question。
也要注意,在“Question”旁边的“Add another”按钮,每个带有外键管理到其他对象的对象都会自动生成这个按钮。当你点击“Add another”的时候,你会得到一个弹出式窗口,关联到“Add Question”表单。如果你在这个表单里添加一个问题,然后点击“Save”,Django会自动把这个question保存到数据库,并且动态的将它也添加到你刚才看到的“Add Choice”表单的选择框里。
但是,实际上,这样添加Choice对象到系统里,确实是一种低效的方式。在你添加Question对象的时候直接添加很多Choice,这样的方式会更好,让我们来试试。
移除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']}),
('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
]
inlines = [ChoiceInline]
admin.site.register(Question, QuestionAdmin)
这告诉Django:“要在Question管理页面编辑Choice对象。默认情况下,为3个choice提供足够的字段。”
加载"Add question"页面,看一下它的样式。
image.png
它看起来像这样:关联的Choices有3个槽——通过Choice的extra设置的数量。每次你返回到创建好的Question的“Change”页面,你都会看到3个额外的槽。
在当前三个槽的结尾处,你会看到一个“Add another Choice”链接,如果你点击它,会添加另外一个新的槽。如果你想移除已经添加的槽,你可以点击已经添加的槽的右上方的X来删除它。下面的图片显示一个已经添加的槽:
image.png
但是还有一个小问题就是,它需要很多屏幕空间来显示已经创建好的,和Question相关联的Choice对象的所有字段。基于这个原因,Django提供一个表格形式来显示内联的对象。你只需要修改ChoiceInline的声明类型。:
class ChoiceInline(admin.TabularInline):
#...
使用TabularInline(替换StackedInline),关联的对象会以更紧凑,基于表格的格式来显示:
image.png
注意,有一个额外的"Delete"列,用来删除通过"Add Another Choice"按钮添加的行,或者已经保存的行。
定制管理页面的修改列表
现在Question的管理页面看起来很棒,现在我们来对“change list”页面做一些调整。这个页面是用来显示系统里的所有questions的页面。
下面是它现在看起来的样子:
image.png
默认情况下,Django显示每个对象的str(),但是有些时候我们可以显示不同的字段的话可能更有帮助。要实现这样的功能,我们使用list_display这个管理选项,这是一个用来在对象的“change list”页面要显示的字段名称组成的元组。
class QuestionAdmin(admin.ModelAdmin):
# ...
list_display = ('question_text', 'pub_date')
为了更方便,我们把第2节里的was_published_recently()方法也加上:
class QuestionAdmin(admin.ModelAdmin):
# ...
list_display = ('question_text', 'pub_date', 'was_published_recently')
现在question的修改页面看起来像下面的样子:
image.png
你可以点击列的头部来通过值排序——除了was_published_recently头部, 因为通过一个不确定的方法的输出值来排序,目前还是不支持的.也要注意,was_published_recently这一列的头部默认情况下就是方法的名称(使用下划线替换空格),并且每一行包含方法输出的字符串格式.
你可以通过这个方法(在polls/models里)的一些属性来提高页面的可读性,像下面这样:
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来过滤Question. 把下面的行添加到QuestionAdmin里:
list_filter = ['pub_date']
这行代码提供了一个"Filter"侧边栏,让用户可以通过pub_date字段过滤修改列表,如下所示:
过滤器的类型依赖于你要过滤的字段的类型,因为pub_date是一个DateTimeField,Django知道给出一个相近的选项:"Any date", "Today", "Past 7 days", "This Month", "This year".
这种筛选方式非常好,我们再添加一些搜索的兼容性:
search_fields = ['question_text']
这行代码会再修改列表顶部添加一个搜索框.当有人输入搜索选项的时候,Django会搜索question_text字段。你可以使用任意多个你想要的字段,但是因为在后台使用的是LIKE查询,所以限制了搜索字段的数量到合理的值,让数据库搜索的时候更简单。
修改列表也给了你一个免费的分页功能,默认的是显示一页100个选项。Change_list_pagination,search_boxes,filters,date-hierarchies和column-headerr-ordering会像你认为的那样工作(个人理解是根据他们函数的名称那样工作,比如filter就是用来过滤)。
定制管理页面的外观和感觉
显然,在每个管理页面的顶端都有"Django administration"很扯淡,它们仅仅只是占位符内容。
但是直接使用Django的模板系统,这些也很容易修改。Django的管理页面是它自己支持的,并且它的接口使用Django自己的模板系统。
定制你自己的项目的模板
直接在你的项目目录(包含manage.py的目录)里创建一个templates目录,模板可以在你的文件系统任何位置,只要Django可以访问到就行。(Django可以用你服务器上的任何用户来运行),但是,保证模板在你的项目里,是一个指的遵守的良好规则。
打开你的设置文件(mysite/settings.py,记住)然后添加一个DIRS选项到TEMPLATES设置里:
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)。我们会在重复使用app教程里讨论我们为什么要这样做。
现在在templates里面创建一个叫做admin的目录,然后在Django它自己的(django/contrib/admin/templates)的源代码的Django默认管理模板目录里复制admin/base_site.html模板到admin目录下面。
Django源文件在哪?
如果在你的系统上查找Django源文件比较困难,运行下面的命令:
$ python -c "import django;
print(django.__path__)"
然后编辑base_site.html文件,将{{ 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页面,就像我们在第三节里看的那样。
要注意,任何Django的默认管理模板都能被覆盖,要覆盖一个模板,只需要像你在base_site.html里做的那样。把它从默认目录里复制出来,放到你的定制目录里去,然后做修改。
定制你的应用的模板
精明的读者可能会问:如果默认情况下DIRS是空的,Django怎么找到默认的管理页面?回答是,只要APP_DIRS设置为True,Django会自动在每一个应用包里找templates子目录,作为一个返回值使用(不要忘了django.contrib.admin是一个应用)。
我们的投票应用不是非常复杂,并且不需要定制admin 模板,但是如果它成长得更加复杂,为了实现它的一些功能,需要修改Django的标准管理模板。直接修改应用的模板比修改项目的模板更明智。通过这种方式,你可以再任何心的项目里包含投票应用,并且保证它能够找到自己定制后的模板。
Django怎么查找它的模板的更多细节,请查看模板加载文档。
定制管理首页
在一些类似的说明中,你可能会想定制Django管理首页的外观和感觉。
默认情况下,它用字母排序来显示所有在INSTALLED_APPS里,且使用admin应用注册了的应用。你可能想对布局进行大的修改,毕竟,首页可能是管理页面里最重要的页面,它应该易于使用。
需要定制的模板是admin/index.html(和前面部分里对admin/base_site.html做的一样— 把它从默认的目录里复制到你的定制模板目录里)。编辑这个文件,然后你会看到它使用一个叫做app_list的模板变量,这个变量包含每个安装了的Django app。为了替换这个,你可以使用硬编码的链接指到特定对象的管理页面,用你自己觉得最好的方式来做。
下一步做什么?
教程的初始部分到这就结束了,与此同时,你可能想在where to go from here里看到一些提示。
如果你对Python打包很熟悉,且对学习怎么把投票应用转变为一个可重复使用的app非常感兴趣,可以看高级教程:怎么写重复使用的app。