Adding custom filters to Django admin is easy!

Feb 22, 2019 · by Tim Kamanin

I'm building a backend for a scraping tool that has two simple models Website and Page, where Website can have many pages.

In a simplified form it looks like:

class Website(models.Model):
  url = models.URLField(unique=True)

class Page(models.Model):
  website = models.ForeignKey(
  url = models.URLField(max_length=2083)
  title = models.CharField(max_length=255)
  content = models.TextField()

And I have an admin panel set in like this:

from django.contrib.admin import ModelAdmin

class WebsiteAdmin(AdminViews):
  actions = [scrape_website]

  list_display = ['url']
  search_fields = ['url']

Now what I want to do is to add a filter to the right-hand sidebar. It will allow me to filter websites by a scraped/not scraped status. And I take that "scraped" means a website has at least one page in the DB and "not scraped" means websites without any pages saved in the database.

And this is where we need to create a custom filter for Django admin. Like many things with Django, it's ridiculously simple, follow my hands:

from django.contrib.admin import ModelAdmin, SimpleListFilter

class ScrapeStatusFilter(SimpleListFilter):
  title = 'Scrape status' # a label for our filter
  parameter_name = 'pages' # you can put anything here

  def lookups(self, request, model_admin):
    # This is where you create filter options; we have two:
    return [
        ('scraped', 'Scraped'),
        ('not_scraped', 'Not scraped'),

  def queryset(self, request, queryset):
    # This is where you process parameters selected by use via filter options:
    if self.value() == 'scraped':
        # Get websites that have at least one page.
  return queryset.distinct().filter(pages__isnull=False)
    if self.value():
        # Get websites that don't have any pages.
        return queryset.distinct().filter(pages__isnull=True)

The last step is to add ScrapeStatusFilter to WebsiteAdmin:

class WebsiteAdmin(AdminViews):
  actions = [scrape_website]

  list_display = ['url']
  search_fields = ['url']
  list_filter = (ScrapeStatusFilter, )

And that should be it. You can now filter a list of websites via the custom crafted filter.

You're welcome!

Hey, if you've found this useful, please share the post to help other folks find it:

There's even more:

Subscribe for updates

  • via Twitter: @timonweb
  • old school RSS:
  • or evergreen email ↓ ↓ ↓