From e0c2e6fdc29814e7181d8d0ed34c7e23ee078852 Mon Sep 17 00:00:00 2001 From: qqii Date: Thu, 5 Jun 2025 18:08:03 +0100 Subject: [PATCH 1/4] Add missing decorator: versioning_class --- rest_framework/decorators.py | 10 ++++++++++ tests/test_decorators.py | 14 +++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 864ff73958..d84525585a 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -70,6 +70,9 @@ def handler(self, *args, **kwargs): WrappedAPIView.permission_classes = getattr(func, 'permission_classes', APIView.permission_classes) + WrappedAPIView.versioning_class = getattr(func, "versioning_class", + APIView.versioning_class) + WrappedAPIView.schema = getattr(func, 'schema', APIView.schema) @@ -113,6 +116,13 @@ def decorator(func): return decorator +def versioning_class(versioning_class): + def decorator(func): + func.versioning_class = versioning_class + return func + return decorator + + def schema(view_inspector): def decorator(func): func.schema = view_inspector diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 8d0805cbba..9cc6c41e3e 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -7,7 +7,8 @@ from rest_framework.authentication import BasicAuthentication from rest_framework.decorators import ( action, api_view, authentication_classes, parser_classes, - permission_classes, renderer_classes, schema, throttle_classes + permission_classes, renderer_classes, schema, throttle_classes, + versioning_class ) from rest_framework.parsers import JSONParser from rest_framework.permissions import IsAuthenticated @@ -16,6 +17,7 @@ from rest_framework.schemas import AutoSchema from rest_framework.test import APIRequestFactory from rest_framework.throttling import UserRateThrottle +from rest_framework.versioning import QueryParameterVersioning from rest_framework.views import APIView @@ -150,6 +152,16 @@ def view(request): response = view(request) assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS + def test_versioning_class(self): + @api_view(["GET"]) + @versioning_class(QueryParameterVersioning) + def view(request): + return Response({"version": request.version}) + + request = self.factory.get("/?version=1.2.3") + response = view(request) + assert response.data == {"version": "1.2.3"} + def test_schema(self): """ Checks CustomSchema class is set on view From 097302e9298d43dcd1ca224d393e537d16574ad0 Mon Sep 17 00:00:00 2001 From: qqii Date: Tue, 5 Aug 2025 18:41:15 +0100 Subject: [PATCH 2/4] Add missing decorator: metadata_class --- rest_framework/decorators.py | 10 ++++++++++ tests/test_decorators.py | 14 +++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index d84525585a..043ec3f7a7 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -70,6 +70,9 @@ def handler(self, *args, **kwargs): WrappedAPIView.permission_classes = getattr(func, 'permission_classes', APIView.permission_classes) + WrappedAPIView.metadata_class = getattr(func, 'metadata_class', + APIView.metadata_class) + WrappedAPIView.versioning_class = getattr(func, "versioning_class", APIView.versioning_class) @@ -116,6 +119,13 @@ def decorator(func): return decorator +def metadata_class(metadata_class): + def decorator(func): + func.metadata_class = metadata_class + return func + return decorator + + def versioning_class(versioning_class): def decorator(func): func.versioning_class = versioning_class diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 9cc6c41e3e..25b4cbe44d 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -6,7 +6,7 @@ from rest_framework import status from rest_framework.authentication import BasicAuthentication from rest_framework.decorators import ( - action, api_view, authentication_classes, parser_classes, + action, api_view, authentication_classes, metadata_class, parser_classes, permission_classes, renderer_classes, schema, throttle_classes, versioning_class ) @@ -162,6 +162,18 @@ def view(request): response = view(request) assert response.data == {"version": "1.2.3"} + def test_metadata_class(self): + # From TestMetadata.test_none_metadata() + @api_view() + @metadata_class(None) + def view(request): + return Response({}) + + request = self.factory.options('/') + response = view(request) + assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED + assert response.data == {'detail': 'Method "OPTIONS" not allowed.'} + def test_schema(self): """ Checks CustomSchema class is set on view From 96223eb3b9af9f294c6d699bdb7b37682758c3ac Mon Sep 17 00:00:00 2001 From: qqii Date: Tue, 5 Aug 2025 19:10:14 +0100 Subject: [PATCH 3/4] Add missing decorator: content_negotiation --- rest_framework/decorators.py | 10 ++++++++++ tests/test_decorators.py | 22 +++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 043ec3f7a7..93e0751b7a 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -70,6 +70,9 @@ def handler(self, *args, **kwargs): WrappedAPIView.permission_classes = getattr(func, 'permission_classes', APIView.permission_classes) + WrappedAPIView.content_negotiation_class = getattr(func, 'content_negotiation_class', + APIView.content_negotiation_class) + WrappedAPIView.metadata_class = getattr(func, 'metadata_class', APIView.metadata_class) @@ -119,6 +122,13 @@ def decorator(func): return decorator +def content_negotiation_class(content_negotiation_class): + def decorator(func): + func.content_negotiation_class = content_negotiation_class + return func + return decorator + + def metadata_class(metadata_class): def decorator(func): func.metadata_class = metadata_class diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 25b4cbe44d..0c070bc10b 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -6,10 +6,11 @@ from rest_framework import status from rest_framework.authentication import BasicAuthentication from rest_framework.decorators import ( - action, api_view, authentication_classes, metadata_class, parser_classes, - permission_classes, renderer_classes, schema, throttle_classes, - versioning_class + action, api_view, authentication_classes, content_negotiation_class, + metadata_class, parser_classes, permission_classes, renderer_classes, + schema, throttle_classes, versioning_class ) +from rest_framework.negotiation import BaseContentNegotiation from rest_framework.parsers import JSONParser from rest_framework.permissions import IsAuthenticated from rest_framework.renderers import JSONRenderer @@ -174,6 +175,21 @@ def view(request): assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED assert response.data == {'detail': 'Method "OPTIONS" not allowed.'} + def test_content_negotiation(self): + class CustomContentNegotiation(BaseContentNegotiation): + def select_renderer(self, request, renderers, format_suffix): + assert request.META['HTTP_ACCEPT'] == 'custom/type' + return (renderers[0], renderers[0].media_type) + + @api_view(["GET"]) + @content_negotiation_class(CustomContentNegotiation) + def view(request): + return Response({}) + + request = self.factory.get('/', HTTP_ACCEPT='custom/type') + response = view(request) + assert response.status_code == status.HTTP_200_OK + def test_schema(self): """ Checks CustomSchema class is set on view From 712256829ad9cc31c2dd04e3e3656426cff9444d Mon Sep 17 00:00:00 2001 From: qqii Date: Mon, 11 Aug 2025 18:21:11 +0100 Subject: [PATCH 4/4] Add docs for new decorators --- docs/api-guide/views.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md index b293de75ab..05a35c3d62 100644 --- a/docs/api-guide/views.md +++ b/docs/api-guide/views.md @@ -186,8 +186,13 @@ The available decorators are: * `@authentication_classes(...)` * `@throttle_classes(...)` * `@permission_classes(...)` +* `@content_negotiation_class(...)` +* `@metadata_class(...)` +* `@versioning_class(...)` -Each of these decorators takes a single argument which must be a list or tuple of classes. +Each of these decorators is equivalent to setting their respective [api policy attributes][api-policy-attributes]. + +All decorators take a single argument. The ones that end with `_class` expect a single class while the ones ending in `_classes` expect a list or tuple of classes. ## View schema decorator @@ -224,4 +229,5 @@ You may pass `None` in order to exclude the view from schema generation. [throttling]: throttling.md [schemas]: schemas.md [classy-drf]: http://www.cdrf.co +[api-policy-attributes]: views.md#api-policy-attributes