Django admin read record options
When you're on the main page of
the Django admin and click on a Django model, you're taken to a
page which shows the record list of that specific model. Figure 11-1 and figure 11-2 illustrate this record list page for a Store
model.
Figure 11-1. Django admin record list page with no model __str__ definition
Figure 11-2. Django admin record list page with model __str__ definition
As you can see in figure 11-1 and
11-2, each Django model record is displayed with a string. By
default, this string is generated from the __str__()
method definition of the Django model, as described in the Chapter
7. If the __str__
method is missing from a Django
model, then the Django admin displays the records like figure 11-1
as 'Store object', otherwise it returns the result generated by the
__str__
method for each record -- which in this case
of figure 11-2 is the name, city and state attributes of each
Store
record.
Record display: list_display, format_html,empty_value_display
While the basic display behavior
presented in figure 11-1 and figure 11-2 is helpful, it can be a very
limited for models with an inexpressive or complex
__str__()
method . A Django admin class can be
configured with the list_display
option to split up
the record list with a model's various fields, thereby making it
easier to view and sort records. Listing 11-2 illustrates a Django
admin class with the list_display option.
Listing 11-2. Django admin list_display option
from django.contrib import admin from coffeehouse.stores.models import Store class StoreAdmin(admin.ModelAdmin): list_display = ['name','address','city','state'] admin.site.register(Store, StoreAdmin)
As you can see in listing 11-2,
the Django admin StoreAdmin
class defines the
list_display
option with a list of values. This list
corresponds to Django model fields, which in this case are from the
Store
model. Figures 11-3 and 11-4 illustrate the
modified record list layout by adding the list_display
option.
Figure 11-3. Django admin record list page with list_display
Figure 11-4. Django admin record list page with model list_display sorted
Tip If you want to keep displaying the value generated by a Django model through its__str__
method in the Django admin, it's valid to add it to thelist_display
option (e.g.list_display = ['name','__str__']
).
In figure 11-3 you can see a much cleaner record list layout where each of the fields declared in list_display has its own column. In addition, if you click on any of the column headers -- which represent model fields -- the records are automatically sorted by that attribute, a process that's illustrated in figure 11-4 and which greatly enhances the discoverability of records.
Besides supporting the inclusion
of Django model fields, the list_display
option also
supports other variations to generate more sophisticated list
layouts. For example, if the database records are not homogeneous
(e.g. mixed upper and lower case text) you can generate a callable
method to manipulate the records and display them in the Django
admin in a uniform manner (e.g. all upper case). Additionally, you
can also create a callable that generates a composite value from
record fields that aren't explicitly in the database (e.g. domain
names belonging to email records) that makes the visualization of
the record list more powerful in the Django admin. Listing 11-3
illustrates several of these callable examples using several method
variations.
Listing 11-3 Django admin list_display option with callables
from django.contrib import admin from coffeehouse.stores.models import Store # Option 1 # admin.py def upper_case_city_state(obj): return ("%s %s" % (obj.city, obj.state)).upper() upper_case_city_state.short_description = 'City/State' class StoreAdmin(admin.ModelAdmin): list_display = ['name','address',upper_case_city_state] # Option 2 # admin.py class StoreAdmin(admin.ModelAdmin): list_display = ['name','address','upper_case_city_state'] def upper_case_city_state(self, obj): return ("%s %s" % (obj.city, obj.state)).upper() upper_case_city_state.short_description = 'City/State' # Option 3 # models.py from django.db import models class Store(models.Model): name = models.CharField(max_length=30) email = models.EmailField() def email_domain(self): return self.email.split("@")[-1] email_domain.short_description = 'Email domain' # admin.py class StoreAdmin(admin.ModelAdmin): list_display = ['name','email_domain']
In listing 11-3 you can see three
callable variations that are all acceptable as
list_display
options. Option one in listing 11-3 is a
callable that's declared outside a class and is then used as part
of the list_display
option. Option two declares the
callable as part of the Django admin class and then uses it as part
of the list_display
option. Finally, option three
declares a callable as part of the Django model class which is then
used as part of the list_display
option in the Django
admin class. Neither approach in listing 11-3 is 'better' or
'inferior' than the other, the options simply vary in the syntax
and arguments used to achieve the same result, you can use whatever
approach you like.
On certain occasions you may want
to render HTML as part of a record list in the Django admin (e.g.
add bold <b> tags or colored <span> tags). To include
HTML in these circumstances, you must use the
format_html
method because the Django admin escapes
all HTML output by default -- since it works with Django templates.
Listing 11-4 illustrates the use of the format_html
method.
Listing 11-4 Django admin list_display option with callable and format_html
# models.py from django.db import models from django.utils.html import format_html class Store(models.Model): name = models.CharField(max_length=30) address = models.CharField(max_length=30,unique=True) city = models.CharField(max_length=30) state = models.CharField(max_length=2) def full_address(self): return format_html('%s - <b>%s,%s</b>' % (self.address,self.city,self.state)) # admin.py from django.contrib import admin from coffeehouse.stores.models import Store class StoreAdmin(admin.ModelAdmin): list_display = ['name','full_address']
When a model field uses a
BooleanField
or NullBooleanField
data
type, the Django admin displays an "on" or "off" icon instead of
True
or False
values. In addition, when a
value for a field in list_display
is
None
, an empty string, and for cases when a field in
list_display
is any empty iterable (e.g. list), Django
displays a dash -
, as illustrated in figure 11-5.
It's possible to override this
last behavior with the empty_value_display
option as
illustrated in figure 11-6. You can configure the
empty_value_display
option to take effect on all
Django admin models, on a specific Django admin class or individual
Django admin fields as illustrated in listing 11-5.
Figure 11-5. Django admin default display for empty values
Figure 11-6. Django admin override display for empty values with empty_value_display
Listing 11-5. Django admin empty_value_display option global, class or field level configuration
# Option 1 - Globally set empty values to ??? # settings.py from django.contrib import admin admin.site.empty_value_display = '???' # Option 2 - Set all fields in a class to 'Unknown Item field' # admin.py to show "Unknown Item field" instead of '-' for NULL values in all Item fields # NOTE: Item model in items app class ItemAdmin(admin.ModelAdmin): list_display = ['menu','name','price'] empty_value_display = 'Unknown Item field' admin.site.register(Item, ItemAdmin) # Option 3 - Set individual field in a class to 'No known price' class ItemAdmin(admin.ModelAdmin): list_display = ['menu','name','price_view'] def price_view(self, obj): return obj.price price_view.empty_value_display = 'No known price'
Record order: admin_order_field and ordering
When you use custom fields in
list_display
(i.e. fields that aren't actually in the
database, but are rather composite or helper fields calculated in
Django) such fields can't be used for sorting operations because
sorting takes places at the database level. However, if an element
in list_display
is associated with a database field,
it's possible to create an association for sorting purposes with
the admin_order_field
option. This process is
illustrated in listing 11-6.
Listing 11-6. Django admin with admin_order_field option
# models.py from django.db import models from django.utils.html import format_html class Store(models.Model): name = models.CharField(max_length=30) address = models.CharField(max_length=30,unique=True) city = models.CharField(max_length=30) state = models.CharField(max_length=2) def full_address(self): return format_html('%s - <b>%s,%s</b>' % (self.address,self.city,self.state)) full_address.admin_order_field = 'city' # admin.py from django.contrib import admin from coffeehouse.stores.models import Store class StoreAdmin(admin.ModelAdmin): list_display = ['name','full_address']
As you can see in listing 11-6,
the admin_order_field
declaration tells Django to
order the model records by city
when attempting to
perform a sort operation in the Django admin through the composite
full_address
field. Note that it's also possible to
add a preceding - to the admin_order_field
value to
specify descending order (e.g. full_address.admin_order_field
= '-city'
), just like it's done in standard model sort
operations.
By default, record list values
are sorted by their database pk
(primary key) field --
which is generally the id field -- as you can appreciate in figure
11-3 (i.e. record pk
1
at the bottom and
record pk
4
at the top). And if you click
on any of the header columns the sort order changes as you can see
in figure 11-4.
To set a default sorting behavior
-- without the need to click on the header column -- you can use
the Django admin class ordering
option or the Django
model meta ordering
option which you learned about in
Chapter 7. If no ordering option is specified in either class then
pk
ordering takes place. If the ordering
option is specified in the Django model meta option, this sorting
behavior is used universally and if both a Django model and Django
admin class have ordering
options, then the Django
admin class definition takes precedence .
The ordering
option
accepts a list of field values to specify the default ordering of a
record list. By default, the ordering
behavior is
ascending (e.g. Z values first@bottom, A values top@last), but it's
possible to alter this behavior to descending (e.g. A values
first@bottom, Z values top@last) by prefixing a -
(minus sign) to the field value. For example, to produce a record
list like the one if figure 11-4 by default you would use
ordering = ['name']
and to produce an inverted record
list of figure 11-4 (i.e. Uptown at the top and Corporate at the
bottom) you would use ordering = ['-name']
.
Record links and inline edit: list_display_links and list_editable
If you look at some of the past
figures you'll notice there's always a generated link for each item
in the record list. For example, in figure 11-2 you can see each
'Store'
record is a link that takes you
to a page where you can edit the 'Store'
values, similarly in figure 11-4 you can see the
'Store'
name field is a link that takes
you to a page where you can edit the
'Store'
values and in figure 11-6 you can
see each 'Menu'
name field is a link that
takes you to a page where you can edit the
'Menu'
values.
This is a default behavior that
lets you drill-down on each record, but you customize this behavior
through the list_display_links
option to generate no
links or inclusively more links. Listing 11-7 illustrates two
variations of the list_display_links
option and
figure 11-7 and figure 11-8 the respective interfaces.
Listing 11-7 Django admin with list_display_links option
# Sample 1) # admin.py from django.contrib import admin from coffeehouse.stores.models import Store class StoreAdmin(admin.ModelAdmin): list_display = ['name','address','city','state'] list_display_links = None admin.site.register(Store, StoreAdmin) # Sample 2) # admin.py from django.contrib import admin from coffeehouse.items.models import Item class ItemAdmin(admin.ModelAdmin): list_display = ['menu','name','price_view'] list_display_links = ['menu','name'] admin.site.register(Item, ItemAdmin)
Figure 11-7. Django admin no links in records list due to list_display_links
Figure 11-8. Django admin multiple links in records list due to list_display_links
The first sample in listing 11-7
illustrates how the StoreAdmin
class is set with
list_display_links = None
which results in the page
presented in figure 11-7 that lacks links. The second sample in
listing 11-7 shows the ItemAdmin
class with the
list_display_links = ['menu','name']
that tells Django
to generate links on both menu
and name
fields values and which results in the page presented in figure 11-8 that contains multiple links.
The need to click on individual
links on a record list to edit records can become tiresome if you
need to edit multiple records. To simplify the editing of records,
the list_editable
option allows Django to generate
inline forms on each record value, effectively allowing the editing
of records in bulk without the need to leave the record list
screen. Listing 11-8 illustrates the use of the list_editable
option and figure 11-9 the respective interface.
Note Technically list_editable is a Django admin update option, but since the update is done inline and on a page designed to read records, it's included here.
Listing 11-8 Django admin with list_editable option
# admin.py from django.contrib import admin from coffeehouse.stores.models import Store class StoreAdmin(admin.ModelAdmin): list_display = ['name','address','city','state'] list_editable = ['address','city','state'] admin.site.register(Store, StoreAdmin)
Figure 11-9. Django admin editable fields due to list_editable
In listing 11-8 you can see the
list_editable = ['address','city','state']
option,
which tells the Django admin to allow the editing of
address
, city
and state
values in the record list. In figure 11-9 you can see how each of
these field values in the record list is turned into an editable
form and toward the bottom of the page the Django admin generates a
'Save'
button to save changes when an
edit is made.
It's worth mentioning that any
field value declared in the list_editable
option must
also be declared as part of the list_display
option,
since it's not possible to edit fields that aren't displayed. In
addition, any field values declared in the
list_editable
option must not be part of the
list_display
option, since it's not possible for a
field to be both a form and a link.
Record pagination: list_per_page, list_max_show_all, paginator
When record lists grow too large
in the Django admin they are automatically split into different
pages. By default, the Django admin generates additional pages for
every 100 records. You can control this setting with the Django
admin class list_per_page
option. Listing 11-9
illustrates the use of the list_per_page option and figure 11-10
shows the corresponding record list generated by the configuration
in listing 11-9.
Listing 11-9 Django admin with list_per_page option
# admin.py from django.contrib import admin from coffeehouse.items.models import Item class ItemAdmin(admin.ModelAdmin): list_display = ['menu','name','price'] list_per_page = 5 admin.site.register(Item, ItemAdmin)
Figure 11-10. Django admin list_per_page option limit to 5
As you can see in figure 11-10,
the display of nine records is split into two pages due to the
list_per_page = 5
option illustrated in listing 11-9.
In addition to the page icons at the bottom-left of figure 11-10,
notice the right hand side of these icons is a 'Show all' link. The
'Show all' link is used to generate a record list with all the
records in a single page. But note that because this additional
database operation can be costly, by default, the 'Show all' link
is only shown when a record list is 200 items or less.
You can control the display of
the 'Show all' link with the list_max_show_all
option.
If the total record list count is less than or equal the
list_max_show_all
value the 'Show all' link is
displayed, if the total record list count is above this number then
no 'Show all' link is generated. For example, if you declare
list_max_show_all
option to 8
in listing 11-9, then no
'Show all' link would appear in figure 11-10 because the total
record list count is 9.
The Django admin uses the
django.core.paginator.Paginator
class to generate the
pagination sequence, but it's also possible to provide a custom
paginator class through the paginator option. Note that if the
custom paginator class does not inherit its behavior from
django.core.paginator.Paginator
then you must also
provide an implementation for
ModelAdmin.get_paginator()
method.
Record search: search_fields, list_filter, show_full_result_count, preserve_filters
The Django admin also supports
search functionality. The Django admin class
search_fields
option adds search functionality for
text model fields through a search box -- see table 7-1 for a list
of Django model text data types. Listing 11-10 illustrates a Django
admin class with the search_fields option and figure 11-11
illustrates how a search box is added to the top of the record
list.
Listing 11-10.- Django admin search_fields option
from django.contrib import admin from coffeehouse.stores.models import Store class StoreAdmin(admin.ModelAdmin): search_fields = ['city','state'] admin.site.register(Store, StoreAdmin)
Figure 11-11. Django admin search box due to search_fields option
In listing 11-10 the city and
state fields are added to the search_fields
option,
which tell the Django admin to perform searches across these two
fields. Be aware that adding too many fields to the
search_fields
option can result in slow search
results, due to the way Django executes this type of search query.
Table 11-1 presents different search_fields
options
and the generated SQL for a given search term.
Table 11-1. Django search_fields options and generated SQL for search term
search_fields option |
Search term |
Generated SQL condition |
---|---|---|
search_fields = ['city','state'] |
San Diego |
WHERE (city ILIKE '%San%' OR state ILIKE '%San%') AND (city ILIKE '%Diego%' OR state ILIKE '%Diego%') |
search_fields = ['^city','^state'] |
San Diego |
WHERE (city ILIKE 'San%' OR state ILIKE 'San%') AND (city ILIKE 'Diego%' OR state ILIKE 'Diego%') |
search_fields = ['=city','=state'] |
San Diego |
WHERE (city ILIKE 'San' OR state ILIKE 'San') AND (city ILIKE 'Diego' OR state ILIKE 'Diego') |
*search_fields = ['@city','@state'] |
San Diego |
(Full-text search) WHERE (city ILIKE '%San%' OR state ILIKE '%San%') AND (city ILIKE '%Diego%' OR state ILIKE '%Diego%') |
* Full-text search option only supported for MySQL database
As you can see in table 11-1, the
search_fields
option constructs a query by splitting
the provided search string into words and performs a case
insensitive search (i.e. SQL ILIKE
) where each word
must be in at least one of the search_fields. In addition, notice
in table 11-1 it's possible to declare the
search_fields
values with different prefixes to alter
the search query.
By default, if you just provide
model field names to search_fields
Django generates a
query with SQL wildcards %
at the start and end of
each word, which can be a very costly operation, since it searches
across all text in a field record. If you prefix the
search_field
with a ^
-- as illustrated
in table 11-1 -- Django generates a query with an SQL wildcard
%
at the end of each word, making the search operation
more efficient because it's restricted to text that starts with the
word patterns. If you prefix the search_field with a =
-- as illustrated in table 11-1 -- Django generates a query for an
exact match with no SQL wildcard %
, making the search
operation the most efficient, because it's restricted to exact
matches of the word patterns. Finally, if you're using a MySQL
database, it's also possible to add the @
prefix to
search_fields
to enable full-text search.
Search engines offer various kinds of power search syntax to customize search queries, but the Django admin
search_fields
option doesn't support this type of syntax. For example, in a search engine it's possible to quote the search term"San Diego"
to make an exact search for both words, but if you attempt this with the Django admin search, Django attempts to search for literal quotes:"San"
and"Diego"
separately. To tweak the default search_fields behavior you must use the options presented in table 11-1 orModelAdmin.get_search_results()
.The default search behavior for a Django admin class can be customized to any requirements with the
ModelAdmin.get_search_results()
method which accepts the request, a queryset that applies the current filters, and the user-provided search term. In this manner, you can generate non-text searches (e.g. on Integers) or rely on other third party tools (e.g. Solr, Haystack) to generate search results.
The list_filter
option offers quicker access to model field values and works like
pre-built search links. Unlike the search_fields
option, the list_filter
option is more flexible in
terms of the data types it can work with and accepts more than just
text model fields (i.e. it also supports boolean fields, date
fields,etc). Listing 11-11 illustrates a Django admin class with
the list_filter
option and figure 11-12 illustrates
the list of filters generated on the right hand side of the record
list.
Listing 11-11. Django admin list_filter option
from django.contrib import admin from coffeehouse.items.models import Item class ItemAdmin(admin.ModelAdmin): list_display = ['menu','name','price'] list_filter = ['menu','price'] admin.site.register(Item, ItemAdmin)
Figure 11-12. Django admin list filters due to search_fields option
In listing 11-11 the list_filter
option is declared with the menu and price fields, which tell
Django to create filters with these two fields. As you can
appreciate in figure 11-12, on the right hand side of the record
list is a column with various filters that includes all the values
for the menu
and price
field values. If
you click on any of the filter links, the Django admin displays the
records that match the filter in the record list, a process that's
illustrated in figures 11-13, 11-14 and 11-15.
Figure 11-13. Django admin list with single filter
Figure 11-14. Django admin list with single filter
Figure 11-15. Django admin list with dual filter
An interesting aspect of Django admin filters that can be see in figure 11-15 is that you can apply multiple filters, making it easier to drill-down into records that match very specific criteria.
In addition, if you look at
figures 11-13, 11-14 and 11-15 you can see how filters are
reflected as url query strings. For example, in figure 11-13 the
?menu__id__exact=2
string is appended to the url,
which tells Django admin to display a list of records with a menu
id
of 2
; in figure 11-15 the
?menu__id__exact=3&price=3.99
string tells Django
admin to display a list of records with a menu id of 3
and a price value of 3.99
. This url argument syntax is
based on the same syntax used to make standard Django model queries
-- described in Chapter 8 -- and which is helpful to generate more
sophisticated filters 'on the fly' without the need to modify or
add options to the underlying Django admin class.
When you apply a filter or
filters to a record list and the filtered results are greater than
99 records, Django limits the initial display to 99 records and
also adds pagination, but in addition also displays the full count
of objects that match the filter(s) (e.g. 99 results (153 total)).
This additional count requires an additional query that can slow
things down with a large number of records. To disable the
generation of this additional count applicable to filter use you
can set the show_full_result_count
option to
False
.
Another characteristic of
applying a filter or filters is that when you create, edit or
delete a record and finish the operation, Django takes you back to
the filtered list. While this can be a desired behavior, it's
possible to override this behavior through the
preserve_filters
option so the Django admin sends you
back to the original record list. If you set the
preserve_filters = False
option in a Django admin
class while on a filtered record list and create, edit or delete a
record, the Django admin takes you back to the original record list
with no filters.
Record dates: date_hierarchy
Dates and times are displayed as
you would expect in the Django admin UI, as string representations
of the underlying Python datetime
value. But there's a
special option for DateField
and
DateTimeField
model data types that works like a
specialized filter. If you use the date_hierarchy
option on a Django admin class and assign it a field that's a
DateField
or DateTimeField
(e.g.
date_hierarchy = 'created'
, where
timestamp
is the name of the field) Django generates
an intelligent date filter at the top of the record list like the
one illustrated in figures 11-16, 11-17 and 11-18.
Figure 11-16. Django date filter by month with date_hierarchy
Figure 11-17. Django date filter by day with date_hierarchy
Figure 11-18. Django date filter single day with date_hierarchy
As you can see in figures 11-16,
11-17 and 11-18, the intelligent behavior comes from the fact that
upon loading the record list, Django generates a unique list of the
available months or days corresponding to the values of the
date_hierarchy
field. If you click on any of option of
this filter list, Django then generates a unique list of records
that match the values of the month or day in the filter list.
Record actions: actions_on_top, actions_on_bottom, actions
Besides the ability to click on an item in a Django admin record list to edit or delete it, at the top of the record list there's a drop down menu preceded with the word 'Action' which you can see in many of the previous figures. By default, the 'Action' drop down menu provides the 'Delete selected options' item to delete multiple records simultaneously by selecting the check-box on the left hand side of each record.
If you wish to remove the
'Action' menu from the top of the record list you can use the
actions_on_top
options and set it to
False
. In addition, if you wish to add the 'Action'
menu to the bottom of the record list you can use the
actions_on_bottom = True
option which is illustrated
in figure 11-19 -- note that it's possible to have an 'Action' menu
on both the bottom and top of the record list page.
Figure 11-19. Django admin list with Action menu on bottom due to actions_on_bottom
Another option related to the
'Action' menu is the actions_selection_counter
which
displays the amount of selected records on the right hand side of
the 'Action' menu and which can also be seen in figure 11-.19. If
you set actions_selection_counter = False
then the
Django admin omits the amount of selected records related to the
'Action' menu.
Although the 'Action' menu is
limited to a single action -- that of deleting records -- it's
possible to define a list of actions through the
actions
option in Django admin classes[1].
Record relationships
Django model relationships -- One to one, one to many and many to many -- described in the previous model chapters, have certain behaviors in the context of Django admin classes and the Django admin that are worth describing separately in the following sub-sections.
Display: list_display (continued)
When you have a one to many
relationship and declare the related ForeignKey
field
as part of the list_display
option, the Django admin
uses the __str__
representation of the related model.
This last behavior is presented in figure 11-5 with a list of
Item
records, where the Item
model
defines the menu
field with
models.ForeignKey(Menu)
and thus the output of the
field is the Menu
model __str__
method.
The list_display
option can't accept a ManyToManyField
field
directly because it would require executing a separate SQL
statement for each row in the table, nevertheless it's possible to
integrate a ManyToManyField
into
list_display
through a custom method in a Django admin
class, a process that's illustrated in listing 11-12 and figure
11-20.
Listing 11-12 Django admin list_display option with ManyToManyField field
# models.py from django.db import models class Amenity(models.Model): name = models.CharField(max_length=30) description = models.CharField(max_length=100) class Store(models.Model): name = models.CharField(max_length=30) address = models.CharField(max_length=30) city = models.CharField(max_length=30) state = models.CharField(max_length=2) email = models.EmailField() amenities = models.ManyToManyField(Amenity,blank=True) # admin.py from django.contrib import admin from coffeehouse.stores.models import Store class StoreAdmin(admin.ModelAdmin): list_display = ['name','address','city','state','list_of_amenities'] def list_of_amenities(self, obj): return ("%s" % ','.join([amenity.name for amenity in obj.amenities.all()])) list_of_amenities.short_description = 'Store amenities' admin.site.register(Store, StoreAdmin)
Figure 11-20. Django admin list_display option with ManyToManyField field
In listing 11-12 you can see the
Store
model has a ManyToManyField
field
with the Amenity
model. In order to present the values
of the ManyToManyField
field in the Django admin
through list_display
you can see it's necessary to
create a custom method that makes an additional query for these
records. Figure 11-20 presents the rendered Django admin record
list for this ManyToManyField
field. Be aware this
design can place a heavy burden on the database because it requires
an additional query for each individual record.
Order: admin_order_field (continued)
The
admin_order_field
option also supports sorting on
fields that are part of related models. For example, in listing
11-13, you can see the admin_order_field
option is
applied to a field that's part of the model with a
ForeignKey
field relationship.
Listing 11-13. Django admin admin_order_field option with ForeignKey field
# models.py class Menu(models.Model): name = models.CharField(max_length=30) creator = models.CharField(max_length=100,default='Coffeehouse Chef') def __str__(self): return u"%s" % (self.name) class Item(models.Model): menu = models.ForeignKey(Menu) name = models.CharField(max_length=30) # admin.py from django.contrib import admin from coffeehouse.stores.models import Store class ItemAdmin(admin.ModelAdmin): list_display = ['menu','name','menu_creator'] def menu_creator(self, obj): return obj.menu.creator menu_creator.admin_order_field = 'menu__creator' admin.site.register(Item, ItemAdmin)
The most important thing worth
noting about listing 11-13 is the double underscore to specify the
field menu__creator
, which tells the Django admin to
access a field in the related model -- note this double underscore
is the same syntax used to perform queries in Django model
relationships queries described in Chapter 8.
Search: search_fields and list_filter (continued), admin.RelatedOnlyFieldListFilter, list_select_related
Two other Django admin class
options that support the same double underscore syntax (a.k.a.
"follow notation") to work across relationships are
search_fields
and list_filter
. This means
you can enable search and generate filters for related models (e.g.
search_fields = ['menu__creator']
).
A variation of the
list_filter
option that only applies to model
relationships is admin.RelatedOnlyFieldListFilter
.
When model records that belong to a relationship can span beyond a
single relationship, it can lead to the creation of unneeded
filters.
For example, lets take a
relationship between Store
and Amenity
models, you can generate Django admin filters for
Amenity
values on the Store
record list,
but if the Amenity
model records are generic and used
beyond the Store
model (e.g. Amenity
values for Employees
) you'll see inapplicable filter
values in the Store record list. The use of the
admin.RelatedOnlyFieldListFilter
prevents this, a
process that's illustrated in listing 11-14 and figures 11-21 and
11-22.
Listing 11-14 - Django admin list_filter option with admin.RelatedOnlyFieldListFilter
# admin.py class StoreAdmin(admin.ModelAdmin): list_display = ['name','address','city','state','list_of_amenities'] list_filter = [['amenities',admin.RelatedOnlyFieldListFilter]] def list_of_amenities(self, obj): return ("%s" % ','.join([amenity.name for amenity in obj.amenities.all()])) list_of_amenities.short_description = 'Store amenities'
Figure 11-21. Django admin list_filter option with no RelatedOnlyFieldListFilterDjango
Figure 11-22. Django admin list_filter option with RelatedOnlyFieldListFilter
In listing 11-14 notice how the
field to generate filters on -- in this case amenities
-- is wrapped in its own list along with
admin.RelatedOnlyFieldListFilter
. To understand the
difference between the use and absence of
admin.RelatedOnlyFieldListFilter
look at figure 11-21
and figure 11-22. In figure 11-21 notice the last filter on the list is
'Massage Chairs' -- an Amenity
record -- and yet no
Store
record on the main list has this Amenity. To
eliminate this inapplicable filter from the Store
record list you can use
admin.RelatedOnlyFieldListFilter
and get the results
from figure 11-22, which only show Amenity
filters
related to Store
records.
Finally, another option that's
applicable to Django admin classes with model relationships is the
list_select_related
. The
list_select_related
option functions just like the
list_select_related
option used in queries involving
relationships, to reduce the amount of database queries that
involve relationships (e.g. it creates a single complex query,
instead of later needing to issue multiple queries for each
relationships).The list_select_related
option can
accept a boolean or list value. By default, the
list_select_related
option receives a
False
value (i.e. it's not used). Under the hood, the
list_select_related
option uses the same
select_related()
model method to retrieve related
records, described in Chapter 8.
If list_select_related =
True
then select_related()
is always used. For
finer-grained control of list_select_related
you can
specify a list, noting that an empty list prevents Django from
calling select_related()
at all and any other list
values are passed directly to select_related()
as
parameters.