[Fixed]-Django admin list_filter – filter field by is empty (None or empty string "")

24👍

admin.py

class ImageListFilter(admin.SimpleListFilter):

    title = _('Has photo')

    parameter_name = 'has_photo'

    def lookups(self, request, model_admin):

        return (
            ('yes', _('Yes')),
            ('no',  _('No')),
        )

    def queryset(self, request, queryset):

        if self.value() == 'yes':
            return queryset.filter(image__isnull=False).exclude(image='')

        if self.value() == 'no':
            return queryset.filter(Q(image__isnull=True) | Q(image__exact=''))


class UserAdmin(admin.ModelAdmin):

    list_filter = [ImageListFilter]

13👍

As of Django 3.1, you can use EmptyFieldListFilter as follows:

list_filter = (
    ("my_fk_field", admin.EmptyFieldListFilter),
)

See the docs for details. See the release notes as well.

The code is available here, if you need to customize it or backport it.

6👍

Based on MaxCore’s answer, I created customised class that I can use to modify title and parameter name:

from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.db.models import Q

class NotNullFilter(admin.SimpleListFilter):
    title = _('Filter title not set')

    parameter_name = 'parameter name not set'

    def lookups(self, request, model_admin):

        return (
            ('not_null', _('Not empty only')),
            ('null', _('Empty only')),
        )

    def queryset(self, request, queryset):

        if self.value() == 'not_null':
            is_null_false = {
                self.parameter_name + "__isnull": False
            }
            exclude = {
                self.parameter_name: ""
            }
            return queryset.filter(**is_null_false).exclude(**exclude)

        if self.value() == 'null':
            is_null_true = {
                self.parameter_name + "__isnull": True
            }
            param_exact = {
                self.parameter_name + "__exact": ""
            }
            return queryset.filter(Q(**is_null_true) | Q(**param_exact))


class YoutubeNotNullFilter(NotNullFilter):
    title = "Youtube"
    parameter_name = "youtube_videoid"
👤K.H.

3👍

K.H.’s approach of a base class that could be reused easily was really helpful for me. I couldn’t get the written example to work, but with a small tweak it worked perfectly (Python 2.7, Django 1.10) to achieve this.

from django.contrib import admin


class NotNullFilter(admin.SimpleListFilter):
    title = 'Filter title not set'
    parameter_name = 'parameter name not set'

    def lookups(self, request, model_admin):
        return (
            ('not_null', 'Not empty only'),
            ('null', 'Empty only'),
        )

    def queryset(self, request, queryset):
        filter_string = self.parameter_name + '__isnull'
        if self.value() == 'not_null':
            is_null_false = {
                filter_string: False
            }
            return queryset.filter(**is_null_false)

        if self.value() == 'null':
            is_null_true = {
                filter_string: True
            }
            return queryset.filter(**is_null_true)


class YoutubeNotNullFilter(NotNullFilter):
    title = "Youtube"
    parameter_name = "youtube_videoid"


class SomeVideoClass(admin.ModelAdmin):
    ...
    list_filter = [YouTubeNotNullFilter, ...]

3👍

A bit more flexible version based on MaxCore’s answer, which creates new classes on-the-fly

def by_null_filter(attr, name, null_label='is Null', non_null_label='not Null', bool_dt=False, bool_value=False):

    class ByNullFilter(admin.SimpleListFilter):
        """List display filter to show null/not null values"""
        parameter_name = attr
        title = name

        def lookups(self, request, model_admin):
            if bool_dt:
                label_null = 'not %s' % attr
                label_non_null = attr
            elif bool_value:
                label_null = 'no'
                label_non_null = 'yes'
            else:
                label_null = null_label
                label_non_null = non_null_label

            return (
                ('not_null', label_non_null),
                ('null', label_null)
            )

        def queryset(self, request, queryset):
            filter_string = attr + '__isnull'
            if self.value() == 'not_null':
                is_null_false = {
                    filter_string: False
                }
                return queryset.filter(**is_null_false)

            if self.value() == 'null':
                is_null_true = {
                    filter_string: True
                }
                return queryset.filter(**is_null_true)

    return ByNullFilter

Usage
if you have processed DateTime field and you want to filter on it – does it have value or null

# with default filter labels (`not Null`, `is Null`)
list_filter = (by_null_filter('processed', 'Processed'), )
# Processed header with labels based on field name (`processed`, `not processed`)
list_filter = (by_null_filter('processed', 'Processed', bool_dt=True), )
# Paid header filter with labels based on bool(end_time) (`yes`, `no`)
list_filter = (by_null_filter('end_time', 'Paid', bool_value=True), )
👤pymen

Leave a comment