Django REST Framework: Как работать с фильтрами, формами и шаблоном does_not_exist?

Django REST Framework (DRF) предоставляет мощные инструменты для создания API, включая гибкие механизмы фильтрации, сериализации данных (аналогично формам в Django) и стандартные способы обработки ошибок, таких как отсутствие шаблонов. Рассмотрим ключевые аспекты работы с этими компонентами.

Фильтры в Django REST Framework

Фильтрация позволяет клиентам API запрашивать подмножества данных, соответствующие определенным критериям. DRF интегрируется с библиотекой django-filter для предоставления продвинутых возможностей фильтрации.

Настройка фильтров: основные параметры и бэкенды

Для глобальной активации фильтров необходимо добавить соответствующий бэкенд в настройки DRF в settings.py:

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',
        'rest_framework.filters.SearchFilter',
        'rest_framework.filters.OrderingFilter',
    ],
    # ... другие настройки
}

Основные бэкенды:

DjangoFilterBackend: Для использования FilterSet и точной фильтрации по полям.

SearchFilter: Для полнотекстового поиска по указанным полям.

OrderingFilter: Для сортировки результатов по указанным полям.

Использование Generic Filtering и OrderingFilter

Для конкретного ViewSet или APIView можно указать поля, по которым разрешена фильтрация и сортировка:

# views.py
from rest_framework import generics
from .models import AdCampaign
from .serializers import AdCampaignSerializer
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter

class AdCampaignListView(generics.ListAPIView):
    """Представление для списка рекламных кампаний с фильтрацией и сортировкой."""
    queryset = AdCampaign.objects.all()
    serializer_class = AdCampaignSerializer
    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    filterset_fields = ['status', 'budget']  # Поля для точной фильтрации
    search_fields = ['name', 'description']  # Поля для поиска
    ordering_fields = ['name', 'budget', 'start_date'] # Поля для сортировки
    ordering = ['-start_date'] # Сортировка по умолчанию

Клиенты могут использовать query-параметры: ?status=active&budget=1000, ?search=summer+sale, ?ordering=-budget.

Создание собственных фильтров: FilterSet и custom filter fields

Для более сложной логики фильтрации создается класс FilterSet:

# filters.py
import django_filters
from .models import AdCampaign
from typing import List

class AdCampaignFilter(django_filters.FilterSet):
    """Набор фильтров для модели AdCampaign."""
    min_budget = django_filters.NumberFilter(field_name="budget", lookup_expr='gte')
    max_budget = django_filters.NumberFilter(field_name="budget", lookup_expr='lte')
    start_date_after = django_filters.DateFilter(field_name="start_date", lookup_expr='gte')
    active_within_dates = django_filters.DateFromToRangeFilter(field_name="start_date") # Пример сложного фильтра

    class Meta:
        model = AdCampaign
        fields: List[str] = ['status', 'name'] # Можно указать базовые поля здесь

# views.py
class AdCampaignListView(generics.ListAPIView):
    # ...
    filterset_class = AdCampaignFilter # Используем кастомный FilterSet
    # filterset_fields убираем или оставляем для базовых полей, не описанных в AdCampaignFilter

Примеры реализации фильтров для различных типов данных (строки, числа, даты)

Строки: ?name=MyCampaign (точное совпадение), ?name__icontains=promo (содержит без учета регистра).

Числа: ?budget=500, ?budget__gte=1000 (больше или равно), ?budget__lt=200 (меньше).

Даты: ?start_date=2023-10-26, ?start_date__gte=2023-01-01, ?start_date__year=2023.

Диапазоны (с DateFromToRangeFilter): ?active_within_dates_after=2023-01-01&active_within_dates_before=2023-12-31

Работа с формами в Django REST Framework

В контексте DRF, роль "форм" для API выполняют сериализаторы (serializers), которые отвечают за преобразование сложных типов данных (например, моделей Django) в нативные типы Python и обратно, а также за валидацию данных.

Использование ModelSerializer для создания форм на основе моделей

ModelSerializer автоматически генерирует поля и валидаторы на основе указанной модели Django, что значительно упрощает создание API для CRUD-операций.

# serializers.py
from rest_framework import serializers
from .models import AdCampaign
from typing import Dict, Any

class AdCampaignSerializer(serializers.ModelSerializer):
    """Сериализатор для модели AdCampaign."""
    class Meta:
        model = AdCampaign
        fields: List[str] = ['id', 'name', 'budget', 'status', 'start_date', 'end_date', 'description']
        read_only_fields: List[str] = ['id'] # Поля только для чтения

    def validate_budget(self, value: float) -> float:
        """Пример кастомной валидации поля budget."""
        if value  Dict[str, Any]:
        """Пример валидации на уровне всего объекта."""
        if data.get('start_date') and data.get('end_date') and data['start_date'] > data['end_date']:
            raise serializers.ValidationError("Дата начала не может быть позже даты окончания.")
        return data

Настройка валидации данных с помощью Django forms и ModelSerializer

Основная валидация происходит в ModelSerializer (валидаторы модели, validate_<field_name>, validate). Использование django.forms.Form напрямую в DRF для валидации API-запросов является редким случаем, обычно предпочтение отдается сериализаторам. Однако, логику валидации из форм можно переиспользовать в методах validate сериализатора при необходимости.

Обработка данных форм во viewsets и generic views

Generic views и ViewSets DRF автоматически используют сериализаторы для обработки входящих данных (POST, PUT, PATCH).

# views.py
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from typing import Any

class AdCampaignViewSet(viewsets.ModelViewSet):
    """ViewSet для управления рекламными кампаниями."""
    queryset = AdCampaign.objects.all()
    serializer_class = AdCampaignSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_class = AdCampaignFilter

    def perform_create(self, serializer: AdCampaignSerializer) -> None:
        """Кастомизация процесса создания объекта."""
        # Можно добавить дополнительную логику перед сохранением
        # Например, связать с текущим пользователем (если аутентификация настроена)
        # serializer.save(owner=self.request.user)
        serializer.save()

    def create(self, request: Any, *args: Any, **kwargs: Any) -> Response:
        """Обработка POST-запроса для создания кампании."""
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True) # Валидация данных
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    # Методы update, partial_update и т.д. работают аналогично

Отображение форм в HTML шаблонах (если требуется frontend)

DRF предоставляет Browsable API, который рендерит HTML-представление API, включая формы для отправки POST/PUT запросов. Это удобно для тестирования. Если же требуется интеграция с полноценным Django-фронтендом, можно использовать сериализатор для рендеринга полей формы в шаблоне, хотя чаще фронтенд (React, Vue, etc.) работает с API напрямую.

# Нетипичный случай: рендеринг сериализатора как формы в Django шаблоне
# views.py (Django view, не DRF)
from django.shortcuts import render
from .serializers import AdCampaignSerializer

def campaign_add_view(request):
    serializer = AdCampaignSerializer() # Пустой сериализатор для рендеринга
    # Логика обработки POST (если форма сабмитится на этот же view)
    return render(request, 'campaign_form.html', {'serializer': serializer})

# templates/campaign_form.html

    {% csrf_token %}
    {% for field in serializer %}
        
{{ field.label_tag }} {{ field }} {% if field.help_text %}

{{ field.help_text|safe }}

{% endif %} {{ field.errors }}
{% endfor %}

Обработка исключения TemplateDoesNotExist

Это исключение возникает, когда Django не может найти запрошенный шаблон.

Причины возникновения TemplateDoesNotExist в Django проектах

Неправильно указан путь к шаблону в render(), get_template() или теге {% include %}/{% extends %}.

Реклама

Шаблон физически отсутствует в директориях, указанных в settings.TEMPLATES['DIRS'].

Приложение, содержащее шаблон, не добавлено в INSTALLED_APPS (если используется app_directories.Loader).

Опечатка в имени файла или пути.

Проверка корректности путей к шаблонам в настройках Django (TEMPLATE_DIRS)

Убедитесь, что настройка TEMPLATES в settings.py корректна и включает все директории, где могут лежать шаблоны.

# settings.py
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')], # Основная директория шаблонов
        'APP_DIRS': True, # Искать шаблоны в директориях приложений (app/templates/)
        'OPTIONS': {
            'context_processors': [
                # ... стандартные процессоры
            ],
        },
    },
]

Использование template loaders для поиска шаблонов в разных директориях

Django использует загрузчики шаблонов (loaders) для поиска файлов. Основные:

django.template.loaders.filesystem.Loader: Ищет в директориях, перечисленных в DIRS.

django.template.loaders.app_directories.Loader: Ищет в поддиректории templates каждого приложения из INSTALLED_APPS.

Порядок загрузчиков в TEMPLATES['OPTIONS']['loaders'] (если указан явно, по умолчанию используется комбинация filesystem и app_directories) имеет значение.

Стратегии обработки исключения TemplateDoesNotExist: кастомные страницы ошибок, логирование

Кастомная страница 404: Django автоматически покажет страницу 404, если DEBUG=False и возникает TemplateDoesNotExist при рендеринге ответа. Можно создать кастомный шаблон 404.html в корневой директории шаблонов (templates/404.html). Также можно определить кастомный обработчик handler404 в urls.py.

Логирование: Настройте логирование Django для записи информации об ошибках TemplateDoesNotExist, особенно в продакшене, чтобы быстро выявлять проблемы с путями к шаблонам.

Обработка в коде: В редких случаях можно обернуть вызов рендеринга в try...except TemplateDoesNotExist для выполнения альтернативной логики.

Привязка фильтров и форм к REST API

Передача параметров фильтрации через URL query parameters

Как показано ранее, фильтры DRF (стандартные и кастомные) автоматически считывают параметры из URL: GET /api/campaigns/?status=active&min_budget=500&ordering=name.

Использование HTML форм для фильтрации данных через REST API (с примерами)

Хотя API обычно используется программно, можно создать HTML-форму (например, в админке или отдельном фронтенде), которая будет генерировать GET-запрос с параметрами фильтрации к вашему API.



    
    
        Любой
        Активна
        На паузе
        Завершена
    

    
    

    
    
        По имени (А-Я)
        По имени (Я-А)
        По бюджету (возр.)
        По бюджету (убыв.)
    

    

При отправке этой формы браузер перейдет на /api/campaigns/?status=active&min_budget=100&ordering=-budget (пример), и DRF ViewSet применит соответствующие фильтры.

Обработка данных форм и фильтров на стороне сервера (viewsets и serializers)

Фильтры: Обрабатываются автоматически бэкендами (DjangoFilterBackend, SearchFilter, OrderingFilter) перед тем, как queryset будет передан в сериализатор для ответа.

Данные форм (POST/PUT/PATCH): Поступают в request.data. ViewSet передает эти данные в соответствующий Serializer (get_serializer(data=request.data)). Сериализатор выполняет валидацию (is_valid(raise_exception=True)). Если валидация успешна, данные используются для создания (serializer.save()) или обновления объекта.

Практические примеры и распространенные ошибки

Пример: фильтрация списка товаров по цене и категории

# models.py
class Category(models.Model):
    name = models.CharField(max_length=100)

class Product(models.Model):
    name = models.CharField(max_length=200)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    price = models.DecimalField(max_digits=10, decimal_places=2)

# filters.py
import django_filters
from .models import Product

class ProductFilter(django_filters.FilterSet):
    min_price = django_filters.NumberFilter(field_name="price", lookup_expr='gte')
    max_price = django_filters.NumberFilter(field_name="price", lookup_expr='lte')
    category_name = django_filters.CharFilter(field_name='category__name', lookup_expr='icontains')

    class Meta:
        model = Product
        fields = ['category'] # Можно фильтровать по ID категории

# views.py
class ProductListView(generics.ListAPIView):
    queryset = Product.objects.select_related('category').all()
    serializer_class = ProductSerializer # Определите сериализатор ProductSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_class = ProductFilter

Запрос: /api/products/?category=1&min_price=10&max_price=50&category_name=Electronics

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

# models.py
class Comment(models.Model):
    author = models.CharField(max_length=100)
    text = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    parent_campaign = models.ForeignKey(AdCampaign, related_name='comments', on_delete=models.CASCADE)

# serializers.py
class CommentSerializer(serializers.ModelSerializer):
    author = serializers.CharField(max_length=100, required=True)
    text = serializers.CharField(required=True)

    class Meta:
        model = Comment
        fields = ['id', 'author', 'text', 'created_at', 'parent_campaign']
        read_only_fields = ['id', 'created_at']
        extra_kwargs = {
            'parent_campaign': {'write_only': True, 'required': True} # Не показываем в ответе, но требуем при создании
        }

    def validate_text(self, value: str) -> str:
        if len(value) < 5:
            raise serializers.ValidationError("Комментарий слишком короткий.")
        # Проверка на нецензурную лексику (пример)
        # if contains_profanity(value):
        #    raise serializers.ValidationError("Комментарий содержит недопустимые слова.")
        return value

# views.py
class CommentCreateView(generics.CreateAPIView):
    serializer_class = CommentSerializer
    # queryset не нужен для CreateAPIView

    # Можно переопределить perform_create для дополнительной логики,
    # например, чтобы убедиться, что parent_campaign существует или доступен пользователю.

Запрос: POST /api/comments/ с телом {"author": "John Doe", "text": "Great campaign!", "parent_campaign": 123}

Разбор типичных ошибок при работе с фильтрами, формами и шаблонами в Django REST Framework

Фильтры:

Неправильное имя поля или lookup (field_name='category__namme' вместо category__name).

Забыли добавить DjangoFilterBackend в filter_backends или в settings.py.

Использование filterset_fields вместе с filterset_class, если поля дублируются или конфликтуют.

Сложные фильтры (например, по связанным моделям) требуют явного определения в FilterSet.

Формы (Сериализаторы):

Ошибка валидации (ValidationError) не обрабатывается во view (забыли serializer.is_valid(raise_exception=True) или не настроили обработку исключений DRF).

Поля read_only или write_only используются некорректно (пытаемся записать read_only поле или не передаем обязательное write_only поле).

Неправильная обработка вложенных сериализаторов или связей ForeignKey/ManyToManyField.

N+1 проблема при сериализации связанных объектов (используйте select_related и prefetch_related в queryset во view).

Шаблоны (TemplateDoesNotExist):

Опечатка в имени шаблона или пути в render()/include/extends.

Директория с шаблоном не указана в settings.TEMPLATES['DIRS'].

Забыли включить APP_DIRS=True или приложение не в INSTALLED_APPS.

Проблема чаще возникает при кастомизации Browsable API или при смешивании DRF с традиционными Django views.


Добавить комментарий