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. Вы можете адаптировать фильтры под самые сложные сценарии, создавая удобный и производительный интерфейс для ваших пользователей.

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

1
Задача
Модуль 3: Django, 19 уровень, 7 лекция
Недоступна
Поиск элементов в API
Поиск элементов в API
1
Задача
Модуль 3: Django, 19 уровень, 7 лекция
Недоступна
Кастомизация логики фильтрации и поиска
Кастомизация логики фильтрации и поиска
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ