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 the list_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.

Power searches, non-text searches and other back-ends for Django admin searches

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 or ModelAdmin.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.

  1. https://docs.djangoproject.com/en/1.11/ref/contrib/admin/actions/