Django admin custom page layout, data & behaviors

In addition to the Django admin class options described in previous sections, there are multiple ways to customize the layout, data and behaviors of Django admin pages. You can customize certain global values used across all Django admin pages without the need to modify any Django template. But in addition, it's also possible to customize any template used by a Django admin page -- like the log in, log out, password update, display record and create/update/delete record page -- to alter its layout (e.g. modify the default blue CSS skin or component positions in the page).

Finally, it's also possible to customize the data passed to Django admin pages, as well as modify the default behavior run by Django admin pages (e.g. CRUD actions) by means of methods and fields declared as part of a Django admin class.

Django admin custom global values for default templates

By default, the Django admin is configured as part of a Django project's urls.py file, as shown in the following snippet:

from django.conf.urls import url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

While admin.site.urls -- from the django.contrib package -- lets you set up the Django admin on the /admin/ url, the same django.contrib.admin.site object also allows you to customize certain values used by all Django admin pages.

Listing 11-22 illustrates how to customize several Django admin fields through the django.contrib.admin.site object.

Listing 11-22. Django admin django.contrib.admin.site object to customize fields

from django.conf.urls import url
from django.contrib import admin

admin.site.site_header = 'Coffeehouse admin'
admin.site.site_title = 'Coffeehouse admin'
admin.site.site_url = 'http://coffeehouse.com/'
admin.site.index_title = 'Coffeehouse administration'
admin.empty_value_display = '**Empty**'

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]
Tip It's also possible to define the custom admin field values in listing 11-22 inside the settings.py file. Simply import django.contrib.admin and declare the admin fields to centralize them with other custom configurations in settings.py.

As you can see in listing 11-22, before declaring admin.site.urls as a url statement, there are a series of declarations on the admin.site object that are also part of the django.contrib package:

Figure 11-45. Django admin main index with custom global values

Figure 11-46. Django admin log in page with custom global values

As you can see figures 11-45 and 11-46, with a few simple statements like the ones listing 11-22, you can customize the Django admin template content, without the need to interact with templates or HTML.

It's worth noting the admin.empty_value_display option described in listing 11-22 is applied to all Django admin models when a record field contains an empty value. Examples of the admin.empty_value_display option were described earlier in this chapter in the 'Record Display' section, specifically in figure 11-5 and figure 11-6, as well as listing 11-5.

Django admin custom page layout with custom templates

Although the django.contrib.admin.site object options presented in listing 11-22 offer a quick way to customize Django admin pages, they can fall short in the face of more sophisticated requirements, in which case you must rely on custom templates.

The default templates used by the Django admin are located under the /django/contrib/admin/templates/ directory of your Django installation inside your operating system's or virtual env Python environment (e.g. <virtual_env_directory>/lib/python3.5/site-packages/django/contrib/admin/templates/).

Similar to the Django template customization techniques described in previous chapters (e.g. Django form widgets, Django allauth), you can create a copy of these default templates and place them inside your project. In this manner, the templates inside a project take precedence over the default Django admin templates, where you can customize the project templates to fit your needs.

The Django admin /django/contrib/admin/templates/ directory contains two template folders: admin and registration. Copy them to a project directory that's part of a DIRS folder of the TEMPLATES/DIRS variable in settings.py.

Tip See the book's accompanying source code which includes the layout of all Django admin templates.

All the Django admin templates inherit their behavior from the admin/base_site.html template, which itself inherits its behavior from the admin/base.html template. If you're unfamiliar with how Django template inheritance works, look over Chapter 3 which describes this topic.

If you open the admin/base.html template, you can see the core structure behind every Django admin page, such as: the HTML <head> section (e.g. CSS files, meta tags), navigation header and message notification block, among other things. Therefore, you can modify the admin/base.html template to include custom CSS or JavaScript files to alter the 'look & feel' of every Django admin page.

In addition to the admin/base.html template, there are many other templates inside the admin and registration directories whose functions are escribed in the following list:

Note Other pages in the admin folder not described in this list (e.g. filter.html, object_history.html) are more granular templates -- included as part of the larger templates in this list -- which you can customize as necessary.

As you can see, by creating a copy of the Django admin templates and placing them in your project, you can fine tune the layout of every Django admin page by modifying its backing template.An important modular behavior worth mentioning about the Django admin index.html, change_list.html and change_form.html templates, is how they can be applied to individual Django admin apps or models.

By default, if you provide a custom layout for the admin/index.html, admin/change_list.html or admin/change_form.html templates, these templates are used for all apps and models in the Django admin (i.e. globally). However, sometimes it can be necessary to customize Django admin index pages, list pages or form pages for only certain apps (e.g. stores app) or inclusively an individual model (e.g. Item model).

To define a custom Django admin template for all models in an app, you can create a Django admin template and place it under the template path admin/<app_name>/ (e.g. admin/stores/change_list.html to define a change_list.html template for all stores app models).

To define a custom Django admin template for a single model, you can create a Django admin template and place it under the template path admin/<app_name>/<model>/ (e.g. admin/items/item/change_list.html to define a change_list.html template to use on the Item model of the items app).

Note Only the templates admin/index.html-admin/app_index.html, change_form.html, change_list.html, delete_confirmation.html, object_history.html and popup_response.html can be customized on a per app and per model basis.

Django admin custom static resources

If you customize the Django admin admin/base.html template in your project with custom CSS or JavaScript files, these static resources take effect on every Django admin page. While this can be a desired effect in certain circumstances, in other cases, it can be necessary to only apply custom static resources to certain Django admin pages.

Django admin classes support the Media class to define both CSS and JavaScript files and include them on all pages associated with a given Django admin class. The advantage of using the Media class on a Django admin class, is that you don't need to deal with templates or HTML markup, with the Django admin automatically loading the static resources as part of every admin page linked to an admin class. Listing 11-23 illustrates a Django admin class that makes use of the Media class.

Listing 11-23. Django admin class with Media class to define custom static resources.

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

class ItemAdmin(admin.ModelAdmin):
      list_per_page = 5
      class Media:
            css = {
                "screen": ("css/items/items.css",)
            }
            js = ("js/items/items.js",)

admin.site.register(Item, ItemAdmin)

As you can see in listing 11-23, the Media class supports the css and js fields to declare both CSS and JavaScript static files,respectively. In the case of css, listing 11-23 declares a dictionary, where the key corresponds to the CSS media type and the value is a tuple with a CSS file. For the case of js, listing 11-23 declares a tuple pointing to a JavaScript file. All files declared as part of a Media class are automatically searched for in Django's static file directory paths -- as described in Chapter 5.

The final outcome of listing 11-23 is that all Django admin pages associated with the ItemAdmin admin class (e.g. index.html, change_list.html, change_form.html) will include an additional CSS import statement (e.g. <link href="/static/css/items/items.css" type="text/css" media="screen" rel="stylesheet" />), as well as an additional JavaScript import statement (e.g. <script type="text/javascript" src="/static/js/items/items.js"></script>).

It's worth pointing out that while you can include any 3rd party CSS or JavaScript library in a Django admin page (e.g. Bootstrap, D3), Django admin pages already include the popular jQuery 2.2 library under the django.jQuery namespace to fulfill certain functionalities. The Django admin uses a jQuery namespace, to let you import any other jQuery library version in Django admin pages without fear of conflict. If you want to leverage the included Django admin jQuery library for your own custom JavaScript, you must wrap your JavaScript logic in this namespace, as illustrated in the following snippet:

(function($) {
    // Custom JavaScript logic leveraging the Django admin built-in jQuery libray  
    $(document).ready(function() {
        $('.deletelink').on('click',function() {
            if( !confirm('Are you sure you want to delete this record ?')) {
                return false;
            }
        });
    });
})(django.jQuery); // <-- Note wrapping namespace

As you can see in this last snippet, by wrapping your custom JavaScript logic in the django.jQuery namespace, it gains access to the Django admin built-in jQuery library (i.e. the custom JavaScript logic gains access to the jQuery $ scope).

Grappelli project -- an out-of-the-box Django admin supplement

If you want try a different 'look & feel' for the Django admin, without having to write custom templates or supporting CSS & JavaScript files, there are various Django apps designed for this purpose.

One of the most popular apps is the 'Grappelli Project'[3].Grappelli uses the 'Compass' CSS authoring Framework to include additional Django admin features like: auto-complete, inline sortable 'drag & drop' and support for jQuery plugins, among other things.

Django admin custom data and behaviors with admin class fields and methods

Although the modification of Django admin templates allows you to generate any type of Django admin page layout, it can still fall short for cases where you need to include custom data in Django admin pages (e.g. add data from another model for reference) or override the default CRUD behaviors of Django admin pages (e.g. perform a custom audit trail for delete actions).

Django admin classes like the ones you've written in this chapter since listing 11-1, rely on over two dozen fields -- all of which you explored in the previous sections in this chapter as Django admin read options & create/update/delete options -- and over three dozen methods[4] to define a Django admin page's default data and behaviors.

In a very similar way to how you can customize the default behaviors and data used by Django class-based views -- described in Chapter 9 -- Django admin classes can also define their own custom fields and methods to override their default data and behaviors.

The bulk of this chapter already covered all of the Django admin class fields to customize Django admin page behaviors, so I won't re-address them once again. However, I will provide examples of the most common Django admin class methods to illustrate how to add custom data and override other default behaviors in Django admin pages.

Listing 11-24 illustrates a Django admin class that uses a custom implementation of the changelist_view() method -- which adds custom data to access in the underlying Django admin change_list.html template -- as well as a custom implementation of the delete_view() method -- to execute custom logic when a delete action is taken on a Django admin class.

Listing 11-24. Django admin class with custom changelist_view() and delete_view() methods.

from coffeehouse.stores.models import Store

class StoreAdmin(admin.ModelAdmin):
    search_fields = ['city','state']
    def changelist_view(self, request, extra_context=None):
        # Add extra context data to pass to change list template
        extra_context = extra_context or {}
        extra_context['my_store_data'] = {'onsale':['Item 1','Item 2']}
        # Execute default logic from parent class changelist_view()
        return super(StoreAdmin, self).changelist_view(
            request, extra_context=extra_context
        )
    def delete_view(self, request, object_id, extra_context=None):
        # Add custom audit logic here
        # Execute default logic from parent class delete_view()
        return super(StoreAdmin, self).delete_view(
            request, object_id, extra_context=extra_context
        )
admin.site.register(Store, StoreAdmin)

As you can in listing 11-24, both the changelist_view() and delete_view() methods are declared inline with the Django admin search_fields option you learned earlier. In this case, the changelist_view() method in listing 11-24 is triggered whenever you visit the list view page of the Store model in the Django admin (e.g. illustrated in figure 11-10). Notice the changelist_view() method adds a custom value to the extra_context variable which is then returned as part of the response, in this case it's a hard-coded value, but you can equally add any type of data like a model query or 3rd party API call to pass to the Django admin page. Because of this last workflow, the list view page of the Store model (i.e. the change_list.html template) can gain access to custom data to display as part of the page.

The delete_view() method in listing 11-24 is triggered whenever you delete a Store model in the Django admin. In this case, the delete_view() method in listing 11-24 simply triggers the default action of deleting a record by calling the parent class's delete_view() method, but you can see how it's possible to execute custom logic (e.g. create an audit trail) whenever a delete action if performed on a Store model in the Django admin.

As I've already mentioned, Django admin classes rely on over three dozen methods to implement their default behavior, all of which you can customize to fit your requirements. Given the amount of custom variations this amount of methods can generate, you can use the example in listing 11-24 as a guide and consult the footnote on the page for other methods you can customize in Django admin classes.

  1. http://grappelliproject.com/    

  2. https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#adminsite-methods