第七章

这篇教程承接?教程第 6 部分?结束的地方。我们继续修改在线投票应用,这次我们专注于自定义我们在?教程第 2 部分?初见过的 Django 自动生成后台的过程。

自定义后台表单?

通过?admin.site.register(Question)?注册?Question?模型,Django 能够构建一个默认的表单用于展示。通常来说,你期望能自定义表单的外观和工作方式。你可以在注册模型时将这些设置告诉 Django。

让我们通过重排列表单上的字段来看看它是怎么工作的。用以下内容替换?admin.site.register(Question):

polls/admin.py

fromdjango.contribimportadminfrom.modelsimportQuestionclassQuestionAdmin(admin.ModelAdmin):fields=['pub_date','question_text']admin.site.register(Question,QuestionAdmin)

你需要遵循以下流程——创建一个模型后台类,接着将其作为第二个参数传给?admin.site.register()?——在你需要修改模型的后台管理选项时这么做。

以上修改使得 "Publication date" 字段显示在 "Question" 字段之前:

这在只有两个字段时显得没啥卵用,但对于拥有数十个字段的表单来说,为表单选择一个直观的排序方法就显得你的针很细了。

说到拥有数十个字段的表单,你可能更期望将表单分为几个字段集:

polls/admin.py

fromdjango.contribimportadminfrom.modelsimportQuestionclassQuestionAdmin(admin.ModelAdmin):fieldsets=[(None,{'fields':['question_text']}),('Date information',{'fields':['pub_date']}),]admin.site.register(Question,QuestionAdmin)

fieldsets?元组中的第一个元素是字段集的标题。以下是我们的表单现在的样子:

添加关联的对象?

好了,现在我们有了投票的后台页。不过,一个?Question?有多个?Choice,但后台页却没有显示多个选项。

好了。

有两个方法可以解决这个问题。第一个就是仿照我们向后台注册?Question?一样注册?Choice?。这很简单:

polls/admin.py

fromdjango.contribimportadminfrom.modelsimportChoice,Question# ...admin.site.register(Choice)

现在 "Choices" 在 Django 后台页中是一个可用的选项了?!疤砑友∠睢钡谋淼タ雌鹄聪裾庋?/p>

在这个表单中,"Question" 字段是一个包含数据库中所有投票的选择框。Django 知道要将?ForeignKey?在后台中以选择框?<select>?的形式展示。此时,我们只有一个投票。

同时也注意下 "Question" 旁边的“添加”按钮。每个使用?ForeignKey?关联到另一个对象的对象会自动获得这个功能。当你点击“添加”按钮时,你会见到一个包含“添加投票”的表单。如果你在这个弹出框中添加了一个投票,并点击了“保存”,Django 会将其保存至数据库,并动态地在你正在查看的“添加选项”表单中选中它。

不过,这是一种很低效地添加“选项”的方法。更好的办法是在你创建“投票”对象时直接添加好几个选项。让我们实现它。

移除调用?register()?注册?Choice?模型的代码。随后,像这样修改?Question?的注册代码:

polls/admin.py

fromdjango.contribimportadminfrom.modelsimportChoice,QuestionclassChoiceInline(admin.StackedInline):model=Choiceextra=3classQuestionAdmin(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 个足够的选项字段?!?/p>

加载“添加投票”页面来看看它长啥样:

它看起来像这样:有三个关联的选项插槽——由?extra?定义,且每次你返回任意已创建的对象的“修改”页面时,你会见到三个新的插槽。

在三个插槽的末端,你会看到一个“添加新选项”的按钮。如果你单击它,一个新的插槽会被添加。如果你想移除已有的插槽,可以点击插槽右上角的X。注意,你不能移除原始的 3 个插槽。以下图片展示了一个已添加的插槽:

不过,仍然有点小问题。它占据了大量的屏幕区域来显示所有关联的?Choice?对象的字段。对于这个问题,Django 提供了一种表格式的单行显示关联对象的方法。你只需按如下形式修改?ChoiceInline?申明:

polls/admin.py

classChoiceInline(admin.TabularInline):#...

通过?TabularInline``(替代?``StackedInline?),关联对象以一种表格式的方式展示,显得更加紧凑:

注意这里有一个额外的“删除?”列,这允许移除通过“添加新选项”按钮添加的,或是已被保存的行。

自定义后台更改列表?

现在投票的后台页看起来很不错,让我们对“更改列表”页面进行一些调整——改成一个能展示系统中所有投票的页面。

以下是它此时的外观:

默认情况下,Django 显示每个对象的?str()?返回的值。但有时如果我们能够显示单个字段,它会更有帮助。为此,使用?list_display?后台选项,它是一个包含要显示的字段名的元组,在更改列表页中以列的形式展示这个对象:

polls/admin.py

classQuestionAdmin(admin.ModelAdmin):# ...list_display=('question_text','pub_date')

为了更好用,让我们也包含?教程第 2 部分?中的?was_published_recently()?方法:

polls/admin.py

classQuestionAdmin(admin.ModelAdmin):# ...list_display=('question_text','pub_date','was_published_recently')

现在修改投票的列表页看起来像这样:

你可以点击列标题来对这些行进行排序——除了?was_published_recently?这个列,因为没有实现排序方法。顺便看下这个列的标题?was_published_recently,默认就是方法名(用空格替换下划线),该列的每行都以字符串形式展示出处。

你可以通过给这个方法(在?polls/models.py?中)一些属性来达到优化的目的,像这样:

polls/models.py

classQuestion(models.Model):# ...defwas_published_recently(self):now=timezone.now()returnnow-datetime.timedelta(days=1)<=self.pub_date<=nowwas_published_recently.admin_order_field='pub_date'was_published_recently.boolean=Truewas_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 自己的模板系统。

自定义你的?工程的?模板?

在你的工程目录(指包含?manage.py?的那个文件夹)内创建一个名为?templates?的目录。模板可放在你系统中任何 Django 能找到的位置。(谁启动了 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 默认模板的目录(django/contrib/admin/templates)内的模板文件?admin/base_site.html?复制到这个目录内。

Django 的源文件在哪里?

如果你不知道 Django 源码在你系统的哪个位置,运行以下命令:

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

接着,用你站点的名字替换文件内的?``{{ site_header|default:_('Django administration') }}``(包含大括号)。完成后,你应该看到如下代码:

{%blockbranding%}<h1id="site-name"><ahref="{%url'admin:index'%}">Polls Administration</a></h1>{%endblock%}

我们会用这个方法来教你复写模板。在一个实际工程中,你可能更期望使用?django.contrib.admin.AdminSite.site_header?来进行简单的定制。

这个模板文件包含很多类似?{%?block?branding?%}?和?{{?title?}}?的文本。?{%?和?{{?标签是 Django 模板语言的一部分。当 Django 渲染?admin/base_site.html?时,这个模板语言会被求值,生成最终的网页,就像我们在?教程第 3 部分?所学的一样。

注意,所有的 Django 默认后台模板均可被复写。若要复写模板,像你修改?base_site.html?一样修改其它文件——先将其从默认目录中拷贝到你的自定义目录,再做修改。

自定义你?应用的?模板?

机智的同学可能会问:?DIRS?默认是空的,Django 是怎么找到默认的后台模板的?因为?APP_DIRS?被置为?True,Django 会自动在每个应用包内递归查找?templates/?子目录(不要忘了?django.contrib.admin?也是一个应用)。

我们的投票应用不是非常复杂,所以无需自定义后台模板。不过,如果它变的更加复杂,需要修改 Django 的标准后台模板功能时,修改?应用?的模板会比?工程?的更加明智。这样,在其它工程包含这个投票应用时,可以确保它总是能找到需要的自定义模板文件。

更多关于 Django 如何查找模板的文档,参见?加载模板文档

自定义后台主页?

在类似的说明中,你可能想要自定义 Django 后台索引页的外观。

默认情况下,它展示了所有配置在?INSTALLED_APPS?中,已通过后台应用注册,按拼音排序的应用。你可能想对这个页面的布局做重大的修改。毕竟,索引页是后台的重要页面,它应该便于使用。

需要自定义的模板是?admin/index.html。(像上一节修改?admin/base_site.html?那样修改此文件——从默认目录中拷贝此文件至自定义模板目录)。打开此文件,你将看到它使用了一个叫做?app_list?的模板变量。这个变量包含了每个安装的 Django 应用。你可以用任何你期望的硬编码链接(链接至特定对象的管理页)替代使用这个变量。

接下来要做什么??

初学者教程到这就结束了。随后,你可能想阅读?下一步看什么,看看下一步能做什么。

如果你很熟悉 Python 打包,且对学习如何把投票应用改成“可复用应用”感兴趣,查看?进阶教程:如何创建可复用应用。

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容