JavaRush /Курси /Модуль 3: Django /Кастомізація фільтрації та пошук

Кастомізація фільтрації та пошук

Модуль 3: Django
Рівень 19 , Лекція 7
Відкрита

На минулій лекції ми розібралися з фільтрацією та пошуком у DRF. Але іноді стандартної функціональності DjangoFilterBackend або SearchFilter може бути недостатньо. Наприклад:

  1. У вас складна бізнес-логіка: потрібно відфільтрувати дані на основі множинних складних зв'язків.
  2. Необхідно виконувати фільтрацію на основі користувацьких функцій або кастомних умов.
  3. 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. Ви можете адаптувати фільтри під найскладніші сценарії, створюючи зручний і продуктивний інтерфейс для ваших користувачів.

Не бійтеся експериментувати з підходами, але пам’ятайте про баланс між продуктивністю та складністю запитів. Комбінуючи фільтрацію та пошук, можна досягти приголомшливих результатів навіть для великих наборів даних.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ