На прошлой лекции мы разобрались с фильтрацией и поиском в DRF. Но порой стандартной функциональности DjangoFilterBackend или SearchFilter может быть недостаточно. Например:
- У вас сложная бизнес-логика: нужно отфильтровать данные на основе множественных сложных связей.
- Требуется выполнять фильтрацию на основе пользовательских функций или кастомных условий.
- API должен поддерживать специфическую обработку запросов, например, сложные диапазоны дат, регулярные выражения или кастомные параметры.
Когда стандартные фильтры не справляются, мы берем дело в свои руки и создаем кастомные фильтры.
Создание кастомных фильтров
Попробуем реализовать кастомную логику фильтрации. Допустим, у нас есть модель Product, которая представляет товары интернет-магазина.
# models.py
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=10, decimal_places=2)
in_stock = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
Теперь создадим представление для этой модели, но с уникальной фильтрацией: например, нам нужно реализовать фильтр по диапазону цен и наличию товара.
Подход №1: фильтрация через DjangoFilterBackend с кастомными фильтрами
Для начала подключим django-filter, чтобы задать кастомные фильтры:
pip install django-filter
Добавим кастомные фильтры в наш проект:
# filters.py
from django_filters import rest_framework as filters
from .models import Product
class ProductFilter(filters.FilterSet):
price_min = filters.NumberFilter(field_name="price", lookup_expr='gte')
price_max = filters.NumberFilter(field_name="price", lookup_expr='lte')
in_stock = filters.BooleanFilter()
class Meta:
model = Product
fields = ['price_min', 'price_max', 'in_stock']
Здесь мы добавили:
price_min: минимальная цена (фильтрация черезgte— больше или равно).price_max: максимальная цена (фильтрация черезlte— меньше или равно).in_stock: фильтр по наличию товара.
Теперь применим наш фильтр в представлении:
# views.py
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.generics import ListAPIView
from .models import Product
from .serializers import ProductSerializer
from .filters import ProductFilter
class ProductListView(ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = ProductFilter
Обратите внимание, что мы указали filter_backends для представления и добавили собственный класс фильтрации ProductFilter. Теперь запрос будет выглядеть так:
GET /api/products/?price_min=50&price_max=200&in_stock=true
Подход №2: полностью кастомная логика фильтрации
Иногда может потребоваться полностью "рулить" логикой фильтрации, не используя DjangoFilterBackend. Например, если нужно фильтровать товары по наличию в определенном диапазоне времени.
Для таких случаев переопределим метод get_queryset():
# views.py
from rest_framework.generics import ListAPIView
from django.utils.timezone import now
from .models import Product
from .serializers import ProductSerializer
class CustomProductListView(ListAPIView):
serializer_class = ProductSerializer
def get_queryset(self):
queryset = Product.objects.all()
price_min = self.request.query_params.get('price_min')
price_max = self.request.query_params.get('price_max')
in_stock = self.request.query_params.get('in_stock')
if price_min:
queryset = queryset.filter(price__gte=price_min)
if price_max:
queryset = queryset.filter(price__lte=price_max)
if in_stock is not None:
in_stock = in_stock.lower() == 'true'
queryset = queryset.filter(in_stock=in_stock)
# Пример фильтрации по дате (последние 7 дней)
last_7_days = self.request.query_params.get('last_7_days')
if last_7_days and last_7_days.lower() == 'true':
queryset = queryset.filter(created_at__gte=now() - timedelta(days=7))
return queryset
Теперь мы можем фильтровать товары по параметрам:
GET /api/products/?price_min=50&in_stock=true&last_7_days=true
Кастомизация поиска
Если вам нужно более сложное поведение, чем стандартная фильтрация, предлагаем использовать SearchFilter с кастомным подходом.
Настройка SearchFilter для поиска
Предположим, мы хотим искать товары по имени name и ограничить количество символов в запросе.
# views.py
from rest_framework.filters import SearchFilter
from rest_framework.generics import ListAPIView
from .models import Product
from .serializers import ProductSerializer
class ProductSearchListView(ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [SearchFilter]
search_fields = ['name']
def get_queryset(self):
queryset = super().get_queryset()
# Ограничение длины поискового запроса
search_param = self.request.query_params.get('search')
if search_param and len(search_param) > 50:
raise ValueError("Поисковый запрос слишком длинный!")
return queryset
Используя этот подход, запрос будет выглядеть так:
GET /api/products/?search=Лампа
Создание кастомного фильтра поиска
Иногда нужен поиск, который выполняет более специфические задачи, например, поиск по нескольким полям с весовыми коэффициентами. В таких случаях мы реализуем свой фильтр.
# filters.py
from rest_framework.filters import BaseFilterBackend
class CustomSearchFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
search_query = request.query_params.get('search')
if search_query:
queryset = queryset.filter(
models.Q(name__icontains=search_query) |
models.Q(description__icontains=search_query)
)
return queryset
После этого подключим этот фильтр к представлению:
# views.py
from .filters import CustomSearchFilter
class CustomProductSearchListView(ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [CustomSearchFilter]
Теперь можно искать по нескольким полям:
GET /api/products/?search=лампа
Настройка более сложных поисковых запросов
Например, для реализации поиска с использованием регулярных выражений можно поступить следующим образом:
# filters.py
import re
class RegexSearchFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
search_query = request.query_params.get('search')
if search_query:
pattern = r'\b' + re.escape(search_query) + r'\b'
queryset = queryset.filter(name__iregex=pattern)
return queryset
Подведение итогов
Теперь вы знаете, как реализовать кастомизацию фильтрации и поиска для вашего API. Эти подходы пригодятся, когда нужно выйти за рамки стандартной функциональности DRF. Вы можете адаптировать фильтры под самые сложные сценарии, создавая удобный и производительный интерфейс для ваших пользователей.
Не бойтесь экспериментировать с подходами, но помните о балансе между производительностью и сложностью запросов. Комбинируя фильтрацию и поиск, можно добиться потрясающих результатов даже для больших наборов данных.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ