I'm building a backend for a scraping tool that has two simple models
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
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.