Function-based views, Class-based views, Generic views, ViewSets
DRF предоставляет несколько уровней абстракции для представлений: от простых function-based views до мощных ViewSets с автоматическим роутингом.
Самый простой способ создать view в DRF — использовать декоратор @api_view:
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Article
from .serializers import ArticleSerializer
@api_view(['GET', 'POST'])
def article_list(request):
"""
Список всех статей или создание новой.
"""
if request.method == 'GET':
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = ArticleSerializer(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(['GET', 'PUT', 'PATCH', 'DELETE'])
def article_detail(request, pk):
"""
Получение, обновление или удаление статьи по ID.
"""
try:
article = Article.objects.get(pk=pk)
except Article.DoesNotExist:
return Response(
{'error': 'Статья не найдена'},
status=status.HTTP_404_NOT_FOUND
)
if request.method == 'GET':
serializer = ArticleSerializer(article)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = ArticleSerializer(article, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'PATCH':
# Частичное обновление — только переданные поля
serializer = ArticleSerializer(article, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
article.delete()
return Response(status=status.HTTP_204_NO_CONTENT)Преимущества:
Недостатки:
Базовый класс для представлений в DRF:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class ArticleList(APIView):
"""
Список статей или создание новой.
"""
def get(self, request, format=None):
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = ArticleSerializer(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class ArticleDetail(APIView):
"""
Получение, обновление или удаление статьи по ID.
"""
def get_object(self, pk):
try:
return Article.objects.get(pk=pk)
except Article.DoesNotExist:
return None
def get(self, request, pk, format=None):
article = self.get_object(pk)
if article is None:
return Response(
{'error': 'Статья не найдена'},
status=status.HTTP_404_NOT_FOUND
)
serializer = ArticleSerializer(article)
return Response(serializer.data)
def put(self, request, pk, format=None):
article = self.get_object(pk)
if article is None:
return Response(
{'error': 'Статья не найдена'},
status=status.HTTP_404_NOT_FOUND
)
serializer = ArticleSerializer(article, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
article = self.get_object(pk)
if article is None:
return Response(
{'error': 'Статья не найдена'},
status=status.HTTP_404_NOT_FOUND
)
article.delete()
return Response(status=status.HTTP_204_NO_CONTENT)Преимущества перед function-based:
DRF предоставляет готовые классы для типовых операций:
from rest_framework import generics
class ArticleList(generics.ListCreateAPIView):
"""
Список статей (GET) или создание новой (POST).
"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def perform_create(self, serializer):
# Автоматически устанавливаем автора
serializer.save(author=self.request.user)
class ArticleDetail(generics.RetrieveUpdateDestroyAPIView):
"""
Получение (GET), обновление (PUT/PATCH) или удаление (DELETE).
"""
queryset = Article.objects.all()
serializer_class = ArticleSerializerГотовые классы Generic Views:
| Класс | Методы | Описание |
|---|---|---|
ListAPIView | GET | Список объектов (read-only) |
CreateAPIView | POST | Создание объекта |
ListCreateAPIView | GET, POST | Список + создание |
RetrieveAPIView | GET | Получение одного объекта (read-only) |
UpdateAPIView | PUT, PATCH | Обновление объекта |
DestroyAPIView | DELETE | Удаление объекта |
RetrieveUpdateAPIView | GET, PUT, PATCH | Получение + обновление |
RetrieveDestroyAPIView | GET, DELETE | Получение + удаление |
RetrieveUpdateDestroyAPIView | GET, PUT, PATCH, DELETE | Полный CRUD для одного объекта |
Базовые классы, предоставляющие отдельные действия:
from rest_framework import mixins
from rest_framework.generics import GenericAPIView
class ArticleList(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class ArticleDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)Миксины:
ListModelMixin — .list() для GET спискаCreateModelMixin — .create() для POSTRetrieveModelMixin — .retrieve() для GET одного объектаUpdateModelMixin — .update() для PUT/PATCHDestroyModelMixin — .destroy() для DELETEViewSet объединяет логику для набора связанных views:
from rest_framework import viewsets
class ArticleViewSet(viewsets.ModelViewSet):
"""
ViewSet для статей — предоставляет все CRUD-операции.
"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)ModelViewSet автоматически предоставляет:
list() — GET спискаcreate() — POST созданиеretrieve() — GET одного объектаupdate() — PUT полное обновлениеpartial_update() — PATCH частичное обновлениеdestroy() — DELETEБазовые классы ViewSets:
| Класс | Действия |
|---|---|
ViewSet | Базовый класс без готовых действий |
GenericViewSet | ViewSet + GenericAPIView (queryset, serializer_class) |
ModelViewSet | Полный CRUD (list, create, retrieve, update, destroy) |
ReadOnlyModelViewSet | Только чтение (list, retrieve) |
class ArticleViewSet(viewsets.ModelViewSet):
serializer_class = ArticleSerializer
def get_queryset(self):
"""
Возвращает queryset, фильтруя по автору для не-админов.
"""
user = self.request.user
if user.is_staff:
return Article.objects.all()
return Article.objects.filter(author=user)
def perform_create(self, serializer):
serializer.save(author=self.request.user)class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
def get_serializer_class(self):
if self.action == 'list':
return ArticleListSerializer
elif self.action == 'retrieve':
return ArticleDetailSerializer
elif self.action == 'create':
return ArticleCreateSerializer
return ArticleSerializerfrom rest_framework.decorators import action
from rest_framework.response import Response
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
"""Публикация статьи."""
article = self.get_object()
article.is_published = True
article.save()
return Response({'status': 'article published'})
@action(detail=False, methods=['get'])
def recent(self, request):
"""Последние 5 статей."""
recent_articles = Article.objects.order_by('-created_at')[:5]
serializer = self.get_serializer(recent_articles, many=True)
return Response(serializer.data)
@action(detail=True, methods=['get'])
def comments(self, request, pk=None):
"""Комментарии к статье."""
article = self.get_object()
comments = article.comments.all()
return Response(CommentSerializer(comments, many=True).data)Параметры @action:
detail=True — действие для одного объекта (/articles/{pk}/publish/)detail=False — действие для коллекции (/articles/recent/)methods — список разрешённых HTTP-методовurl_path — кастомный путь вместо имени методаurl_name — кастомное имя для reverse| Тип | Когда использовать |
|---|---|
@api_view | Простые endpoints, нестандартная логика |
APIView | Нужен полный контроль, сложная логика |
GenericAPIView + Mixins | Стандартные CRUD с кастомизацией |
ViewSet | RESTful API с полным CRUD, нужен автоматический роутинг |
ModelViewSet | Быстрое создание стандартного CRUD API |
get_queryset() вместо queryset = ... для избежания утечек данных между пользователямиperform_create()/perform_update() для логики сохранения@action для нестандартных операцийВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.