toxinu

Filtering and ordering with Restless

Posted on 29 December 2014

Let’s continue with my previous blog post on Restless introduction with Django. Today I’ll show you two quick and simple Mixins for Restless. One for filtering and another for ordering.

I won’t show you how to create Django app, models, etc. This is not the subject.

Just to be clear, I have this models.py for our Pizza shop.

from django.db import models


class Pizza(models.Model):
    name = models.CharField(max_length=100)
    price = models.FloatField()
    is_vegetarian = models.BooleanField(default=False)


class Ingredient(models.Model):
    name = models.CharField(max_length=100)


class PizzaIngredient(models.Model):
    pizza = models.ForeignKey('Pizza', related_name='ingredients')
    ingredient = models.ForeignKey('Ingredient')

And my resources.py:

from restless.dj import DjangoResource
from restless.preparers import FieldsPreparer

from .models import Pizza


class PizzaResource(DjangoResource):
    preparer = FieldsPreparer(fields={
        'id': 'id', 'name': 'name'})

    def list(self):
        return Pizza.objects.all()

Filtering

We are building a very simple and not complete APIFilterMixin class.

1 class APIFilterMixin:
2     allowed_fields_filter = []
3 
4     def filter(self, queryset):
5         filters = {}
6         for arg in self.request.GET:
7             if arg in self.allowed_fields_filter:
8                 filters.update({arg: self.request.GET.get(arg)})
9         return queryset.filter(**filters)

As you can see allowed_fields_filter allow us to put filters (with lookup) that we want to authorize on our Resource.

This mixin only expose a single filter method which take one argument, a QuerySet instance.

At line 6 we just iterate over all request arguments, check if this argument is in allowed_fields_filter, update filters dictionary and at (line 9), we unpack filters in a filter method.

And this is updated revision of our previous resources.py with new mixin:

class PizzaResource(APIFilterMixin, DjangoResource):
    preparer = FieldsPreparer(fields={
        'id': 'id', 'name': 'name'})
    allowed_fields_filter = [
        'name', 'name__startswith',
        'price', 'price__lte', 'price__gte',
        'ingredients__ingredient__name']

    def list(self):
        qs = Pizza.objects.all()
        return self.filter(qs)

We can now give all filters we have allowed in allowed_fields_filter, including relation lookups.

Ordering

Ordering is even more simple than filtering. Look how restless allow us to cleanly create our minimal API.

 1 class APIOrderingMixin:
 2     allowed_fields_ordering = []
 3     ordering_field = 'order_by'
 4 
 5     def ordering(self, queryset):
 6         order_by = self.request.GET.get(self.ordering_field)
 7         if not order_by:
 8             return queryset
 9 
10         if order_by.split('-')[-1] in self.allowed_fields_ordering:
11             return queryset.order_by(order_by)
12 
13         return queryset

This mixin support custom ordering field name with ordering_field. We can allow fields we want with allowed_fields_ordering. Another simple but cool feature is reverse ordering support (line 10).

Just add APIOrderingMixin to your PizzaResource and here we go.

Happy programming everybody!