Skip to content

Commit cf92351

Browse files
authored
Add missing decorators: @versioning_class(), @content_negotiation_class(), @metadata_class() for function based views (#9719)
1 parent 2001878 commit cf92351

File tree

3 files changed

+79
-3
lines changed

3 files changed

+79
-3
lines changed

docs/api-guide/views.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,13 @@ The available decorators are:
186186
* `@authentication_classes(...)`
187187
* `@throttle_classes(...)`
188188
* `@permission_classes(...)`
189+
* `@content_negotiation_class(...)`
190+
* `@metadata_class(...)`
191+
* `@versioning_class(...)`
189192

190-
Each of these decorators takes a single argument which must be a list or tuple of classes.
193+
Each of these decorators is equivalent to setting their respective [api policy attributes][api-policy-attributes].
194+
195+
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.
191196

192197

193198
## View schema decorator
@@ -224,4 +229,5 @@ You may pass `None` in order to exclude the view from schema generation.
224229
[throttling]: throttling.md
225230
[schemas]: schemas.md
226231
[classy-drf]: http://www.cdrf.co
232+
[api-policy-attributes]: views.md#api-policy-attributes
227233

rest_framework/decorators.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ def handler(self, *args, **kwargs):
7070
WrappedAPIView.permission_classes = getattr(func, 'permission_classes',
7171
APIView.permission_classes)
7272

73+
WrappedAPIView.content_negotiation_class = getattr(func, 'content_negotiation_class',
74+
APIView.content_negotiation_class)
75+
76+
WrappedAPIView.metadata_class = getattr(func, 'metadata_class',
77+
APIView.metadata_class)
78+
79+
WrappedAPIView.versioning_class = getattr(func, "versioning_class",
80+
APIView.versioning_class)
81+
7382
WrappedAPIView.schema = getattr(func, 'schema',
7483
APIView.schema)
7584

@@ -113,6 +122,27 @@ def decorator(func):
113122
return decorator
114123

115124

125+
def content_negotiation_class(content_negotiation_class):
126+
def decorator(func):
127+
func.content_negotiation_class = content_negotiation_class
128+
return func
129+
return decorator
130+
131+
132+
def metadata_class(metadata_class):
133+
def decorator(func):
134+
func.metadata_class = metadata_class
135+
return func
136+
return decorator
137+
138+
139+
def versioning_class(versioning_class):
140+
def decorator(func):
141+
func.versioning_class = versioning_class
142+
return func
143+
return decorator
144+
145+
116146
def schema(view_inspector):
117147
def decorator(func):
118148
func.schema = view_inspector

tests/test_decorators.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@
66
from rest_framework import status
77
from rest_framework.authentication import BasicAuthentication
88
from rest_framework.decorators import (
9-
action, api_view, authentication_classes, parser_classes,
10-
permission_classes, renderer_classes, schema, throttle_classes
9+
action, api_view, authentication_classes, content_negotiation_class,
10+
metadata_class, parser_classes, permission_classes, renderer_classes,
11+
schema, throttle_classes, versioning_class
1112
)
13+
from rest_framework.negotiation import BaseContentNegotiation
1214
from rest_framework.parsers import JSONParser
1315
from rest_framework.permissions import IsAuthenticated
1416
from rest_framework.renderers import JSONRenderer
1517
from rest_framework.response import Response
1618
from rest_framework.schemas import AutoSchema
1719
from rest_framework.test import APIRequestFactory
1820
from rest_framework.throttling import UserRateThrottle
21+
from rest_framework.versioning import QueryParameterVersioning
1922
from rest_framework.views import APIView
2023

2124

@@ -150,6 +153,43 @@ def view(request):
150153
response = view(request)
151154
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS
152155

156+
def test_versioning_class(self):
157+
@api_view(["GET"])
158+
@versioning_class(QueryParameterVersioning)
159+
def view(request):
160+
return Response({"version": request.version})
161+
162+
request = self.factory.get("/?version=1.2.3")
163+
response = view(request)
164+
assert response.data == {"version": "1.2.3"}
165+
166+
def test_metadata_class(self):
167+
# From TestMetadata.test_none_metadata()
168+
@api_view()
169+
@metadata_class(None)
170+
def view(request):
171+
return Response({})
172+
173+
request = self.factory.options('/')
174+
response = view(request)
175+
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
176+
assert response.data == {'detail': 'Method "OPTIONS" not allowed.'}
177+
178+
def test_content_negotiation(self):
179+
class CustomContentNegotiation(BaseContentNegotiation):
180+
def select_renderer(self, request, renderers, format_suffix):
181+
assert request.META['HTTP_ACCEPT'] == 'custom/type'
182+
return (renderers[0], renderers[0].media_type)
183+
184+
@api_view(["GET"])
185+
@content_negotiation_class(CustomContentNegotiation)
186+
def view(request):
187+
return Response({})
188+
189+
request = self.factory.get('/', HTTP_ACCEPT='custom/type')
190+
response = view(request)
191+
assert response.status_code == status.HTTP_200_OK
192+
153193
def test_schema(self):
154194
"""
155195
Checks CustomSchema class is set on view

0 commit comments

Comments
 (0)