URL, query params, headers — стратегии версионирования
Версионирование позволяет изменять API без поломки существующих клиентов. DRF предоставляет несколько стратегий версионирования.
Версионирование API — практика поддержки нескольких версий API одновременно. Нужно, когда:
Без версионирования: Изменение API ломает всех существующих клиентов.
С версионированием: Клиенты постепенно мигрируют на новую версию.
Глобальная настройка в settings.py:
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
'DEFAULT_VERSION': 'v1',
'ALLOWED_VERSIONS': ['v1', 'v2'],
'VERSION_PARAM': 'version',
}Версия в URL пути:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
'DEFAULT_VERSION': 'v1',
'ALLOWED_VERSIONS': ['v1', 'v2'],
}
# urls.py
urlpatterns = [
path('api/<str:version>/', include(router.urls)),
]
# views.py
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
def get_queryset(self):
version = self.request.version
if version == 'v2':
return Article.objects.select_related('author').all()
return Article.objects.all()Запросы:
GET /api/v1/articles/ — версия 1GET /api/v2/articles/ — версия 2Преимущества:
Недостатки:
Версия через namespace URL:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
'DEFAULT_VERSION': 'v1',
}
# urls.py
urlpatterns = [
path('api/v1/', include((router.urls, 'api'), namespace='v1')),
path('api/v2/', include((router.urls, 'api'), namespace='v2')),
]
# views.py
class ArticleViewSet(viewsets.ModelViewSet):
def get_queryset(self):
version = self.request.version # 'v1' или 'v2'
# Логика по версиямЗапросы:
GET /api/v1/articles/GET /api/v2/articles/Отличие от URLPathVersioning: Версия определяется через namespace, а не параметр URL.
Версия в query-параметре:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.QueryParameterVersioning',
'DEFAULT_VERSION': 'v1',
'VERSION_PARAM': 'version',
}
# urls.py
urlpatterns = [
path('api/', include(router.urls)), # Без версии в пути
]
# views.py
class ArticleViewSet(viewsets.ModelViewSet):
def get_queryset(self):
version = self.request.version
# Логика по версиямЗапросы:
GET /api/articles/?version=v1GET /api/articles/?version=v2Преимущества:
Недостатки:
Версия в поддомене:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.HostNameVersioning',
'DEFAULT_VERSION': 'v1',
'ALLOWED_VERSIONS': ['v1', 'v2'],
'HOSTNAME_VERSION_PATTERN': r'(?P<version>v\d+)\.',
}
# urls.py
urlpatterns = [
path('api/', include(router.urls)),
]Запросы:
GET http://v1.example.com/api/articles/GET http://v2.example.com/api/articles/Преимущества:
Недостатки:
Версия в заголовке Accept:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
'DEFAULT_VERSION': 'v1',
}
# views.py
class ArticleViewSet(viewsets.ModelViewSet):
def get_queryset(self):
version = self.request.versionЗапросы:
GET /api/articles/
Accept: application/json; version=1.0
# Или с медиа-типом:
Accept: application/vnd.example.v1+jsonПреимущества:
Недостатки:
# serializers.py
class ArticleSerializerV1(serializers.ModelSerializer):
# Старый формат
author_name = serializers.CharField(source='author.username', read_only=True)
class Meta:
model = Article
fields = ['id', 'title', 'content', 'author_name', 'created_at']
class ArticleSerializerV2(serializers.ModelSerializer):
# Новый формат с вложенным автором
author = AuthorSerializer(read_only=True)
class Meta:
model = Article
fields = ['id', 'title', 'content', 'author', 'created_at', 'updated_at']
# views.py
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
def get_serializer_class(self):
if self.request.version == 'v2':
return ArticleSerializerV2
return ArticleSerializerV1class ArticleViewSet(viewsets.ModelViewSet):
def get_queryset(self):
version = self.request.version
if version == 'v1':
# Старая логика — только опубликованные
return Article.objects.filter(is_published=True)
elif version == 'v2':
# Новая логика — все для авторизованных
if self.request.user.is_authenticated:
return Article.objects.all()
return Article.objects.filter(is_published=True)
return Article.objects.filter(is_published=True)
def perform_create(self, serializer):
version = self.request.version
if version == 'v1':
# V1 не позволяет устанавливать автора
serializer.save(author=self.request.user)
else:
# V2 позволяет указать автора в данных
if 'author' in serializer.validated_data:
serializer.save()
else:
serializer.save(author=self.request.user)Пометьте старую версию как устаревшую:
from rest_framework.response import Response
from rest_framework import status
class ArticleViewSet(viewsets.ModelViewSet):
def get_queryset(self):
version = self.request.version
if version == 'v1':
# Предупреждение о депрекации
from rest_framework.exceptions import APIException
if version == 'v1':
# Возвращаем предупреждение в заголовке
self.headers = {
'Deprecation': 'true',
'Sunset': '2024-12-31', # Дата удаления версии
}
return Article.objects.all()Заголовки ответа:
Deprecation: true
Sunset: Sun, 31 Dec 2024 23:59:59 GMTВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.