Using django-filter =================== Django-filter provides a simple way to filter down a queryset based on parameters a user provides. Say we have a ``Product`` model and we want to let our users filter which products they see on a list page. The model -------- Let's start with our model:: from django.db import models class Product(models.Model): name = models.CharField(max_length=255) price = models.DecimalField() description = models.TextField() release_date = models.DateField() manufacturer = models.ForeignKey(Manufacturer) The filter ---------- We have a number of fields and we want to let our users filter based on the price or the release_date. We create a ``FilterSet`` for this:: import django_filters class ProductFilter(django_filters.FilterSet): class Meta: model = Product fields = ['price', 'release_date'] As you can see this uses a very similar API to Django's ``ModelForm``. Just like with a ``ModelForm`` we can also override filters, or add new ones using a declarative syntax (see below for even more concise syntax):: import django_filters class ProductFilter(django_filters.FilterSet): price = django_filters.NumberFilter(lookup_type='lt') class Meta: model = Product fields = ['price', 'release_date'] Filters take a ``lookup_type`` argument which specifies what lookup type to use with Django's ORM. So here when a user entered a price it would show all Products with a price less than that. See below for a compact way to declare it. Filters also take any arbitrary keyword arguments which get passed onto the ``django.forms.Field`` initializer. These extra keyword arguments get stored in ``Filter.extra``, so it's possible to override the initializer of a ``FilterSet`` to add extra ones:: class ProductFilterSet(django_filters.FilterSet): class Meta: model = Product fields = ['manufacturer'] def __init__(self, *args, **kwargs): super(ProductFilterSet, self).__init__(*args, **kwargs) self.filters['manufacturer'].extra.update( {'empty_label': 'All Manufacturers'}) more concise filter declaration -------------------------------- As an alternative way to set ``lookup_type`` you can add the ``lookup_type`` to the field_name in the fields list:: class ProductFilter(django_filters.FilterSet): class Meta: model = Product fields = ['price__lt', 'release_date'] The view -------- Now we need to write a view:: def product_list(request): f = ProductFilter(request.GET, queryset=Product.objects.all()) return render_to_response('my_app/template.html', {'filter': f}) If a queryset argument isn't provided then all the items in the default manager of the model will be used. The URL conf ------------ We need a URL pattern to call the view:: url(r'^list$', views.product_list) The template ------------ And lastly we need a template:: {% extends "base.html" %} {% block content %}
{{ filter.form.as_p }}
{% for obj in filter %} {{ obj.name }} - ${{ obj.price }}
{% endfor %} {% endblock %} And that's all there is to it! The ``form`` attribute contains a normal Django form, and when we iterate over the ``FilterSet`` we get the objects in the resulting queryset. You can also allow the user to control ordering, this is done by providing the ``order_by`` argument in the Filter's Meta class. ``order_by`` can be either a ``list`` or ``tuple`` of field names, in which case those are the options, or it can be a ``bool`` which, if True, indicates that all fields that the user can filter on can also be sorted on. If you want to control the display of items in ``order_by``, you can set it to a list or tuple of 2-tuples in the format ``(field_name, display_name)``. This lets you override the displayed names for your ordering fields:: order_by = ( ('name', 'Company Name'), ('average_rating', 'Stars'), ) Note that the default query parameter name used for ordering is ``o``. You can override this by setting an ``order_by_field`` attribute on the ``FilterSet`` class to the string value you would like to use. The inner ``Meta`` class also takes an optional ``form`` argument. This is a form class from which ``FilterSet.form`` will subclass. This works similar to the ``form`` option on a ``ModelAdmin.`` Items in the ``fields`` sequence in the ``Meta`` class may include "relationship paths" using Django's ``__`` syntax to filter on fields on a related model. If you want to use a custom widget, or in any other way override the ordering field you can override the ``get_ordering_field()`` method on a ``FilterSet``. This method just needs to return a Form Field. Generic View ------------ In addition to the above usage there is also a class-based generic view included in django-filter, which lives at ``django_filters.views.FilterView``. You must provide either a ``model`` or ``filter_class`` argument, similar to ``ListView`` in Django itself:: # urls.py from django.conf.urls import patterns, url from django_filters.views import FilterView from myapp.models import Product urlpatterns = patterns('', (r'^list/$', FilterView.as_view(model=Product)), ) You must provide a template at ``/_filter.html`` which gets the context parameter ``filter``. Additionally, the context will contain ``object_list`` which holds the filtered queryset. Lookup type: compact declaration ================================= As a mean to add readability, when declaring the fields you can add the lookup_type:: class ProductFilterSet(django_filters.FilterSet): class Meta: model = Product fields = ['manufacturer__icontains', 'date__gte'] A legacy functional generic view is still included in django-filter, although its use is deprecated. It can be found at ``django_filters.views.object_filter``. You must provide the same arguments to it as the class based view:: # urls.py from django.conf.urls import patterns, url from myapp.models import Product urlpatterns = patterns('', (r'^list/$', 'django_filters.views.object_filter', {'model': Product}), ) The needed template and its context variables will also be the same as the class-based view above. setting a label --------------- A further step in customisazion is the possibility to set a label for the filter directly in the field name specification:: class ProductFilterSet(django_filters.FilterSet): class Meta: model = Product fields = [name__icontains:product', 'manufacturer__name__icontains:manifacturer',] this is particularly needed when several different filters are done against field whose name is just the same (``name`` in the example above)