{"title":"Writing your first Django app, part 7","version":"5.1","locale":"en","docname":"intro/tutorial07","url":"/en/5.1/intro/tutorial07/","canonical":"https://djangodocs.dev/en/5.1/intro/tutorial07/","summary":"This tutorial begins where Tutorial 6 left off. We’re continuing the web-poll application and will focus on customizing Django’s automatically-generated admin site…","html":"<h1>Writing your first Django app, part 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>This tutorial begins where <a class=\"reference internal\" href=\"/en/5.1/intro/tutorial06/\"><span class=\"doc\">Tutorial 6</span></a> left off. We’re\ncontinuing the web-poll application and will focus on customizing Django’s\nautomatically-generated admin site that we first explored in <a class=\"reference internal\" href=\"/en/5.1/intro/tutorial02/\"><span class=\"doc\">Tutorial 2</span></a>.</p>\n<aside class=\"admonition-where-to-get-help admonition\">\n<p class=\"admonition-title\">Where to get help:</p>\n<p>If you’re having trouble going through this tutorial, please head over to\nthe <a class=\"reference internal\" href=\"/en/5.1/faq/help/\"><span class=\"doc\">Getting Help</span></a> section of the FAQ.</p>\n</aside>\n<section id=\"customize-the-admin-form\">\n<h2>Customize the admin form<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>By registering the <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> model with <code class=\"docutils literal notranslate\"><span class=\"pre\">admin.site.register(Question)</span></code>,\nDjango was able to construct a default form representation. Often, you’ll want\nto customize how the admin form looks and works. You’ll do this by telling\nDjango the options you want when you register the object.</p>\n<p>Let’s see how this works by reordering the fields on the edit form. Replace\nthe <code class=\"docutils literal notranslate\"><span class=\"pre\">admin.site.register(Question)</span></code> line with:</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>You’ll follow this pattern – create a model admin class, then pass it as the\nsecond argument to <code class=\"docutils literal notranslate\"><span class=\"pre\">admin.site.register()</span></code> – any time you need to change the\nadmin options for a model.</p>\n<p>This particular change above makes the “Publication date” come before the\n“Question” field:</p>\n<img alt=\"Fields have been reordered\" src=\"/en/5.1/_images/admin07.png\" />\n<p>This isn’t impressive with only two fields, but for admin forms with dozens\nof fields, choosing an intuitive order is an important usability detail.</p>\n<p>And speaking of forms with dozens of fields, you might want to split the form\nup into fieldsets:</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>The first element of each tuple in\n<a class=\"reference internal\" href=\"/en/5.1/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> is the title of the fieldset.\nHere’s what our form looks like now:</p>\n<img alt=\"Form has fieldsets now\" src=\"/en/5.1/_images/admin08t.png\" />\n</section>\n<section id=\"adding-related-objects\">\n<h2>Adding related objects<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>OK, we have our Question admin page, but a <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> has multiple\n<code class=\"docutils literal notranslate\"><span class=\"pre\">Choice</span></code>s, and the admin page doesn’t display choices.</p>\n<p>Yet.</p>\n<p>There are two ways to solve this problem. The first is to register <code class=\"docutils literal notranslate\"><span class=\"pre\">Choice</span></code>\nwith the admin just as we did with <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<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>Now “Choices” is an available option in the Django admin. The “Add choice” form\nlooks like this:</p>\n<img alt=\"Choice admin page\" src=\"/en/5.1/_images/admin09.png\" />\n<p>In that form, the “Question” field is a select box containing every question in the\ndatabase. Django knows that a <a class=\"reference internal\" href=\"/en/5.1/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> should be\nrepresented in the admin as a <code class=\"docutils literal notranslate\"><span class=\"pre\">&lt;select&gt;</span></code> box. In our case, only one question\nexists at this point.</p>\n<p>Also note the “Add another question” link next to “Question.” Every object with\na <code class=\"docutils literal notranslate\"><span class=\"pre\">ForeignKey</span></code> relationship to another gets this for free. When you click\n“Add another question”, you’ll get a popup window with the “Add question” form.\nIf you add a question in that window and click “Save”, Django will save the\nquestion to the database and dynamically add it as the selected choice on the\n“Add choice” form you’re looking at.</p>\n<p>But, really, this is an inefficient way of adding <code class=\"docutils literal notranslate\"><span class=\"pre\">Choice</span></code> objects to the system.\nIt’d be better if you could add a bunch of Choices directly when you create the\n<code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> object. Let’s make that happen.</p>\n<p>Remove the <code class=\"docutils literal notranslate\"><span class=\"pre\">register()</span></code> call for the <code class=\"docutils literal notranslate\"><span class=\"pre\">Choice</span></code> model. Then, edit the <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code>\nregistration code to read:</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>This tells Django: “<code class=\"docutils literal notranslate\"><span class=\"pre\">Choice</span></code> objects are edited on the <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> admin page. By\ndefault, provide enough fields for 3 choices.”</p>\n<p>Load the “Add question” page to see how that looks:</p>\n<img alt=\"Add question page now has choices on it\" src=\"/en/5.1/_images/admin10t.png\" />\n<p>It works like this: There are three slots for related Choices – as specified\nby <code class=\"docutils literal notranslate\"><span class=\"pre\">extra</span></code> – and each time you come back to the “Change” page for an\nalready-created object, you get another three extra slots.</p>\n<p>At the end of the three current slots you will find an “Add another Choice”\nlink.  If you click on it, a new slot will be added. If you want to remove the\nadded slot, you can click on the X to the top right of the added slot. This\nimage shows an added slot:</p>\n<img alt=\"Additional slot added dynamically\" src=\"/en/5.1/_images/admin14t.png\" />\n<p>One small problem, though. It takes a lot of screen space to display all the\nfields for entering related <code class=\"docutils literal notranslate\"><span class=\"pre\">Choice</span></code> objects. For that reason, Django offers a\ntabular way of displaying inline related objects. To use it, change the\n<code class=\"docutils literal notranslate\"><span class=\"pre\">ChoiceInline</span></code> declaration to read:</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>With that <code class=\"docutils literal notranslate\"><span class=\"pre\">TabularInline</span></code> (instead of <code class=\"docutils literal notranslate\"><span class=\"pre\">StackedInline</span></code>), the\nrelated objects are displayed in a more compact, table-based format:</p>\n<img alt=\"Add question page now has more compact choices\" src=\"/en/5.1/_images/admin11t.png\" />\n<p>Note that there is an extra “Delete?” column that allows removing rows added\nusing the “Add another Choice” button and rows that have already been saved.</p>\n</section>\n<section id=\"customize-the-admin-change-list\">\n<h2>Customize the admin change list<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>Now that the Question admin page is looking good, let’s make some tweaks to the\n“change list” page – the one that displays all the questions in the system.</p>\n<p>Here’s what it looks like at this point:</p>\n<img alt=\"Polls change list page\" src=\"/en/5.1/_images/admin04t.png\" />\n<p>By default, Django displays the <code class=\"docutils literal notranslate\"><span class=\"pre\">str()</span></code> of each object. But sometimes it’d be\nmore helpful if we could display individual fields. To do that, use the\n<a class=\"reference internal\" href=\"/en/5.1/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 option, which is a\nlist of field names to display, as columns, on the change list page for the\nobject:</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>For good measure, let’s also include the <code class=\"docutils literal notranslate\"><span class=\"pre\">was_published_recently()</span></code> method\nfrom <a class=\"reference internal\" href=\"/en/5.1/intro/tutorial02/\"><span class=\"doc\">Tutorial 2</span></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\">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>Now the question change list page looks like this:</p>\n<img alt=\"Polls change list page, updated\" src=\"/en/5.1/_images/admin12t.png\" />\n<p>You can click on the column headers to sort by those values – except in the\ncase of the <code class=\"docutils literal notranslate\"><span class=\"pre\">was_published_recently</span></code> header, because sorting by the output\nof an arbitrary method is not supported. Also note that the column header for\n<code class=\"docutils literal notranslate\"><span class=\"pre\">was_published_recently</span></code> is, by default, the name of the method (with\nunderscores replaced with spaces), and that each line contains the string\nrepresentation of the output.</p>\n<p>You can improve that by using the <a class=\"reference internal\" href=\"/en/5.1/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>\ndecorator on that method (extending the <code class=\"file docutils literal notranslate\"><span class=\"pre\">polls/models.py</span></code> file that was\ncreated in <a class=\"reference internal\" href=\"/en/5.1/intro/tutorial02/\"><span class=\"doc\">Tutorial 2</span></a>), as follows:</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>For more information on the properties configurable via the decorator, see\n<a class=\"reference internal\" href=\"/en/5.1/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>Edit your <code class=\"file docutils literal notranslate\"><span class=\"pre\">polls/admin.py</span></code> file again and add an improvement to the\n<code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> change list page: filters using the\n<a class=\"reference internal\" href=\"/en/5.1/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>. Add the following line to\n<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>That adds a “Filter” sidebar that lets people filter the change list by the\n<code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> field:</p>\n<img alt=\"Polls change list page, updated\" src=\"/en/5.1/_images/admin13t.png\" />\n<p>The type of filter displayed depends on the type of field you’re filtering on.\nBecause <code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> is a <a class=\"reference internal\" href=\"/en/5.1/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\nknows to give appropriate filter options: “Any date”, “Today”, “Past 7 days”,\n“This month”, “This year”.</p>\n<p>This is shaping up well. Let’s add some search capability:</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>That adds a search box at the top of the change list. When somebody enters\nsearch terms, Django will search the <code class=\"docutils literal notranslate\"><span class=\"pre\">question_text</span></code> field. You can use as many\nfields as you’d like – although because it uses a <code class=\"docutils literal notranslate\"><span class=\"pre\">LIKE</span></code> query behind the\nscenes, limiting the number of search fields to a reasonable number will make\nit easier for your database to do the search.</p>\n<p>Now’s also a good time to note that change lists give you free pagination. The\ndefault is to display 100 items per page. <a class=\"reference internal\" href=\"/en/5.1/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\">Change</span> <span class=\"pre\">list</span> <span class=\"pre\">pagination</span></code></a>, <a class=\"reference internal\" href=\"/en/5.1/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\">search</span> <span class=\"pre\">boxes</span></code></a>, <a class=\"reference internal\" href=\"/en/5.1/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\">filters</span></code></a>, <a class=\"reference internal\" href=\"/en/5.1/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\">date-hierarchies</span></code></a>, and\n<a class=\"reference internal\" href=\"/en/5.1/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\">column-header-ordering</span></code></a>\nall work together like you think they should.</p>\n</section>\n<section id=\"customize-the-admin-look-and-feel\">\n<h2>Customize the admin look and feel<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>Clearly, having “Django administration” at the top of each admin page is\nridiculous. It’s just placeholder text.</p>\n<p>You can change it, though, using Django’s template system. The Django admin is\npowered by Django itself, and its interfaces use Django’s own template system.</p>\n<section id=\"customizing-your-project-s-templates\">\n<span id=\"ref-customizing-your-projects-templates\"></span><h3>Customizing your <em>project’s</em> templates<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>Create a <code class=\"docutils literal notranslate\"><span class=\"pre\">templates</span></code> directory in your <code class=\"docutils literal notranslate\"><span class=\"pre\">djangotutorial</span></code> directory.\nTemplates can live anywhere on your filesystem that Django can access. (Django\nruns as whatever user your server runs.) However, keeping your templates within\nthe project is a good convention to follow.</p>\n<p>Open your settings file (<code class=\"file docutils literal notranslate\"><span class=\"pre\">mysite/settings.py</span></code>, remember) and add a\n<a class=\"reference internal\" href=\"/en/5.1/ref/settings/#std-setting-TEMPLATES-DIRS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">DIRS</span></code></a> option in the <a class=\"reference internal\" href=\"/en/5.1/ref/settings/#std-setting-TEMPLATES\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">TEMPLATES</span></code></a> setting:</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.debug&quot;</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=\"/en/5.1/ref/settings/#std-setting-TEMPLATES-DIRS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">DIRS</span></code></a> is a list of filesystem directories to check\nwhen loading Django templates; it’s a search path.</p>\n<aside class=\"admonition-organizing-templates admonition\">\n<p class=\"admonition-title\">Organizing templates</p>\n<p>Just like the static files, we <em>could</em> have all our templates together, in\none big templates directory, and it would work perfectly well. However,\ntemplates that belong to a particular application should be placed in that\napplication’s template directory (e.g. <code class=\"docutils literal notranslate\"><span class=\"pre\">polls/templates</span></code>) rather than the\nproject’s (<code class=\"docutils literal notranslate\"><span class=\"pre\">templates</span></code>). We’ll discuss in more detail in the\n<a class=\"reference internal\" href=\"/en/5.1/intro/reusable-apps/\"><span class=\"doc\">reusable apps tutorial</span></a> <em>why</em> we do this.</p>\n</aside>\n<p>Now create a directory called <code class=\"docutils literal notranslate\"><span class=\"pre\">admin</span></code> inside <code class=\"docutils literal notranslate\"><span class=\"pre\">templates</span></code>, and copy the\ntemplate <code class=\"docutils literal notranslate\"><span class=\"pre\">admin/base_site.html</span></code> from within the default Django admin\ntemplate directory in the source code of Django itself\n(<a class=\"extlink-source reference external\" href=\"https://github.com/django/django/blob/stable/5.1.x/django/contrib/admin/templates\">django/contrib/admin/templates</a>) into that directory.</p>\n<aside class=\"admonition-where-are-the-django-source-files admonition\">\n<p class=\"admonition-title\">Where are the Django source files?</p>\n<p>If you have difficulty finding where the Django source files are located\non your system, run the following command:</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>Then, edit the file and replace\n<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> (including the curly\nbraces) with your own site’s name as you see fit. You should end up with\na section of code like:</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>We use this approach to teach you how to override templates. In an actual\nproject, you would probably use\nthe <a class=\"reference internal\" href=\"/en/5.1/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> attribute to more easily\nmake this particular customization.</p>\n<p>This template file contains lots of text like <code class=\"docutils literal notranslate\"><span class=\"pre\">{%</span> <span class=\"pre\">block</span> <span class=\"pre\">branding</span> <span class=\"pre\">%}</span></code>\nand <code class=\"docutils literal notranslate\"><span class=\"pre\">{{</span> <span class=\"pre\">title</span> <span class=\"pre\">}}</span></code>. The <code class=\"docutils literal notranslate\"><span class=\"pre\">{%</span></code> and <code class=\"docutils literal notranslate\"><span class=\"pre\">{{</span></code> tags are part of Django’s\ntemplate language. When Django renders <code class=\"docutils literal notranslate\"><span class=\"pre\">admin/base_site.html</span></code>, this\ntemplate language will be evaluated to produce the final HTML page, just like\nwe saw in <a class=\"reference internal\" href=\"/en/5.1/intro/tutorial03/\"><span class=\"doc\">Tutorial 3</span></a>.</p>\n<p>Note that any of Django’s default admin templates can be overridden. To\noverride a template, do the same thing you did with <code class=\"docutils literal notranslate\"><span class=\"pre\">base_site.html</span></code> – copy\nit from the default directory into your custom directory, and make changes.</p>\n</section>\n<section id=\"customizing-your-application-s-templates\">\n<h3>Customizing your <em>application’s</em> templates<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>Astute readers will ask: But if <a class=\"reference internal\" href=\"/en/5.1/ref/settings/#std-setting-TEMPLATES-DIRS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">DIRS</span></code></a> was empty by\ndefault, how was Django finding the default admin templates? The answer is\nthat, since <a class=\"reference internal\" href=\"/en/5.1/ref/settings/#std-setting-TEMPLATES-APP_DIRS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">APP_DIRS</span></code></a> is set to <code class=\"docutils literal notranslate\"><span class=\"pre\">True</span></code>,\nDjango automatically looks for a <code class=\"docutils literal notranslate\"><span class=\"pre\">templates/</span></code> subdirectory within each\napplication package, for use as a fallback (don’t forget that\n<code class=\"docutils literal notranslate\"><span class=\"pre\">django.contrib.admin</span></code> is an application).</p>\n<p>Our poll application is not very complex and doesn’t need custom admin\ntemplates. But if it grew more sophisticated and required modification of\nDjango’s standard admin templates for some of its functionality, it would be\nmore sensible to modify the <em>application’s</em> templates, rather than those in the\n<em>project</em>. That way, you could include the polls application in any new project\nand be assured that it would find the custom templates it needed.</p>\n<p>See the <a class=\"reference internal\" href=\"/en/5.1/topics/templates/#template-loading\"><span class=\"std std-ref\">template loading documentation</span></a> for more\ninformation about how Django finds its templates.</p>\n</section>\n</section>\n<section id=\"customize-the-admin-index-page\">\n<h2>Customize the admin index page<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>On a similar note, you might want to customize the look and feel of the Django\nadmin index page.</p>\n<p>By default, it displays all the apps in <a class=\"reference internal\" href=\"/en/5.1/ref/settings/#std-setting-INSTALLED_APPS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">INSTALLED_APPS</span></code></a> that have been\nregistered with the admin application, in alphabetical order. You may want to\nmake significant changes to the layout. After all, the index is probably the\nmost important page of the admin, and it should be easy to use.</p>\n<p>The template to customize is <code class=\"docutils literal notranslate\"><span class=\"pre\">admin/index.html</span></code>. (Do the same as with\n<code class=\"docutils literal notranslate\"><span class=\"pre\">admin/base_site.html</span></code> in the previous section – copy it from the default\ndirectory to your custom template directory). Edit the file, and you’ll see it\nuses a template variable called <code class=\"docutils literal notranslate\"><span class=\"pre\">app_list</span></code>. That variable contains every\ninstalled Django app. Instead of using that, you can hard-code links to\nobject-specific admin pages in whatever way you think is best.</p>\n<p>When you’re comfortable with the admin, read <a class=\"reference internal\" href=\"/en/5.1/intro/tutorial08/\"><span class=\"doc\">part 8 of this\ntutorial</span></a> to learn how to use third-party packages.</p>\n</section>","rootId":"writing-your-first-django-app-part-7","toc":[{"title":"Customize the admin form","anchor":"customize-the-admin-form","children":[]},{"title":"Adding related objects","anchor":"adding-related-objects","children":[]},{"title":"Customize the admin change list","anchor":"customize-the-admin-change-list","children":[]},{"title":"Customize the admin look and feel","anchor":"customize-the-admin-look-and-feel","children":[{"title":"Customizing your project’s templates","anchor":"customizing-your-project-s-templates","children":[]},{"title":"Customizing your application’s templates","anchor":"customizing-your-application-s-templates","children":[]}]},{"title":"Customize the admin index page","anchor":"customize-the-admin-index-page","children":[]}],"breadcrumbs":[{"docname":"intro/index","title":"Getting started","url":"/en/5.1/intro/"}],"prev":{"docname":"intro/tutorial06","title":"Writing your first Django app, part 6","url":"/en/5.1/intro/tutorial06/"},"next":{"docname":"intro/tutorial08","title":"Writing your first Django app, part 8","url":"/en/5.1/intro/tutorial08/"},"formats":{"html":"/en/5.1/intro/tutorial07/","markdown":"/en/5.1/intro/tutorial07.md","json":"/en/5.1/intro/tutorial07.json"},"source":"https://github.com/django/django/blob/stable/5.1.x/docs/intro/tutorial07.txt","official":"https://docs.djangoproject.com/en/5.1/intro/tutorial07/","inVersions":["dev","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","1.11","1.10","1.9"],"inLocales":["en","zh-hans","fr","ja","id","it","pt-br","ko","es","el","pl"]}