Django admin add custom filter Django admin add custom filter python python

Django admin add custom filter


I would go with customized FieldListFilter as it allows to bind filter to different model fields based on your requirements.

What is we actually do to implement such filter is next:

  • build lookup_kwargs gte and lte and specify them as expected_parameters
  • define choices to return empty list otherwise NotImplementedError
  • create form to care fields validation
  • create custom template which just outputs form, e.g. {{spec.form}}
  • if form is valid take it's cleaned data, filter out Nones and filter queryset otherwise do something with errors (in code below errors are silenced)

Filter code:

class StartTimeFilter(admin.filters.FieldListFilter):    # custom template which just outputs form, e.g. {{spec.form}}    template = 'start_time_filter.html'    def __init__(self, *args, **kwargs):        field_path = kwargs['field_path']        self.lookup_kwarg_since = '%s__gte' % field_path        self.lookup_kwarg_upto = '%s__lte' % field_path        super(StartTimeFilter, self).__init__(*args, **kwargs)        self.form = StartTimeForm(data=self.used_parameters, field_name=field_path)    def expected_parameters(self):        return [self.lookup_kwarg_since, self.lookup_kwarg_upto]    # no predefined choices    def choices(self, cl):        return []    def queryset(self, request, queryset):        if self.form.is_valid():            filter_params = {                p: self.form.cleaned_data.get(p) for p in self.expected_parameters()                if self.form.cleaned_data.get(p) is not None            }            return queryset.filter(**filter_params)        else:            return queryset

Form can be as simple as follows:

class StartTimeForm(forms.Form):    def __init__(self, *args, **kwargs):        self.field_name = kwargs.pop('field_name')        super(StartTimeForm, self).__init__(*args, **kwargs)        self.fields['%s__gte' % self.field_name] = forms.DateField()        self.fields['%s__lte' % self.field_name] = forms.DateField()


This isn't exactly what you've asked for, but you could instead have the filter on the JobAdDuration modelAdmin. This way, you can get the corresponding jobs filtered according to the ad_activated and ad_finished fields. And I've added a link to the job field, so you can directly click it for easier navigation.

To make it a date html5 filter, I've used django-admin-rangefilter library.

from django.urls import reversefrom django.contrib import adminfrom .models import Job, JobAdDurationfrom django.utils.html import format_htmlfrom rangefilter.filter import DateRangeFilter@admin.register(JobAdDuration)class JobAdDurationAdmin(admin.ModelAdmin):    list_filter = (('ad_activated', DateRangeFilter), ('ad_finished', DateRangeFilter))    list_display = ('id', 'job_link', 'ad_activated', 'ad_finished')    def job_link(self, obj):        return format_html('<a href="{}">{}</a>', reverse('admin:job_job_change', args=[obj.job.id]), obj.job.title)    job_link.short_description = 'Job'

If you indeed want to go the existing route (filter inside JobAdmin), then things will get quite complicated.


I have recently faced similar problem where I needed to filter data based on value from another model. This can be done using SimpleListFilter. You just need a little tweak in the lookup and queryset function. I will suggest you to install django debug toolbar so that you may know what sql queries are being executed internally by django.

#import your corresponding models firstclass StartTimeFilter(SimpleListFilter):title = ('Start date')parameter_name = 'ad_finished'  def lookups(self, request, model_admin):   data = []   qs = JobAdDuration.objects.filter()   # Note : if you do not have distinct values of ad_activated apply distinct filter here to only get distinct values   print qs   for c in qs:       data.append([c.ad_activated, c.ad_activated])  # The first c.activated is the queryset condition your filter will execute on your Job model to filter data ... and second c.ad_activated is the data that will be displayed in dropdown in StartTimeFilter   return data  def queryset(self, request, queryset):     if self.value():       assigned = JobAdDuration.objects.filter(ad_activated__exact = self.value())  # add your custom filter function based on your requirement       return Job.objects.filter(pk__in=[current.job.id for current in assigned])     else:       return queryset

and in list_filter

list_filter = (StartTimeFilter) # no quotes else it will search for a field in the model 'job'.