March 16, 2022
A Django admin list filter for PostgreSQL ArrayFields
Django’s ArrayField is a nifty resource straight out of the Field API, helping you avoid polluting your database with tables that you already know won’t be subject to advanced aggregations. Yet, there is no such thing as a native helper for a trivial implementation of an admin list filter feeding from an ArrayField. Let’s fill the gap!
Say you have a WebPage
model with a domains
ArrayField. You could use a clean solution and have a list filter for that field, requiring only a display title and the query string parameter name:
class DomainsListFilter(ArrayFieldListFilter):
"""An admin list filter for domains."""
title = "domain"
parameter_name = "domains"
To achieve this, we can have the ArrayFieldListFilter
class helper inherit from SimpleListFilter
. In the lookups
method override, we can parse the input received from the domains
field as a lexicographically sorted list of non-falsy values for the filter widget and also replace the original queryset
method to have the URL lookup value processed in two alternative control branches — allowing for either a proper (non-exact) value or an empty one disabling the filtering altogether.
from django.contrib.admin import SimpleListFilter
class ArrayFieldListFilter(SimpleListFilter):
"""An admin list filter for ArrayFields."""
def lookups(self, request, model_admin):
"""Return the filtered queryset."""
queryset_values = model_admin.model.objects.values_list(
self.parameter_name, flat=True
)
values = []
for sublist in queryset_values:
if sublist:
for value in sublist:
if value:
values.append((value, value))
else:
values.append(("null", "-"))
return sorted(set(values))
def queryset(self, request, queryset):
"""Return the filtered queryset."""
lookup_value = self.value()
if lookup_value:
lookup_filter = (
{"{}__isnull".format(self.parameter_name): True}
if lookup_value == "null"
else {"{}__contains".format(self.parameter_name):
[lookup_value]}
)
queryset = queryset.filter(**lookup_filter)
return queryset
Finally, all is left to do is to use the class in the ModelAdmin
, like so:
from django.contrib import admin
class WebPageAdmin(admin.ModelAdmin):
"""The web page admin."""
list_filter = (DomainsListFilter,)
Find here the helper class’ source code.