| ===================================== |
| Writing your first Django app, part 2 |
| ===================================== |
| |
| This tutorial begins where `Tutorial 1`_ left off. We're continuing the Web-poll |
| application and will focus on Django's automatically-generated admin site. |
| |
| .. _Tutorial 1: ../tutorial1/ |
| |
| .. admonition:: Philosophy |
| |
| Generating admin sites for your staff or clients to add, change and delete |
| content is tedious work that doesn't require much creativity. For that reason, |
| Django entirely automates creation of admin interfaces for models. |
| |
| Django was written in a newsroom environment, with a very clear separation |
| between "content publishers" and the "public" site. Site managers use the |
| system to add news stories, events, sports scores, etc., and that content is |
| displayed on the public site. Django solves the problem of creating a unified |
| interface for site administrators to edit content. |
| |
| The admin isn't necessarily intended to be used by site visitors; it's for site |
| managers. |
| |
| Activate the admin site |
| ======================= |
| |
| The Django admin site is not activated by default -- it's an opt-in thing. To |
| activate the admin site for your installation, do these three things: |
| |
| * Add ``"django.contrib.admin"`` to your ``INSTALLED_APPS`` setting. |
| * Run ``python manage.py syncdb``. Since you have added a new application |
| to ``INSTALLED_APPS``, the database tables need to be updated. |
| * Edit your ``mysite/urls.py`` file and uncomment the line below |
| "Uncomment this for admin:". This file is a URLconf; we'll dig into |
| URLconfs in the next tutorial. For now, all you need to know is that it |
| maps URL roots to applications. |
| |
| Start the development server |
| ============================ |
| |
| Let's start the development server and explore the admin site. |
| |
| Recall from Tutorial 1 that you start the development server like so:: |
| |
| python manage.py runserver |
| |
| Now, open a Web browser and go to "/admin/" on your local domain -- e.g., |
| http://127.0.0.1:8000/admin/. You should see the admin's login screen: |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin01.png |
| :alt: Django admin login screen |
| |
| Enter the admin site |
| ==================== |
| |
| Now, try logging in. (You created a superuser account in the first part of this |
| tutorial, remember?) You should see the Django admin index page: |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin02t.png |
| :alt: Django admin index page |
| :target: http://media.djangoproject.com/img/doc/tutorial/admin02.png |
| |
| By default, you should see two types of editable content: groups and users. |
| These are core features Django ships with by default. |
| |
| .. _"I can't log in" questions: ../faq/#the-admin-site |
| |
| Make the poll app modifiable in the admin |
| ========================================= |
| |
| But where's our poll app? It's not displayed on the admin index page. |
| |
| Just one thing to do: We need to specify in the ``Poll`` model that ``Poll`` |
| objects have an admin interface. Edit the ``mysite/polls/models.py`` file and |
| make the following change to add an inner ``Admin`` class:: |
| |
| class Poll(models.Model): |
| # ... |
| class Admin: |
| pass |
| |
| The ``class Admin`` will contain all the settings that control how this model |
| appears in the Django admin. All the settings are optional, however, so |
| creating an empty class means "give this object an admin interface using |
| all the default options." |
| |
| Now reload the Django admin page to see your changes. Note that you don't have |
| to restart the development server -- the server will auto-reload your project, |
| so any modifications code will be seen immediately in your browser. |
| |
| Explore the free admin functionality |
| ==================================== |
| |
| Now that ``Poll`` has the inner ``Admin`` class, Django knows that it should be |
| displayed on the admin index page: |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin03t.png |
| :alt: Django admin index page, now with polls displayed |
| :target: http://media.djangoproject.com/img/doc/tutorial/admin03.png |
| |
| Click "Polls." Now you're at the "change list" page for polls. This page |
| displays all the polls in the database and lets you choose one to change it. |
| There's the "What's up?" poll we created in the first tutorial: |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin04t.png |
| :alt: Polls change list page |
| :target: http://media.djangoproject.com/img/doc/tutorial/admin04.png |
| |
| Click the "What's up?" poll to edit it: |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin05t.png |
| :alt: Editing form for poll object |
| :target: http://media.djangoproject.com/img/doc/tutorial/admin05.png |
| |
| Things to note here: |
| |
| * The form is automatically generated from the Poll model. |
| * The different model field types (``models.DateTimeField``, ``models.CharField``) |
| correspond to the appropriate HTML input widget. Each type of field knows |
| how to display itself in the Django admin. |
| * Each ``DateTimeField`` gets free JavaScript shortcuts. Dates get a "Today" |
| shortcut and calendar popup, and times get a "Now" shortcut and a convenient |
| popup that lists commonly entered times. |
| |
| The bottom part of the page gives you a couple of options: |
| |
| * Save -- Saves changes and returns to the change-list page for this type of |
| object. |
| * Save and continue editing -- Saves changes and reloads the admin page for |
| this object. |
| * Save and add another -- Saves changes and loads a new, blank form for this |
| type of object. |
| * Delete -- Displays a delete confirmation page. |
| |
| Change the "Date published" by clicking the "Today" and "Now" shortcuts. Then |
| click "Save and continue editing." Then click "History" in the upper right. |
| You'll see a page listing all changes made to this object via the Django admin, |
| with the timestamp and username of the person who made the change: |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin06t.png |
| :alt: History page for poll object |
| :target: http://media.djangoproject.com/img/doc/tutorial/admin06.png |
| |
| Customize the admin form |
| ======================== |
| |
| Take a few minutes to marvel at all the code you didn't have to write. |
| |
| Let's customize this a bit. We can reorder the fields by explicitly adding a |
| ``fields`` parameter to ``Admin``:: |
| |
| class Admin: |
| fields = ( |
| (None, {'fields': ('pub_date', 'question')}), |
| ) |
| |
| That made the "Publication date" show up first instead of second: |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin07.png |
| :alt: Fields have been reordered |
| |
| This isn't impressive with only two fields, but for admin forms with dozens |
| of fields, choosing an intuitive order is an important usability detail. |
| |
| And speaking of forms with dozens of fields, you might want to split the form |
| up into fieldsets:: |
| |
| class Admin: |
| fields = ( |
| (None, {'fields': ('question',)}), |
| ('Date information', {'fields': ('pub_date',)}), |
| ) |
| |
| The first element of each tuple in ``fields`` is the title of the fieldset. |
| Here's what our form looks like now: |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin08t.png |
| :alt: Form has fieldsets now |
| :target: http://media.djangoproject.com/img/doc/tutorial/admin08.png |
| |
| You can assign arbitrary HTML classes to each fieldset. Django provides a |
| ``"collapse"`` class that displays a particular fieldset initially collapsed. |
| This is useful when you have a long form that contains a number of fields that |
| aren't commonly used:: |
| |
| class Admin: |
| fields = ( |
| (None, {'fields': ('question',)}), |
| ('Date information', {'fields': ('pub_date',), 'classes': 'collapse'}), |
| ) |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin09.png |
| :alt: Fieldset is initially collapsed |
| |
| Adding related objects |
| ====================== |
| |
| OK, we have our Poll admin page. But a ``Poll`` has multiple ``Choices``, and |
| the admin page doesn't display choices. |
| |
| Yet. |
| |
| There are two ways to solve this problem. The first is to give the ``Choice`` |
| model its own inner ``Admin`` class, just as we did with ``Poll``. Here's what |
| that would look like:: |
| |
| class Choice(models.Model): |
| # ... |
| class Admin: |
| pass |
| |
| Now "Choices" is an available option in the Django admin. The "Add choice" form |
| looks like this: |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin10.png |
| :alt: Choice admin page |
| |
| In that form, the "Poll" field is a select box containing every poll in the |
| database. Django knows that a ``ForeignKey`` should be represented in the admin |
| as a ``<select>`` box. In our case, only one poll exists at this point. |
| |
| Also note the "Add Another" link next to "Poll." Every object with a ForeignKey |
| relationship to another gets this for free. When you click "Add Another," you'll |
| get a popup window with the "Add poll" form. If you add a poll in that window |
| and click "Save," Django will save the poll to the database and dynamically add |
| it as the selected choice on the "Add choice" form you're looking at. |
| |
| But, really, this is an inefficient way of adding Choice objects to the system. |
| It'd be better if you could add a bunch of Choices directly when you create the |
| Poll object. Let's make that happen. |
| |
| Remove the ``Admin`` for the Choice model. Then, edit the ``ForeignKey(Poll)`` |
| field like so:: |
| |
| poll = models.ForeignKey(Poll, edit_inline=models.STACKED, num_in_admin=3) |
| |
| This tells Django: "Choice objects are edited on the Poll admin page. By |
| default, provide enough fields for 3 Choices." |
| |
| Then change the other fields in ``Choice`` to give them ``core=True``:: |
| |
| choice = models.CharField(maxlength=200, core=True) |
| votes = models.IntegerField(core=True) |
| |
| This tells Django: "When you edit a Choice on the Poll admin page, the 'choice' |
| and 'votes' fields are required. The presence of at least one of them signifies |
| the addition of a new Choice object, and clearing both of them signifies the |
| deletion of that existing Choice object." |
| |
| Load the "Add poll" page to see how that looks: |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin11t.png |
| :alt: Add poll page now has choices on it |
| :target: http://media.djangoproject.com/img/doc/tutorial/admin11.png |
| |
| It works like this: There are three slots for related Choices -- as specified |
| by ``num_in_admin`` -- but each time you come back to the "Change" page for an |
| already-created object, you get one extra slot. (This means there's no |
| hard-coded limit on how many related objects can be added.) If you wanted space |
| for three extra Choices each time you changed the poll, you'd use |
| ``num_extra_on_change=3``. |
| |
| One small problem, though. It takes a lot of screen space to display all the |
| fields for entering related Choice objects. For that reason, Django offers an |
| alternate way of displaying inline related objects:: |
| |
| poll = models.ForeignKey(Poll, edit_inline=models.TABULAR, num_in_admin=3) |
| |
| With that ``edit_inline=models.TABULAR`` (instead of ``models.STACKED``), the |
| related objects are displayed in a more compact, table-based format: |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin12.png |
| :alt: Add poll page now has more compact choices |
| |
| Customize the admin change list |
| =============================== |
| |
| Now that the Poll admin page is looking good, let's make some tweaks to the |
| "change list" page -- the one that displays all the polls in the system. |
| |
| Here's what it looks like at this point: |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin04t.png |
| :alt: Polls change list page |
| :target: http://media.djangoproject.com/img/doc/tutorial/admin04.png |
| |
| By default, Django displays the ``str()`` of each object. But sometimes it'd |
| be more helpful if we could display individual fields. To do that, use the |
| ``list_display`` option, which is a tuple of field names to display, as columns, |
| on the change list page for the object:: |
| |
| class Poll(models.Model): |
| # ... |
| class Admin: |
| # ... |
| list_display = ('question', 'pub_date') |
| |
| Just for good measure, let's also include the ``was_published_today`` custom |
| method from Tutorial 1:: |
| |
| list_display = ('question', 'pub_date', 'was_published_today') |
| |
| Now the poll change list page looks like this: |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin13t.png |
| :alt: Polls change list page, updated |
| :target: http://media.djangoproject.com/img/doc/tutorial/admin13.png |
| |
| You can click on the column headers to sort by those values -- except in the |
| case of the ``was_published_today`` header, because sorting by the output of |
| an arbitrary method is not supported. Also note that the column header for |
| ``was_published_today`` is, by default, the name of the method (with |
| underscores replaced with spaces). But you can change that by giving that |
| method a ``short_description`` attribute:: |
| |
| def was_published_today(self): |
| return self.pub_date.date() == datetime.date.today() |
| was_published_today.short_description = 'Published today?' |
| |
| |
| Let's add another improvement to the Poll change list page: Filters. Add the |
| following line to ``Poll.admin``:: |
| |
| list_filter = ['pub_date'] |
| |
| That adds a "Filter" sidebar that lets people filter the change list by the |
| ``pub_date`` field: |
| |
| .. image:: http://media.djangoproject.com/img/doc/tutorial/admin14t.png |
| :alt: Polls change list page, updated |
| :target: http://media.djangoproject.com/img/doc/tutorial/admin14.png |
| |
| The type of filter displayed depends on the type of field you're filtering on. |
| Because ``pub_date`` is a DateTimeField, Django knows to give the default |
| filter options for DateTimeFields: "Any date," "Today," "Past 7 days," |
| "This month," "This year." |
| |
| This is shaping up well. Let's add some search capability:: |
| |
| search_fields = ['question'] |
| |
| That adds a search box at the top of the change list. When somebody enters |
| search terms, Django will search the ``question`` field. You can use as many |
| fields as you'd like -- although because it uses a ``LIKE`` query behind the |
| scenes, keep it reasonable, to keep your database happy. |
| |
| Finally, because Poll objects have dates, it'd be convenient to be able to |
| drill down by date. Add this line:: |
| |
| date_hierarchy = 'pub_date' |
| |
| That adds hierarchical navigation, by date, to the top of the change list page. |
| At top level, it displays all available years. Then it drills down to months |
| and, ultimately, days. |
| |
| Now's also a good time to note that change lists give you free pagination. The |
| default is to display 50 items per page. Change-list pagination, search boxes, |
| filters, date-hierarchies and column-header-ordering all work together like you |
| think they should. |
| |
| Customize the admin look and feel |
| ================================= |
| |
| Clearly, having "Django administration" and "example.com" at the top of each |
| admin page is ridiculous. It's just placeholder text. |
| |
| That's easy to change, though, using Django's template system. The Django admin |
| is powered by Django itself, and its interfaces use Django's own template |
| system. (How meta!) |
| |
| Open your settings file (``mysite/settings.py``, remember) and look at the |
| ``TEMPLATE_DIRS`` setting. ``TEMPLATE_DIRS`` is a tuple of filesystem |
| directories to check when loading Django templates. It's a search path. |
| |
| By default, ``TEMPLATE_DIRS`` is empty. So, let's add a line to it, to tell |
| Django where our templates live:: |
| |
| TEMPLATE_DIRS = ( |
| "/home/my_username/mytemplates", # Change this to your own directory. |
| ) |
| |
| Now copy the template ``admin/base_site.html`` from within the default Django |
| admin template directory (``django/contrib/admin/templates``) into an ``admin`` |
| subdirectory of whichever directory you're using in ``TEMPLATE_DIRS``. For |
| example, if your ``TEMPLATE_DIRS`` includes ``"/home/my_username/mytemplates"``, |
| as above, then copy ``django/contrib/admin/templates/admin/base_site.html`` to |
| ``/home/my_username/mytemplates/admin/base_site.html``. Don't forget that |
| ``admin`` subdirectory. |
| |
| Then, just edit the file and replace the generic Django text with your own |
| site's name and URL as you see fit. |
| |
| Note that any of Django's default admin templates can be overridden. To |
| override a template, just do the same thing you did with ``base_site.html`` -- |
| copy it from the default directory into your custom directory, and make |
| changes. |
| |
| Astute readers will ask: But if ``TEMPLATE_DIRS`` was empty by default, how was |
| Django finding the default admin templates? The answer is that, by default, |
| Django automatically looks for a ``templates/`` subdirectory within each app |
| package, for use as a fallback. See the `loader types documentation`_ for full |
| information. |
| |
| .. _loader types documentation: ../templates_python/#loader-types |
| |
| Customize the admin index page |
| ============================== |
| |
| On a similar note, you might want to customize the look and feel of the Django |
| admin index page. |
| |
| By default, it displays all available apps, according to your ``INSTALLED_APPS`` |
| setting. But the order in which it displays things is random, and you may want |
| to make significant changes to the layout. After all, the index is probably the |
| most important page of the admin, and it should be easy to use. |
| |
| The template to customize is ``admin/index.html``. (Do the same as with |
| ``admin/base_site.html`` in the previous section -- copy it from the default |
| directory to your custom template directory.) Edit the file, and you'll see it |
| uses a template tag called ``{% get_admin_app_list as app_list %}``. That's the |
| magic that retrieves every installed Django app. Instead of using that, you can |
| hard-code links to object-specific admin pages in whatever way you think is |
| best. |
| |
| Django offers another shortcut in this department. Run the command |
| ``python manage.py adminindex polls`` to get a chunk of template code for |
| inclusion in the admin index template. It's a useful starting point. |
| |
| For full details on customizing the look and feel of the Django admin site in |
| general, see the `Django admin CSS guide`_. |
| |
| When you're comfortable with the admin site, read `part 3 of this tutorial`_ to |
| start working on public poll views. |
| |
| .. _Django admin CSS guide: ../admin_css/ |
| .. _part 3 of this tutorial: ../tutorial3/ |