320 lines
10 KiB
Plaintext
320 lines
10 KiB
Plaintext
===============
|
|
Getting Started
|
|
===============
|
|
|
|
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):
|
|
name = django_filters.CharFilter(lookup_expr='iexact')
|
|
|
|
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.
|
|
|
|
Declaring filters
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
The declarative syntax provides you with the most flexibility when creating
|
|
filters, however it is fairly verbose. We'll use the below example to outline
|
|
the :ref:`core filter arguments <core-arguments>` on a ``FilterSet``::
|
|
|
|
class ProductFilter(django_filters.FilterSet):
|
|
price = django_filters.NumberFilter()
|
|
price__gt = django_filters.NumberFilter(name='price', lookup_expr='gt')
|
|
price__lt = django_filters.NumberFilter(name='price', lookup_expr='lt')
|
|
|
|
release_year = django_filters.NumberFilter(name='release_date', lookup_expr='year')
|
|
release_year__gt = django_filters.NumberFilter(name='release_date', lookup_expr='year__gt')
|
|
release_year__lt = django_filters.NumberFilter(name='release_date', lookup_expr='year__lt')
|
|
|
|
manufacturer__name = django_filters.CharFilter(lookup_expr='icontains')
|
|
|
|
class Meta:
|
|
model = Product
|
|
|
|
There are two main arguments for filters:
|
|
|
|
- ``name``: The name of the model field to filter on. You can traverse
|
|
"relationship paths" using Django's ``__`` syntax to filter fields on a
|
|
related model. ex, ``manufacturer__name``.
|
|
- ``lookup_expr``: The `field lookup`_ to use when filtering. Django's ``__``
|
|
syntax can again be used in order to support lookup transforms.
|
|
ex, ``year__gte``.
|
|
|
|
.. _`field lookup`: https://docs.djangoproject.com/en/dev/ref/models/querysets/#field-lookups
|
|
|
|
Together, the field ``name`` and ``lookup_expr`` represent a complete Django
|
|
lookup expression. A detailed explanation of lookup expressions is provided in
|
|
Django's `lookup reference`_. django-filter supports expressions containing
|
|
both transforms and a final lookup for version 1.9 of Django and above.
|
|
For Django version 1.8, transformed expressions are not supported.
|
|
|
|
.. _`lookup reference`: https://docs.djangoproject.com/en/dev/ref/models/lookups/#module-django.db.models.lookups
|
|
|
|
|
|
Generating filters with Meta.fields
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
The FilterSet Meta class provides a ``fields`` attribute that can be used for
|
|
easily specifying multiple filters without significant code duplication. The
|
|
base syntax supports a list of multiple field names::
|
|
|
|
import django_filters
|
|
|
|
class ProductFilter(django_filters.FilterSet):
|
|
class Meta:
|
|
model = Product
|
|
fields = ['price', 'release_date']
|
|
|
|
The above generates 'exact' lookups for both the 'price' and 'release_date'
|
|
fields.
|
|
|
|
Additionally, a dictionary can be used to specify multiple lookup expressions
|
|
for each field::
|
|
|
|
import django_filters
|
|
|
|
class ProductFilter(django_filters.FilterSet):
|
|
class Meta:
|
|
model = Product
|
|
fields = {
|
|
'price': ['lt', 'gt'],
|
|
'release_date': ['exact', 'year__gt'],
|
|
}
|
|
|
|
The above would generate 'price__lt', 'price__gt', 'release_date', and
|
|
'release_date__year__gt' filters.
|
|
|
|
.. note::
|
|
|
|
The filter lookup type 'exact' is an implicit default and therefore never
|
|
added to a filter name. In the above example, the release date's exact
|
|
filter is 'release_date', not 'release_date__exact'.
|
|
|
|
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::
|
|
|
|
class ProductFilter(django_filters.FilterSet):
|
|
class Meta:
|
|
model = Product
|
|
fields = ['manufacturer__country']
|
|
|
|
|
|
Overriding default filters
|
|
""""""""""""""""""""""""""
|
|
|
|
Like ``django.contrib.admin.ModelAdmin``, it is possible to override
|
|
default filters for all the models fields of the same kind using
|
|
``filter_overrides`` on the ``Meta`` class::
|
|
|
|
class ProductFilter(django_filters.FilterSet):
|
|
|
|
class Meta:
|
|
model = Product
|
|
fields = {
|
|
'name': ['exact'],
|
|
'release_date': ['isnull'],
|
|
}
|
|
filter_overrides = {
|
|
models.CharField: {
|
|
'filter_class': django_filters.CharFilter,
|
|
'extra': lambda f: {
|
|
'lookup_expr': 'icontains',
|
|
},
|
|
},
|
|
models.BooleanField: {
|
|
'filter_class': django_filters.BooleanFilter,
|
|
'extra': lambda f: {
|
|
'widget': forms.CheckboxInput,
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
Request-based filtering
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
The ``FilterSet`` may be initialized with an optional ``request`` argument. If
|
|
a request object is passed, then you may access the request during filtering.
|
|
This allows you to filter by properties on the request, such as the currently
|
|
logged-in user or the ``Accepts-Languages`` header.
|
|
|
|
|
|
Filtering the primary ``.qs``
|
|
"""""""""""""""""""""""""""""
|
|
|
|
To filter the primary queryset by the ``request`` object, simply override the
|
|
``FilterSet.qs`` property. For example, you could filter blog articles to only
|
|
those that are published and those that are owned by the logged-in user
|
|
(presumably the author's draft articles).
|
|
|
|
.. code-block:: python
|
|
|
|
class ArticleFilter(django_filters.FilterSet):
|
|
|
|
class Meta:
|
|
model = Article
|
|
fields = [...]
|
|
|
|
@property
|
|
def qs(self):
|
|
parent = super(ArticleFilter, self).qs
|
|
return parent.filter(is_published=True) \
|
|
| parent.filter(author=request.user)
|
|
|
|
|
|
Filtering the related queryset for ``ModelChoiceFilter``
|
|
""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
|
|
|
The ``queryset`` argument for ``ModelChoiceFilter`` and ``ModelMultipleChoiceFilter``
|
|
supports callable behavior. If a callable is passed, it will be invoked with the
|
|
``request`` as its only argument. This allows you to perform the same kinds of
|
|
request-based filtering without resorting to overriding ``FilterSet.__init__``.
|
|
|
|
.. code-block:: python
|
|
|
|
def departments(request):
|
|
company = request.user.company
|
|
return company.department_set.all()
|
|
|
|
class EmployeeFilter(filters.FilterSet):
|
|
department = filters.ModelChoiceFilter(queryset=departments)
|
|
...
|
|
|
|
|
|
Customize filtering with ``Filter.method``
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
You can control the behavior of a filter by specifying a ``method`` to perform
|
|
filtering. View more information in the :ref:`method reference <filter-method>`.
|
|
Note that you may access the filterset's properties, such as the ``request``.
|
|
|
|
.. code-block:: python
|
|
|
|
class F(django_filters.FilterSet):
|
|
username = CharFilter(method='my_custom_filter')
|
|
|
|
class Meta:
|
|
model = User
|
|
fields = ['username']
|
|
|
|
def my_custom_filter(self, queryset, name, value):
|
|
return queryset.filter(**{
|
|
name: value,
|
|
})
|
|
|
|
|
|
The view
|
|
--------
|
|
|
|
Now we need to write a view::
|
|
|
|
def product_list(request):
|
|
f = ProductFilter(request.GET, queryset=Product.objects.all())
|
|
return render(request, '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.
|
|
|
|
If you want to access the filtered objects in your views, for example if you
|
|
want to paginate them, you can do that. They are in f.qs
|
|
|
|
|
|
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 %}
|
|
<form action="" method="get">
|
|
{{ filter.form.as_p }}
|
|
<input type="submit" />
|
|
</form>
|
|
{% for obj in filter.qs %}
|
|
{{ obj.name }} - ${{ obj.price }}<br />
|
|
{% 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.qs`` we get the objects in
|
|
the resulting queryset.
|
|
|
|
|
|
Generic view & configuration
|
|
-----------------------------
|
|
|
|
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 ``filterset_class`` argument, similar to
|
|
``ListView`` in Django itself::
|
|
|
|
# urls.py
|
|
from django.conf.urls import url
|
|
from django_filters.views import FilterView
|
|
from myapp.models import Product
|
|
|
|
urlpatterns = [
|
|
url(r'^list/$', FilterView.as_view(model=Product)),
|
|
]
|
|
|
|
You must provide a template at ``<app>/<model>_filter.html`` which gets the
|
|
context parameter ``filter``. Additionally, the context will contain
|
|
``object_list`` which holds the filtered queryset.
|
|
|
|
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 url
|
|
from django_filters.views import object_filter
|
|
from myapp.models import Product
|
|
|
|
urlpatterns = [
|
|
url(r'^list/$', object_filter, {'model': Product}),
|
|
]
|
|
|
|
The needed template and its context variables will also be the same as the
|
|
class-based view above.
|