A Django admin list filter for PostgreSQL ArrayFields

## Django’s ArrayField is a nifty Field API feature that avoids unnecessary tables when advanced aggregations aren’t needed. Yet, there’s no native helper for a simple admin list filter based on 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](https://docs.djangoproject.com/en/3.2/ref/contrib/admin/). 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,)

The code used to be a Github gist and is currently being evaluated for integration in the django-more-admin-filters package.

52 claps