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