SimpleRouter, DefaultRouter, кастомные роутеры, URL-конфигурация
Роутеры DRF автоматически генерируют URL-паттерны для ViewSets, избавляя от необходимости вручную прописывать каждый путь.
SimpleRouter создаёт базовые URL-паттерны для ViewSet:
# urls.py
from rest_framework.routers import SimpleRouter
from .views import ArticleViewSet
router = SimpleRouter()
router.register(r'articles', ArticleViewSet, basename='article')
urlpatterns = router.urlsСгенерированные паттерны:
| URL | Метод | Действие | Имя URL |
|---|---|---|---|
/articles/ | GET | list | article-list |
/articles/ | POST | create | article-list |
/articles/{pk}/ | GET | retrieve | article-detail |
/articles/{pk}/ | PUT | update | article-detail |
/articles/{pk}/ | PATCH | partial_update | article-detail |
/articles/{pk}/ | DELETE | destroy | article-detail |
Параметры register():
prefix — префикс URL (например, 'articles')viewset — класс ViewSetbasename — базовое имя для reverse (опционально, по умолчанию от названия viewset)DefaultRouter добавляет автоматический API root view:
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'articles', ArticleViewSet, basename='article')
router.register(r'users', UserViewSet, basename='user')
urlpatterns = [
path('api/', include(router.urls)),
]Особенности DefaultRouter:
/api/) со списком всех endpoints.json, .api к URLПример API root:
{
"articles": "http://example.com/api/articles/",
"users": "http://example.com/api/users/"
}Можно создать свой роутер, наследуясь от SimpleRouter или DefaultRouter:
from rest_framework.routers import DefaultRouter
class CustomRouter(DefaultRouter):
"""Кастомный роутер с дополнительными настройками"""
trailing_slash = False # URL без завершающего слэша
def get_default_basename(self, viewset):
"""Кастомное имя basename по умолчанию"""
if self.basename_regex:
return re.sub(self.basename_regex, '', viewset.__name__)
return super().get_default_basename(viewset)
router = CustomRouter()
router.register(r'articles', ArticleViewSet)
# URL: /articles (без слэша в конце)Для вложенных ресурсов (например, комментарии к статье) используйте drf-nested-routers:
pip install drf-nested-routers# urls.py
from rest_framework.routers import DefaultRouter
from rest_framework_nested.routers import NestedDefaultRouter
from .views import ArticleViewSet, CommentViewSet
router = DefaultRouter()
router.register(r'articles', ArticleViewSet, basename='article')
# Вложенный роутер для комментариев
articles_router = NestedDefaultRouter(router, r'articles', lookup='article')
articles_router.register(r'comments', CommentViewSet, basename='article-comments')
urlpatterns = [
path('api/', include(router.urls)),
path('api/', include(articles_router.urls)),
]Сгенерированные URL:
| URL | Действие |
|---|---|
/api/articles/ | Список статей |
/api/articles/{pk}/ | Одна статья |
/api/articles/{article_pk}/comments/ | Список комментариев к статье |
/api/articles/{article_pk}/comments/{pk}/ | Один комментарий |
Использование в ViewSet:
class CommentViewSet(viewsets.ModelViewSet):
serializer_class = CommentSerializer
def get_queryset(self):
# Фильтруем комментарии по статье
article_pk = self.kwargs['article_pk']
return Comment.objects.filter(article_id=article_pk)
def perform_create(self, serializer):
# Автоматически устанавливаем статью
article_pk = self.kwargs['article_pk']
serializer.save(article_id=article_pk)Можно использовать обычные Django URL patterns:
# urls.py
from django.urls import path
from .views import ArticleList, ArticleDetail
urlpatterns = [
path('api/articles/', ArticleList.as_view(), name='article-list'),
path('api/articles/<int:pk>/', ArticleDetail.as_view(), name='article-detail'),
]Когда использовать ручной роутинг:
HyperlinkedModelSerializer использует URL вместо первичных ключей:
# serializers.py
class ArticleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Article
fields = ['url', 'title', 'content', 'author']
# 'url' — гиперссылка на детальное представление
# views.py
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializerПример ответа:
{
"url": "http://example.com/api/articles/1/",
"title": "Моя статья",
"content": "Текст статьи",
"author": "http://example.com/api/users/5/"
}Настройка lookup field:
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
lookup_field = 'slug' # Использовать slug вместо pk
# URL: /articles/my-article-slug/from django.urls import reverse
# Получить URL для list
url = reverse('article-list') # '/api/articles/'
# Получить URL для detail
url = reverse('article-detail', kwargs={'pk': 1}) # '/api/articles/1/'<a href="{% url 'article-list' %}">Список статей</a>
<a href="{% url 'article-detail' pk=1 %}">Статья 1</a>class ArticleSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField()
def get_url(self, obj):
request = self.context['request']
return request.build_absolute_uri(
reverse('article-detail', kwargs={'pk': obj.pk})
)Для разделения API на версии или модули используйте namespaces:
# urls.py
urlpatterns = [
path('api/v1/', include((router.urls, 'api'), namespace='v1')),
path('api/v2/', include((router.urls, 'api'), namespace='v2')),
]
# В коде
reverse('v1:article-list') # '/api/v1/articles/'
reverse('v2:article-list') # '/api/v2/articles/'/articles/{id}/comments//articles/my-slug/ вместо /articles/1//api/v1/, /api/v2/Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.