Opened 9 years ago
Closed 9 years ago
#27116 closed New feature (wontfix)
Deferrable Admin Filters
| Reported by: | Austin Pua | Owned by: | nobody |
|---|---|---|---|
| Component: | contrib.admin | Version: | dev |
| Severity: | Normal | Keywords: | admin SimpleListFilter |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Hi Guys!
I implemented a custom SimpleListFilter for one of my projects, but due to the nature of what it does, it is a very expensive process. It involves, from a base queryset, splitting it into two copies and then performing additional filtering (different for each split), prefetching different objects per split, performing validation vs those prefetch objects, and then return pk values of all positive matches as a brand new queryset using __in lookup.
I read the django admin code, and due to the lazy evaluation of querysets, my custom SimpleListFilter ends up having to go through all entries of the model, even though there are other filters in place. Maybe my own code can use some optimizations, but is there any plausible use case for deferrable admin filters?
Basically, it should perform all other admin filtering first, so that the base queryset will be as small as possible which will limit the entries and also limit the number of Prefetch() objects. I was able to hack up some code (shown below) that does this, but is it sound to implement it out-of-the-box?
from django.contrib import admin
from django.contrib.admin.views.main import ChangeList
class MyTestFilter(admin.SimpleListFilter):
# ...
defer = True
# ...
class MyTestChangeList(ChangeList):
# ...
def get_filters(self, request):
filter_specs, status, lookup_params, use_distinct = super(MyTestChangeList, self).get_filters(request)
revised_filter_specs = []
deferred_filter_specs = []
for filter_spec in filter_specs:
if hasattr(filter_spec, 'defer') and filter_spec.defer:
deferred_filter_specs.append(filter_spec)
else:
revised_filter_specs.append(filter_spec)
self.deferred_filter_specs = deferred_filter_specs
return revised_filter_specs, status, lookup_params, use_distinct
def get_queryset(self, request):
qs = super(MyTestChangeList, self).get_queryset(request)
for filter_spec in self.deferred_filter_specs:
new_qs = filter_spec.queryset(request, qs)
if new_qs is not None:
qs = new_qs
return qs
At first glance, it seems to add a non-trivial amount of complexity for what likely isn't a common use case. Feel free to raise the idea on the DevelopersMailingList to get more feedback and see if anyone else has a similar need.