Пора углубиться в кастомизацию маршрутов! Потому что иногда стандартные подходы — это прекрасно, но уникальность проекта требует нестандартных решений.
Зачем кастомизировать маршруты?
Роутеры в DRF удобны, но они создают маршруты по стандартным правилам. Например, для ViewSet с названием UserViewSet роутеры автоматически добавят CRUD-маршруты по шаблону /users/.
Но что, если:
- Вам нужно изменить URL, чтобы он был более читабельным и подходил под специфику проекта, например,
/accounts/вместо/users/. - Нужно добавить кастомное поведение, например, маршрут для массового экспорта данных
/users/export/. - Вы хотите скрыть некоторые стандартные CRUD-операции, например, запретить удаление пользователей через API.
Вот тут и вступает в игру кастомизация маршрутов.
Как кастомизировать маршруты: теория и основные подходы
В DRF существует несколько подходов к кастомизации маршрутов:
Переопределение стандартных маршрутов
Меняем URL или используем свои имена маршрутов.Добавление новых маршрутов через кастомные действия (actions)
Добавляем новые эндпоинты к уже существующим ViewSet.Создание полностью специализированных роутеров
Если вы хотите пойти дальше и разработать свою логику маршрутизации, DRF позволяет создать кастомные роутеры.
Кастомизация маршрутов: простые примеры
Давайте сразу перейдём к практике. Мы будем работать с проектом, в котором есть модель Book и ViewSet для неё. Вот краткий контекст:
Модель Book
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=255)
author = models.CharField(max_length=255)
published_date = models.DateField()
isbn = models.CharField(max_length=13, unique=True)
ViewSet для работы с книгами
from rest_framework.viewsets import ModelViewSet
from .models import Book
from .serializers import BookSerializer
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
Подключение маршрутов через DefaultRouter
from rest_framework.routers import DefaultRouter
from .views import BookViewSet
router = DefaultRouter()
router.register(r'books', BookViewSet, basename='books')
urlpatterns = router.urls
Теперь у нас есть стандартные маршруты:
GET /books/ # Список всех книг
POST /books/ # Добавление новой книги
GET /books/{id}/ # Детали конкретной книги
PUT /books/{id}/ # Обновление книги
DELETE /books/{id}/ # Удаление книги
- Переопределение стандартных маршрутов
Предположим, вы хотите заменить /books/ на /library/books/. Для этого вам нужно указать кастомный prefix при регистрации ViewSet:
router.register(r'library/books', BookViewSet, basename='library-books')
Теперь маршруты будут выглядеть так:
GET /library/books/ # Список всех книг
POST /library/books/ # Добавление новой книги
GET /library/books/{id}/ # Детали конкретной книги
PUT /library/books/{id}/ # Обновление книги
DELETE /library/books/{id}/ # Удаление книги
- Добавление новых маршрутов (actions)
Допустим, нам нужно добавить новый маршрут /books/latest/, который будет возвращать последние добавленные книги. В DRF для добавления таких маршрутов используется декоратор @action внутри ViewSet.
Пример:
from rest_framework.decorators import action
from rest_framework.response import Response
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
@action(detail=False, methods=['get'], url_path='latest')
def latest_books(self, request):
"""Возвращает последние 5 добавленных книг"""
latest_books = self.queryset.order_by('-published_date')[:5]
serializer = self.get_serializer(latest_books, many=True)
return Response(serializer.data)
Мы добавили кастомное действие latest_books. Теперь маршрут автоматически подключен к роутеру:
GET /books/latest/ # Возвращает последние добавленные книги
detail=Falseозначает, что маршрут не требует ID объекта (он относится к коллекции).methods=['get']задаёт HTTP-методы, которые обрабатывает маршрут.url_path='latest'позволяет настроить путь. По умолчанию будет использовано имя функции.
- Кастомизация стандартных операций
Что, если мы хотим изменить, как работает стандартный маршрут /books/{id}/? Например, добавить в него дополнительную проверку или изменить ответ.
Всё, что нужно сделать, — переопределить стандартный метод ViewSet, например, retrieve:
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
def retrieve(self, request, *args, **kwargs):
"""Добавляем дополнительное поведение при получении объекта"""
instance = self.get_object()
data = self.get_serializer(instance).data
data['message'] = 'Это ваши данные по книге!'
return Response(data)
Теперь при запросе GET /books/{id}/ в ответе будет дополнительное поле message.
- Создание кастомного роутера
Иногда стандартные роутеры (например, DefaultRouter) оказываются слишком ограниченными. В таких случаях можно написать свой роутер, который будет генерировать маршруты по вашим правилам.
Вот пример минимального кастомного роутера:
from rest_framework.routers import Route, SimpleRouter
class CustomRouter(SimpleRouter):
routes = [
Route(
url=r'^{prefix}/custom/$', # Определяем наш URL
mapping={'get': 'custom'}, # Определяем HTTP-методы и соответствующие им действия
name='{basename}-custom',
detail=False,
initkwargs={'suffix': 'Custom'}
),
]
Теперь можно подключить этот роутер к нашему ViewSet. К примеру, захотим добавить новый маршрут /books/custom/:
# В BookViewSet нужно определить метод custom
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
def custom(self, request):
"""Пример для кастомного метода"""
return Response({'message': 'Это кастомный маршрут!'})
# Используем наш роутер
router = CustomRouter()
router.register(r'books', BookViewSet, basename='books')
urlpatterns = router.urls
Типичные проблемы и как их избежать
Конфликты маршрутов
Если вы добавляете кастомные действия или маршруты, убедитесь, что их пути не пересекаются с существующими маршрутами.Потеря семантики
Старайтесь выбирать такие URL и названия действий, которые описывают их назначение. Например,/books/latest/вместо/books/abc/.Избыточная кастомизация
Не переусердствуйте: DRF уже хорошо автоматизирует рутину. Используйте кастомизацию только в случаях, когда это действительно необходимо.
Практическое задание
- Создайте ViewSet для модели
Authorc полямиnameиbiography. - Добавьте кастомный маршрут
/authors/top/, который возвращает авторов с наибольшим количеством книг. - Настройте свой роутер, чтобы заменить маршрут
/authors/на/writers/.
Переходите к реализации — и да пребудет с вами сила DRF и вдохновение!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ