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.