Tweaks for making django admin faster
Here follow a number of tricks I’ve employed in the past to make django admin faster.
Editable foreign keys in the changelist
If you have foreign keys in list_editable django will make 1 database query for each item in the changelist. Quite a lot for a changelist of 100 items. The trick is to cache the choices for that formfield:
class MyAdmin(admin.ModelAdmin):
list_editable = 'myfield',
def formfield_for_dbfield(self, db_field, **kwargs):
request = kwargs['request']
formfield = super(MyAdmin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name == 'myfield':
myfield_choices_cache = getattr(request, 'myfield_choices_cache', None)
if myfield_choices_cache is not None:
formfield.choices = myfield_choices_cache
else:
request.myfield_choices_cache = formfield.choices
return formfield
Foreign keys or many to many fields in admin inlines
If you have fk of m2m fields on InlineModelAdmin for every object in the formset you’ll get a database hit. You can avoid this by having something like:
class MyAdmin(admin.TabularInline):
fields = 'myfield',
def formfield_for_dbfield(self, db_field, **kwargs):
formfield = super(MyAdmin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name == 'myfield':
# dirty trick so queryset is evaluated and cached in .choices
formfield.choices = formfield.choices
return formfield
Enable template caching
It’s amazing how easy it is to forget to add this in your settings:
TEMPLATE_LOADERS = (
('django.template.loaders.cached.Loader', (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)),
)
Use select_related for the edit forms too
In case you have some readonly fields on the edit form and they need related data to display list_select_related doesn’t help. Eg:
class MyAdmin(admin.ModelAdmin):
readonly_fields = 'myfield',
def queryset(self, request):
return super(MyAdmin, self).queryset(request).select_related('myfield')
Use annotations if possible for function entries list_display instead of making additional queries
Check the aggregation api to see if you can use this. Here’s the typical example:
class AuthorAdmin(admin.ModelAdmin):
list_display = 'books_count',
def books_count(self, obj):
return obj.books_count
def queryset(self, request):
return super(AuthorAdmin, self).queryset(
request).annotate(books_count=Count('books'))
The models would look like this:
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
name = models.CharField(max_length=100)
author = models.ForeignKey(Author, related_name="books")
Cache the filters from the admin changelist
This has the obvious tradeoff that you’ll have stale data in the list of filter but if they don’t change that often and those distinct queries are killing your database then this will help a lot. Just add a custom change_list.html template in your project (eg: templates/<myapp>/change_list.html):
{% extends "admin/change_list.html" %}
{% load admin_list i18n cache %}
{% block filters %}
{% cache 300 admin_filters request.GET.items request.path request.user.username %}
{% if cl.has_filters %}
<div id="changelist-filter">
<h2>{% trans 'Filter' %}</h2>
{% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
</div>
{% endif %}
{% endcache %}
{% endblock %}
Bonus trick
frame = sys._getframe(1)
while frame:
if frame.f_code.co_name == 'render_change_form':
if 'request' in frame.f_locals:
request = frame.f_locals['request']
break
frame = frame.f_back
else:
raise RuntimeError("Could not find request object.")
# do stuff with request
This could be used in some specific cases (eg: you need the request in a widget’s render method), as a last resort ofcourse ;)
–
What did you do to make django admin faster ?
Does caching or optimizing django-admin makes any sense? Some additional database hits fired only by admin action are nothing compared to queries executed by thousands of visits on public pages.
If your edit page makes 500 queries to the database and you have a dozen users hammering the admin then it makes perfect sense.
These are some great tips. Nice list.
@seler Sometimes it’s nice to make the admin nice and snappy for the client (the one that pays you) to increase their satisfaction–that alone is reason enough.
Great stuff, Johnny!!
@seler Not to mention that hammering your database via the admin can have a serious performance impact to your finely tuned public facing stuff. And this is easy stuff… Ionel’s point about the template caching is spot on.
Annotated queryset sometimes is much slower than additional querysets for each entry on a page. Imagine that you have 4000 authors, then the books are counted for each author even if (s)he isn’t shown in a page when you use annotation.