Download Django Admin Cookbook and more Quizzes Computer science in PDF only on Docsity!
Django Admin Cookbook
- Release 2.
- 1 Django Admin Cookbook - How to do things with Django admin. Contents
- 2 Text and Design
- 3 Calculated fields
- 4 Bulk and custom actions
- 5 Permissions
- 6 Multiple models and inlines
- 7 Listview Page
- 8 Changeview Page
- 9 Misc
- 10 Indices and tables
1 Django Admin Cookbook - How to do things with Django admin.
This is a book about doing things with Django admin. It takes the form of about forty questions and common tasks
with Django admin we answer.
The chapters are based on a common set of models, which you can read in detail here (Models used in this book). In
short, we have two apps, events and entities. The models are
- Events: Epic, Event, EventHero, EventVillian
- Entities: Category, Origin, Hero, Villain
1.1 Introduction
Django Admin Cookbook is a book about doing things with Django admin. It is targeted towards intermediate Django
developers, who have some experience with Django admin, but are looking to expand their knowledge of Django
admin and achieve mastery of Django admin.
It takes the form of question and answers about common tasks you might do with Django admin. All the chapters are
based on a common set of models, which you can read in detail here (Models used in this book). In short, we have two
apps, events and entities. The models are
- Events: Epic, Event, EventHero, EventVillain
- Entities: Category, Origin, Hero, Villain
site_header can be set to change this.
Listview Page
BY default it looks like this and is set to “Site administration”
index_title can be set to change this.
HTML title tag
By default it looks like this and is set to “Django site admin”
site_title can be set to change this.
We can make the three changes in urls.py:
2.3 How to create two independent admin sites?
The usual way to create admin pages is to put all models in a single admin. However it is possible to have multiple
admin sites in a single Django app.
Right now our entity and event models are in same place. UMSRA has two distinct group researching Events
and Entities, and so wants to split the admins.
We will keep the default admin for entities and create a new subclass of AdminSite for events.
In our events/admin.py we do:
from django.contrib.admin import AdminSite class EventAdminSite (AdminSite): site_header = "UMSRA Events Admin" site_title = "UMSRA Events Admin Portal" index_title = "Welcome to UMSRA Researcher Events Portal"
event_admin_site = EventAdminSite(name='event_admin')
event_admin_site.register(Epic) event_admin_site.register(Event) event_admin_site.register(EventHero) event_admin_site.register(EventVillain)
And change the urls.py to
from events.admin import event_admin_site
(continues on next page)
(continued from previous page)
urlpatterns = [ path('entity-admin/', admin.site.urls), path('event-admin/', event_admin_site.urls), ]
This separates the admin. Both admins are available at their respective urls, /entity-admin/ and
event-admin/.
2.4 How to remove default apps from Django admin?
Django will include django.contrib.auth in INSTALLED_APPS, which means User and Groups models are
included in admin automatically.
If you want to remove it, you will have to unregister them.
from django.contrib.auth.models import User, Group
admin.site.unregister(User) admin.site.unregister(Group)
After making these changes, your admin should look like this.
(continued from previous page)
With the changes your base_site.html will look like this:
{% extends "admin/base.html" %}
{% load staticfiles %}
{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% ˓→endblock %}
{% block branding %}
{% endblock %}
{% block nav- global %}{% endblock %}
And your admin will look like this
2.6 How to override Django admin templates?
https://docs.djangoproject.com/en/dev/ref/contrib/admin/#overriding-admin-templates
3 Calculated fields
3.1 How to show calculated fields on listview page?
You have an admin for the Origin model like this:
@admin .register(Origin) class OriginAdmin (admin.ModelAdmin): list_display = ("name",)
Apart from the name, we also want to show the number of heroes and number of villains for each origin, which is not
a DB field on Origin. You can do this in two ways.
Adding a method to the model
You can add two methods to your Origin model like this:
def hero_count(self,): return self.hero_set.count()
def villain_count(self): return self.villain_set.count()
And change list_display to list_display = ("name", "hero_count", "villain_count").
Adding a method to the ModelAdmin
If you don’t want to add method to the model, you can do instead add the method to the ModelAdmin.
def hero_count(self, obj): return obj.hero_set.count()
def villain_count(self, obj): return obj.villain_set.count()
The list_display, as earlier, changes to list_display = ("name", "hero_count",
"villain_count").
Performance considerations for calculated_fields
With either of the above approaches, you would be running two exta queries per object (One per calculated field). You
can find how to optimize this in How to optimize queries in Django admin?.
With any of these changes your admin looks like this:
(continued from previous page) def hero_count(self, obj): return obj._hero_count
def villain_count(self, obj): return obj._villain_count
There are no per object extra queries. Your admin continues to look like it did before the annotate call.
3.3 How to enable sorting on calculated fields?
Django adds sorting capabilities on fields which are attributes on the models. When you add a calculated field Django
doesn’t know how to do a order_by, so it doesn’t add sorting capability on that field.
If you want to add sorting on a calculated field, you have to tell Django what to pass to order_by. You can do this
by setting the admin_order_field attribute on the calculated field method.
You start from the admin you wrote in the previous chapter (How to optimize queries in Django admin?).:
hero_count.admin_order_field = '_hero_count' villain_count.admin_order_field = '_villain_count'
With these changes your admin becomes:
@admin .register(Origin) class OriginAdmin (admin.ModelAdmin): list_display = ("name", "hero_count", "villain_count")
def get_queryset(self, request): queryset = super().get_queryset(request) queryset = queryset.annotate( _hero_count=Count("hero", distinct= True ), _villain_count=Count("villain", distinct= True ), ) return queryset (continues on next page)
(continued from previous page)
def hero_count(self, obj): return obj._hero_count
def villain_count(self, obj): return obj._villain_count
hero_count.admin_order_field = '_hero_count' villain_count.admin_order_field = '_villain_count'
Here is the admin sorted on hero_count
3.4 How to enable filtering on calculated fields?
You have a Hero admin which looks like this:
@admin .register(Hero) class HeroAdmin (admin.ModelAdmin): list_display = ("name", "is_immortal", "category", "origin", "is_very_benevolent") list_filter = ("is_immortal", "category", "origin",)
def is_very_benevolent(self, obj): return obj.benevolence_factor > 75
It has one calculated field is_very_benevolent, and your admin looks like this
3.5 How to show “on” or “off” icons for calculated boolean fields?
In the previous chapter, How to enable filtering on calculated fields? you added a boolean field.:
def is_very_benevolent(self, obj): return obj.benevolence_factor > 75
Which looks like this
The is_very_benevolent field shows the string True and False, unlike the builtin BooleanFields which show an
on and off indicator. To fix this, you add a boolean attribute on your method. You final modeladmin looks like this:
@admin .register(Hero) class HeroAdmin (admin.ModelAdmin): list_display = ("name", "is_immortal", "category", "origin", "is_very_benevolent") list_filter = ("is_immortal", "category", "origin", IsVeryBenevolentFilter)
def is_very_benevolent(self, obj): return obj.benevolence_factor > 75
is_very_benevolent.boolean = True
And your admin looks like this
(continued from previous page)
export_as_csv.short_description = "Export Selected"
This adds an action called export selected, which looks like this:
You will then change the export_as_csv to this:
import csv from django.http import HttpResponse ...
def export_as_csv(self, request, queryset):
meta = self.model._meta field_names = [field.name for field in meta.fields]
response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta) writer = csv.writer(response)
writer.writerow(field_names) for obj in queryset: row = writer.writerow([getattr(obj, field) for field in field_names])
return response
This exports all of the selected rows. If you notice, export_as_csv doens’t have anything specific to Hero, so
you can extract the method to a mixin.
With the changes, your code looks like this:
class ExportCsvMixin : def export_as_csv(self, request, queryset):
meta = self.model._meta field_names = [field.name for field in meta.fields]
response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta) writer = csv.writer(response)
writer.writerow(field_names) for obj in queryset: row = writer.writerow([getattr(obj, field) for field in field_names])
return response
export_as_csv.short_description = "Export Selected"
@admin .register(Hero) class HeroAdmin (admin.ModelAdmin, ExportCsvMixin): list_display = ("name", "is_immortal", "category", "origin", "is_very_benevolent") list_filter = ("is_immortal", "category", "origin", IsVeryBenevolentFilter) actions = ["export_as_csv"]
...
@admin .register(Villain) class VillainAdmin (admin.ModelAdmin, ExportCsvMixin): list_display = ("name", "category", "origin") actions = ["export_as_csv"]
You can add such an export to other models by subclassing from ExportCsvMixin
4.3 How to remove the delete selected action in Django admin?
By default Django adds a Delete Selected action to the listview page. You have been asked to remove the action from
the Hero admin.
The method ModelAdmin.get_actions returns the actions shown. By overriding this method, to remove
delete_selected We can remove it form the dropdown. Your code looks like this with the changes.:
def get_actions(self, request): actions = super().get_actions(request) if 'delete_selected' in actions: del actions['delete_selected'] return actions
And your admin looks like this