|
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: ../tutorial1/ |
|
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/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/admin02t.png |
|
61 :alt: Django admin index page |
|
62 :target: http://media.djangoproject.com/img/doc/tutorial/admin02.png |
|
63 |
|
64 By default, you should see two types of editable content: groups and users. |
|
65 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/admin03t.png |
|
99 :alt: Django admin index page, now with polls displayed |
|
100 :target: http://media.djangoproject.com/img/doc/tutorial/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/admin04t.png |
|
107 :alt: Polls change list page |
|
108 :target: http://media.djangoproject.com/img/doc/tutorial/admin04.png |
|
109 |
|
110 Click the "What's up?" poll to edit it: |
|
111 |
|
112 .. image:: http://media.djangoproject.com/img/doc/tutorial/admin05t.png |
|
113 :alt: Editing form for poll object |
|
114 :target: http://media.djangoproject.com/img/doc/tutorial/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/admin06t.png |
|
142 :alt: History page for poll object |
|
143 :target: http://media.djangoproject.com/img/doc/tutorial/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/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/admin08t.png |
|
179 :alt: Form has fieldsets now |
|
180 :target: http://media.djangoproject.com/img/doc/tutorial/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/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/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(maxlength=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/admin11t.png |
|
254 :alt: Add poll page now has choices on it |
|
255 :target: http://media.djangoproject.com/img/doc/tutorial/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/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/admin04t.png |
|
285 :alt: Polls change list page |
|
286 :target: http://media.djangoproject.com/img/doc/tutorial/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/admin13t.png |
|
307 :alt: Polls change list page, updated |
|
308 :target: http://media.djangoproject.com/img/doc/tutorial/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/admin14t.png |
|
331 :alt: Polls change list page, updated |
|
332 :target: http://media.djangoproject.com/img/doc/tutorial/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" and "example.com" at the top of each |
|
366 admin page is 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 and URL 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: ../tutorial3/ |