DjangoDjango

Adding custom filters to Django admin is easy!

Feb 22, 2019 · Updated: Jul 12, 2021 · 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(
        "Website", on_delete=models.CASCADE, related_name="pages"
    )
    url = models.URLField(max_length=2083)
    title = models.CharField(max_length=255)
    content = models.TextField()

And I have an admin panel set in admin.py 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 ↓ ↓ ↓