Skip to content

Instantly share code, notes, and snippets.

@AndrewIngram
Last active May 28, 2021 12:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AndrewIngram/1724325685e681162fced2f672b5e6c2 to your computer and use it in GitHub Desktop.
Save AndrewIngram/1724325685e681162fced2f672b5e6c2 to your computer and use it in GitHub Desktop.
Filter Count Mixin for Django's admin
from django.contrib import admin
from django.db.models import Count, F
class FilterCountMixin(admin.SimpleListFilter):
def __init__(self, request, params, model, model_admin):
# Store the request so we can reapply all the filters properly later
self.request = request
super().__init__(request, params, model, model_admin)
def get_filter_counts_parameter_name(self):
return self.parameter_name
def get_filter_counts_queryset(self, qs):
return qs
def choices(self, changelist):
choices = super().choices(changelist)
qs = changelist.root_queryset
# To get the counts for *this* filter, we have to get the final queryset for the
# changelist with every filter applied *except* this one, then we can do our
# aggregations from that point.
for filter_spec in changelist.filter_specs:
if filter_spec is not self:
new_qs = filter_spec.queryset(self.request, qs)
if new_qs is not None:
qs = new_qs
counts = (
self.get_filter_counts_queryset(qs)
.annotate(count_lookup_field=F(self.get_filter_counts_parameter_name()))
.values("count_lookup_field")
.annotate(total=Count("id"))
)
total = sum(x["total"] for x in counts)
lookup_choices = self.lookup_choices.copy()
lookup_choices.insert(0, None)
for (spec, choice) in zip(lookup_choices, choices): # type: ignore
if spec is None:
choice["display"] = f'{choice["display"]} ({total})'
else:
count = next(
(x["total"] for x in counts if x["count_lookup_field"] == spec[0]),
0,
)
choice["display"] = f'{choice["display"]} ({count})'
yield choice
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment