123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494 |
- =====================================
- Writing your first Django app, part 2
- =====================================
- This tutorial begins where :doc:`Tutorial 1 </intro/tutorial01>` left off. We're
- continuing the Web-poll application and will focus on Django's
- automatically-generated admin site.
- .. 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 intended to be used by site visitors. It's for site
- managers.
- Start the development server
- ============================
- The Django admin site is activated by default. Let's start the development
- server and explore it.
- Recall from Tutorial 1 that you start the development server like so:
- .. code-block:: bash
- 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:: _images/admin01.png
- :alt: Django admin login screen
- Since :doc:`translation </topics/i18n/translation>` is turned on by default,
- the login screen may be displayed in your own language, depending on your
- browser's settings and on whether Django has a translation for this language.
- .. admonition:: Doesn't match what you see?
- If at this point, instead of the above login page, you get an error
- page reporting something like::
- ImportError at /admin/
- cannot import name patterns
- ...
- then you're probably using a version of Django that doesn't match this
- tutorial version. You'll want to either switch to the older tutorial or the
- newer Django version.
- Enter the admin site
- ====================
- Now, try logging in. You created a superuser account in the first part of this
- tutorial, remember? If you didn't create one or forgot the password you can
- :ref:`create another one <topics-auth-creating-superusers>`.
- You should see the Django admin index page:
- .. image:: _images/admin02t.png
- :alt: Django admin index page
- You should see a few types of editable content: groups and users. They are
- provided by :mod:`django.contrib.auth`, the authentication framework shipped
- by Django.
- 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 tell the admin that ``Poll``
- objects have an admin interface. To do this, open the :file:`polls/admin.py`
- file, and edit it to look like this::
- from django.contrib import admin
- from polls.models import Poll
- admin.site.register(Poll)
- Explore the free admin functionality
- ====================================
- Now that we've registered ``Poll``, Django knows that it should be displayed on
- the admin index page:
- .. image:: _images/admin03t.png
- :alt: Django admin index page, now with polls displayed
- 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:: _images/admin04t.png
- :alt: Polls change list page
- Click the "What's up?" poll to edit it:
- .. image:: _images/admin05t.png
- :alt: Editing form for poll object
- Things to note here:
- * The form is automatically generated from the ``Poll`` model.
- * The different model field types (:class:`~django.db.models.DateTimeField`,
- :class:`~django.db.models.CharField`) correspond to the appropriate HTML
- input widget. Each type of field knows how to display itself in the Django
- admin.
- * Each :class:`~django.db.models.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.
- If the value of "Date published" doesn't match the time when you created the
- poll in Tutorial 1, it probably means you forgot to set the correct value for
- the :setting:`TIME_ZONE` setting. Change it, reload the page and check that
- the correct value appears.
- 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:: _images/admin06t.png
- :alt: History page for poll object
- Customize the admin form
- ========================
- Take a few minutes to marvel at all the code you didn't have to write. By
- registering the Poll model with ``admin.site.register(Poll)``, Django was able
- to construct a default form representation. Often, you'll want to customize how
- the admin form looks and works. You'll do this by telling Django the options
- you want when you register the object.
- Let's see how this works by re-ordering the fields on the edit form. Replace
- the ``admin.site.register(Poll)`` line with::
- from django.contrib import admin
- from polls.models import Poll
- class PollAdmin(admin.ModelAdmin):
- fields = ['pub_date', 'question']
- admin.site.register(Poll, PollAdmin)
- You'll follow this pattern -- create a model admin object, then pass it as the
- second argument to ``admin.site.register()`` -- any time you need to change the
- admin options for an object.
- This particular change above makes the "Publication date" come before the
- "Question" field:
- .. image:: _images/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::
- from django.contrib import admin
- from polls.models import Poll
- class PollAdmin(admin.ModelAdmin):
- fieldsets = [
- (None, {'fields': ['question']}),
- ('Date information', {'fields': ['pub_date']}),
- ]
- admin.site.register(Poll, PollAdmin)
- The first element of each tuple in ``fieldsets`` is the title of the fieldset.
- Here's what our form looks like now:
- .. image:: _images/admin08t.png
- :alt: Form has fieldsets now
- 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::
- from django.contrib import admin
- from polls.models import Poll
- class PollAdmin(admin.ModelAdmin):
- fieldsets = [
- (None, {'fields': ['question']}),
- ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
- ]
- .. image:: _images/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 register ``Choice``
- with the admin just as we did with ``Poll``. That's easy::
- from django.contrib import admin
- from polls.models import Choice
- admin.site.register(Choice)
- Now "Choices" is an available option in the Django admin. The "Add choice" form
- looks like this:
- .. image:: _images/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 :class:`~django.db.models.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 ``register()`` call for the ``Choice`` model. Then, edit the ``Poll``
- registration code to read::
- from django.contrib import admin
- from polls.models import Choice, Poll
- class ChoiceInline(admin.StackedInline):
- model = Choice
- extra = 3
- class PollAdmin(admin.ModelAdmin):
- fieldsets = [
- (None, {'fields': ['question']}),
- ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
- ]
- inlines = [ChoiceInline]
- admin.site.register(Poll, PollAdmin)
- This tells Django: "``Choice`` objects are edited on the ``Poll`` admin page. By
- default, provide enough fields for 3 choices."
- Load the "Add poll" page to see how that looks:
- .. image:: _images/admin11t.png
- :alt: Add poll page now has choices on it
- It works like this: There are three slots for related Choices -- as specified
- by ``extra`` -- and each time you come back to the "Change" page for an
- already-created object, you get another three extra slots.
- At the end of the three current slots you will find an "Add another Choice"
- link. If you click on it, a new slot will be added. If you want to remove the
- added slot, you can click on the X to the top right of the added slot. Note
- that you can't remove the original three slots. This image shows an added slot:
- .. image:: _images/admin15t.png
- :alt: Additional slot added dynamically
- 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 a
- tabular way of displaying inline related objects; you just need to change
- the ``ChoiceInline`` declaration to read::
- class ChoiceInline(admin.TabularInline):
- #...
- With that ``TabularInline`` (instead of ``StackedInline``), the
- related objects are displayed in a more compact, table-based format:
- .. image:: _images/admin12t.png
- :alt: Add poll page now has more compact choices
- Note that there is an extra "Delete?" column that allows removing rows added
- using the "Add Another Choice" button and rows that have already been saved.
- 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:: _images/admin04t.png
- :alt: Polls change list page
- 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`` admin option, which is a tuple of field names to display, as
- columns, on the change list page for the object::
- class PollAdmin(admin.ModelAdmin):
- # ...
- list_display = ('question', 'pub_date')
- Just for good measure, let's also include the ``was_published_recently`` custom
- method from Tutorial 1::
- class PollAdmin(admin.ModelAdmin):
- # ...
- list_display = ('question', 'pub_date', 'was_published_recently')
- Now the poll change list page looks like this:
- .. image:: _images/admin13t.png
- :alt: Polls change list page, updated
- You can click on the column headers to sort by those values -- except in the
- case of the ``was_published_recently`` header, because sorting by the output
- of an arbitrary method is not supported. Also note that the column header for
- ``was_published_recently`` is, by default, the name of the method (with
- underscores replaced with spaces), and that each line contains the string
- representation of the output.
- You can improve that by giving that method (in :file:`polls/models.py`) a few
- attributes, as follows::
- class Poll(models.Model):
- # ...
- def was_published_recently(self):
- return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
- was_published_recently.admin_order_field = 'pub_date'
- was_published_recently.boolean = True
- was_published_recently.short_description = 'Published recently?'
- Edit your :file:`polls/admin.py` file again and add an improvement to the Poll
- change list page: Filters. Add the following line to ``PollAdmin``::
- list_filter = ['pub_date']
- That adds a "Filter" sidebar that lets people filter the change list by the
- ``pub_date`` field:
- .. image:: _images/admin14t.png
- :alt: Polls change list page, updated
- The type of filter displayed depends on the type of field you're filtering on.
- Because ``pub_date`` is a :class:`~django.db.models.DateTimeField`, Django
- knows to give appropriate filter options: "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.
- Now's also a good time to note that change lists give you free pagination. The
- default is to display 100 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" 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.
- .. _ref-customizing-your-projects-templates:
- Customizing your *project's* templates
- --------------------------------------
- Create a ``templates`` directory in your project directory. Templates can
- live anywhere on your filesystem that Django can access. (Django runs as
- whatever user your server runs.) However, keeping your templates within the
- project is a good convention to follow.
- Open your settings file (:file:`mysite/settings.py`, remember) and add a
- :setting:`TEMPLATE_DIRS` setting::
- TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')]
- :setting:`TEMPLATE_DIRS` is an iterable of filesystem directories to check when
- loading Django templates; it's a search path.
- Now create a directory called ``admin`` inside ``templates``, and copy the
- template ``admin/base_site.html`` from within the default Django admin
- template directory in the source code of Django itself
- (``django/contrib/admin/templates``) into that directory.
- .. admonition:: Where are the Django source files?
- If you have difficulty finding where the Django source files are located
- on your system, run the following command:
- .. code-block:: bash
- python -c "
- import sys
- sys.path = sys.path[1:]
- import django
- print(django.__path__)"
- Then, just edit the file and replace the generic Django text with your own
- site's name as you see fit.
- This template file contains lots of text like ``{% block branding %}``
- and ``{{ title }}``. The ``{%`` and ``{{`` tags are part of Django's
- template language. When Django renders ``admin/base_site.html``, this
- template language will be evaluated to produce the final HTML page.
- Don't worry if you can't make any sense of the template right now --
- we'll delve into Django's templating language in Tutorial 3.
- 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.
- Customizing your *application's* templates
- ------------------------------------------
- Astute readers will ask: But if :setting:`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 application package, for use as a fallback (don't forget that
- ``django.contrib.admin`` is an application).
- Our poll application is not very complex and doesn't need custom admin
- templates. But if it grew more sophisticated and required modification of
- Django's standard admin templates for some of its functionality, it would be
- more sensible to modify the *application's* templates, rather than those in the
- *project*. That way, you could include the polls application in any new project
- and be assured that it would find the custom templates it needed.
- See the :ref:`template loader documentation <template-loaders>` for more
- information about how Django finds its templates.
- 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 the apps in :setting:`INSTALLED_APPS` that have been
- registered with the admin application, in alphabetical order. 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 variable called ``app_list``. That variable contains 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. Again,
- don't worry if you can't understand the template language -- we'll cover that
- in more detail in Tutorial 3.
- When you're comfortable with the admin site, read :doc:`part 3 of this tutorial
- </intro/tutorial03>` to start working on public poll views.
|