JavaRush /Курсы /Модуль 3: Django /Примеры Сложных Фильтров в API

Примеры Сложных Фильтров в API

Модуль 3: Django
19 уровень , 8 лекция
Открыта

В этой лекции мы углубимся в фильтрацию ещё сильнее.

Рассмотрим реальную ситуацию: ваше приложение — это платформа для онлайн-магазина с тысячами продуктов. Клиенты могут искать продукты по категории, цене, наличию, рейтингу и другим критериям. При этом они могут комбинировать условия, например, "все ноутбуки в категории electronics с рейтингом выше 4.5, стоимостью менее $1000 и бесплатной доставкой".

Как справиться с таким запросом, не потеряв производительности? Именно здесь на помощь приходят сложные фильтры, которые мы сегодня подробно разберем.

Использование Q-объектов для сложных условий фильтрации

Q-объекты в Django позволяют создавать сложные SQL-запросы с условиями AND, OR и NOT. Они обеспечивают гибкость при фильтрации данных, позволяя объединять несколько условий.

Давайте сначала реализуем простейший сценарий: вернуть из базы продукты, которые либо относятся к категории electronics, либо стоят меньше $500. Условие, как вы наверное догадались здесь будет соответствовать условию OR.

from django.db.models import Q
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Product
from .serializers import ProductSerializer

class ProductListView(APIView):
    def get(self, request):
        # Используем Q-объекты для фильтрации
        products = Product.objects.filter(
            Q(category='electronics') | Q(price__lt=500)
        )
        serializer = ProductSerializer(products, many=True)
        return Response(serializer.data)

Здесь оператор | соответствует логическому OR. Наша фильтрация возвращает все продукты, которые относятся к категории electronics или имеют цену ниже $500.

Теперь давайте усложним: мы хотим найти продукты, которые относятся к категории books, имеют рейтинг выше 3, но при этом их цена должна быть больше $20 и они не должны быть распроданы.

class AdvancedProductListView(APIView):
    def get(self, request):
        products = Product.objects.filter(
            Q(category='books') & Q(rating__gt=3) & Q(price__gt=20) & ~Q(is_on_sale=True)
        )
        serializer = ProductSerializer(products, many=True)
        return Response(serializer.data)
  1. Логический оператор & — это AND.
  2. Оператор ~ — это NOT, т.е. инвертирует условие.

Комбинирование Q-объектов с фильтрами QuerySet

Если вам нужно комбинировать сложную фильтрацию с базовой, это легко достигается добавлением обычных фильтров после применения Q-объектов.

Пример: фильтрация по связанным объектам

Предположим, у нас есть модель Order, связанная с пользователем и продуктами через ForeignKey. Мы хотим найти все заказы, сделанные пользователями из города New York, содержащие продукты из категории clothing и сделанные в последние 30 дней.

from datetime import timedelta
from django.utils.timezone import now

class OrderListView(APIView):
    def get(self, request):
        today = now()
        last_30_days = today - timedelta(days=30)

        orders = Order.objects.filter(
            Q(user__city='New York') & Q(products__category='clothing') & Q(created_at__gte=last_30_days)
        ).distinct()

        serializer = OrderSerializer(orders, many=True)
        return Response(serializer.data)

Здесь мы видим сочетание:

  • Q(user__city='New York') для фильтрации по связанному объекту user.
  • Q(products__category='clothing') для фильтрации по связанным объектам products.
  • Q(created_at__gte=last_30_days) для фильтрации по дате.

Метод distinct() убирает дубликаты результатов, которые могут появиться при фильтрации через связанные модели.

Сложные фильтрации с параметрами запроса

Давайте интегрируем сложные условия в API, принимая параметры фильтрации из URL. Это позволит конечным пользователям задавать критерии фильтрации динамически.

Предположим, у нас есть API для поиска продуктов, поддерживающий фильтрацию по цене, категории, рейтингу и наличию:

class DynamicProductFilterView(APIView):
    def get(self, request):
        # Получение параметров запроса
        category = request.query_params.get('category')
        min_price = request.query_params.get('min_price')
        max_price = request.query_params.get('max_price')
        min_rating = request.query_params.get('min_rating')
        available = request.query_params.get('available')

        # Начинаем создавать базовый QuerySet
        products = Product.objects.all()

        # Добавляем условие фильтрации, если параметр задан
        if category:
            products = products.filter(category=category)
        if min_price:
            products = products.filter(price__gte=min_price)
        if max_price:
            products = products.filter(price__lte=max_price)
        if min_rating:
            products = products.filter(rating__gte=min_rating)
        if available is not None:
            products = products.filter(is_available=available.lower() == 'true')

        # Сериализация данных
        serializer = ProductSerializer(products, many=True)
        return Response(serializer.data)

Теперь вы можете делать запросы вроде:

GET /api/products?category=electronics&min_price=100&max_price=1000&min_rating=4.5

Работа с фильтрацией через DjangoFilterBackend

Для более сложных сценариев фильтрации рекомендуется использовать DjangoFilterBackend. Оно поддерживает создание кастомных фильтров прямо в коде.

Давайте создадим фильтр для модели Product с помощью filters.FilterSet.

from django_filters import rest_framework as filters
from .models import Product

class ProductFilter(filters.FilterSet):
    min_price = filters.NumberFilter(field_name="price", lookup_expr='gte')
    max_price = filters.NumberFilter(field_name="price", lookup_expr='lte')
    min_rating = filters.NumberFilter(field_name="rating", lookup_expr='gte')
    category = filters.CharFilter(field_name="category")

    class Meta:
        model = Product
        fields = ['category', 'min_price', 'max_price', 'min_rating']

Теперь подключим этот фильтр к нашему представлению:

from rest_framework import generics
from django_filters.rest_framework import DjangoFilterBackend
from .serializers import ProductSerializer

class ProductListView(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_class = ProductFilter

Теперь фильтры автоматически применяются к параметрам из запроса, например:

GET /api/products?category=books&min_price=10&max_price=50&min_rating=4

Примеры комбинированной фильтрации

Для реализации более сложных комбинаций можно комбинировать DjangoFilterBackend, кастомные фильтры и Q-объекты.

В примере ниже мы фильтруем с использованием Q-объектов внутри FilterSet

class CombinedProductFilter(filters.FilterSet):
    complex_filter = filters.BooleanFilter(method='filter_complex_logic')

    class Meta:
        model = Product
        fields = ['category', 'price', 'rating']

    def filter_complex_logic(self, queryset, name, value):
        if value:
            return queryset.filter(
                Q(category='electronics') | Q(price__lte=100)
            )
        return queryset

Теперь при установке параметра complex_filter=true, выполняется наша сложная фильтрация.

Ошибки и типичные проблемы

Сложные запросы часто приводят к неожиданным результатам или высокой нагрузке на базу данных. Например, забытый метод distinct() при фильтрации через связанные объекты может привести к дублированию данных. Еще одна распространенная ошибка — выполнение сложных условий без индексации базы данных, что приводит к снижению производительности. Всегда профилируйте свои запросы и используйте инструменты вроде django-debug-toolbar.

На этом этапе вы должны чувствовать себя более уверенно при создании сложных фильтров в API. Если у вас еще остались вопросы, то знайте: Django и DRF — ваши друзья, и все, что вам нужно, есть в официальной документации DRF. 😉

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