источник:
- filters.py
Корневой QuerySet, предоставляемый менеджером, описывает все объекты в таблице базы данных. Однако обычно требуется выбрать только подмножество из полного набора объектов.
По умолчанию общие списочные представления DRF возвращают весь QuerySet
для менеджера модели. Часто требуется, чтобы API ограничивал количество элементов, возвращаемых QuerySet
.
Простейшим способом фильтрации QuerySet
любого представления, подкласса GenericAPIView
, является переопределение метода .get_queryset()
.
Переопределение этого метода позволяет настраивать QuerySet
, возвращаемый представлением, различными способами.
Возможно, потребуется отфильтровать QuerySet
, чтобы возвращать только результаты, относящиеся к текущему аутентифицированному пользователю, сделавшему запрос.
Это можно сделать с помощью фильтрации по значению request.user
.
Например:
from myapp.models import Purchase
from myapp.serializers import PurchaseSerializer
from rest_framework import generics
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
def get_queryset(self):
"""
This view should return a list of all the purchases
for the currently authenticated user.
"""
user = self.request.user
return Purchase.objects.filter(purchaser=user)
Другой стиль фильтрации может включать ограничение QuerySet
на основе некоторой части URL.
Например, если в конфигурации URL содержится запись следующего вида:
re_path('^purchases/(?P<username>.+)/$', PurchaseList.as_view()),
Затем вы можете написать представление, возвращающее QuerySet
покупок, отфильтрованный по имени пользователя в части URL:
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
def get_queryset(self):
"""
This view should return a list of all the purchases for
the user as determined by the username portion of the URL.
"""
username = self.kwargs['username']
return Purchase.objects.filter(purchaser__username=username)
Следующим примером фильтрации исходного QuerySet может быть определение исходного QuerySet на основе параметров запроса в url.
Мы можем переопределить .get_queryset()
для работы с такими URL, как http://example.com/api/purchases?username=denvercoder9
, и фильтровать QuerySet только в том случае, если в URL включен параметр username
:
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
def get_queryset(self):
"""
Optionally restricts the returned purchases to a given user,
by filtering against a `username` query parameter in the URL.
"""
queryset = Purchase.objects.all()
username = self.request.query_params.get('username')
if username is not None:
queryset = queryset.filter(purchaser__username=username)
return queryset
Помимо возможности переопределения стандартного QuerySet, DRF также включает поддержку общих бэкендов фильтрации, которые позволяют легко строить сложные поисковые запросы и фильтры.
Общие фильтры также могут быть представлены в виде элементов управления HTML в API просмотра и API администрирования.
Бэкенды фильтров по умолчанию могут быть заданы глобально, с помощью параметра DEFAULT_FILTER_BACKENDS
. Например:
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}
Вы также можете задать бэкенды фильтров для каждого вида или для каждого набора видов, используя класс GenericAPIView
, основанный на представлениях.
import django_filters.rest_framework
from django.contrib.auth.models import User
from myapp.serializers import UserSerializer
from rest_framework import generics
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
Обратите внимание, что если для представления настроен бэкэнд фильтрации, то он будет использоваться не только для фильтрации списочных представлений, но и для фильтрации QuerySet
, используемых для возврата одного объекта.
Например, если взять предыдущий пример и товар с идентификатором 4675
, то следующий URL будет либо возвращать соответствующий объект, либо возвращать ответ 404, в зависимости от того, были ли выполнены условия фильтрации для данного экземпляра товара:
http://example.com/api/products/4675/?category=clothing&max_price=10.00
Обратите внимание, что можно использовать и переопределенный .get_queryset()
, и общую фильтрацию, и все будет работать так, как ожидается. Например, если у Product
есть отношение "многие-ко-многим" с User
, названное purchase
, вы можете написать представление следующим образом:
class PurchasedProductsList(generics.ListAPIView):
"""
Return a list of all the products that the authenticated
user has ever purchased, with optional filtering.
"""
model = Product
serializer_class = ProductSerializer
filterset_class = ProductFilter
def get_queryset(self):
user = self.request.user
return user.purchase_set.all()
Библиотека django-filter
включает класс DjangoFilterBackend
, который поддерживает высоконастраиваемую фильтрацию полей для DRF.
Чтобы использовать DjangoFilterBackend
, сначала установите django-filter
.
pip install django-filter
Затем добавьте 'django_filters'
в INSTALLED_APPS
Django:
INSTALLED_APPS = [
...
'django_filters',
...
]
Теперь необходимо либо добавить бэкэнд фильтра в настройки:
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}
Или добавить бэкэнд фильтрации в отдельный View
или ViewSet
.
from django_filters.rest_framework import DjangoFilterBackend
class UserListView(generics.ListAPIView):
...
filter_backends = [DjangoFilterBackend]
Если вам нужна простая фильтрация на основе равенства, вы можете установить атрибут filterset_fields
для представления или набора представлений, перечислив набор полей, по которым вы хотите осуществлять фильтрацию.
class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['category', 'in_stock']
Это автоматически создаст класс FilterSet
для заданных полей и позволит выполнять такие запросы, как:
http://example.com/api/products?category=clothing&in_stock=True
Для более сложных требований к фильтрации можно указать класс FilterSet
, который должен использоваться представлением. Более подробно о FilterSet
можно прочитать в документации django-filter. Также рекомендуется прочитать раздел Интеграция DRF.
Класс SearchFilter
поддерживает простой поиск по одному параметру запроса и основан на функциональности поиска в админ-панели Django.
При использовании в состав Web-интерфейса API будет входить элемент управления SearchFilter
:
Класс SearchFilter
будет применяться только в том случае, если у представления установлен атрибут search_fields
. Атрибут search_fields
должен представлять собой список имен полей текстового типа в модели, например CharField
или TextField
.
from rest_framework import filters
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_backends = [filters.SearchFilter]
search_fields = ['username', 'email']
Это позволит клиенту фильтровать элементы списка, задавая такие запросы, как:
http://example.com/api/users?search=russell
Также можно выполнить связанный поиск по полю ForeignKey
или ManyToManyField
с помощью нотации двойного подчеркивания в API поиска:
search_fields = ['username', 'email', 'profile__profession']
Для полей JSONField и HStoreField можно осуществлять фильтрацию по вложенным значениям внутри структуры данных, используя ту же нотацию двойного подчеркивания:
search_fields = ['data__breed', 'data__owner__other_pets__0__name']
По умолчанию в поиске используются частичные совпадения без учета регистра. Параметр search
может содержать несколько условий поиска, которые должны быть разделены пробелами и/или запятыми. Если используется несколько условий поиска, то объекты будут возвращены в списке только при совпадении всех указанных условий. Поиск может содержать цитируемые фразы с пробелами, каждая фраза рассматривается как один поисковый термин.
Поведение поиска может быть задано путем добавления префикса к именам полей в search_fields
одним из следующих символов (что эквивалентно добавлению __<lookup>
к полю):
Префикс | Поиск | |
---|---|---|
^ |
istartswith |
Начинается с поиска. |
= |
iexact |
Точные совпадения. |
$ |
iregex |
Regex-поиск. |
@ |
search |
Полнотекстовый поиск (в настоящее время поддерживается только бэкенд Django PostgreSQL). |
None | icontains |
Содержит поиск (по умолчанию). |
Например:
search_fields = ['=username', '=email']
По умолчанию параметр поиска называется 'search'
, но это можно переопределить с помощью параметра SEARCH_PARAM
.
Для динамического изменения полей поиска в зависимости от содержимого запроса можно подклассифицировать SearchFilter
и переопределить функцию get_search_fields()
. Например, следующий подкласс будет искать по title
, только если в запросе присутствует параметр запроса title_only
:
from rest_framework import filters
class CustomSearchFilter(filters.SearchFilter):
def get_search_fields(self, view, request):
if request.query_params.get('title_only'):
return ['title']
return super().get_search_fields(view, request)
Более подробную информацию можно найти в документации Django.
Класс OrderingFilter
поддерживает простое упорядочивание результатов, управляемое параметрами запроса.
По умолчанию параметр запроса называется 'ordering'
, но это можно переопределить с помощью параметра ORDERING_PARAM
.
Например, чтобы упорядочить пользователей по имени пользователя:
http://example.com/api/users?ordering=username
Клиент может задать и обратный порядок, добавив к имени поля префикс '-', например, так:
http://example.com/api/users?ordering=-username
Также может быть задано несколько порядков:
http://example.com/api/users?ordering=account,username
Рекомендуется явно указывать, какие поля API должен разрешать в фильтре упорядочивания. Это можно сделать, установив атрибут ordering_fields
на представлении, например, так:
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_backends = [filters.OrderingFilter]
ordering_fields = ['username', 'email']
Это позволяет предотвратить непредвиденную утечку данных, например, разрешить пользователям заказывать по хэш-полю пароли или другие конфиденциальные данные.
Если не указать атрибут orderering_fields
для представления, то класс фильтра будет по умолчанию позволять пользователю фильтровать по любым читаемым полям на сериализаторе, указанном атрибутом serializer_class
.
Если вы уверены, что используемый представлением Queryset
не содержит конфиденциальных данных, вы также можете явно указать, что представление должно разрешать упорядочивание по любому полю модели или агрегату Queryset
, используя специальное значение '__all__'
.
class BookingsListView(generics.ListAPIView):
queryset = Booking.objects.all()
serializer_class = BookingSerializer
filter_backends = [filters.OrderingFilter]
ordering_fields = '__all__'
Если у представления установлен атрибут ordering
, то он будет использоваться в качестве упорядочивания по умолчанию.
Обычно для этого используется параметр order_by
в исходном Queryset
, но использование параметра ordering
в представлении позволяет указать порядок таким образом, что он может быть автоматически передан в качестве контекста в шаблон рендеринга. Это позволяет автоматически отображать заголовки столбцов по-разному, если они используются для упорядочивания результатов.
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_backends = [filters.OrderingFilter]
ordering_fields = ['username', 'email']
ordering = ['username']
Атрибут orderering
может быть как строкой, так и списком/кортежем строк.
Вы также можете предоставить свой собственный бэкэнд фильтрации или написать устанавливаемое приложение для использования другими разработчиками.
Для этого переопределите BaseFilterBackend
и переопределите метод .filter_queryset(self, request, queryset, view)
. Метод должен возвращать новый, отфильтрованный QuerySet
.
Помимо того, что клиенты могут выполнять поиск и фильтрацию, общие бэкенды фильтров могут быть полезны для ограничения того, какие объекты должны быть видны для каждого конкретного запроса или пользователя.
Например, может потребоваться ограничить доступ пользователей только к созданным ими объектам.
class IsOwnerFilterBackend(filters.BaseFilterBackend):
"""
Filter that only allows users to see their own objects.
"""
def filter_queryset(self, request, queryset, view):
return queryset.filter(owner=request.user)
Мы могли бы добиться такого же поведения, переопределив get_queryset()
в представлениях, но использование бэкенда фильтров позволяет более просто добавить это ограничение к нескольким представлениям или применить его ко всему API.
Общие фильтры также могут представлять интерфейс в Web-интерфейсе API. Для этого необходимо реализовать метод to_html()
, который возвращает отрендеренное HTML-представление фильтра. Этот метод должен иметь следующую сигнатуру:
to_html(self, request, queryset, view)
.
Метод должен возвращать отрендеренную HTML-строку.
Следующие пакеты сторонних производителей предоставляют дополнительные реализации фильтров.
django-rest-framework-filters работает совместно с классом DjangoFilterBackend
и позволяет легко создавать фильтры по отношениям, а также создавать несколько типов фильтров для поиска по заданному полю.
djangorestframework-word-filter разработан как альтернатива filters.SearchFilter
, который будет искать полное слово в тексте, либо точное совпадение.
django-url-filter предоставляет безопасный способ фильтрации данных по удобным для человека URL-адресам. Он работает очень похоже на сериализаторы и поля DRF в том смысле, что они могут быть вложенными, за исключением того, что они называются filtersets
и filters
. Это обеспечивает простой способ фильтрации связанных данных. Кроме того, эта библиотека является универсальной, поэтому ее можно использовать для фильтрации других источников данных, а не только Django QuerySet
.
drf-url-filter - это простое Django-приложение для применения фильтров к Queryset
в ModelViewSet
чистым, простым и настраиваемым способом. Оно также поддерживает валидацию входящих параметров запроса и их значений. Для валидации входящих параметров запроса используется красивый python-пакет Voluptuous
. Самое приятное в Voluptuous
то, что вы можете определить свои собственные валидации в соответствии с требованиями к параметрам запроса.