When using ModelChoiceField or ModelMultipleChoiceField in a Django form, is there a way to pass in a cached set of choices? Currently, if I specify the choices via the queryset parameter, it results in a database hit.
I'd like to cache these choices using memcached and prevent unnecessary hits to the database when displaying a form with such a field.
You can override "all" method in QuerySet something like
And then change queryset of field before form using (for exmple when you use formsets)
Here is a little hack I use with Django 1.10 to cache a queryset in a formset:
@jnns I noticed that in your code the queryset is evaluated twice (at least in my Admin inline context), which seems to be an overhead of django admin anyway, even without this mixin (plus one time per inline when you don't have this mixing).
In the case of this mixin, this is due to the fact that formfield.choices has a setter that (to simplify) triggers the re-evaluation of the object's queryset.all()
I propose an improvement which consists of dealing directly with formfield.cache_choices and formfield.choice_cache
Here it is:
The reason that
ModelChoiceField
in particular creates a hit when generating choices - regardless of whether the QuerySet has been populated previously - lies in this linein
django.forms.models.ModelChoiceIterator
. As the Django documentation on caching of QuerySets highlights,So I'd prefer to just use
even though I'm not 100% sure about all implications of this (I do know I do not have big plans with the queryset afterwards, so I think I'm fine without the copy
.all()
creates). I'm tempted to change this in the source code, but since I'm going to forget about it at the next install (and it's bad style to begin with) I ended up writing my customModelChoiceField
:This does not solve the general problem of database caching, but since you're asking about
ModelChoiceField
in particular and that's exactly what got me thinking about that caching in the first place, thought this might help.I also stumbled over this problem while using an InlineFormset in the Django Admin that itself referenced two other Models. A lot of unnecessary queries are generated because, as Nicolas87 explained,
ModelChoiceIterator
fetches the queryset everytime from scratch.The following Mixin can be added to
admin.ModelAdmin
,admin.TabularInline
oradmin.StackedInline
to reduce the number of queries to just the ones needed to fill the cache. The cache is tied to theRequest
object, so it invalidates on a new request.