На минулій лекції ми розібралися з фільтрацією та пошуком у 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. Ви можете адаптувати фільтри під найскладніші сценарії, створюючи зручний і продуктивний інтерфейс для ваших користувачів.
Не бійтеся експериментувати з підходами, але пам’ятайте про баланс між продуктивністю та складністю запитів. Комбінуючи фільтрацію та пошук, можна досягти приголомшливих результатів навіть для великих наборів даних.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ