How to do advanced filtering with Django admin ?

Django admin custom filter

In this tutorial we will learn basic, custom and advanced filtering like chaining filters with the Django admin.

You can find the source code in this repository

For this tutorial will use the models Shop and Product

class Shop(models.Model):
    user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)
    name = models.TextField()
    email = models.CharField(max_length=200)
    address = models.CharField(max_length=1024,null=True, blank=True)
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)

    def __str__(self):
        return u'%s' % (self.name)
class Product(models.Model):
    user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)
    refShop = models.ForeignKey(Shop, db_index=True,on_delete=models.CASCADE)
    title = models.TextField(max_length=65)
    price = models.FloatField(default=0)
    withEndDate = models.BooleanField(default=False)
    endDate = models.DateField(blank=True,null=True)
    description = models.CharField(max_length=65, default='', null=True, blank=True)
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)
    available = models.BooleanField(default=True)

Really simple a product belongs to a shop.

1. How to add basic filtering to django admin interface

Let’s say we would like to filter our Shop in the admin by the user. To do that just add the list_filter instruction to the ShopAdmin class (in admin.py) and specify the field(s) on which we could filter.

list_filter = ('user', )

The full code

class ShopAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {'fields': ['user','name','email','address']}),
    ]
    list_display = ('user','name', 'email',  )
    list_filter = ('user', )
    ordering = ('user',)

And now a filtering option will appear at the top right of the screen.

Even better, let’s say we would like to filter only for users having a shop. This time we will write:

list_filter = (('user', admin.RelatedOnlyFieldListFilter),)

And now the filtering option only contain one user, because other users don’t have Shops.

2. How to add custom filter to django admin

Now let’s imagine we would like to add a filter based on business rules and not on a particular field. To do that we can create our own class heriting from admin.SimpleListFilter and need to implement two methods : lookups and queryset. The first method need to return a list of tuples. The first element is the coded value that will appear in the url query and the second element is the name of the filter that will be displayed on the admin interface. The second method is the queryset that will implement the filter.

class ProductPriceFilter(admin.SimpleListFilter):
    title = 'price'
    parameter_name = 'price'
    def lookups(self, request, model_admin):
        """
                Returns a list of tuples. The first element in each
                tuple is the coded value for the option that will
                appear in the URL query. The second element is the
                human-readable name for the option that will appear
                in the right sidebar.
         """
        return (("low_price","Filter by low price"),("high_price","Filter by high price"))

    def queryset(self, request, queryset):
        """
                Returns the filtered queryset based on the value
                provided in the query string and retrievable via
                `self.value()`.
         """

        if self.value()=="low_price":
            return queryset.filter(price__lte=5)
        elif self.value() == "high_price":
            return queryset.filter(price__gt=5)
        else:
            return queryset.all()

In this example, we will add the possibility to filter products by low price or high price. We consider that a low price is a price under or equal to 5 whereas a high price will be above 5.

To use our filter we just need to indicate our class name to the list_filter option

 list_filter=(ProductPriceFilter,)

As we can see we have our new “By price” filter in the interface. If we click one of the value, the list of displayed results will change and if we pay attention to the url we can see the selected filter value in the url will contains our parameter added in the lookups method:

http://127.0.0.1:8000/admin/backoffice/product/?price=low_price

3. How to chain filters in the Django admin

Ok so now let’s go a little bit further. Let’s say we would like to implement related filters (i.e chaining our filters). We can add a user filtering on our product

list_filter=('user',ProductPriceFilter,'refShop',)

Now if we filter by user, we will still see all the values of Shop (in Shop filter).

It could be better to show only shops belonging to the user we just filtered on. This is what chaining filter is. The list of values displayed in a filter will depend on value selected in another filter.

To do that we will examine the query url and determine if a user has been used to filtered the results (if so the url will contains ?user__id__exact=<value>)

Based on that we can create a custom filter that will select values only for selected user id value.

class UserShopFilter(admin.SimpleListFilter):
    title = 'shop'
    parameter_name = 'refShop'
    def lookups(self, request, model_admin):
        if 'user__id__exact' in request.GET:
            id = request.GET['user__id__exact']
            shops = set([c.refShop for c in model_admin.model.objects.all().filter(user=id)])
        else:
            shops = set([c.refShop for c in model_admin.model.objects.all()])
        return [(s.id, s.name) for s in shops]

    def queryset(self, request, queryset):
        if self.value():
            return queryset.filter(refShop__id__exact=self.value())

So in the lookups method, we check if we have a user selected and if so will select only shops belonging to that user

 shops = set([c.refShop for c in model_admin.model.objects.all().filter(user=id)])

Otherwise we can select all shops

shops = set([c.refShop for c in model_admin.model.objects.all()])

Then we can send backs results that will be displayed in the interface

return [(s.id, s.name) for s in shops]
Django dependent filter

Now if we select a user B on the admin interface, we will only be able to see and filter on shops belonging to our user B.

That’s it we have implemented a django filter dependent on another filter.

Christophe Surbier