| 1 |
===================================== |
|---|
| 2 |
Writing your first Django app, part 2 |
|---|
| 3 |
===================================== |
|---|
| 4 |
|
|---|
| 5 |
This tutorial begins where `Tutorial 1`_ left off. We're continuing the Web-poll |
|---|
| 6 |
application and will focus on Django's automatically-generated admin site. |
|---|
| 7 |
|
|---|
| 8 |
.. _Tutorial 1: ../tutorial01/ |
|---|
| 9 |
|
|---|
| 10 |
.. admonition:: Philosophy |
|---|
| 11 |
|
|---|
| 12 |
Generating admin sites for your staff or clients to add, change and delete |
|---|
| 13 |
content is tedious work that doesn't require much creativity. For that reason, |
|---|
| 14 |
Django entirely automates creation of admin interfaces for models. |
|---|
| 15 |
|
|---|
| 16 |
Django was written in a newsroom environment, with a very clear separation |
|---|
| 17 |
between "content publishers" and the "public" site. Site managers use the |
|---|
| 18 |
system to add news stories, events, sports scores, etc., and that content is |
|---|
| 19 |
displayed on the public site. Django solves the problem of creating a unified |
|---|
| 20 |
interface for site administrators to edit content. |
|---|
| 21 |
|
|---|
| 22 |
The admin isn't necessarily intended to be used by site visitors; it's for site |
|---|
| 23 |
managers. |
|---|
| 24 |
|
|---|
| 25 |
Activate the admin site |
|---|
| 26 |
======================= |
|---|
| 27 |
|
|---|
| 28 |
The Django admin site is not activated by default -- it's an opt-in thing. To |
|---|
| 29 |
activate the admin site for your installation, do these three things: |
|---|
| 30 |
|
|---|
| 31 |
* Add ``"django.contrib.admin"`` to your ``INSTALLED_APPS`` setting. |
|---|
| 32 |
* Run ``python manage.py syncdb``. Since you have added a new application |
|---|
| 33 |
to ``INSTALLED_APPS``, the database tables need to be updated. |
|---|
| 34 |
* Edit your ``mysite/urls.py`` file and uncomment the line below |
|---|
| 35 |
"Uncomment this for admin:". This file is a URLconf; we'll dig into |
|---|
| 36 |
URLconfs in the next tutorial. For now, all you need to know is that it |
|---|
| 37 |
maps URL roots to applications. |
|---|
| 38 |
|
|---|
| 39 |
Start the development server |
|---|
| 40 |
============================ |
|---|
| 41 |
|
|---|
| 42 |
Let's start the development server and explore the admin site. |
|---|
| 43 |
|
|---|
| 44 |
Recall from Tutorial 1 that you start the development server like so:: |
|---|
| 45 |
|
|---|
| 46 |
python manage.py runserver |
|---|
| 47 |
|
|---|
| 48 |
Now, open a Web browser and go to "/admin/" on your local domain -- e.g., |
|---|
| 49 |
http://127.0.0.1:8000/admin/. You should see the admin's login screen: |
|---|
| 50 |
|
|---|
| 51 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin01.png |
|---|
| 52 |
:alt: Django admin login screen |
|---|
| 53 |
|
|---|
| 54 |
Enter the admin site |
|---|
| 55 |
==================== |
|---|
| 56 |
|
|---|
| 57 |
Now, try logging in. (You created a superuser account in the first part of this |
|---|
| 58 |
tutorial, remember?) You should see the Django admin index page: |
|---|
| 59 |
|
|---|
| 60 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin02t.png |
|---|
| 61 |
:alt: Django admin index page |
|---|
| 62 |
:target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin02.png |
|---|
| 63 |
|
|---|
| 64 |
You should see a few other types of editable content, including groups, users |
|---|
| 65 |
and sites. These are core features Django ships with by default. |
|---|
| 66 |
|
|---|
| 67 |
.. _"I can't log in" questions: ../faq/#the-admin-site |
|---|
| 68 |
|
|---|
| 69 |
Make the poll app modifiable in the admin |
|---|
| 70 |
========================================= |
|---|
| 71 |
|
|---|
| 72 |
But where's our poll app? It's not displayed on the admin index page. |
|---|
| 73 |
|
|---|
| 74 |
Just one thing to do: We need to specify in the ``Poll`` model that ``Poll`` |
|---|
| 75 |
objects have an admin interface. Edit the ``mysite/polls/models.py`` file and |
|---|
| 76 |
make the following change to add an inner ``Admin`` class:: |
|---|
| 77 |
|
|---|
| 78 |
class Poll(models.Model): |
|---|
| 79 |
# ... |
|---|
| 80 |
class Admin: |
|---|
| 81 |
pass |
|---|
| 82 |
|
|---|
| 83 |
The ``class Admin`` will contain all the settings that control how this model |
|---|
| 84 |
appears in the Django admin. All the settings are optional, however, so |
|---|
| 85 |
creating an empty class means "give this object an admin interface using |
|---|
| 86 |
all the default options." |
|---|
| 87 |
|
|---|
| 88 |
Now reload the Django admin page to see your changes. Note that you don't have |
|---|
| 89 |
to restart the development server -- the server will auto-reload your project, |
|---|
| 90 |
so any modifications code will be seen immediately in your browser. |
|---|
| 91 |
|
|---|
| 92 |
Explore the free admin functionality |
|---|
| 93 |
==================================== |
|---|
| 94 |
|
|---|
| 95 |
Now that ``Poll`` has the inner ``Admin`` class, Django knows that it should be |
|---|
| 96 |
displayed on the admin index page: |
|---|
| 97 |
|
|---|
| 98 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin03t.png |
|---|
| 99 |
:alt: Django admin index page, now with polls displayed |
|---|
| 100 |
:target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin03.png |
|---|
| 101 |
|
|---|
| 102 |
Click "Polls." Now you're at the "change list" page for polls. This page |
|---|
| 103 |
displays all the polls in the database and lets you choose one to change it. |
|---|
| 104 |
There's the "What's up?" poll we created in the first tutorial: |
|---|
| 105 |
|
|---|
| 106 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin04t.png |
|---|
| 107 |
:alt: Polls change list page |
|---|
| 108 |
:target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin04.png |
|---|
| 109 |
|
|---|
| 110 |
Click the "What's up?" poll to edit it: |
|---|
| 111 |
|
|---|
| 112 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin05t.png |
|---|
| 113 |
:alt: Editing form for poll object |
|---|
| 114 |
:target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin05.png |
|---|
| 115 |
|
|---|
| 116 |
Things to note here: |
|---|
| 117 |
|
|---|
| 118 |
* The form is automatically generated from the Poll model. |
|---|
| 119 |
* The different model field types (``models.DateTimeField``, ``models.CharField``) |
|---|
| 120 |
correspond to the appropriate HTML input widget. Each type of field knows |
|---|
| 121 |
how to display itself in the Django admin. |
|---|
| 122 |
* Each ``DateTimeField`` gets free JavaScript shortcuts. Dates get a "Today" |
|---|
| 123 |
shortcut and calendar popup, and times get a "Now" shortcut and a convenient |
|---|
| 124 |
popup that lists commonly entered times. |
|---|
| 125 |
|
|---|
| 126 |
The bottom part of the page gives you a couple of options: |
|---|
| 127 |
|
|---|
| 128 |
* Save -- Saves changes and returns to the change-list page for this type of |
|---|
| 129 |
object. |
|---|
| 130 |
* Save and continue editing -- Saves changes and reloads the admin page for |
|---|
| 131 |
this object. |
|---|
| 132 |
* Save and add another -- Saves changes and loads a new, blank form for this |
|---|
| 133 |
type of object. |
|---|
| 134 |
* Delete -- Displays a delete confirmation page. |
|---|
| 135 |
|
|---|
| 136 |
Change the "Date published" by clicking the "Today" and "Now" shortcuts. Then |
|---|
| 137 |
click "Save and continue editing." Then click "History" in the upper right. |
|---|
| 138 |
You'll see a page listing all changes made to this object via the Django admin, |
|---|
| 139 |
with the timestamp and username of the person who made the change: |
|---|
| 140 |
|
|---|
| 141 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin06t.png |
|---|
| 142 |
:alt: History page for poll object |
|---|
| 143 |
:target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin06.png |
|---|
| 144 |
|
|---|
| 145 |
Customize the admin form |
|---|
| 146 |
======================== |
|---|
| 147 |
|
|---|
| 148 |
Take a few minutes to marvel at all the code you didn't have to write. |
|---|
| 149 |
|
|---|
| 150 |
Let's customize this a bit. We can reorder the fields by explicitly adding a |
|---|
| 151 |
``fields`` parameter to ``Admin``:: |
|---|
| 152 |
|
|---|
| 153 |
class Admin: |
|---|
| 154 |
fields = ( |
|---|
| 155 |
(None, {'fields': ('pub_date', 'question')}), |
|---|
| 156 |
) |
|---|
| 157 |
|
|---|
| 158 |
That made the "Publication date" show up first instead of second: |
|---|
| 159 |
|
|---|
| 160 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin07.png |
|---|
| 161 |
:alt: Fields have been reordered |
|---|
| 162 |
|
|---|
| 163 |
This isn't impressive with only two fields, but for admin forms with dozens |
|---|
| 164 |
of fields, choosing an intuitive order is an important usability detail. |
|---|
| 165 |
|
|---|
| 166 |
And speaking of forms with dozens of fields, you might want to split the form |
|---|
| 167 |
up into fieldsets:: |
|---|
| 168 |
|
|---|
| 169 |
class Admin: |
|---|
| 170 |
fields = ( |
|---|
| 171 |
(None, {'fields': ('question',)}), |
|---|
| 172 |
('Date information', {'fields': ('pub_date',)}), |
|---|
| 173 |
) |
|---|
| 174 |
|
|---|
| 175 |
The first element of each tuple in ``fields`` is the title of the fieldset. |
|---|
| 176 |
Here's what our form looks like now: |
|---|
| 177 |
|
|---|
| 178 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin08t.png |
|---|
| 179 |
:alt: Form has fieldsets now |
|---|
| 180 |
:target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin08.png |
|---|
| 181 |
|
|---|
| 182 |
You can assign arbitrary HTML classes to each fieldset. Django provides a |
|---|
| 183 |
``"collapse"`` class that displays a particular fieldset initially collapsed. |
|---|
| 184 |
This is useful when you have a long form that contains a number of fields that |
|---|
| 185 |
aren't commonly used:: |
|---|
| 186 |
|
|---|
| 187 |
class Admin: |
|---|
| 188 |
fields = ( |
|---|
| 189 |
(None, {'fields': ('question',)}), |
|---|
| 190 |
('Date information', {'fields': ('pub_date',), 'classes': 'collapse'}), |
|---|
| 191 |
) |
|---|
| 192 |
|
|---|
| 193 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin09.png |
|---|
| 194 |
:alt: Fieldset is initially collapsed |
|---|
| 195 |
|
|---|
| 196 |
Adding related objects |
|---|
| 197 |
====================== |
|---|
| 198 |
|
|---|
| 199 |
OK, we have our Poll admin page. But a ``Poll`` has multiple ``Choices``, and |
|---|
| 200 |
the admin page doesn't display choices. |
|---|
| 201 |
|
|---|
| 202 |
Yet. |
|---|
| 203 |
|
|---|
| 204 |
There are two ways to solve this problem. The first is to give the ``Choice`` |
|---|
| 205 |
model its own inner ``Admin`` class, just as we did with ``Poll``. Here's what |
|---|
| 206 |
that would look like:: |
|---|
| 207 |
|
|---|
| 208 |
class Choice(models.Model): |
|---|
| 209 |
# ... |
|---|
| 210 |
class Admin: |
|---|
| 211 |
pass |
|---|
| 212 |
|
|---|
| 213 |
Now "Choices" is an available option in the Django admin. The "Add choice" form |
|---|
| 214 |
looks like this: |
|---|
| 215 |
|
|---|
| 216 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin10.png |
|---|
| 217 |
:alt: Choice admin page |
|---|
| 218 |
|
|---|
| 219 |
In that form, the "Poll" field is a select box containing every poll in the |
|---|
| 220 |
database. Django knows that a ``ForeignKey`` should be represented in the admin |
|---|
| 221 |
as a ``<select>`` box. In our case, only one poll exists at this point. |
|---|
| 222 |
|
|---|
| 223 |
Also note the "Add Another" link next to "Poll." Every object with a ForeignKey |
|---|
| 224 |
relationship to another gets this for free. When you click "Add Another," you'll |
|---|
| 225 |
get a popup window with the "Add poll" form. If you add a poll in that window |
|---|
| 226 |
and click "Save," Django will save the poll to the database and dynamically add |
|---|
| 227 |
it as the selected choice on the "Add choice" form you're looking at. |
|---|
| 228 |
|
|---|
| 229 |
But, really, this is an inefficient way of adding Choice objects to the system. |
|---|
| 230 |
It'd be better if you could add a bunch of Choices directly when you create the |
|---|
| 231 |
Poll object. Let's make that happen. |
|---|
| 232 |
|
|---|
| 233 |
Remove the ``Admin`` for the Choice model. Then, edit the ``ForeignKey(Poll)`` |
|---|
| 234 |
field like so:: |
|---|
| 235 |
|
|---|
| 236 |
poll = models.ForeignKey(Poll, edit_inline=models.STACKED, num_in_admin=3) |
|---|
| 237 |
|
|---|
| 238 |
This tells Django: "Choice objects are edited on the Poll admin page. By |
|---|
| 239 |
default, provide enough fields for 3 Choices." |
|---|
| 240 |
|
|---|
| 241 |
Then change the other fields in ``Choice`` to give them ``core=True``:: |
|---|
| 242 |
|
|---|
| 243 |
choice = models.CharField(max_length=200, core=True) |
|---|
| 244 |
votes = models.IntegerField(core=True) |
|---|
| 245 |
|
|---|
| 246 |
This tells Django: "When you edit a Choice on the Poll admin page, the 'choice' |
|---|
| 247 |
and 'votes' fields are required. The presence of at least one of them signifies |
|---|
| 248 |
the addition of a new Choice object, and clearing both of them signifies the |
|---|
| 249 |
deletion of that existing Choice object." |
|---|
| 250 |
|
|---|
| 251 |
Load the "Add poll" page to see how that looks: |
|---|
| 252 |
|
|---|
| 253 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin11t.png |
|---|
| 254 |
:alt: Add poll page now has choices on it |
|---|
| 255 |
:target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin11.png |
|---|
| 256 |
|
|---|
| 257 |
It works like this: There are three slots for related Choices -- as specified |
|---|
| 258 |
by ``num_in_admin`` -- but each time you come back to the "Change" page for an |
|---|
| 259 |
already-created object, you get one extra slot. (This means there's no |
|---|
| 260 |
hard-coded limit on how many related objects can be added.) If you wanted space |
|---|
| 261 |
for three extra Choices each time you changed the poll, you'd use |
|---|
| 262 |
``num_extra_on_change=3``. |
|---|
| 263 |
|
|---|
| 264 |
One small problem, though. It takes a lot of screen space to display all the |
|---|
| 265 |
fields for entering related Choice objects. For that reason, Django offers an |
|---|
| 266 |
alternate way of displaying inline related objects:: |
|---|
| 267 |
|
|---|
| 268 |
poll = models.ForeignKey(Poll, edit_inline=models.TABULAR, num_in_admin=3) |
|---|
| 269 |
|
|---|
| 270 |
With that ``edit_inline=models.TABULAR`` (instead of ``models.STACKED``), the |
|---|
| 271 |
related objects are displayed in a more compact, table-based format: |
|---|
| 272 |
|
|---|
| 273 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin12.png |
|---|
| 274 |
:alt: Add poll page now has more compact choices |
|---|
| 275 |
|
|---|
| 276 |
Customize the admin change list |
|---|
| 277 |
=============================== |
|---|
| 278 |
|
|---|
| 279 |
Now that the Poll admin page is looking good, let's make some tweaks to the |
|---|
| 280 |
"change list" page -- the one that displays all the polls in the system. |
|---|
| 281 |
|
|---|
| 282 |
Here's what it looks like at this point: |
|---|
| 283 |
|
|---|
| 284 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin04t.png |
|---|
| 285 |
:alt: Polls change list page |
|---|
| 286 |
:target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin04.png |
|---|
| 287 |
|
|---|
| 288 |
By default, Django displays the ``str()`` of each object. But sometimes it'd |
|---|
| 289 |
be more helpful if we could display individual fields. To do that, use the |
|---|
| 290 |
``list_display`` option, which is a tuple of field names to display, as columns, |
|---|
| 291 |
on the change list page for the object:: |
|---|
| 292 |
|
|---|
| 293 |
class Poll(models.Model): |
|---|
| 294 |
# ... |
|---|
| 295 |
class Admin: |
|---|
| 296 |
# ... |
|---|
| 297 |
list_display = ('question', 'pub_date') |
|---|
| 298 |
|
|---|
| 299 |
Just for good measure, let's also include the ``was_published_today`` custom |
|---|
| 300 |
method from Tutorial 1:: |
|---|
| 301 |
|
|---|
| 302 |
list_display = ('question', 'pub_date', 'was_published_today') |
|---|
| 303 |
|
|---|
| 304 |
Now the poll change list page looks like this: |
|---|
| 305 |
|
|---|
| 306 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin13t.png |
|---|
| 307 |
:alt: Polls change list page, updated |
|---|
| 308 |
:target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin13.png |
|---|
| 309 |
|
|---|
| 310 |
You can click on the column headers to sort by those values -- except in the |
|---|
| 311 |
case of the ``was_published_today`` header, because sorting by the output of |
|---|
| 312 |
an arbitrary method is not supported. Also note that the column header for |
|---|
| 313 |
``was_published_today`` is, by default, the name of the method (with |
|---|
| 314 |
underscores replaced with spaces). But you can change that by giving that |
|---|
| 315 |
method a ``short_description`` attribute:: |
|---|
| 316 |
|
|---|
| 317 |
def was_published_today(self): |
|---|
| 318 |
return self.pub_date.date() == datetime.date.today() |
|---|
| 319 |
was_published_today.short_description = 'Published today?' |
|---|
| 320 |
|
|---|
| 321 |
|
|---|
| 322 |
Let's add another improvement to the Poll change list page: Filters. Add the |
|---|
| 323 |
following line to ``Poll.Admin``:: |
|---|
| 324 |
|
|---|
| 325 |
list_filter = ['pub_date'] |
|---|
| 326 |
|
|---|
| 327 |
That adds a "Filter" sidebar that lets people filter the change list by the |
|---|
| 328 |
``pub_date`` field: |
|---|
| 329 |
|
|---|
| 330 |
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin14t.png |
|---|
| 331 |
:alt: Polls change list page, updated |
|---|
| 332 |
:target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin14.png |
|---|
| 333 |
|
|---|
| 334 |
The type of filter displayed depends on the type of field you're filtering on. |
|---|
| 335 |
Because ``pub_date`` is a DateTimeField, Django knows to give the default |
|---|
| 336 |
filter options for DateTimeFields: "Any date," "Today," "Past 7 days," |
|---|
| 337 |
"This month," "This year." |
|---|
| 338 |
|
|---|
| 339 |
This is shaping up well. Let's add some search capability:: |
|---|
| 340 |
|
|---|
| 341 |
search_fields = ['question'] |
|---|
| 342 |
|
|---|
| 343 |
That adds a search box at the top of the change list. When somebody enters |
|---|
| 344 |
search terms, Django will search the ``question`` field. You can use as many |
|---|
| 345 |
fields as you'd like -- although because it uses a ``LIKE`` query behind the |
|---|
| 346 |
scenes, keep it reasonable, to keep your database happy. |
|---|
| 347 |
|
|---|
| 348 |
Finally, because Poll objects have dates, it'd be convenient to be able to |
|---|
| 349 |
drill down by date. Add this line:: |
|---|
| 350 |
|
|---|
| 351 |
date_hierarchy = 'pub_date' |
|---|
| 352 |
|
|---|
| 353 |
That adds hierarchical navigation, by date, to the top of the change list page. |
|---|
| 354 |
At top level, it displays all available years. Then it drills down to months |
|---|
| 355 |
and, ultimately, days. |
|---|
| 356 |
|
|---|
| 357 |
Now's also a good time to note that change lists give you free pagination. The |
|---|
| 358 |
default is to display 50 items per page. Change-list pagination, search boxes, |
|---|
| 359 |
filters, date-hierarchies and column-header-ordering all work together like you |
|---|
| 360 |
think they should. |
|---|
| 361 |
|
|---|
| 362 |
Customize the admin look and feel |
|---|
| 363 |
================================= |
|---|
| 364 |
|
|---|
| 365 |
Clearly, having "Django administration" at the top of each admin page is |
|---|
| 366 |
ridiculous. It's just placeholder text. |
|---|
| 367 |
|
|---|
| 368 |
That's easy to change, though, using Django's template system. The Django admin |
|---|
| 369 |
is powered by Django itself, and its interfaces use Django's own template |
|---|
| 370 |
system. (How meta!) |
|---|
| 371 |
|
|---|
| 372 |
Open your settings file (``mysite/settings.py``, remember) and look at the |
|---|
| 373 |
``TEMPLATE_DIRS`` setting. ``TEMPLATE_DIRS`` is a tuple of filesystem |
|---|
| 374 |
directories to check when loading Django templates. It's a search path. |
|---|
| 375 |
|
|---|
| 376 |
By default, ``TEMPLATE_DIRS`` is empty. So, let's add a line to it, to tell |
|---|
| 377 |
Django where our templates live:: |
|---|
| 378 |
|
|---|
| 379 |
TEMPLATE_DIRS = ( |
|---|
| 380 |
"/home/my_username/mytemplates", # Change this to your own directory. |
|---|
| 381 |
) |
|---|
| 382 |
|
|---|
| 383 |
Now copy the template ``admin/base_site.html`` from within the default Django |
|---|
| 384 |
admin template directory (``django/contrib/admin/templates``) into an ``admin`` |
|---|
| 385 |
subdirectory of whichever directory you're using in ``TEMPLATE_DIRS``. For |
|---|
| 386 |
example, if your ``TEMPLATE_DIRS`` includes ``"/home/my_username/mytemplates"``, |
|---|
| 387 |
as above, then copy ``django/contrib/admin/templates/admin/base_site.html`` to |
|---|
| 388 |
``/home/my_username/mytemplates/admin/base_site.html``. Don't forget that |
|---|
| 389 |
``admin`` subdirectory. |
|---|
| 390 |
|
|---|
| 391 |
Then, just edit the file and replace the generic Django text with your own |
|---|
| 392 |
site's name as you see fit. |
|---|
| 393 |
|
|---|
| 394 |
Note that any of Django's default admin templates can be overridden. To |
|---|
| 395 |
override a template, just do the same thing you did with ``base_site.html`` -- |
|---|
| 396 |
copy it from the default directory into your custom directory, and make |
|---|
| 397 |
changes. |
|---|
| 398 |
|
|---|
| 399 |
Astute readers will ask: But if ``TEMPLATE_DIRS`` was empty by default, how was |
|---|
| 400 |
Django finding the default admin templates? The answer is that, by default, |
|---|
| 401 |
Django automatically looks for a ``templates/`` subdirectory within each app |
|---|
| 402 |
package, for use as a fallback. See the `loader types documentation`_ for full |
|---|
| 403 |
information. |
|---|
| 404 |
|
|---|
| 405 |
.. _loader types documentation: ../templates_python/#loader-types |
|---|
| 406 |
|
|---|
| 407 |
Customize the admin index page |
|---|
| 408 |
============================== |
|---|
| 409 |
|
|---|
| 410 |
On a similar note, you might want to customize the look and feel of the Django |
|---|
| 411 |
admin index page. |
|---|
| 412 |
|
|---|
| 413 |
By default, it displays all available apps, according to your ``INSTALLED_APPS`` |
|---|
| 414 |
setting. But the order in which it displays things is random, and you may want |
|---|
| 415 |
to make significant changes to the layout. After all, the index is probably the |
|---|
| 416 |
most important page of the admin, and it should be easy to use. |
|---|
| 417 |
|
|---|
| 418 |
The template to customize is ``admin/index.html``. (Do the same as with |
|---|
| 419 |
``admin/base_site.html`` in the previous section -- copy it from the default |
|---|
| 420 |
directory to your custom template directory.) Edit the file, and you'll see it |
|---|
| 421 |
uses a template tag called ``{% get_admin_app_list as app_list %}``. That's the |
|---|
| 422 |
magic that retrieves every installed Django app. Instead of using that, you can |
|---|
| 423 |
hard-code links to object-specific admin pages in whatever way you think is |
|---|
| 424 |
best. |
|---|
| 425 |
|
|---|
| 426 |
Django offers another shortcut in this department. Run the command |
|---|
| 427 |
``python manage.py adminindex polls`` to get a chunk of template code for |
|---|
| 428 |
inclusion in the admin index template. It's a useful starting point. |
|---|
| 429 |
|
|---|
| 430 |
For full details on customizing the look and feel of the Django admin site in |
|---|
| 431 |
general, see the `Django admin CSS guide`_. |
|---|
| 432 |
|
|---|
| 433 |
When you're comfortable with the admin site, read `part 3 of this tutorial`_ to |
|---|
| 434 |
start working on public poll views. |
|---|
| 435 |
|
|---|
| 436 |
.. _Django admin CSS guide: ../admin_css/ |
|---|
| 437 |
.. _part 3 of this tutorial: ../tutorial03/ |
|---|