{"title":"编写你的第一个 Django 应用，第 7 部分","version":"5.2","locale":"zh-hans","docname":"intro/tutorial07","url":"/zh-hans/5.2/intro/tutorial07/","canonical":"https://djangodocs.dev/zh-hans/5.2/intro/tutorial07/","summary":"本教程从 教程第 6 部分 结束的地方开始。我们继续修改在线投票应用，这次我们专注于自定义我们在 教程第 2 部分 初见过的 Django 自动生成后台的过程。 从哪里获得帮助： 如果你在阅读本教程的过程中有任何疑问，可以前往 FAQ 的 获取帮助 的版块。 自定义后台表单 Link to this heading # 通过…","html":"<h1>编写你的第一个 Django 应用，第 7 部分<a class=\"heading-anchor\" href=\"#writing-your-first-django-app-part-7\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h1>\n<p>本教程从 <a class=\"reference internal\" href=\"/zh-hans/5.2/intro/tutorial06/\"><span class=\"doc\">教程第 6 部分</span></a> 结束的地方开始。我们继续修改在线投票应用，这次我们专注于自定义我们在 <a class=\"reference internal\" href=\"/zh-hans/5.2/intro/tutorial02/\"><span class=\"doc\">教程第 2 部分</span></a> 初见过的 Django 自动生成后台的过程。</p>\n<aside class=\"admonition-where-to-get-help admonition\">\n<p class=\"admonition-title\">从哪里获得帮助：</p>\n<p>如果你在阅读本教程的过程中有任何疑问，可以前往 FAQ 的 <a class=\"reference internal\" href=\"/zh-hans/5.2/faq/help/\"><span class=\"doc\">获取帮助</span></a> 的版块。</p>\n</aside>\n<section id=\"customize-the-admin-form\">\n<h2>自定义后台表单<a class=\"heading-anchor\" href=\"#customize-the-admin-form\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>通过 <code class=\"docutils literal notranslate\"><span class=\"pre\">admin.site.register(Question)</span></code> 注册 <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> 模型，Django 能够构建一个默认的表单用于展示。通常来说，你期望能自定义表单的外观和工作方式。你可以在注册模型时将这些设置告诉 Django。</p>\n<p>让我们通过重排列表单上的字段来看看它是怎么工作的。用以下内容替换 <code class=\"docutils literal notranslate\"><span class=\"pre\">admin.site.register(Question)</span></code>：</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\"><code class=\"docutils literal notranslate\"><span class=\"pre\">polls/admin.py</span></code></figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.contrib</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">admin</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.models</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">Question</span>\n\n\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">QuestionAdmin</span><span class=\"p\">(</span><span class=\"n\">admin</span><span class=\"o\">.</span><span class=\"n\">ModelAdmin</span><span class=\"p\">):</span>\n    <span class=\"n\">fields</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s2\">&quot;pub_date&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;question_text&quot;</span><span class=\"p\">]</span>\n\n\n<span class=\"n\">admin</span><span class=\"o\">.</span><span class=\"n\">site</span><span class=\"o\">.</span><span class=\"n\">register</span><span class=\"p\">(</span><span class=\"n\">Question</span><span class=\"p\">,</span> <span class=\"n\">QuestionAdmin</span><span class=\"p\">)</span>\n</code></pre></figure>\n<p>你需要遵循以下流程——创建一个模型后台类，接着将其作为第二个参数传给 <code class=\"docutils literal notranslate\"><span class=\"pre\">admin.site.register()</span></code> ——在你需要修改模型的后台管理选项时这么做。</p>\n<p>以上修改使得 &quot;Publication date&quot; 字段显示在 &quot;Question&quot; 字段之前：</p>\n<img alt=\"字段已重新排序\" src=\"/zh-hans/5.2/_images/admin07.png\" />\n<p>这在只有两个字段时显得没啥卵用，但对于拥有数十个字段的表单来说，为表单选择一个直观的排序方法就显得你的针很细了。</p>\n<p>说到拥有数十个字段的表单，你可能更期望将表单分为几个字段集：</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\"><code class=\"docutils literal notranslate\"><span class=\"pre\">polls/admin.py</span></code></figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.contrib</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">admin</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.models</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">Question</span>\n\n\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">QuestionAdmin</span><span class=\"p\">(</span><span class=\"n\">admin</span><span class=\"o\">.</span><span class=\"n\">ModelAdmin</span><span class=\"p\">):</span>\n    <span class=\"n\">fieldsets</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n        <span class=\"p\">(</span><span class=\"kc\">None</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"s2\">&quot;fields&quot;</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"s2\">&quot;question_text&quot;</span><span class=\"p\">]}),</span>\n        <span class=\"p\">(</span><span class=\"s2\">&quot;Date information&quot;</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"s2\">&quot;fields&quot;</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"s2\">&quot;pub_date&quot;</span><span class=\"p\">]}),</span>\n    <span class=\"p\">]</span>\n\n\n<span class=\"n\">admin</span><span class=\"o\">.</span><span class=\"n\">site</span><span class=\"o\">.</span><span class=\"n\">register</span><span class=\"p\">(</span><span class=\"n\">Question</span><span class=\"p\">,</span> <span class=\"n\">QuestionAdmin</span><span class=\"p\">)</span>\n</code></pre></figure>\n<p><a class=\"reference internal\" href=\"/zh-hans/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.fieldsets\" title=\"django.contrib.admin.ModelAdmin.fieldsets\"><code class=\"xref py py-attr docutils literal notranslate\"><span class=\"pre\">fieldsets</span></code></a> 元组中的第一个元素是字段集的标题。以下是我们的表单现在的样子：</p>\n<img alt=\"表单现在有字段集\" src=\"/zh-hans/5.2/_images/admin08t.png\" />\n</section>\n<section id=\"adding-related-objects\">\n<h2>添加关联的对象<a class=\"heading-anchor\" href=\"#adding-related-objects\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>好了，现在我们有了投票的后台页。不过，一个 <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> 有多个 <code class=\"docutils literal notranslate\"><span class=\"pre\">Choice</span></code>，但后台页却没有显示多个选项。</p>\n<p>好了。</p>\n<p>有两个方法可以解决这个问题。第一个就是仿照我们向后台注册 <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> 一样注册 <code class=\"docutils literal notranslate\"><span class=\"pre\">Choice</span></code> ：</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\"><code class=\"docutils literal notranslate\"><span class=\"pre\">polls/admin.py</span></code></figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.contrib</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">admin</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.models</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">Choice</span><span class=\"p\">,</span> <span class=\"n\">Question</span>\n\n<span class=\"c1\"># ...</span>\n<span class=\"n\">admin</span><span class=\"o\">.</span><span class=\"n\">site</span><span class=\"o\">.</span><span class=\"n\">register</span><span class=\"p\">(</span><span class=\"n\">Choice</span><span class=\"p\">)</span>\n</code></pre></figure>\n<p>现在 &quot;Choices&quot; 在 Django 后台页中是一个可用的选项了。“添加选项”的表单看起来像这样：</p>\n<img alt=\"选择管理页面\" src=\"/zh-hans/5.2/_images/admin09.png\" />\n<p>在这个表单中，&quot;Question&quot; 字段是一个包含数据库中所有投票的选择框。Django 知道要将 <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/models/fields/#django.db.models.ForeignKey\" title=\"django.db.models.ForeignKey\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">ForeignKey</span></code></a> 在后台中以选择框 <code class=\"docutils literal notranslate\"><span class=\"pre\">&lt;select&gt;</span></code> 的形式展示。此时，我们只有一个投票。</p>\n<p>还请注意“问题”旁边的“添加另一个问题”链接。每个与另一个具有`ForeignKey``关系的对象都可以免费获得此链接。当你点击“添加另一个问题”时，你会看到一个带有“添加问题”表单的弹出窗口。如果你在该窗口中添加问题并点击“保存”，Django会将问题保存到数据库中，并将其动态添加为你正在查看的“添加选项”表单上的选定选项。</p>\n<p>不过，这是一种很低效地添加“选项”的方法。更好的办法是在你创建“投票”对象时直接添加好几个选项。让我们实现它。</p>\n<p>移除调用 <code class=\"docutils literal notranslate\"><span class=\"pre\">register()</span></code> 注册 <code class=\"docutils literal notranslate\"><span class=\"pre\">Choice</span></code> 模型的代码。随后，像这样修改 <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> 的注册代码：</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\"><code class=\"docutils literal notranslate\"><span class=\"pre\">polls/admin.py</span></code></figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.contrib</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">admin</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.models</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">Choice</span><span class=\"p\">,</span> <span class=\"n\">Question</span>\n\n\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">ChoiceInline</span><span class=\"p\">(</span><span class=\"n\">admin</span><span class=\"o\">.</span><span class=\"n\">StackedInline</span><span class=\"p\">):</span>\n    <span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">Choice</span>\n    <span class=\"n\">extra</span> <span class=\"o\">=</span> <span class=\"mi\">3</span>\n\n\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">QuestionAdmin</span><span class=\"p\">(</span><span class=\"n\">admin</span><span class=\"o\">.</span><span class=\"n\">ModelAdmin</span><span class=\"p\">):</span>\n    <span class=\"n\">fieldsets</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n        <span class=\"p\">(</span><span class=\"kc\">None</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"s2\">&quot;fields&quot;</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"s2\">&quot;question_text&quot;</span><span class=\"p\">]}),</span>\n        <span class=\"p\">(</span><span class=\"s2\">&quot;Date information&quot;</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"s2\">&quot;fields&quot;</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"s2\">&quot;pub_date&quot;</span><span class=\"p\">],</span> <span class=\"s2\">&quot;classes&quot;</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"s2\">&quot;collapse&quot;</span><span class=\"p\">]}),</span>\n    <span class=\"p\">]</span>\n    <span class=\"n\">inlines</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">ChoiceInline</span><span class=\"p\">]</span>\n\n\n<span class=\"n\">admin</span><span class=\"o\">.</span><span class=\"n\">site</span><span class=\"o\">.</span><span class=\"n\">register</span><span class=\"p\">(</span><span class=\"n\">Question</span><span class=\"p\">,</span> <span class=\"n\">QuestionAdmin</span><span class=\"p\">)</span>\n</code></pre></figure>\n<p>这会告诉 Django：“<code class=\"docutils literal notranslate\"><span class=\"pre\">Choice</span></code> 对象要在 <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> 后台页面编辑。默认提供 3 个足够的选项字段。”</p>\n<p>加载“添加投票”页面来看看它长啥样：</p>\n<img alt=\"添加问题页面现在可以选择\" src=\"/zh-hans/5.2/_images/admin10t.png\" />\n<p>它看起来像这样：有三个关联的选项插槽——由 <code class=\"docutils literal notranslate\"><span class=\"pre\">extra</span></code> 定义，且每次你返回任意已创建的对象的“修改”页面时，你会见到三个新的插槽。</p>\n<p>在三个插槽的末端，你会看到一个“添加新选项”的按钮。如果你单击它，一个新的插槽会被添加。如果你想移除已有的插槽，可以点击插槽右上角的X。以下图片展示了一个已添加的插槽：</p>\n<img alt=\"动态添加的附加插槽\" src=\"/zh-hans/5.2/_images/admin14t.png\" />\n<p>不过，仍然有点小问题。它占据了大量的屏幕区域来显示所有关联的 <code class=\"docutils literal notranslate\"><span class=\"pre\">Choice</span></code> 对象的字段。对于这个问题，Django 提供了一种表格式的单行显示关联对象的方法。要使用它，只需按如下形式修改 <code class=\"docutils literal notranslate\"><span class=\"pre\">ChoiceInline</span></code> 申明：</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\"><code class=\"docutils literal notranslate\"><span class=\"pre\">polls/admin.py</span></code></figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">ChoiceInline</span><span class=\"p\">(</span><span class=\"n\">admin</span><span class=\"o\">.</span><span class=\"n\">TabularInline</span><span class=\"p\">):</span> <span class=\"o\">...</span>\n</code></pre></figure>\n<p>通过 <code class=\"docutils literal notranslate\"><span class=\"pre\">TabularInline</span></code> （替代 <code class=\"docutils literal notranslate\"><span class=\"pre\">StackedInline</span></code> ），关联对象以一种表格式的方式展示，显得更加紧凑：</p>\n<img alt=\"添加问题页面现在有更紧凑的选择\" src=\"/zh-hans/5.2/_images/admin11t.png\" />\n<p>请注意，有一个额外的“删除？”列，允许删除使用“添加另一个选项”按钮添加的行和已保存的行。</p>\n</section>\n<section id=\"customize-the-admin-change-list\">\n<h2>自定义后台更改列表<a class=\"heading-anchor\" href=\"#customize-the-admin-change-list\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>现在投票的后台页看起来很不错，让我们对“更改列表”页面进行一些调整——改成一个能展示系统中所有投票的页面。</p>\n<p>以下是它此时的外观：</p>\n<img alt=\"民意调查更改列表页面\" src=\"/zh-hans/5.2/_images/admin04t.png\" />\n<p>默认情况下，Django 显示每个对象的 <code class=\"docutils literal notranslate\"><span class=\"pre\">str()</span></code>。但有时如果我们能显示单个字段会更有帮助。为此，请使用 <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display\" title=\"django.contrib.admin.ModelAdmin.list_display\"><code class=\"xref py py-attr docutils literal notranslate\"><span class=\"pre\">list_display</span></code></a> admin 选项，该选项是要在对象的更改列表页上以列形式显示的字段名称列表：</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\"><code class=\"docutils literal notranslate\"><span class=\"pre\">polls/admin.py</span></code></figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">QuestionAdmin</span><span class=\"p\">(</span><span class=\"n\">admin</span><span class=\"o\">.</span><span class=\"n\">ModelAdmin</span><span class=\"p\">):</span>\n    <span class=\"c1\"># ...</span>\n    <span class=\"n\">list_display</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s2\">&quot;question_text&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;pub_date&quot;</span><span class=\"p\">]</span>\n</code></pre></figure>\n<p>另外，让我们把 <a class=\"reference internal\" href=\"/zh-hans/5.2/intro/tutorial02/\"><span class=\"doc\">教程第 2 部分</span></a> 中的 <code class=\"docutils literal notranslate\"><span class=\"pre\">was_published_recently()</span></code> 方法也加上：</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\"><code class=\"docutils literal notranslate\"><span class=\"pre\">polls/admin.py</span></code></figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">QuestionAdmin</span><span class=\"p\">(</span><span class=\"n\">admin</span><span class=\"o\">.</span><span class=\"n\">ModelAdmin</span><span class=\"p\">):</span>\n    <span class=\"c1\"># ...</span>\n    <span class=\"n\">list_display</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s2\">&quot;question_text&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;pub_date&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;was_published_recently&quot;</span><span class=\"p\">]</span>\n</code></pre></figure>\n<p>现在修改投票的列表页看起来像这样：</p>\n<img alt=\"投票更改列表页面，已更新\" src=\"/zh-hans/5.2/_images/admin12t.png\" />\n<p>你可以点击列标题来对这些行进行排序——除了 <code class=\"docutils literal notranslate\"><span class=\"pre\">was_published_recently</span></code> 这个列，因为没有实现排序方法。顺便看下这个列的标题 <code class=\"docutils literal notranslate\"><span class=\"pre\">was_published_recently</span></code>，默认就是方法名（用空格替换下划线），该列的每行都以字符串形式展示出处。</p>\n<p>你可以通过在该方法上使用 <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/contrib/admin/#django.contrib.admin.display\" title=\"django.contrib.admin.display\"><code class=\"xref py py-func docutils literal notranslate\"><span class=\"pre\">display()</span></code></a> 装饰器来改进这一点（扩展在 <a class=\"reference internal\" href=\"/zh-hans/5.2/intro/tutorial02/\"><span class=\"doc\">教程 2</span></a> 中创建的 <code class=\"file docutils literal notranslate\"><span class=\"pre\">polls/models.py</span></code> 文件），如下所示：</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\"><code class=\"docutils literal notranslate\"><span class=\"pre\">polls/models.py</span></code></figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.contrib</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">admin</span>\n\n\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">Question</span><span class=\"p\">(</span><span class=\"n\">models</span><span class=\"o\">.</span><span class=\"n\">Model</span><span class=\"p\">):</span>\n    <span class=\"c1\"># ...</span>\n    <span class=\"nd\">@admin</span><span class=\"o\">.</span><span class=\"n\">display</span><span class=\"p\">(</span>\n        <span class=\"n\">boolean</span><span class=\"o\">=</span><span class=\"kc\">True</span><span class=\"p\">,</span>\n        <span class=\"n\">ordering</span><span class=\"o\">=</span><span class=\"s2\">&quot;pub_date&quot;</span><span class=\"p\">,</span>\n        <span class=\"n\">description</span><span class=\"o\">=</span><span class=\"s2\">&quot;Published recently?&quot;</span><span class=\"p\">,</span>\n    <span class=\"p\">)</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">was_published_recently</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n        <span class=\"n\">now</span> <span class=\"o\">=</span> <span class=\"n\">timezone</span><span class=\"o\">.</span><span class=\"n\">now</span><span class=\"p\">()</span>\n        <span class=\"k\">return</span> <span class=\"n\">now</span> <span class=\"o\">-</span> <span class=\"n\">datetime</span><span class=\"o\">.</span><span class=\"n\">timedelta</span><span class=\"p\">(</span><span class=\"n\">days</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"o\">&lt;=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">pub_date</span> <span class=\"o\">&lt;=</span> <span class=\"n\">now</span>\n</code></pre></figure>\n<p>更多关于可通过装饰器设置的属性的信息，请参见 <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display\" title=\"django.contrib.admin.ModelAdmin.list_display\"><code class=\"xref py py-attr docutils literal notranslate\"><span class=\"pre\">list_display</span></code></a>。</p>\n<p>再次编辑文件 <code class=\"file docutils literal notranslate\"><span class=\"pre\">polls/admin.py</span></code>，优化 <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> 变更页：过滤器，使用 <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter\" title=\"django.contrib.admin.ModelAdmin.list_filter\"><code class=\"xref py py-attr docutils literal notranslate\"><span class=\"pre\">list_filter</span></code></a>。将以下代码添加至 <code class=\"docutils literal notranslate\"><span class=\"pre\">QuestionAdmin</span></code>：</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"n\">list_filter</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s2\">&quot;pub_date&quot;</span><span class=\"p\">]</span>\n</code></pre></div>\n<p>这样做添加了一个“过滤器”侧边栏，允许人们以 <code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> 字段来过滤列表：</p>\n<img alt=\"投票更改列表页面，已更新\" src=\"/zh-hans/5.2/_images/admin13t.png\" />\n<p>展示的过滤器类型取决你你要过滤的字段的类型。因为 <code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> 是类 <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/models/fields/#django.db.models.DateTimeField\" title=\"django.db.models.DateTimeField\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">DateTimeField</span></code></a>，Django 知道要提供哪个过滤器：“任意时间”，“今天”，“过去7天”，“这个月”和“今年”。</p>\n<p>这已经弄的很好了。让我们再扩充些功能:</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"n\">search_fields</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s2\">&quot;question_text&quot;</span><span class=\"p\">]</span>\n</code></pre></div>\n<p>在列表的顶部增加一个搜索框。当输入待搜项时，Django 将搜索 <code class=\"docutils literal notranslate\"><span class=\"pre\">question_text</span></code> 字段。你可以使用任意多的字段——由于后台使用 <code class=\"docutils literal notranslate\"><span class=\"pre\">LIKE</span></code> 来查询数据，将待搜索的字段数限制为一个不会出问题大小，会便于数据库进行查询操作。</p>\n<p>现在是给你的修改列表页增加分页功能的好时机。默认每页显示 100 项。<a class=\"reference internal\" href=\"/zh-hans/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_per_page\" title=\"django.contrib.admin.ModelAdmin.list_per_page\"><code class=\"xref py py-attr docutils literal notranslate\"><span class=\"pre\">变更页分页</span></code></a>, <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields\" title=\"django.contrib.admin.ModelAdmin.search_fields\"><code class=\"xref py py-attr docutils literal notranslate\"><span class=\"pre\">搜索框</span></code></a>, <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter\" title=\"django.contrib.admin.ModelAdmin.list_filter\"><code class=\"xref py py-attr docutils literal notranslate\"><span class=\"pre\">过滤器</span></code></a>, <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.date_hierarchy\" title=\"django.contrib.admin.ModelAdmin.date_hierarchy\"><code class=\"xref py py-attr docutils literal notranslate\"><span class=\"pre\">日期层次结构</span></code></a>, 和 <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display\" title=\"django.contrib.admin.ModelAdmin.list_display\"><code class=\"xref py py-attr docutils literal notranslate\"><span class=\"pre\">列标题排序</span></code></a> 均以你期望的方式合作运行。</p>\n</section>\n<section id=\"customize-the-admin-look-and-feel\">\n<h2>自定义后台界面和风格<a class=\"heading-anchor\" href=\"#customize-the-admin-look-and-feel\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>在每个后台页顶部显示“Django 管理员”显得很滑稽。这只是一串占位文本。</p>\n<p>不过，你可以通过 Django 的模板系统来修改。Django 的后台由自己驱动，且它的交互接口采用 Django 自己的模板系统。</p>\n<section id=\"customizing-your-project-s-templates\">\n<span id=\"ref-customizing-your-projects-templates\"></span><h3>自定义你的 <em>工程的</em> 模板<a class=\"heading-anchor\" href=\"#customizing-your-project-s-templates\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>在你的 <code class=\"docutils literal notranslate\"><span class=\"pre\">djangotutorial</span></code> 目录中创建一个 <code class=\"docutils literal notranslate\"><span class=\"pre\">templates</span></code> 目录。模板可以存放在 Django 可以访问的文件系统中的任何位置。（Django 以服务器运行的任何用户身份运行。）然而，将模板保留在项目内是一个值得遵循的良好惯例。</p>\n<p>打开你的设置文件（<code class=\"file docutils literal notranslate\"><span class=\"pre\">mysite/settings.py</span></code>，牢记），在 <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/settings/#std-setting-TEMPLATES\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">TEMPLATES</span></code></a> 设置中添加 <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/settings/#std-setting-TEMPLATES-DIRS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">DIRS</span></code></a> 选项：</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\"><code class=\"docutils literal notranslate\"><span class=\"pre\">mysite/settings.py</span></code></figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"n\">TEMPLATES</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n    <span class=\"p\">{</span>\n        <span class=\"s2\">&quot;BACKEND&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;django.template.backends.django.DjangoTemplates&quot;</span><span class=\"p\">,</span>\n        <span class=\"s2\">&quot;DIRS&quot;</span><span class=\"p\">:</span> <span class=\"p\">[</span><span class=\"n\">BASE_DIR</span> <span class=\"o\">/</span> <span class=\"s2\">&quot;templates&quot;</span><span class=\"p\">],</span>\n        <span class=\"s2\">&quot;APP_DIRS&quot;</span><span class=\"p\">:</span> <span class=\"kc\">True</span><span class=\"p\">,</span>\n        <span class=\"s2\">&quot;OPTIONS&quot;</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n            <span class=\"s2\">&quot;context_processors&quot;</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n                <span class=\"s2\">&quot;django.template.context_processors.request&quot;</span><span class=\"p\">,</span>\n                <span class=\"s2\">&quot;django.contrib.auth.context_processors.auth&quot;</span><span class=\"p\">,</span>\n                <span class=\"s2\">&quot;django.contrib.messages.context_processors.messages&quot;</span><span class=\"p\">,</span>\n            <span class=\"p\">],</span>\n        <span class=\"p\">},</span>\n    <span class=\"p\">},</span>\n<span class=\"p\">]</span>\n</code></pre></figure>\n<p><a class=\"reference internal\" href=\"/zh-hans/5.2/ref/settings/#std-setting-TEMPLATES-DIRS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">DIRS</span></code></a> 是一个包含多个系统目录的文件列表，用于在载入 Django 模板时使用，是一个待搜索路径。</p>\n<aside class=\"admonition-organizing-templates admonition\">\n<p class=\"admonition-title\">组织模板</p>\n<p>就像静态文件一样，我们 <em>可以</em> 把所有的模板文件放在一个大模板目录内，这样它也能工作的很好。但是，属于特定应用的模板文件最好放在应用所属的模板目录（例如 <code class=\"docutils literal notranslate\"><span class=\"pre\">polls/templates</span></code>），而不是工程的模板目录（<code class=\"docutils literal notranslate\"><span class=\"pre\">templates</span></code>）。我们会在 <a class=\"reference internal\" href=\"/zh-hans/5.2/intro/reusable-apps/\"><span class=\"doc\">创建可复用的应用教程</span></a> 中讨论 <em>为什么</em> 我们要这样做。</p>\n</aside>\n<p>现在在 <code class=\"docutils literal notranslate\"><span class=\"pre\">templates</span></code> 目录内创建一个名为 <code class=\"docutils literal notranslate\"><span class=\"pre\">admin</span></code> 的目录，并将默认的 Django 管理界面模板目录中的模板文件 <code class=\"docutils literal notranslate\"><span class=\"pre\">admin/base_site.html</span></code> 复制到该目录中。默认的 Django 管理界面模板目录位于 Django 源代码中（<a class=\"extlink-source reference external\" href=\"https://github.com/django/django/blob/stable/5.2.x/django/contrib/admin/templates\">django/contrib/admin/templates</a>）。</p>\n<aside class=\"admonition-where-are-the-django-source-files admonition\">\n<p class=\"admonition-title\">Django 的源文件在哪里？</p>\n<p>如果你不知道 Django 源码在你系统的哪个位置，运行以下命令：</p>\n<div class=\"console\" data-console><div class=\"console-panel\" data-platform=\"unix\"><p class=\"console-label\" id=\"console-0-unix-label\">Linux / macOS</p><div class=\"code-block\" data-language=\"console\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Shell</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Shell code\"><code><span class=\"gp\">$ </span>python<span class=\"w\"> </span>-c<span class=\"w\"> </span><span class=\"s2\">&quot;import django; print(django.__path__)&quot;</span>\n</code></pre></div>\n</div><div class=\"console-panel\" data-platform=\"windows\"><p class=\"console-label\" id=\"console-0-windows-label\">Windows</p><div class=\"code-block\" data-language=\"doscon\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Windows</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Windows shell\"><code><span class=\"gp\">...\\&gt;</span> py -c <span class=\"s2\">&quot;import django; print(django.__path__)&quot;</span>\n</code></pre></div></div></div>\n</aside>\n<p>接着，用你网页站点的名字编辑替换文件内的 <code class=\"docutils literal notranslate\"><span class=\"pre\">{{</span> <span class=\"pre\">site_header|default:_('Django</span> <span class=\"pre\">administration')</span> <span class=\"pre\">}}</span></code> （包含大括号）。完成后，你应该看到如下代码：</p>\n<div class=\"code-block\" data-language=\"html+django\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Django template</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Django template code\"><code><span class=\"cp\">{%</span> <span class=\"k\">block</span> <span class=\"nv\">branding</span> <span class=\"cp\">%}</span>\n<span class=\"p\">&lt;</span><span class=\"nt\">div</span> <span class=\"na\">id</span><span class=\"o\">=</span><span class=\"s\">&quot;site-name&quot;</span><span class=\"p\">&gt;&lt;</span><span class=\"nt\">a</span> <span class=\"na\">href</span><span class=\"o\">=</span><span class=\"s\">&quot;</span><span class=\"cp\">{%</span> <span class=\"k\">url</span> <span class=\"s1\">&#39;admin:index&#39;</span> <span class=\"cp\">%}</span><span class=\"s\">&quot;</span><span class=\"p\">&gt;</span>Polls Administration<span class=\"p\">&lt;/</span><span class=\"nt\">a</span><span class=\"p\">&gt;&lt;/</span><span class=\"nt\">div</span><span class=\"p\">&gt;</span>\n<span class=\"cp\">{%</span> <span class=\"k\">if</span> <span class=\"nv\">user.is_anonymous</span> <span class=\"cp\">%}</span>\n  <span class=\"cp\">{%</span> <span class=\"k\">include</span> <span class=\"s2\">&quot;admin/color_theme_toggle.html&quot;</span> <span class=\"cp\">%}</span>\n<span class=\"cp\">{%</span> <span class=\"k\">endif</span> <span class=\"cp\">%}</span>\n<span class=\"cp\">{%</span> <span class=\"k\">endblock</span> <span class=\"cp\">%}</span>\n</code></pre></div>\n<p>我们会用这个方法来教你复写模板。在一个实际工程中，你可能更期望使用 <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/contrib/admin/#django.contrib.admin.AdminSite.site_header\" title=\"django.contrib.admin.AdminSite.site_header\"><code class=\"xref py py-attr docutils literal notranslate\"><span class=\"pre\">django.contrib.admin.AdminSite.site_header</span></code></a> 来进行简单的定制。</p>\n<p>这个模板文件包含很多类似 <code class=\"docutils literal notranslate\"><span class=\"pre\">{%</span> <span class=\"pre\">block</span> <span class=\"pre\">branding</span> <span class=\"pre\">%}</span></code> 和 <code class=\"docutils literal notranslate\"><span class=\"pre\">{{</span> <span class=\"pre\">title</span> <span class=\"pre\">}}</span></code> 的文本。 <code class=\"docutils literal notranslate\"><span class=\"pre\">{%</span></code> 和 <code class=\"docutils literal notranslate\"><span class=\"pre\">{{</span></code> 标签是 Django 模板语言的一部分。当 Django 渲染 <code class=\"docutils literal notranslate\"><span class=\"pre\">admin/base_site.html</span></code> 时，这个模板语言会被求值，生成最终的网页，就像我们在 <a class=\"reference internal\" href=\"/zh-hans/5.2/intro/tutorial03/\"><span class=\"doc\">教程第 3 部分</span></a> 所学的一样。</p>\n<p>注意，所有的 Django 默认后台模板均可被复写。若要复写模板，像你修改 <code class=\"docutils literal notranslate\"><span class=\"pre\">base_site.html</span></code> 一样修改其它文件——先将其从默认目录中拷贝到你的自定义目录，再做修改。</p>\n</section>\n<section id=\"customizing-your-application-s-templates\">\n<h3>自定义你 <em>应用的</em> 模板<a class=\"heading-anchor\" href=\"#customizing-your-application-s-templates\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>机智的同学可能会问： <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/settings/#std-setting-TEMPLATES-DIRS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">DIRS</span></code></a> 默认是空的，Django 是怎么找到默认的后台模板的？因为 <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/settings/#std-setting-TEMPLATES-APP_DIRS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">APP_DIRS</span></code></a> 被置为 <code class=\"docutils literal notranslate\"><span class=\"pre\">True</span></code>，Django 会自动在每个应用包内递归查找 <code class=\"docutils literal notranslate\"><span class=\"pre\">templates/</span></code> 子目录（不要忘了 <code class=\"docutils literal notranslate\"><span class=\"pre\">django.contrib.admin</span></code> 也是一个应用）。</p>\n<p>我们的投票应用不是非常复杂，所以无需自定义后台模板。不过，如果它变的更加复杂，需要修改 Django 的标准后台模板功能时，修改 <em>应用</em> 的模板会比 <em>工程</em> 的更加明智。这样，在其它工程包含这个投票应用时，可以确保它总是能找到需要的自定义模板文件。</p>\n<p>更多关于 Django 如何查找模板的文档，参见 <a class=\"reference internal\" href=\"/zh-hans/5.2/topics/templates/#template-loading\"><span class=\"std std-ref\">加载模板文档</span></a>。</p>\n</section>\n</section>\n<section id=\"customize-the-admin-index-page\">\n<h2>自定义后台主页<a class=\"heading-anchor\" href=\"#customize-the-admin-index-page\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>在类似的说明中，你可能想要自定义 Django 后台索引页的外观。</p>\n<p>默认情况下，它展示了所有配置在 <a class=\"reference internal\" href=\"/zh-hans/5.2/ref/settings/#std-setting-INSTALLED_APPS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">INSTALLED_APPS</span></code></a> 中，已通过后台应用注册，按拼音排序的应用。你可能想对这个页面的布局做重大的修改。毕竟，索引页是后台的重要页面，它应该便于使用。</p>\n<p>需要自定义的模板是 <code class=\"docutils literal notranslate\"><span class=\"pre\">admin/index.html</span></code>。（像上一节修改 <code class=\"docutils literal notranslate\"><span class=\"pre\">admin/base_site.html</span></code> 那样修改此文件——从默认目录中拷贝此文件至自定义模板目录）。打开此文件，你将看到它使用了一个叫做 <code class=\"docutils literal notranslate\"><span class=\"pre\">app_list</span></code> 的模板变量。这个变量包含了每个安装的 Django 应用。你可以用任何你期望的硬编码链接（链接至特定对象的管理页）替代使用这个变量。</p>\n<p>当你熟悉了管理员界面后，阅读 <a class=\"reference internal\" href=\"/zh-hans/5.2/intro/tutorial08/\"><span class=\"doc\">本教程的第 8 部分</span></a>，学习如何使用第三方包。</p>\n</section>","rootId":"writing-your-first-django-app-part-7","toc":[{"title":"自定义后台表单","anchor":"customize-the-admin-form","children":[]},{"title":"添加关联的对象","anchor":"adding-related-objects","children":[]},{"title":"自定义后台更改列表","anchor":"customize-the-admin-change-list","children":[]},{"title":"自定义后台界面和风格","anchor":"customize-the-admin-look-and-feel","children":[{"title":"自定义你的 工程的 模板","anchor":"customizing-your-project-s-templates","children":[]},{"title":"自定义你 应用的 模板","anchor":"customizing-your-application-s-templates","children":[]}]},{"title":"自定义后台主页","anchor":"customize-the-admin-index-page","children":[]}],"breadcrumbs":[{"docname":"intro/index","title":"开始","url":"/zh-hans/5.2/intro/"}],"prev":{"docname":"intro/tutorial06","title":"编写你的第一个 Django 应用，第 6 部分","url":"/zh-hans/5.2/intro/tutorial06/"},"next":{"docname":"intro/tutorial08","title":"编写你的第一个 Django 应用程序，第 8 部分","url":"/zh-hans/5.2/intro/tutorial08/"},"formats":{"html":"/zh-hans/5.2/intro/tutorial07/","markdown":"/zh-hans/5.2/intro/tutorial07.md","json":"/zh-hans/5.2/intro/tutorial07.json"},"source":"https://github.com/django/django/blob/stable/5.2.x/docs/intro/tutorial07.txt","official":"https://docs.djangoproject.com/zh-hans/5.2/intro/tutorial07/","inVersions":["6.1","6.0","5.2","5.1","5.0","4.2","4.1","4.0","3.2","3.1","3.0","2.2","2.1","2.0"],"inLocales":["en","sv","zh-hans","ga","fr","ja","id","it","pt-br","ko","es","el","pl"]}