Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit90ae57c

Browse files
author
Ryan P Kilby
committed
Add method mapping to ViewSet actions
1 parent21a2035 commit90ae57c

File tree

6 files changed

+153
-12
lines changed

6 files changed

+153
-12
lines changed

‎rest_framework/decorators.py‎

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ def action(methods=None, detail=None, name=None, url_path=None, url_name=None, *
146146
)
147147

148148
defdecorator(func):
149-
func.bind_to_methods=methods
149+
func.mapping=MethodMapper(func,methods)
150+
150151
func.detail=detail
151152
func.name=nameifnameelsepretty_name(func.__name__)
152153
func.url_path=url_pathifurl_pathelsefunc.__name__
@@ -156,10 +157,70 @@ def decorator(func):
156157
'name':func.name,
157158
'description':func.__doc__orNone
158159
})
160+
159161
returnfunc
160162
returndecorator
161163

162164

165+
classMethodMapper(dict):
166+
"""
167+
Enables mapping HTTP methods to different ViewSet methods for a single,
168+
logical action.
169+
170+
Example usage:
171+
172+
class MyViewSet(ViewSet):
173+
174+
@action(detail=False)
175+
def example(self, request, **kwargs):
176+
...
177+
178+
@example.mapping.post
179+
def create_example(self, request, **kwargs):
180+
...
181+
"""
182+
183+
def__init__(self,action,methods):
184+
self.action=action
185+
formethodinmethods:
186+
self[method]=self.action.__name__
187+
188+
def_map(self,method,func):
189+
assertmethodnotinself, (
190+
"Method '%s' has already been mapped to '.%s'."% (method,self[method]))
191+
assertfunc.__name__!=self.action.__name__, (
192+
"Method mapping does not behave like the property decorator. You "
193+
"cannot use the same method name for each mapping declaration.")
194+
195+
self[method]=func.__name__
196+
197+
returnfunc
198+
199+
defget(self,func):
200+
returnself._map('get',func)
201+
202+
defpost(self,func):
203+
returnself._map('post',func)
204+
205+
defput(self,func):
206+
returnself._map('put',func)
207+
208+
defpatch(self,func):
209+
returnself._map('patch',func)
210+
211+
defdelete(self,func):
212+
returnself._map('delete',func)
213+
214+
defhead(self,func):
215+
returnself._map('head',func)
216+
217+
defoptions(self,func):
218+
returnself._map('options',func)
219+
220+
deftrace(self,func):
221+
returnself._map('trace',func)
222+
223+
163224
defdetail_route(methods=None,**kwargs):
164225
"""
165226
Used to mark a method on a ViewSet that should be routed for detail requests.

‎rest_framework/routers.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,7 @@ def _get_dynamic_route(self, route, action):
208208

209209
returnRoute(
210210
url=route.url.replace('{url_path}',url_path),
211-
mapping={http_method:action.__name__
212-
forhttp_methodinaction.bind_to_methods},
211+
mapping=action.mapping,
213212
name=route.name.replace('{url_name}',action.url_name),
214213
detail=route.detail,
215214
initkwargs=initkwargs,

‎rest_framework/viewsets.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232

3333
def_is_extra_action(attr):
34-
returnhasattr(attr,'bind_to_methods')
34+
returnhasattr(attr,'mapping')
3535

3636

3737
classViewSetMixin(object):

‎tests/test_decorators.py‎

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def test_defaults(self):
177177
deftest_action(request):
178178
"""Description"""
179179

180-
asserttest_action.bind_to_methods==['get']
180+
asserttest_action.mapping=={'get':'test_action'}
181181
asserttest_action.detailisTrue
182182
asserttest_action.name=='Test action'
183183
asserttest_action.url_path=='test_action'
@@ -195,6 +195,42 @@ def test_action(request):
195195

196196
assertstr(excinfo.value)=="@action() missing required argument: 'detail'"
197197

198+
deftest_method_mapping(self):
199+
@action(detail=False)
200+
deftest_action(request):
201+
pass
202+
203+
@test_action.mapping.post
204+
deftest_action_post(request):
205+
pass
206+
207+
# The secondary handler methods should not have the action attributes
208+
fornamein ['mapping','detail','name','url_path','url_name','kwargs']:
209+
asserthasattr(test_action,name)andnothasattr(test_action_post,name)
210+
211+
deftest_method_mapping_already_mapped(self):
212+
@action(detail=True)
213+
deftest_action(request):
214+
pass
215+
216+
msg="Method 'get' has already been mapped to '.test_action'."
217+
withself.assertRaisesMessage(AssertionError,msg):
218+
@test_action.mapping.get
219+
deftest_action_get(request):
220+
pass
221+
222+
deftest_method_mapping_overwrite(self):
223+
@action(detail=True)
224+
deftest_action():
225+
pass
226+
227+
msg= ("Method mapping does not behave like the property decorator. You "
228+
"cannot use the same method name for each mapping declaration.")
229+
withself.assertRaisesMessage(AssertionError,msg):
230+
@test_action.mapping.post
231+
deftest_action():
232+
pass
233+
198234
deftest_detail_route_deprecation(self):
199235
withpytest.warns(PendingDeprecationWarning)asrecord:
200236
@detail_route()

‎tests/test_routers.py‎

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
fromdjango.core.exceptionsimportImproperlyConfigured
88
fromdjango.dbimportmodels
99
fromdjango.testimportTestCase,override_settings
10-
fromdjango.urlsimportresolve
10+
fromdjango.urlsimportresolve,reverse
1111

1212
fromrest_frameworkimportpermissions,serializers,viewsets
1313
fromrest_framework.compatimportget_regex_pattern
@@ -107,8 +107,23 @@ def action1(self, request, *args, **kwargs):
107107
defaction2(self,request,*args,**kwargs):
108108
returnResponse({'method':'action2'})
109109

110+
@action(methods=['post'],detail=True)
111+
defaction3(self,request,pk,*args,**kwargs):
112+
returnResponse({'post':pk})
113+
114+
@action3.mapping.delete
115+
defaction3_delete(self,request,pk,*args,**kwargs):
116+
returnResponse({'delete':pk})
117+
118+
119+
classTestSimpleRouter(URLPatternsTestCase,TestCase):
120+
router=SimpleRouter()
121+
router.register('basics',BasicViewSet,base_name='basic')
122+
123+
urlpatterns= [
124+
url(r'^api/',include(router.urls)),
125+
]
110126

111-
classTestSimpleRouter(TestCase):
112127
defsetUp(self):
113128
self.router=SimpleRouter()
114129

@@ -127,6 +142,21 @@ def test_action_routes(self):
127142
'delete':'action2',
128143
}
129144

145+
assertroutes[2].url=='^{prefix}/{lookup}/action3{trailing_slash}$'
146+
assertroutes[2].mapping== {
147+
'post':'action3',
148+
'delete':'action3_delete',
149+
}
150+
151+
deftest_multiple_action_handlers(self):
152+
# Standard action
153+
response=self.client.post(reverse('basic-action3',args=[1]))
154+
assertresponse.data== {'post':'1'}
155+
156+
# Additional handler registered with MethodMapper
157+
response=self.client.delete(reverse('basic-action3',args=[1]))
158+
assertresponse.data== {'delete':'1'}
159+
130160

131161
classTestRootView(URLPatternsTestCase,TestCase):
132162
urlpatterns= [

‎tests/test_schemas.py‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ def custom_list_action(self, request):
9797

9898
@action(methods=['post','get'],detail=False,serializer_class=EmptySerializer)
9999
defcustom_list_action_multiple_methods(self,request):
100+
"""Custom description."""
101+
returnsuper(ExampleViewSet,self).list(self,request)
102+
103+
@custom_list_action_multiple_methods.mapping.delete
104+
defcustom_list_action_multiple_methods_delete(self,request):
105+
"""Deletion description."""
100106
returnsuper(ExampleViewSet,self).list(self,request)
101107

102108
defget_serializer(self,*args,**kwargs):
@@ -147,7 +153,8 @@ def test_anonymous_request(self):
147153
'custom_list_action_multiple_methods': {
148154
'read':coreapi.Link(
149155
url='/example/custom_list_action_multiple_methods/',
150-
action='get'
156+
action='get',
157+
description='Custom description.',
151158
)
152159
},
153160
'read':coreapi.Link(
@@ -238,12 +245,19 @@ def test_authenticated_request(self):
238245
'custom_list_action_multiple_methods': {
239246
'read':coreapi.Link(
240247
url='/example/custom_list_action_multiple_methods/',
241-
action='get'
248+
action='get',
249+
description='Custom description.',
242250
),
243251
'create':coreapi.Link(
244252
url='/example/custom_list_action_multiple_methods/',
245-
action='post'
246-
)
253+
action='post',
254+
description='Custom description.',
255+
),
256+
'delete':coreapi.Link(
257+
url='/example/custom_list_action_multiple_methods/',
258+
action='delete',
259+
description='Deletion description.',
260+
),
247261
},
248262
'update':coreapi.Link(
249263
url='/example/{id}/',
@@ -526,7 +540,8 @@ def test_schema_for_regular_views(self):
526540
'custom_list_action_multiple_methods': {
527541
'read':coreapi.Link(
528542
url='/example1/custom_list_action_multiple_methods/',
529-
action='get'
543+
action='get',
544+
description='Custom description.',
530545
)
531546
},
532547
'read':coreapi.Link(

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp