Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
D
django-rest-framework
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
django-rest-framework
Commits
21b1116a
Commit
21b1116a
authored
Sep 14, 2012
by
Jamie Matthews
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
First stab at new view decorators
parent
886f8b47
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
186 additions
and
30 deletions
+186
-30
djangorestframework/decorators.py
+84
-30
djangorestframework/tests/decorators.py
+102
-0
No files found.
djangorestframework/decorators.py
View file @
21b1116a
from
functools
import
wraps
from
django.http
import
Http404
from
django.utils.decorators
import
available_attrs
from
django.core.exceptions
import
PermissionDenied
from
djangorestframework
import
exceptions
from
djangorestframework
import
status
from
djangorestframework.response
import
Response
from
djangorestframework.request
import
Request
from
djangorestframework.settings
import
api_settings
from
djangorestframework.views
import
APIView
class
LazyViewCreator
(
object
):
"""
This class is responsible for dynamically creating an APIView subclass that
will wrap a function-based view. Instances of this class are created
by the function-based view decorators (below), and each decorator is
responsible for setting attributes on the instance that will eventually be
copied onto the final class-based view. The CBV gets created lazily the first
time it's needed, and then cached for future use.
This is done so that the ordering of stacked decorators is irrelevant.
"""
def
__init__
(
self
):
# Each item in this dictionary will be copied onto the final
# class-based view that gets created when this object is called
self
.
final_view_attrs
=
{
'renderer_classes'
:
APIView
.
renderer_classes
,
'parser_classes'
:
APIView
.
parser_classes
,
'authentication_classes'
:
APIView
.
authentication_classes
,
'throttle_classes'
:
APIView
.
throttle_classes
,
'permission_classes'
:
APIView
.
permission_classes
,
}
self
.
_cached_view
=
None
@property
def
view
(
self
):
"""
Accessor for the dynamically created class-based view. This will
be created if necessary and cached for next time.
"""
if
self
.
_cached_view
is
None
:
class
WrappedAPIView
(
APIView
):
pass
for
attr
,
value
in
self
.
final_view_attrs
.
items
():
setattr
(
WrappedAPIView
,
attr
,
value
)
self
.
_cached_view
=
WrappedAPIView
.
as_view
()
return
self
.
_cached_view
def
__call__
(
self
,
*
args
,
**
kwargs
):
"""
This is the actual code that gets run per-request
"""
return
self
.
view
(
*
args
,
**
kwargs
)
@staticmethod
def
maybe_create
(
func
):
"""
If the argument is already an instance of LazyViewCreator,
just return it. Otherwise, create a new one.
"""
if
isinstance
(
func
,
LazyViewCreator
):
return
func
return
LazyViewCreator
()
def
api_view
(
allowed_methods
):
...
...
@@ -19,35 +75,33 @@ def api_view(allowed_methods):
# `Response` objects will have .request set automatically
# APIException instances will be handled
"""
allowed_methods
=
[
method
.
upper
()
for
method
in
allowed_methods
]
def
decorator
(
func
):
@wraps
(
func
,
assigned
=
available_attrs
(
func
))
def
inner
(
request
,
*
args
,
**
kwargs
):
try
:
request
=
Request
(
request
)
wrapper
=
LazyViewCreator
.
maybe_create
(
func
)
if
request
.
method
not
in
allowed_methods
:
raise
exceptions
.
MethodNotAllowed
(
request
.
method
)
response
=
func
(
request
,
*
args
,
**
kwargs
)
@wraps
(
func
,
assigned
=
available_attrs
(
func
))
def
handler
(
self
,
*
args
,
**
kwargs
):
return
func
(
*
args
,
**
kwargs
)
if
isinstance
(
response
,
Response
):
response
.
request
=
request
if
api_settings
.
FORMAT_SUFFIX_KWARG
:
response
.
format
=
kwargs
.
get
(
api_settings
.
FORMAT_SUFFIX_KWARG
,
None
)
return
response
for
method
in
allowed_methods
:
wrapper
.
final_view_attrs
[
method
.
lower
()]
=
handler
except
exceptions
.
APIException
as
exc
:
return
Response
({
'detail'
:
exc
.
detail
},
status
=
exc
.
status_code
)
return
wrapper
return
decorator
except
Http404
as
exc
:
return
Response
({
'detail'
:
'Not found'
},
status
=
status
.
HTTP_404_NOT_FOUND
)
except
PermissionDenied
as
exc
:
return
Response
({
'detail'
:
'Permission denied'
},
status
=
status
.
HTTP_403_FORBIDDEN
)
def
_create_attribute_setting_decorator
(
attribute
):
def
decorator
(
value
):
def
inner
(
func
):
wrapper
=
LazyViewCreator
.
maybe_create
(
func
)
wrapper
.
final_view_attrs
[
attribute
]
=
value
return
wrapper
return
inner
return
decorator
renderer_classes
=
_create_attribute_setting_decorator
(
'renderer_classes'
)
parser_classes
=
_create_attribute_setting_decorator
(
'parser_classes'
)
authentication_classes
=
_create_attribute_setting_decorator
(
'authentication_classes'
)
throttle_classes
=
_create_attribute_setting_decorator
(
'throttle_classes'
)
permission_classes
=
_create_attribute_setting_decorator
(
'permission_classes'
)
djangorestframework/tests/decorators.py
0 → 100644
View file @
21b1116a
from
django.test
import
TestCase
from
djangorestframework.response
import
Response
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework.renderers
import
JSONRenderer
from
djangorestframework.parsers
import
JSONParser
from
djangorestframework.authentication
import
BasicAuthentication
from
djangorestframework.throttling
import
SimpleRateThottle
from
djangorestframework.permissions
import
IsAuthenticated
from
djangorestframework.decorators
import
(
api_view
,
renderer_classes
,
parser_classes
,
authentication_classes
,
throttle_classes
,
permission_classes
,
LazyViewCreator
)
class
DecoratorTestCase
(
TestCase
):
def
setUp
(
self
):
self
.
factory
=
RequestFactory
()
def
test_wrap_view
(
self
):
@api_view
([
'GET'
])
def
view
(
request
):
return
Response
({})
self
.
assertTrue
(
isinstance
(
view
,
LazyViewCreator
))
def
test_calling_method
(
self
):
@api_view
([
'GET'
])
def
view
(
request
):
return
Response
({})
request
=
self
.
factory
.
get
(
'/'
)
response
=
view
(
request
)
self
.
assertEqual
(
response
.
status_code
,
200
)
request
=
self
.
factory
.
post
(
'/'
)
response
=
view
(
request
)
self
.
assertEqual
(
response
.
status_code
,
405
)
def
test_renderer_classes
(
self
):
@renderer_classes
([
JSONRenderer
])
@api_view
([
'GET'
])
def
view
(
request
):
return
Response
({})
request
=
self
.
factory
.
get
(
'/'
)
response
=
view
(
request
)
self
.
assertEqual
(
response
.
renderer_classes
,
[
JSONRenderer
])
def
test_parser_classes
(
self
):
@parser_classes
([
JSONParser
])
@api_view
([
'GET'
])
def
view
(
request
):
return
Response
({})
request
=
self
.
factory
.
get
(
'/'
)
response
=
view
(
request
)
self
.
assertEqual
(
response
.
request
.
parser_classes
,
[
JSONParser
])
def
test_authentication_classes
(
self
):
@authentication_classes
([
BasicAuthentication
])
@api_view
([
'GET'
])
def
view
(
request
):
return
Response
({})
request
=
self
.
factory
.
get
(
'/'
)
response
=
view
(
request
)
self
.
assertEqual
(
response
.
request
.
authentication_classes
,
[
BasicAuthentication
])
# Doesn't look like these bits are working quite yet
# def test_throttle_classes(self):
#
# @throttle_classes([SimpleRateThottle])
# @api_view(['GET'])
# def view(request):
# return Response({})
#
# request = self.factory.get('/')
# response = view(request)
# self.assertEqual(response.request.throttle, [SimpleRateThottle])
# def test_permission_classes(self):
# @permission_classes([IsAuthenticated])
# @api_view(['GET'])
# def view(request):
# return Response({})
# request = self.factory.get('/')
# response = view(request)
# self.assertEqual(response.request.permission_classes, [IsAuthenticated])
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment