debian-django-filter/docs/guide/usage.txt

320 lines
10 KiB
Plaintext
Raw Normal View History

===============
Getting Started
===============
2009-01-30 20:15:57 +01:00
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
2014-09-29 20:20:07 +02:00
our users filter which products they see on a list page.
The model
---------
Let's start with our model::
2009-01-30 20:15:57 +01:00
from django.db import models
2009-01-30 20:15:57 +01:00
class Product(models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField()
description = models.TextField()
release_date = models.DateField()
manufacturer = models.ForeignKey(Manufacturer)
2009-01-30 20:15:57 +01:00
The filter
----------
We have a number of fields and we want to let our users filter based on the
2009-01-30 20:15:57 +01:00
price or the release_date. We create a ``FilterSet`` for this::
import django_filters
class ProductFilter(django_filters.FilterSet):
2016-02-19 18:24:29 +01:00
name = django_filters.CharFilter(lookup_expr='iexact')
2009-01-30 20:15:57 +01:00
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
2016-02-19 18:24:29 +01:00
declarative syntax.
2016-02-19 18:24:29 +01:00
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):
2016-02-19 18:24:29 +01:00
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
2016-02-19 18:24:29 +01:00
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
2009-01-30 20:15:57 +01:00
2016-02-19 18:24:29 +01:00
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
2016-04-04 08:44:51 +02:00
2016-02-19 18:24:29 +01:00
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)
...
2016-08-04 20:39:54 +02:00
Customize filtering with ``Filter.method``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2016-02-19 19:05:16 +01:00
2016-08-04 20:39:54 +02:00
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``.
2016-08-04 20:39:54 +02:00
.. code-block:: python
class F(django_filters.FilterSet):
2016-08-15 03:49:10 +02:00
username = CharFilter(method='my_custom_filter')
2016-08-04 20:39:54 +02:00
class Meta:
model = User
fields = ['username']
2016-08-04 20:39:54 +02:00
def my_custom_filter(self, queryset, name, value):
return queryset.filter(**{
name: value,
})
The view
--------
2009-01-30 20:15:57 +01:00
Now we need to write a view::
2009-01-30 20:15:57 +01:00
def product_list(request):
f = ProductFilter(request.GET, queryset=Product.objects.all())
return render(request, 'my_app/template.html', {'filter': f})
2009-01-30 20:15:57 +01:00
If a queryset argument isn't provided then all the items in the default manager
of the model will be used.
2014-09-29 20:20:07 +02:00
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
2013-06-04 14:01:38 +02:00
The URL conf
------------
We need a URL pattern to call the view::
url(r'^list$', views.product_list)
The template
------------
2009-01-30 20:15:57 +01:00
And lastly we need a template::
2009-01-30 20:15:57 +01:00
{% extends "base.html" %}
2009-01-30 20:15:57 +01:00
{% block content %}
<form action="" method="get">
{{ filter.form.as_p }}
2009-05-23 18:51:50 +02:00
<input type="submit" />
</form>
2016-06-30 07:57:54 +02:00
{% for obj in filter.qs %}
2009-05-23 18:51:50 +02:00
{{ obj.name }} - ${{ obj.price }}<br />
2009-01-30 20:15:57 +01:00
{% endfor %}
{% endblock %}
And that's all there is to it! The ``form`` attribute contains a normal
2016-06-30 07:57:54 +02:00
Django form, and when we iterate over the ``FilterSet.qs`` we get the objects in
2009-01-30 20:15:57 +01:00
the resulting queryset.
2009-01-30 20:36:21 +01:00
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
2014-09-29 20:20:07 +02:00
urlpatterns = [
url(r'^list/$', FilterView.as_view(model=Product)),
]
You must provide a template at ``<app>/<model>_filter.html`` which gets the
2014-09-29 20:20:07 +02:00
context parameter ``filter``. Additionally, the context will contain
``object_list`` which holds the filtered queryset.
2013-06-04 14:01:38 +02:00
A legacy functional generic view is still included in django-filter, although
2014-09-29 20:20:07 +02:00
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.