11from collections import OrderedDict
2+ from functools import wraps
23
34import pytest
45from django .conf .urls import include , url
@@ -33,6 +34,13 @@ class Action(models.Model):
3334 pass
3435
3536
37+ def decorate (fn ):
38+ @wraps (fn )
39+ def wrapper (self , request , * args , ** kwargs ):
40+ return fn (self , request , * args , ** kwargs )
41+ return wrapper
42+
43+
3644class ActionViewSet (GenericViewSet ):
3745 queryset = Action .objects .all ()
3846
@@ -68,6 +76,16 @@ def custom_detail_action(self, request, *args, **kwargs):
6876 def unresolvable_detail_action (self , request , * args , ** kwargs ):
6977 raise NotImplementedError
7078
79+ @action (detail = False )
80+ @decorate
81+ def wrapped_list_action (self , request , * args , ** kwargs ):
82+ raise NotImplementedError
83+
84+ @action (detail = True )
85+ @decorate
86+ def wrapped_detail_action (self , request , * args , ** kwargs ):
87+ raise NotImplementedError
88+
7189
7290class ActionNamesViewSet (GenericViewSet ):
7391
@@ -191,6 +209,8 @@ def test_extra_actions(self):
191209 'detail_action' ,
192210 'list_action' ,
193211 'unresolvable_detail_action' ,
212+ 'wrapped_detail_action' ,
213+ 'wrapped_list_action' ,
194214 ]
195215
196216 self .assertEqual (actual , expected )
@@ -204,9 +224,35 @@ def test_should_only_return_decorated_methods(self):
204224 'detail_action' ,
205225 'list_action' ,
206226 'unresolvable_detail_action' ,
227+ 'wrapped_detail_action' ,
228+ 'wrapped_list_action' ,
207229 ]
208230 self .assertEqual (actual , expected )
209231
232+ def test_attr_name_check (self ):
233+ def decorate (fn ):
234+ def wrapper (self , request , * args , ** kwargs ):
235+ return fn (self , request , * args , ** kwargs )
236+ return wrapper
237+
238+ class ActionViewSet (GenericViewSet ):
239+ queryset = Action .objects .all ()
240+
241+ @action (detail = False )
242+ @decorate
243+ def wrapped_list_action (self , request , * args , ** kwargs ):
244+ raise NotImplementedError
245+
246+ view = ActionViewSet ()
247+ with pytest .raises (AssertionError ) as excinfo :
248+ view .get_extra_actions ()
249+
250+ assert str (excinfo .value ) == (
251+ 'Expected function (`wrapper`) to match its attribute name '
252+ '(`wrapped_list_action`). If using a decorator, ensure the inner '
253+ 'function is decorated with `functools.wraps`, or that '
254+ '`wrapper.__name__` is otherwise set to `wrapped_list_action`.' )
255+
210256
211257@override_settings (ROOT_URLCONF = 'tests.test_viewsets' )
212258class GetExtraActionUrlMapTests (TestCase ):
@@ -218,6 +264,7 @@ def test_list_view(self):
218264 expected = OrderedDict ([
219265 ('Custom list action' , 'http://testserver/api/actions/custom_list_action/' ),
220266 ('List action' , 'http://testserver/api/actions/list_action/' ),
267+ ('Wrapped list action' , 'http://testserver/api/actions/wrapped_list_action/' ),
221268 ])
222269
223270 self .assertEqual (view .get_extra_action_url_map (), expected )
@@ -229,6 +276,7 @@ def test_detail_view(self):
229276 expected = OrderedDict ([
230277 ('Custom detail action' , 'http://testserver/api/actions/1/custom_detail_action/' ),
231278 ('Detail action' , 'http://testserver/api/actions/1/detail_action/' ),
279+ ('Wrapped detail action' , 'http://testserver/api/actions/1/wrapped_detail_action/' ),
232280 # "Unresolvable detail action" excluded, since it's not resolvable
233281 ])
234282
0 commit comments