Представления (views) являются центральным элементом любого веб-фреймворка, определяя логику обработки входящих запросов и формирования ответов. В контексте создания API, представления выполняют ту же фундаментальную роль, но с фокусом на обмен данными, часто в форматах JSON или XML.
Что такое представления в Django REST Framework и зачем они нужны?
Представления в Django REST Framework (DRF) – это функции или классы, которые принимают веб-запросы и возвращают веб-ответы. Они инкапсулируют логику бизнес-процессов, взаимодействуют с моделями данных, используют сериализаторы для преобразования данных между форматами Python и API-представлением, а также применяют механизмы аутентификации, авторизации и ограничения запросов.
Необходимость использования представлений в DRF обусловлена задачами создания полноценных RESTful API. Они предоставляют структурированный подход к реализации таких операций, как:
Получение списка ресурсов (GET)
Получение конкретного ресурса по ID (GET)
Создание нового ресурса (POST)
Обновление существующего ресурса (PUT/PATCH)
Удаление ресурса (DELETE)
DRF значительно упрощает разработку этих операций по сравнению с "чистым" Django.
Разница между представлениями Django и DRF
Основное отличие между стандартными представлениями Django (django.views) и представлениями DRF (rest_framework.views или rest_framework.generics) заключается в их назначении и возможностях:
Django Views: Ориентированы в первую очередь на генерацию HTML-ответов для браузеров. Они работают с объектами HttpRequest и HttpResponse и часто используют шаблоны для рендеринга.
DRF Views: Специализированы для создания API. Они работают с расширенными объектами Request и Response из DRF, которые предоставляют удобный доступ к данным запроса (например, request.data) и позволяют легко возвращать ответы в различных форматах (JSON, XML и т.д.) с правильными HTTP-статусами. DRF-представления интегрируются с сериализаторами, механизмами парсинга и рендеринга.
Обзор основных типов представлений в DRF
DRF предлагает несколько подходов к созданию представлений, различающихся по степени абстракции и удобству для разных задач:
Function Based Views (FBV): Функции Python, декорированные @api_view. Простой и понятный способ для небольших или специфических эндпоинтов. Требуют больше "ручной" работы с сериализаторами и ответами.
Class Based Views (CBV): Классы Python, наследующиеся от APIView или его потомков. Предоставляют более структурированный подход с методами, соответствующими HTTP-глаголам (get, post, put, delete). Позволяют использовать миксины и дженерики.
Generic Class Based Views: Наследники GenericAPIView, комбинируемые с миксинами (ListModelMixin, CreateModelMixin и т.д.). Значительно сокращают код для стандартных операций CRUD, автоматически обрабатывая сериализацию, кверисеты и пермишены.
ViewSets: Классы, объединяющие логику нескольких связанных операций (например, list, retrieve, create, update, destroy) над одним ресурсом в одном классе. Используются в сочетании с роутерами DRF для автоматического создания URL-маршрутов.
Создание простых представлений на основе Function Based Views (FBV)
FBV в DRF строятся вокруг обычных функций Django, но обогащаются функциональностью DRF с помощью декоратора @api_view. Этот декоратор гарантирует, что запрос и ответ будут обработаны соответствующими парсерами и рендерерами DRF.
Реализация представления для обработки GET-запросов
Для обработки только GET-запросов мы используем @api_view(['GET']). Внутри функции мы получаем данные (например, из базы данных), сериализуем их и возвращаем Response.
Пример получения списка статей:
# articles/views.py
from django.http import JsonResponse
from rest_framework.decorators import api_view
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework import status
from articles.models import Article
from articles.serializers import ArticleSerializer
from typing import List, Any
@api_view(['GET'])
def article_list_view(request: Request) -> Response:
"""
Получает список всех статей.
"""
# Получаем все объекты Article из базы данных
articles: List[Article] = Article.objects.all()
# Сериализуем кверисет статей
serializer: ArticleSerializer = ArticleSerializer(articles, many=True)
# Возвращаем сериализованные данные в ответе
return Response(serializer.data, status=status.HTTP_200_OK)Реализация представления для обработки POST-запросов (создание новых объектов)
Для создания объектов используется метод POST. В FBV мы указываем @api_view(['POST']). Данные запроса доступны через request.data и передаются сериализатору для валидации и сохранения.
Пример создания новой статьи:
# articles/views.py
# ... (импорты)
@api_view(['POST'])
def article_create_view(request: Request) -> Response:
"""
Создает новую статью.
"""
# Создаем экземпляр сериализатора с данными запроса
serializer: ArticleSerializer = ArticleSerializer(data=request.data)
# Валидируем данные
if serializer.is_valid():
# Если данные валидны, сохраняем объект
serializer.save()
# Возвращаем созданный объект и статус 201 Created
return Response(serializer.data, status=status.HTTP_201_CREATED)
# Если данные невалидны, возвращаем ошибки и статус 400 Bad Request
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)Использование декораторов `api_view` для настройки представлений
Декоратор @api_view не только указывает разрешенные HTTP-глаголы, но и может принимать другие аргументы для настройки поведения представления, например:
parser_classes: Список классов парсеров, используемых для обработки тела запроса (например, JSONParser, FormParser).
renderer_classes: Список классов рендереров для форматирования ответа (например, JSONRenderer).
authentication_classes: Список классов аутентификации.
permission_classes: Список классов разрешений.
throttle_classes: Список классов троттлинга.
Пример с явным указанием парсеров и рендереров:
# articles/views.py
# ... (импорты)
from rest_framework.parsers import JSONParser
from rest_framework.renderers import JSONRenderer
@api_view(['POST'])
# Явно указываем парсеры и рендереры (обычно DRF делает это автоматически на основе настроек)
@parser_classes([JSONParser])
@renderer_classes([JSONRenderer])
def article_create_view_custom_parsers(request: Request) -> Response:
"""
Создает новую статью с явным указанием парсеров/рендереров.
"""
serializer: ArticleSerializer = ArticleSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)Создание представлений на основе Class Based Views (CBV)
CBV в DRF предлагают более структурированный и переиспользуемый подход по сравнению с FBV, особенно для реализации типичных API-операций. Они наследуются от базовых классов DRF и используют методы с именами HTTP-глаголов.
Использование `APIView` для базовой реализации представлений
APIView – это базовый класс для CBV в DRF. Он предоставляет те же базовые возможности, что и @api_view (обработка запросов/ответов DRF, применение аутентификации/разрешений/дросселирования), но в объектно-ориентированном стиле. Логика для разных HTTP-глаголов размещается в отдельных методах (например, .get(), .post(), .put(), .delete()).
Пример APIView для списка и создания статей:
# articles/views.py
# ... (импорты)
from rest_framework.views import APIView
class ArticleListCreateAPIView(APIView):
"""
Представление для списка статей и создания новой статьи.
Наследует от APIView.
"""
# Явно указываем используемый сериализатор (необязательно для APIView, но хорошая практика)
serializer_class = ArticleSerializer
def get(self, request: Request, format: str | None = None) -> Response:
"""
Обрабатывает GET-запрос для получения списка статей.
"""
articles: List[Article] = Article.objects.all()
serializer: ArticleSerializer = self.serializer_class(articles, many=True)
return Response(serializer.data)
def post(self, request: Request, format: str | None = None) -> Response:
"""
Обрабатывает POST-запрос для создания новой статьи.
"""
serializer: ArticleSerializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)APIView требует ручной реализации всех шагов: получения данных, сериализации, валидации, сохранения и формирования ответа. Он полезен, когда логика сильно отличается от стандартных CRUD-операций или когда вам нужен полный контроль над процессом.
Применение `GenericAPIView` для сокращения boilerplate-кода
GenericAPIView – это наследник APIView, который добавляет атрибуты и методы, полезные для стандартных API-операций, связанных с моделями. Он не предоставляет готовых действий (таких как .list() или .create()), но задает основу для их реализации, предоставляя атрибуты, такие как queryset и serializer_class, и методы, такие как .get_queryset() и .get_serializer(). Это позволяет избежать повторения кода для получения кверисета, сериализатора и применения пермишенов/аутентификации.
Пример GenericAPIView (сам по себе не выполняет действий, требует миксинов):
# articles/views.py
# ... (импорты)
from rest_framework.generics import GenericAPIView
class ArticleBaseGenericAPIView(GenericAPIView):
"""
Базовый класс GenericAPIView для статей (требует миксинов).
"""
# Обязательно указываем кверисет и класс сериализатора
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# Здесь могут быть определены authentication_classes, permission_classes и т.д.
# Но нет методов get, post, put, delete без добавления миксинов.Использование миксинов (mixins) для добавления функциональности (ListModelMixin, CreateModelMixin, RetrieveModelMixin и т.д.)
Миксины DRF (например, ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin) предоставляют реализацию стандартных API-операций. Комбинируя GenericAPIView с одним или несколькими миксинами, можно быстро создавать представления для распространенных сценариев. Каждому миксину соответствует метод HTTP-глагола (например, ListModelMixin добавляет метод .list(), который вызывается методом .get() представления, если get не определен явно).
Пример комбинирования GenericAPIView с миксинами для списка и создания:
# articles/views.py
# ... (импорты)
from rest_framework.mixins import ListModelMixin, CreateModelMixin
class ArticleListCreateGenericView(GenericAPIView, ListModelMixin, CreateModelMixin):
"""
Представление для списка статей и создания новой статьи.
Использует GenericAPIView с миксинами.
"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def get(self, request: Request, *args: Any, **kwargs: Any) -> Response:
"""
Обрабатывает GET-запрос (использует ListModelMixin).
"""
# Метод list() предоставляется миксином ListModelMixin
return self.list(request, *args, **kwargs)
def post(self, request: Request, *args: Any, **kwargs: Any) -> Response:
"""
Обрабатывает POST-запрос (использует CreateModelMixin).
"""
# Метод create() предоставляется миксином CreateModelMixin
return self.create(request, *args, **kwargs)DRF предоставляет также готовые классы, которые комбинируют GenericAPIView с определенным набором миксинов, например, ListCreateAPIView (список + создание), RetrieveUpdateDestroyAPIView (получение, обновление, удаление конкретного объекта) и другие. Это самый распространенный способ реализации типичных API-эндпоинтов с использованием CBV.
# articles/views.py
# ... (импорты)
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
# Готовый класс для списка и создания
class ArticleListCreateAPIViewConvenience(ListCreateAPIView):
"""
Представление для списка статей и создания новой статьи (готовый класс).
"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# Готовый класс для получения, обновления и удаления конкретной статьи
class ArticleDetailAPIView(RetrieveUpdateDestroyAPIView):
"""
Представление для получения, обновления и удаления конкретной статьи.
"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# look_up_field по умолчанию 'pk', можно переопределить на slug и т.д.
# lookup_field = 'slug'Использование ViewSets
ViewSets в DRF предоставляют еще более высокий уровень абстракции, объединяя логику, связанную с целым набором взаимодействий с одним ресурсом (list, create, retrieve, update, partial_update, destroy), в одном классе. В отличие от обычных CBV, которые связываются напрямую с URL-путями, ViewSets связываются с набором действий (.list(), .create(), .retrieve(), и т.д.). Связывание ViewSets с URL-ами обычно осуществляется автоматически с помощью роутеров DRF.
Что такое ViewSet и в чем их преимущество?
A ViewSet – это класс, который не предоставляет реализацию методов .get(), .post() и т.д. напрямую, а вместо этого определяет такие методы, как .list(), .create(), .retrieve(), .update(), .partial_update(), .destroy(). Эти методы соответствуют стандартным действиям с ресурсом.
Преимущество ViewSets:
Консолидация: Логика для всех CRUD-операций над моделью находится в одном месте.
Автоматическая маршрутизация: Роутеры DRF могут автоматически генерировать набор URL-путей для ViewSet.
Читаемость: Ясно видно, какие операции поддерживает ViewSet.
Недостаток:
Менее очевидна связь между HTTP-глаголом и вызываемым методом ViewSet без понимания работы роутеров.
Создание `ModelViewSet` для CRUD операций с моделью
ModelViewSet является наиболее распространенным типом ViewSet. Он наследуется от GenericViewSet и включает миксины ListModelMixin, RetrieveModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin. Это позволяет создать полноценный CRUD API для модели, просто указав queryset и serializer_class.
# articles/views.py
# ... (импорты)
from rest_framework.viewsets import ModelViewSet
class ArticleModelViewSet(ModelViewSet):
"""
ViewSet для выполнения CRUD-операций над моделью Article.
Предоставляет действия list, create, retrieve, update, partial_update, destroy.
"""
# Обязательно указываем кверисет и класс сериализатора
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# Можно также определить permission_classes, authentication_classes и т.д. здесь
# permission_classes = [IsAuthenticatedOrReadOnly]Этот один класс заменяет несколько классов GenericAPIView с миксинами (например, ListCreateAPIView и RetrieveUpdateDestroyAPIView).
Создание `ReadOnlyModelViewSet` для операций только на чтение
ReadOnlyModelViewSet используется для создания API, который позволяет только просматривать список объектов (.list()) или конкретный объект (.retrieve()), но не изменять или создавать их. Он наследуется от GenericViewSet и включает только ListModelMixin и RetrieveModelMixin.
# articles/views.py
# ... (импорты)
from rest_framework.viewsets import ReadOnlyModelViewSet
class ArticleReadOnlyModelViewSet(ReadOnlyModelViewSet):
"""
ViewSet для операций чтения (список и получение) над моделью Article.
"""
queryset = Article.objects.all()
serializer_class = ArticleSerializerНастройка маршрутизации для ViewSet с помощью `routers`
ViewSets не определяют URL-пути напрямую. Вместо этого используются роутеры DRF для автоматической генерации набора URL на основе действий ViewSet. Наиболее часто используемый роутер – DefaultRouter.
В файле urls.py вашего приложения или проекта:
# project/urls.py или app/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from articles.views import ArticleModelViewSet # Импортируем наш ViewSet
# Создаем экземпляр роутера
router = DefaultRouter()
# Регистрируем наш ViewSet с префиксом URL 'articles'
# Это создаст URL вроде /articles/ и /articles/{pk}/
router.register(r'articles', ArticleModelViewSet, basename='article')
# URL-пути, сгенерированные роутером, включаются в urlpatterns
urlpatterns = [
# ... другие URL-пути
path('api/', include(router.urls)), # Включаем URL-ы роутера под префиксом /api/
]DefaultRouter автоматически создает URL-пути, связывая их с соответствующими действиями ViewSet:
GET /articles/ -> ArticleModelViewSet.list()
POST /articles/ -> ArticleModelViewSet.create()
GET /articles/{pk}/ -> ArticleModelViewSet.retrieve()
PUT /articles/{pk}/ -> ArticleModelViewSet.update()
PATCH /articles/{pk}/ -> ArticleModelViewSet.partial_update()
DELETE /articles/{pk}/ -> ArticleModelViewSet.destroy()
Обработка ошибок и валидация данных
Надежный API должен корректно обрабатывать невалидные запросы и ошибки, возвращая клиенту понятную информацию и соответствующие HTTP-статусы.
Валидация входных данных с использованием сериализаторов
Как показано в предыдущих примерах, сериализаторы DRF играют ключевую роль в валидации данных. Метод .is_valid() проверяет данные на соответствие полям сериализатора и их ограничениям (например, максимальная длина строки, тип данных). Если данные невалидны, .errors содержит словарь с описанием ошибок.
# Внутри представления (FBV или CBV)
# ...
serializer: ArticleSerializer = ArticleSerializer(data=request.data)
# Вызов is_valid() выполняет валидацию
if serializer.is_valid():
# Данные прошли валидацию
serializer.save() # Сохранение объекта
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
# Данные невалидны
# serializer.errors содержит словарь с ошибками
print(serializer.errors) # Для отладки
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)Вы также можете выполнять дополнительную валидацию на уровне сериализатора с помощью методов .validate_<field_name>() или .validate().
# articles/serializers.py
from rest_framework import serializers
from articles.models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['id', 'title', 'content', 'created_at']
def validate_title(self, value: str) -> str:
"""
Проверяет, что заголовок статьи не является пустым или слишком коротким.
"""
if len(value) dict:
"""
Выполняет валидацию на уровне всего объекта.
"""
# Пример: Проверяем, что контент присутствует, если статья не черновик (гипотетически)
# if not data.get('is_draft', False) and not data.get('content'):
# raise serializers.ValidationError("Статья не может быть опубликована без контента.")
return dataОбработка исключений и возвращение соответствующих HTTP-кодов ошибок
DRF имеет встроенный механизм обработки исключений. Если в вашем представлении возникает стандартное исключение Django или DRF (например, ValidationError, PermissionDenied, Http404), DRF автоматически преобразует его в соответствующий Response с правильным HTTP-статусом и телом ответа.
Например, если в ArticleDetailAPIView запросить несуществующий объект, Django поднимет Http404, который DRF обработает и вернет Response со статусом 404 Not Found.
Пользовательские ошибки и их обработка
Для обработки пользовательских исключений или специфической логики ошибок можно использовать собственную функцию обработки исключений, указанную в настройке EXCEPTION_HANDLER.
Однако, в большинстве случаев достаточно поднимать стандартные исключения DRF внутри представлений или сериализаторов:
rest_framework.exceptions.ValidationError: Для ошибок валидации данных (статус 400).
rest_framework.exceptions.AuthenticationFailed: Ошибка аутентификации (статус 401).
rest_framework.exceptions.PermissionDenied: Нет разрешений (статус 403).
rest_framework.exceptions.NotFound: Объект не найден (статус 404).
rest_framework.exceptions.MethodNotAllowed: Использован неподдерживаемый HTTP-глагол (статус 405).
rest_framework.exceptions.NotAcceptable: Клиент запросил неподдерживаемый тип контента (статус 406).
rest_framework.exceptions.UnsupportedMediaType: Отправлен неподдерживаемый тип контента (статус 415).
rest_framework.exceptions.Throttled: Превышен лимит запросов (статус 429).
Пример вызова пользовательской ошибки валидации в представлении:
# articles/views.py
# ... (импорты)
from rest_framework.exceptions import ValidationError
# Внутри метода post или put в APIView или CBV
# ...
def post(self, request: Request, format: str | None = None) -> Response:
"""
Пример обработки пользовательской ошибки в представлении.
"""
# Допустим, есть бизнес-правило, что заголовок не может быть 'Test'
if request.data.get('title') == 'Test':
# Поднимаем ValidationError с пользовательским сообщением
raise ValidationError({'title': ['Заголовок не может быть "Test".']}, code='invalid_title')
serializer: ArticleSerializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)Поднятие этих исключений внутри представлений или сериализаторов является стандартным способом сигнализировать об ошибках, и DRF автоматически сформирует соответствующий JSON-ответ с кодом 4xx или 5xx.