6. Filter Products by its Attributes

Besides Full Text Search, adding some filter functionality to an e-commerce site is another very important feature. Customers must be able to narrow down the list of available products to a set of desired products using a combination of prepared filter attributes.

Since in djangoSHOP each product class declares its own database model with its own attributes, often related with foreign data models, filtering must be implemented by the merchant on top of the existing product models. Fortunately the REST framework in combination with `Django Filter`_ makes this a rather simple task.

6.1. Adding a filter to the List View

In djangoSHOP listing the products normally is controlled by shop.views.catalog.ProductListView or shop.views.catalog.CMSPageProductListView. By default these View classes are configured to use the default filter backends as provided by the REST framework. These filter backends can be configured globally through the settings variable DEFAULT_FILTER_BACKENDS.

Additionally we can subclass the filter backends for each View class in our urls.py. Say, we need a special catalog filter, which groups our products by a certain product attribute. Then we can create customized filter backend

filters.py
from rest_framework.filters import BaseFilterBackend

class CatalogFilterBackend(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        queryset = queryset.order_by('attribute__sortmetric')
        return queryset

In urls.py, where we route requests to the class shop.views.catalog.ProductListView, we then replace the default filter backends by our own implementation:

myshop/urls/catalog.py
from django.conf.urls import patterns, url
from rest_framework.settings import api_settings
from shop.views.catalog import ProductListView
from myshop.serializers import ProductSummarySerializer

urlpatterns = patterns('',
    url(r'^$', ProductListView.as_view(
        serializer_class=ProductSummarySerializer,
        filter_backends=[CatalogFilterBackend],
    ),
)

The above example is very simple but gives a rough impression on its possibilities.

6.1.1. Working with Django-Filter

django-filter is a generic, reusable application to alleviate writing some of the more mundane bits of view code. Specifically, it allows users to filter down a queryset based on a model’s fields, displaying the form to let them do this.

REST framework also includes support for generic filtering backends that allow you to easily construct complex searches and filters.

By creating a class which inherit from django_filters.FilterSet, we can build filters against each attribute of our product. This filter then uses the passed in query parameters to restrict the set of products available from our catalog:

myshop/filters.py
import django_filters

class ProductFilter(django_filters.FilterSet):
    width = django_filters.RangeFilter(name='width')
    props = django_filters.MethodFilter(action='filter_properties', widget=SelectMultiple)

    class Meta:
        model = OurProduct
        fields = ['width', 'props']

    def filter_properties(self, queryset, values):
        for value in values:
            queryset = queryset.filter(properties=value)
        return queryset

This example assumes that OurProduct has a numeric attribute named width and a many-to-many field named properties.

We then can add this filter to the list view for our products. In djangoSHOP we normally do this through the url patterns:

myshop/urls.py
urlpatterns = patterns('',
    url(r'^$', ProductListView.as_view(
        serializer_class=ProductSummarySerializer,
        filter_class=ProductFilter,
    )),
    # other patterns
)

By appending ?props=17 to the URL, the above filter class will restrict the products in our list view to those with a property of 17.