[Django]-Filtering Django Admin by Null/Is Not Null

2πŸ‘

βœ…

I ended up using a mixture of the top solution here, along with this snippet.

However, I had to tweak the snippet slightly, dropping the field type restriction and adding the new field_path, recently added in 1.3.

from django.contrib.admin.filterspecs import FilterSpec
from django.db import models
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _

class NullFilterSpec(FilterSpec):
    #fields = (models.CharField, models.IntegerField, models.FileField)

    @classmethod
    def test(cls, field):
        #return field.null and isinstance(field, cls.fields) and not field._choices
        return field.null and not field._choices
    #test = classmethod(test)

    def __init__(self, f, request, params, model, model_admin, field_path=None):
        super(NullFilterSpec, self).__init__(f, request, params, model, model_admin, field_path)
        self.lookup_kwarg = '%s__isnull' % f.name
        self.lookup_val = request.GET.get(self.lookup_kwarg, None)

    def choices(self, cl):
        # bool(v) must be False for IS NOT NULL and True for IS NULL, but can only be a string
        for k, v in ((_('All'), None), (_('Has value'), ''), (_('Omitted'), '1')):
            yield {
                'selected' : self.lookup_val == v,
                'query_string' : cl.get_query_string({self.lookup_kwarg : v}),
                'display' : k
            }

# Here, we insert the new FilterSpec at the first position, to be sure
# it gets picked up before any other
FilterSpec.filter_specs.insert(0,
    # If the field has a `profilecountry_filter` attribute set to True
    # the this FilterSpec will be used
    (lambda f: getattr(f, 'isnull_filter', False), NullFilterSpec)
)
πŸ‘€Cerin

33πŸ‘

After Django 3.1 you can use EmptyFieldListFilter:

class MyAdmin(admin.ModelAdmin):
    list_filter =  (
        ("model_field", admin.EmptyFieldListFilter),
    )
πŸ‘€Calzzetta

16πŸ‘

Since Django 1.4 brings some changes to filters, I thought I save someone the time I just spent modifying the code from Cerin’s accepted answer to work with Django 1.4 rc1.

I have a model that has TimeField(null=True) named β€œstarted” and I wanted to filter for null and non-null values, so it’s prety much the same problem as OP.
So, here is what worked for me…

Defined (actually included) these in admin.py:

from django.contrib.admin.filters import SimpleListFilter

class NullFilterSpec(SimpleListFilter):
    title = u''

    parameter_name = u''

    def lookups(self, request, model_admin):
        return (
            ('1', _('Has value'), ),
            ('0', _('None'), ),
        )

    def queryset(self, request, queryset):
        kwargs = {
        '%s'%self.parameter_name : None,
        }
        if self.value() == '0':
            return queryset.filter(**kwargs)
        if self.value() == '1':
            return queryset.exclude(**kwargs)
        return queryset



class StartNullFilterSpec(NullFilterSpec):
    title = u'Started'
    parameter_name = u'started'

Than just used them in ModelAdmin:

class SomeModelAdmin(admin.ModelAdmin):
    list_filter =  (StartNullFilterSpec, )
πŸ‘€frnhr

8πŸ‘

I have a simpler version of frnhr’s answer, which actually filters on __isnull condition.
(Django 1.4+):

from django.contrib.admin import SimpleListFilter

class NullListFilter(SimpleListFilter):
    def lookups(self, request, model_admin):
        return (
            ('1', 'Null', ),
            ('0', '!= Null', ),
        )

    def queryset(self, request, queryset):
        if self.value() in ('0', '1'):
            kwargs = { '{0}__isnull'.format(self.parameter_name) : self.value() == '1' }
            return queryset.filter(**kwargs)
        return queryset

Then also:

class StartNullListFilter(NullListFilter):
    title = u'Started'
    parameter_name = u'started'

and finally:

class SomeModelAdmin(admin.ModelAdmin):
    list_filter =  (StartNullListFilter, )

I personally don’t like to trash my admin.py with dozens of classes, so I came up with such a helper function:

def null_filter(field, title_=None):
    class NullListFieldFilter(NullListFilter):
        parameter_name = field
        title = title_ or parameter_name
    return NullListFieldFilter

Which I can later apply as in:

class OtherModelAdmin(admin.ModelAdmin):
    list_filter =  (null_filter('somefield'), null_filter('ugly_field', _('Beautiful Name')), )
πŸ‘€Tomasz Gandor

6πŸ‘

there is a simple way:

class RefererFilter(admin.SimpleListFilter):
    title = 'has referer'
    # Parameter for the filter that will be used in the URL query.
    parameter_name = 'referer__isnull'

    def lookups(self, request, model_admin):
        return (
            ('False', 'has referer'),
            ('True', 'has no referer'),
        )

    def queryset(self, request, queryset):
        if self.value() == 'False':
            return queryset.filter(referer__isnull=False)
        if self.value() == 'True':
            return queryset.filter(referer__isnull=True)

Then just used them in ModelAdmin:

class PersonAdmin(admin.ModelAdmin):
    list_filter =  (RefererFilter,) 
πŸ‘€yahuishuo

2πŸ‘

A snippet with a better explanation might be this. Django 1.4 will ship with a simplified filter mechanism.

πŸ‘€schneck

1πŸ‘

There has been a ticket bouncing around for this for 4 years (https://code.djangoproject.com/ticket/5833). It missed the 1.3 milestone, but has reached new feature status and presumably has found it’s way into trunk. If you don’t mind running off of trunk, you can use it now. The patch is supposedly 1.3 compatible, though, so you can probably get by with just patching your currently installation.

πŸ‘€Chris Pratt

0πŸ‘

For, django versions less that 3.1, copy the code of EmptyFieldListFilter below

from django.contrib.admin import FieldListFilter
from django.contrib.admin.options import IncorrectLookupParameters
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.utils.translation import gettext_lazy as _


class EmptyFieldListFilter(FieldListFilter):
    def __init__(self, field, request, params, model, model_admin, field_path):
        if not field.empty_strings_allowed and not field.null:
            raise ImproperlyConfigured(
                "The list filter '%s' cannot be used with field '%s' which "
                "doesn't allow empty strings and nulls." % (
                    self.__class__.__name__,
                    field.name,
                )
            )
        self.lookup_kwarg = '%s__isempty' % field_path
        self.lookup_val = params.get(self.lookup_kwarg)
        super().__init__(field, request, params, model, model_admin, field_path)

    def queryset(self, request, queryset):
        if self.lookup_kwarg not in self.used_parameters:
            return queryset
        if self.lookup_val not in ('0', '1'):
            raise IncorrectLookupParameters

        lookup_condition = models.Q()
        if self.field.empty_strings_allowed:
            lookup_condition |= models.Q(**{self.field_path: ''})
        if self.field.null:
            lookup_condition |= models.Q(**{'%s__isnull' % self.field_path: True})
        if self.lookup_val == '1':
            return queryset.filter(lookup_condition)
        return queryset.exclude(lookup_condition)

    def expected_parameters(self):
        return [self.lookup_kwarg]

    def choices(self, changelist):
        for lookup, title in (
            (None, _('All')),
            ('1', _('Empty')),
            ('0', _('Not empty')),
        ):
            yield {
                'selected': self.lookup_val == lookup,
                'query_string': changelist.get_query_string({self.lookup_kwarg: lookup}),
                'display': title,
            }

You can use this to define null field value filter in admin as

import myfile

class MyAdmin(admin.ModelAdmin):
    list_filter =  (
        ("model_field", myfile.EmptyFieldListFilter),
    )

Leave a comment