Set up and customize Django models in the Django admin site

Problem

You want to use the Django admin to create/read/update/delete Django model records. In addition, you want to customize the default options and layout of Django models in the Django admin.

Note Set up the Django admin.

See the previous the recipe Set up the Django admin site for instructions on setting up the Django admin, as this recipe assumes you already set up the Django admin.

Solution

Create an admin.py file in a Django app to register Django models for display in the Django admin. Register a Django model in admin.py with admin.site.register or the @admin.register() decorator.

To customize the record list of a Django model in the Django admin: for record display, links and order you can use the options list_display, format_html, empty_value_display, admin_order_field, ordering, list_display_links, list_editable; for pagination the options list_per_page, list_max_show_all, paginator; for record search functionality the options search_fields, list_filter, show_full_result_count, preserve_filters; for dates the option date_hierarchy; for actions the options actions_on_top, actions_on_bottom, actions; and for relationships the options list_display, admin_order_field , search_fields, list_filter, admin.RelatedOnlyFieldListFilter, list_select_related.

To customize the form to create, update, delete records in the Django admin: for forms you can use the options fields, readonly_fields, exclude, fieldsets, formfield_overrides, form, prepopulated_fields; for actions, links and positions the options save_on_top, save_as(Clone records), view_on_site; and for relationships the options filter_horizontal, filter_vertical, radio_fields, raw_id_fields, inlines.

How it works

Although the Django admin provides an excellent management tool for a Django project's database, simply creating and installing Django models isn't enough to access their data in the Django admin. In order to access Django model records in the Django admin you must register and configure Django models in admin.py files. An admin.py file must be created and placed inside a Django app alongside the models.py and views.py files. Listing 1 illustrates the location of admin.py files in a Django project layout.

Listing 1 - Django apps with admin.py file
 
# admin.py files created directly under app folders
+---+-<PROJECT_DIR_project_name>
    |
    +-__init__.py
    +-settings.py
    +-urls.py
    +-wsgi.py
    |
    +-about(app)-+
    |            +-__init__.py
    |            +-models.py
    |            +-tests.py
    |            +-views.py
    |            +-admin.py
    |
    |
    +-stores(app)-+
                 +-__init__.py
                 +-models.py
                 +-tests.py
                 +-views.py
                 +-admin.py

As you can see in listing 1 each Django app contains an admin.py file. Even though you can technically have a single admin.py to register and configure all Django models -- just like you could have a single models.py to define all Django models -- it's a recommended practice that each Django app have its own admin.py file to manage its corresponding model definitions in models.py.

There are three ways to register a Django model for the Django admin in admin.py files, all of which are illustrated in listing 2.

Listing 2 - Register Django models in admin.py file

from django.contrib import admin
from coffeehouse.stores.models import Store
# Option 1 (Basic)

admin.site.register(Store)    

# End Option 1
                            
# Option 2 (Allows customization) 
class StoreAdmin(admin.ModelAdmin):
      pass

admin.site.register(Store, StoreAdmin)

# End Option 2

# Option 3 (Decorator)
@admin.register(Store)
class StoreAdmin(admin.ModelAdmin):
      pass

# End Option 3

Option 1 in listing 2 provides basic Django model registration and consists of declaring the Django model class as the input to admin.site.register. If you don't require any customization of the Django model in the Django admin, option 1 is sufficient.

Option 2 in listing 2 makes use of a Django admin class which inherits its behavior from admin.ModelAdmin, in this case you can see the class is empty, but it's possible to customize the Django admin behavior in this type of class as I'll describe throughout this recipe. Once the Django admin class is declared, it must be registered and associated with a Django model using the same admin.site.register method as option 1, where the first argument is the Django model and the second argument is the Django admin class.

Option 3 in listing 2 makes use of a Django admin @admin.register decorator. The syntax difference between option 3 and option 2 is the registration and association takes place decorating the Django admin class with @admin.register where the decorator takes the Django model as its argument. Be aware that although option 3 and option 2 are functionally equal, using the @admin.register decorator -- option 3 -- has the limitation that you can't reference the Django admin class in its __init__() method (e.g. super(StoreAdmin, self).__init__(*args, **kwargs)) which is an issue in Python 2 and for certain designs, if you're in this situation then you must use option 2 for registration with the admin.site.register method.

Now that you know how to register Django models in the Django admin, I'll describe the various options available to customize the Django admin behavior through a Django admin class -- option 2 and option 3 in listing 2. To make it easier to look for custom options, I'll classify the options into two main sections 'Record list page options' and 'Create,update,delete record page options' which are the main screens you work with in the Django admin, in addition I'll also include sub-sections to group functionality under each main section.

Record list page 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 Django model. Figures 1 and 2 illustrate this record list page for a Django model called Store.

Django admin record list page
Figure 1.- Django admin record list page with no model __str__ definition
Django admin record list page
Figure 2.- Django admin record list page with model __str__ definition

Record display, links and order: list_display, format_html, empty_value_display, admin_order_field, ordering, list_display_links, list_editable

As you can see in Figure 1 and 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 Setup Django models recipe. If the __str__ method is missing from the Django model, then the Django admin displays the records like figure 1 as Store object, otherwise it returns the result generated by the __str__ method for each instance -- which in this case is the name, city and state attributes of each Store instance as illustrated in figure 2.

While this display behavior is helpful, it can be very weak when working with more complex Django models. 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 3 illustrates a Django admin class with the list_display option.

Listing 3 - 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 3, the Django admin StoreAdmin class defines the list_display option with a tuple of values. This list corresponds to Django model fields, which in this case are from the Store model. Figures 3 and 4 illustrate the modified record list layout by adding the list_display option.

Django admin record list page with list_display
Figure 3.- Django admin record list page with list_display
Django admin record list page with list_display sorted
Figure 4.- Django admin record list page with model list_display sorted

Note You can use __str__ in list_display.

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 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 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 4 illustrates several of these callable examples using several method variations.

Listing 4 - Django admin list_display option with callables

from django.contrib import admin
from coffeehouse.stores.models import Store

# Option 1
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)

# End Option 1 

# Option 2
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'


# End Option 2

# Option 3 
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'

class StoreAdmin(admin.ModelAdmin):
      list_display = ('name','email_domain')

In listing 4 you can see three callable variations that are all acceptable as list_display options. Option 1 is a callable that's declared outside a class and is then used as part of the list_display option. Option 2 declares the callable as part of the Django admin class and then uses it as part of the list_display option. Finally, option 3 declares a callable as part of the original Django model class which is then used as part of the list_display option in the Django admin class. Neither approach in listing 4 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 HTML-escapes all output by default. Listing 5 illustrates the use of the format_html method.

Listing 5 - Django admin list_display option with callable and format_html

from django.db import models

class Store(models.Model):
    name = models.CharField(max_length=30)    
    status_code = models.CharField(max_length=10)
    def status_color(self):
        return format_html('{}',
                           self.status_code,
                           self.name)

from django.contrib import admin
from django.utils.html import format_html
from coffeehouse.stores.models import Store
class StoreAdmin(admin.ModelAdmin):
      list_display = ('name','status_color')

When a field value is 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 corresponding to a field in list_display is None, an empty string, or an iterable without elements, Django displays a dash -, as illustrated in figure 5. It's possible to override this behavior with the empty_value_display option as illustrated in figure 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 6.

Django admin default display for empty values
Figure 5.- Django admin default display for empty values
Django admin override display for empty values with empty_value_display
Figure 6.- Django admin override display for empty values with empty_value_display

Listing 6 - Django admin empty_value_display option global, class or field level configuration

# Option 1 Globally
from django.contrib import admin
admin.site.empty_value_display = '???'
 
# End Option 1

# Option 2 All fields in a class
# admin.py to show "Unknown Item field" instead of '-' for NULL values in all Item fields
class ItemAdmin(admin.ModelAdmin):
    empty_value_display = 'Unknown Item field'

# End Option 2 

# Option 3 Individual field
class ItemAdmin(admin.ModelAdmin):
    list_display = ('name','price_view')
    def price_view(self, obj):
         return obj.price
    price_view.empty_value_display = 'No known price'

# End Option 3

Be aware that when using 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 through the admin_order_field option. This process is illustrated in listing 7.

Listing 7 - Django admin with admin_order_field option

from django.db import models

class Store(models.Model):
    name = models.CharField(max_length=30)    
    status_code = models.CharField(max_length=10)
    def status_color(self):
        return format_html('{}',
                           self.status_code,
                           self.name)
    status_color.admin_order_field = 'name'

from django.contrib import admin
from django.utils.html import format_html
from coffeehouse.stores.models import Store
class StoreAdmin(admin.ModelAdmin):
      list_display = ('name','status_color')

As you can see in listing 7 the admin_order_field declaration tells Django to order by name when trying to sort by status_color in the Django admin. Note that it's also possible to add a preceding - to the admin_order_field value to specify descending order (e.g. status_color.admin_order_field = '-name')

By default, record list values are sorted by their database id value as you can appreciate in figure 3 (i.e. record id 1 at the bottom and record id 4 at the top) and if you click on any of the header columns the sort order changes as you can see in figure 4. To set a default sorting behavior -- without the need to click on the header column -- you can use the ordering option in the Django admin class or the meta option in the Django model class itself. If no ordering option is specified in either class then id ordering takes place, if the ordering option is specified as a meta option in the Django model class then 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 in the Django admin record list.

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 4 by default you would use ordering = ['name'] and to produce an inverted record list of figure 4 (i.e. Uptown at the top and Corporate at the bottom) you would use ordering = ['-name'].

If you look at some of the past figures you'll also notice there's always a generated link for each item in the record list. For example, in figure 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 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 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 easily drill-down on each record, but can also be overridden through the list_display_links option to generate no links or inclusively more links. Listing 8 illustrates two variations of the list_display_links option and figures 7 and 8 the respective interfaces.

Listing 8 - Django admin with list_display_links option

# Django admin class
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)

# Django admin class 
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)
Django admin no links in records list due to list_display_links
Figure 7.- Django admin no links in records list due to list_display_links
Django admin multiple links in records list due to list_display_links
Figure 8.- Django admin multiple links in records list due to list_display_links

The first option in listing 8 illustrates how the StoreAdmin class is set with list_display_links = None which results in the page presented in figure 7 that lacks links. The second option in listing 8 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 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 9 illustrates the use of the list_editable option and figure 9 the respective interface.

Listing 9 - Django admin with list_editable option

# Django admin class
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)

Django admin editable fields due to list_editable
Figure 9.- Django admin editable fields due to list_editable

In listing 9 you can see we add the list_editable = ('address','city','state') option to tell Django to allow the editing of address, city and state values in the record list. In figure 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 Django 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.

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 list_per_page option in a Django admin class. Listing 10 illustrates the use of the list_per_page option and figure 10 show the corresponding record list generated by the configuration in listing 10.

Listing 10 - Django admin with list_per_page option

# Django admin class
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)

Django admin list_per_page option limit to 5
Figure 10.- Django admin list_per_page option limit to 5

As you can see in figure 10 the display of nine records is split into two pages due to the list_per_page = 5 option illustrated in listing 10. In addition to the page icons in figure 10, notice on the right hand side of the icons is the Show all link. The Show all link is used to generate a record list with all the records in a single page, but 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 added the list_max_show_all = 8 option to listing 10 then no Show all link would appear in figure 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().

Record search functionality: search_fields, list_filter, show_full_result_count, preserve_filters

In addition to the Django admin supporting various columns for a Django model record list, the Django admin also supports search functionality. The search_fields option in Django admin classes adds search functionality for text model fields through a search box -- see the table Django model data type behaviors, options and validations in the previous recipe for a list of text model fields. Listing 11 illustrates a Django admin class with the search_fields option and figure 11 illustrates how a search box is added to the top of the record list.

Listing 11 - 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)

Django admin search box due to search_fields option
Figure 11.- Django admin search box due to search_fields option

In listing 11 we add the field city and state to tell Django to perform a search 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 1 presents different search_fields options and the generated SQL for a given search term.

Table 1 - Django search_fields options and generated SQL for search term
search_fields optionSearch termGenerated SQL condition
search_fields = ('city','state')San DiegoWHERE (city ILIKE '%San%' OR state ILIKE '%San%') AND (city ILIKE '%Diego%' OR state ILIKE '%Diego%')
search_fields = ('^city','^state')San DiegoWHERE (city ILIKE 'San%' OR state ILIKE 'San%') AND (city ILIKE 'Diego%' OR state ILIKE 'Diego%')
search_fields = ('=city','=state')San DiegoWHERE (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 1, the search_fields option constructs a query 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 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 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 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.

Note Don't try power user search syntax with search_fields

Search engines offer various kinds of power user 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 do so through the options presented in table 1 or ModelAdmin.get_search_results() which is described in the next side bar.

Note Non-text searches or other back-ends with 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 12 illustrates a Django admin class with the list_filter option and figure 12 illustrates the list of filters generated on the right hand side of the record list.

Listing 12 - 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)

Django admin list filters due to search_fields option
Figure 12.- Django admin list filters due to search_fields option

In listing 12 we add the field menu and price to tell Django to create filters with these two fields. As you can appreciate in figure 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, Django displays the records that match the filter in the record list, a process that's illustrated in figures 13, 14 and 15.

Django admin list with single filter
Figure 13.- Django admin list with single filter
Django admin list with single filter
Figure 14.- Django admin list with single filter
Django admin list with dual filter
Figure 15.- Django admin list with dual filter

An interesting aspect of Django admin filters that can be see in figure 15 is that you can apply multiple filters, making it easier to drill-down to records that match a very specific criteria. In addition, if you look at figures 13, 14 and 15 you can see how filters are reflected as standard URL arguments. For example, in figure 13 the ?menu__id__exact=2 string is appended to the URL, which tells Django to display a list of records with a menu ID of 2; in figure 15 the ?menu__id__exact=3&price=3.99 string tells Django 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 past recipes -- and which can be 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 Django 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, Django takes you back to the original record list with no filters.

Dates: date_hierarchy

Dates and times are displayed as you would normally expect in Django admin record lists, but there's a special option for DateField and DateTimeField 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 = 'timestamp', 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 16, 17 and 18.

Django date filter by month with date_hierarchy
Figure 16.- Django date filter by month with date_hierarchy
Django date filter by day with date_hierarchy
Figure 17.- Django date filter by day with date_hierarchy
Django date filter single day with date_hierarchy
Figure 18.- Django date filter single day with date_hierarchy

As you can see in figures 16, 17 and 18, the intelligent behavior comes from the fact that upon loading the record list, Django generates a unique list of the months corresponding to the values of the date_hierarchy field. If you click on any of the months in the list, Django then generates a unique list of the days corresponding to the values of the month. And if you click on a day of the list Django generates a list of records for that single day.

Actions: actions_on_top, actions_on_bottom, actions

Besides the ability to click on an item in the 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 illustrates in figure 19 -- note that it's possible to have an Action on both the bottom and top of the record list. 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, if you set actions_selection_counter = False then Django omits the amount of selected records related to the Action menu.

Django admin list with Action menu on bottom due to actions_on_bottom
Figure 19.- Django admin list with Action menu on bottom due to actions_on_bottom

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.

Relationships: list_display continued, admin_order_field continued, search_fields continued, list_filter continued, admin.RelatedOnlyFieldListFilter, list_select_related

Django model relationships -- described in the Set up relationships in Django models: One to one, one to many and many to many recipe -- have certain behaviors in the context of Django admin classes and the Django admin that are worth describing separetly.

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 object. This last behavior is presented in figure 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 13 and figure 20.

Listing 13 - Django admin list_display option with ManyToManyField field

# Django 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)

# Django admin
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)
Django admin list_display option with ManyToManyField field
Figure 20.- Django admin list_display option with ManyToManyField field

In listing 13 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 also see in listing 13 it's necessary to create a custom method that makes an additional query for these records. Figure 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.

The admin_order_field option also supports sorting on related models. For example in listing 14 you can see the admin_order_field applied to a relationship with a ForeignKey field.

Listing 14 - Django admin admin_order_field option with ForeignKey field

# Django models

class Menu(models.Model):
    name = models.CharField(max_length=30)
    creator = models.CharField(max_length=100)
    def __str__(self):
        return u"%s" % (self.name)

class Item(models.Model):
    menu = models.ForeignKey(Menu)
    name = models.CharField(max_length=30)

# Django admin
from django.contrib import admin
from coffeehouse.stores.models import Store

class ItemAdmin(admin.ModelAdmin):
    list_display = ('menu','name','menu_creator')
    def menu_creator_name(self, obj):
        return obj.menu.creator
    menu_creator_name.admin_order_field = 'menu__creator'

admin.site.register(Item, ItemAdmin)

The most important thing worth noting about listing 14 is the special syntax used in the admin_order_field option, other than this the other concepts (e.g. custom methods) are explained in previous sections. Notice the double underscore to specify the field menu__creator, this tells Django to access a field in a related model -- note this double underscore is the same syntax used to perform queries in Django model relationships.

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 applies exclusively to model relationships is admin.RelatedOnlyFieldListFilter. When the records that belong to a relationship span beyond the relationship itself it leads 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 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 and its set up is illustrated in listing 15 and figures 21 and 22.

Listing 15 - Django admin list_filter option with admin.RelatedOnlyFieldListFilter

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'

Django admin list_filter option with no RelatedOnlyFieldListFilter
Figure 21.- Django admin list_filter option with no RelatedOnlyFieldListFilter
Django admin list_filter option with RelatedOnlyFieldListFilter
Figure 22.- Django admin list_filter option with RelatedOnlyFieldListFilter

In listing 15 notice how the field to generate filters on -- in this case amenities -- is wrapped in its own tuple along with admin.RelatedOnlyFieldListFilter. To understand the difference between the use and absence of admin.RelatedOnlyFieldListFilter look at figures 21 and 22. In figure 21 notice the last filter on the list is Massage Chairs -- an Amenity record -- and yet no Store record on the list has this Amenity. To eliminate this inapplicable filter from the Store record list we use admin.RelatedOnlyFieldListFilter and get the results from figure 22, which only show Amenity filters that are 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 functions just like the list_select_related option used in queries involving relationships, helping 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, a list or a tuple and by default receives a False value (i.e. it's not used). Under the hood, the list_select_related option uses the same select_related() method to retrieve related records.

If list_select_related = True then select_related() is always called and if list_select_related = False the default Django inspects list_display and calls select_related() if any field is a ForeignKey. For finer-grained control of list_select_related you can specify a tuple or list, noting that an empty tuple prevents Django from calling select_related at all and any other tuple values are passed directly to select_related() as parameters.

Create, update, delete record page options

Besides Django model record lists which is where you'll spend a fair amount of time in the Django admin, the other Django admin pages where you'll spend the remainder of time are the Django admin pages to create, update and delete Django model records. When you click on the Add <model name> button on the top right of every Django admin record list, you're taken to a form-like page where you can provide values for a new record -- illustrated in figure 23 -- and when you click on a record in a Django admin record list you're also taken to a form-like page where you can edit or delete field values for the record -- illustrated in figure 24.

Django admin page to create model record
Figure 23.- Django admin page to create model record
Django admin page to edit or delete model record
Figure 24.- Django admin page to edit or delete model record

In the next sub-sections I'll describe the various options available in the Django admin to create, update and delete records, which is worth noting is the same underlying page for all three operations.

Forms: fields, readonly_fields, exclude, fieldsets, formfield_overrides, form, prepopulated_fields

By default, the Django admin generates a form for all the fields in a Django model you're working on. For example, in figure 23 and 24 you can see six fields in the Django admin form which also correspond to six field definitions for the Store Django model.

To alter this default Django admin form one of the first alternatives is to use the fields option. The fields option lets you alter the order in which the form fields appear or create a form with a sub-set of model fields. Listing 16 illustrates the use fields in a Django admin class and figure 25 illustrates the corresponding layout for listing 16.

Listing 16 - Django admin fields option for Django admin forms

class StoreAdmin(admin.ModelAdmin):
      fields = ('address','city','state','email')

admin.site.register(Store, StoreAdmin)
Django admin fields option for Django admin forms
Figure 25.- Django admin fields option for Django admin forms
Django admin fields option with wrapped fields for Django admin forms
Figure 26.- Django admin fields option with wrapped fields for Django admin forms

In listing 16 you can see the fields option contains four fields vs. six fields in the original backing Store Django model. Figure 25 shows how the Django admin form is generated for the code in listing 16, which as you can observe is now a form with four fields. Figure 26 is a variation of the same fields option, but notice how the city and state fields appear on the same line in the form, this is something you can achieve by nesting fields in their own tuple. To achieve the results in figure 26 you would define fields = ('address',('city','state'),'email') as the option in the Django admin class.

As helpful as the fields options is, on other occasions it can be necessary to display a form field but not allow it to be changed, instead of omitting it altogether which can lead to confusion. To disallow the editing of a form field you can use the readonly_fields option. Listing 17 illustrates the use of the readonly_fields option and figure 27 the corresponding layout.

Listing 17 - Django admin readonly_fields option for Django admin forms

class StoreAdmin(admin.ModelAdmin):
      readonly_fields = ('name','amenities')

admin.site.register(Store, StoreAdmin)
Django admin readonly_fields option for Django admin forms
Figure 27.- Django admin readonly_fields option for Django admin forms

In listing 17 you can see we make use of the readonly_fields option to make the name and amenities fields read only. Because we don't use the fields option, all the model fields are used to generate a form. In figure 27 you can see how the name and amenities fields are shown as text rather than input forms, making them ineditable. A behavior of only using the readonly_fields option is that these field definitions are placed at the bottom of the form as seen in figure 27. If you want to maintain the same form field order as the original Django model, then you need to explicitly define the form fields using the fields option, in this way the form field order follows the fields option and any field in readonly_field is displayed as read-only, while respecting the field position set in the fields option.

Besides supporting model field names, the readonly_fields option also supports callable methods to allow more customized behavior. Listing 18 illustrates the use of the readonly_fields option with a callable and figure 28 the corresponding layout.

Listing 18 - Django admin readonly_fields option with callable for Django admin forms

from django.utils.safestring import mark_safe

class StoreAdmin(admin.ModelAdmin):
    fields = ('name','address',('city','state'),'email','custom_amenities_display')
    readonly_fields = ('name','custom_amenities_display')
    def custom_amenities_display(self, obj):
        return mark_safe("Amenities can only be modified by special request, please contact the store manager at %s to create a request" % (obj.email,obj.email))
    custom_amenities_display.short_description = "Amenities"

admin.site.register(Store, StoreAdmin)
Django admin readonly_fields option for Django admin forms
Figure 28.- Django admin readonly_fields option for Django admin forms

In listing 18 you can see the readonly_fields option uses the custom_amenities_display callable to create a custom field. In figure 28 toward the bottom, you can see this new custom field -- in place of the original amenities field -- which shows a friendlier message than figure 27 and is also ineditable.

The exclude option for Django admin classes complements the fields option. Where as the fields option requires to explicitly create of list of fields to include in a Django admin form, the exclude offers the inverse behavior, requiring to explicitly list fields that shouldn't be part of a Django admin form. For example, for a Django model with the fields a,b,c the Django admin class fields = ('a','b') option is equivalent to the exclude = ('c') option (i.e. both options generate the same Django admin form).

The fieldsets option for Django admin classes provides greater control over the layout of pages used to create and edit records in the Django admin. Unlike the fields option which can alter the order of form fields or even nest form fields on the same line -- as illustrated in figure 26 -- the fieldsets option works with the fields option to divide a page into sets. Listing 19 illustrates the use of the fieldsets option and figure 29 and 30 the corresponding layout.

Listing 19 - Django admin fieldsets option for Django admin forms

from django.utils.safestring import mark_safe

class StoreAdmin(admin.ModelAdmin):
    fieldsets = (
        ('Store general information', {
            'fields': ('name', 'email')
        }),
        ('Store location options', {
            'classes': ('collapse',),
            'fields': ('address',('city', 'state')),
        }),
    )

admin.site.register(Store, StoreAdmin)
Django admin fieldsets option for Django admin forms
Figure 29.- Django admin fieldsets option for Django admin forms
Django admin fieldsets option for Django admin forms (collapsed)
Figure 30.- Django admin fieldsets option for Django admin forms (collapsed)

In listing 19 you can see the fieldsets options accepts a tuple value composed of two tuples, where each tuple represents a section of the admin page as illustrated in figures 29 and 30. Each of the internal tuples is made up by a first argument that represents the title or header of the section and a second argument that's a dictionary. This last dictionary itself contains values assigned to a fields key -- which functions just like the fields option described previously -- and a classes key which gives the section certain behaviors through CSS classes. In this case, you can see the second section in listing 19 indicates 'classes': ('collapse',), which tells Django to make the section collapsible, in figures 29 and 30 you can appreciate this collapsed and un-collapsed behavior.

In addition to the collapse option used in the classes key in fieldsets, another helpful CSS class option is wide which adds more horizontal space between fields. Note that it's valid to add any number of CSS classes to the classes key, either CSS classes included with the Django admin (i.e. collapse and wide) or even custom CSS classes.

The formfield_overrides option provides a way to override the default form widget associated with a Django model field in a Django admin form. By default, all Django model fields have a given widget assigned to them for the purpose of generating a form, for example a Django model CharField field generates a standard HTML <input> tag as illustrated in figure 31. However, if you feel the default widget for a given model field is inadequate for the Django admin, you can use the formfield_overrides option as illustrated in listing 20.

Listing 20 - Django admin formfield_overrides option for Django admin forms

class MenuAdmin(admin.ModelAdmin):
    formfield_overrides = { 
        models.CharField: {'widget': forms.Textarea}
    }

admin.site.register(Menu, MenuAdmin)
Django admin default CharField field display in Django admin form
Figure 31.- Django admin default CharField field display in Django admin form
Django admin custom CharField field display in Django admin form using formfield_overrides
Figure 32.- Django admin custom CharField field display in Django admin form using formfield_overrides

The formfield_overrides option in listing 20 tells the Django admin to use the forms.Textarea widget -- which generates a standard HTML <textarea> tag -- for all model fields that use the CharField. In figure 32 you can see the effects of applying the formfield_overrides option of listing 20, where as in figure 31 you can see the default widget used for CharField field which is a standard HTML <input> tag.

Note the formfield_overrides option applies the new widget type across all fields of a given model type. In addition it's also possible to use a custom Django widget in formfield_overrides in addition to widgets offered by Django such as the forms.Textarea widget used in listing 20.

While all the previous options allow you to tweak parts of a form used in the Django admin, sometimes it's necessary to create a form from scratch for the Django admin instead of tweaking the underlying form generated by the Django model (e.g. if you require custom validation for the Django admin form). To specify a custom form for a Django admin class you can use the form option.

Finally, one more option associated with Django admin forms that's specific to models that require slug field values is prepopulated_fields. If you're unfamiliar with the term 'slug', in the simplest terms a slug field value is a machine friendly representation of a string, for example upper-case letters are converted to lower case and special characters like spaces are converted to dashes. Through the prepopulated_fields option, you can tell Django that while a user types in a value for a given field in a Django admin form, it automatically fill another field in the form with the slug representation of the first.

For example, for the prepopulated_fields = {"city_for_url": ("city",)} option, if a user types in a value of San Diego into the city form field, Django fills the city_for_url form field with the value of san-diego. It's worth mentioning this functionality is achieved through JavaScript integrated into the Django admin and also that the prepopulated_fields option doesn't accept DateTimeField, ForeignKey or ManyToManyField fields as originating data sources.

Actions, links and positions: save_on_top, save_as(Clone records), view_on_site

At the bottom of each form page to create, update and delete Django model records are all the buttons to perform actions on the page: "Delete","Save and add another","Save and continue editing","Save", which are illustrated in figure 33. If a form is too large it can be difficult to reach these action buttons without scrolling down, so to solve this scenario Django admin classes support the save_on_top option which creates the same action buttons at the top of the page as illustrated in figure 34. Note that to generate the layout in figure 34 you use save_on_top = True.

Django admin standard action button on form page
Figure 33.- Django admin standard action button on form page
Django admin save_on_top option on form page
Figure 34.- Django admin save_on_top option on form page

Sometimes the need can arise to generate an identical or almost identical record from a pre-existing record in the Django admin. Because copy-pasting values from one form to another in the Django admin can be a time consuming and error prone process, Django admin classes also support the save_as option to clone pre-existing records. If you set the save_as = True option on a Django admin class, Django replaces the Save and add another button with the Save as new button, as illustrated in figure 35.

Django admin save_as (Clone) option on form page
Figure 35.- Django admin save_as (Clone) option on form page

If you click on the Save as button illustrated in figure 35, Django saves an identical record -- effectively cloning the record you see on screen -- using a different id value to differentiate between the two. Note that if the underlying Django model prohibits this action (e.g. fields must be unique) the operation does not take place and an error is thrown indicating the cause.

Django model classes support an instance method called get_absolute_url() by which it's possible to resolve the public URL of a record via a Django model's fields (e.g. the URLs /store/1/, /store/2/, /store/3/ fit the pattern where each number represents a Store id value, in which case the get_absolute_url() method for the Store model would return /store/<store_record_id>. In the Django admin, the get_absolute_url() method is tied directly to a link that aids in viewing the record at its public URL destination, figure 36 illustrates this link in the top right corner.

Django admin 'View on site' button due to get_absolute_url() Django model method
Figure 36.- Django admin "View on site" button due to get_absolute_url() Django model method

In figure 36, the View on site generates a link based on the Django model's get_absolute_url() method, as well as the current record's value as defined in this last method. In this manner, in a single click you're able to visualize the record you're editing at its public URL destination. If you wish to disable this button you can add the view_on_site = False option to the Django admin class. Note that if the underlying Django model class does not define the get_absolute_url() method no button is displayed, irrespective of the view_on_site value.

Relationships: filter_horizontal, filter_vertical, radio_fields, raw_id_fields, inlines

Django model relationships also have certain behaviors in the context of Django admin forms that are worth describing separatly.

When you use a ManyToManyField field on a Django model and access it in the Django admin, Django generates HTML <select>/<option> tags to choose the values for the ManyToManyField field -- as illustrated at the bottom of figures 23 and 24. However, because this type of selection method can be cumbersome for large lists, Django offers the filter_horizontal and filter_vertical options to generate separate panels to make value selection easier. Figure 37 illustrates the layout of the filter_horizontal option and figure 38 illustrates the layout of filter_vertical option.

Django admin filter_horizontal option for ManyToManyField
Figure 37.- Django admin filter_horizontal option for ManyToManyField
Django admin filter_vertical option for ManyToManyField
Figure 38.- Django admin filter_vertical option for ManyToManyField

In figures 37 and 38 you can see there are two panels to select and unselect values for a given ManyToManyField, with the only difference being filter_horizontal stacks the panels horizontally -- in figure 37 -- and filter_vertical stacks the panels vertically -- in figure 38. Assuming the ManyToManyField field is named amenities, to achieve the layout in figure 37 you would declare filter_horizontal = ('amenities',) and to achieve the layout in figure 38 you would declare filter_vertical = ('amenities',).

When you use a ForeignKey or the choices option in a Django model field and access it in the Django admin, Django also generates HTML <select>/<option> tags to choose the value for the ForeignKey field -- as illustrated at the top of figure 39. However, as an alternative it's possible to use the radio_fields option to generate a layout with HTML radio buttons. Listing 21 illustrates the two alternatives for the radio_fields option in a Django admin class and figures 40 and 41 the respective layouts.

Django admin default select list for ForeignKey or choices option
Figure 39.- Django admin default select list for ForeignKey or choices option
Django admin horizontal radio_fields option for ForeignKey or choices option
Figure 40.- Django admin horizontal radio_fields option for ForeignKey or choices option
Django admin vertical radio_fields option for ForeignKey or choices option
Figure 41.- Django admin vertical radio_fields option for ForeignKey or choices option

Listing 21 - Django admin radio_fields option for ForeignKey field

from django.contrib import admin
from coffeehouse.items.models import Item

# Option 1 (Horizontal)

class ItemAdmin(admin.ModelAdmin):
    radio_fields = {"menu": admin.HORIZONTAL}

admin.site.register(Item, ItemAdmin)    

# End Option 1

# Option 2 (Vertical)

class ItemAdmin(admin.ModelAdmin):
    radio_fields = {"menu": admin.VERTICAL}

admin.site.register(Item, ItemAdmin)    

# End Option 2             

In listing 21 you can see option 1 defines the radio_fields value with the {"menu": admin.HORIZONTAL} dictionary, where menu represents the ForeignKey field or a field with the choices option and admin.HORIZONTAL is the orientation of the radio fields -- this option generates a layout like the one in figure 40. Option 2 in listing 2 defines the radio_fields in a similar way except it uses the admin.VERTICAL value to tell Django to generate a vertical layout for the radio fields as illustrated in figure 41.

Another Django admin alternative for ForeignKey or ManyToManyField fields is the raw_id_fields option, which as its name implies relies on the raw id field value(s) to assign a ForeignKey value or ManyToManyField values. Figure 42 illustrates how a raw_id_fields option looks like in the Django admin.

Django admin raw_id_fields option for ForeignKey or ManyToManyField option
Figure 42.- Django admin raw_id_fields option for ForeignKey or ManyToManyField option

As you can see in figure 42, Django generates a basic text box where you can assign ids, aided by the adjacent magnifying glass button that allows you to search for and select values. Assuming the ForeignKey or ManyToManyField field is named menu, to achieve the layout in figure 38 you would declare raw_id_fields = ("menu",). Note that for a ForeignKey field the acceptable value is a single id and for a ManyToManyField field you can also introduce a list of ids separated by commas (i.e. CSV)

Finally, we come the Django admin class inlines option which is designed for models with relationships. The inlines option works with the Django admin TabularInline and StackedInline classes which are sub-classes of the InlineModelAdmin class which is a more specialized version of the standard admin.ModelAdmin class used for most Django admin classes.