Check extra action's func.__name__

This commit is contained in:
Ryan P Kilby 2020-01-11 23:01:08 -08:00
parent 20a811dbda
commit ed733b7d4c
2 changed files with 40 additions and 1 deletions

View File

@ -33,6 +33,15 @@ def _is_extra_action(attr):
return hasattr(attr, 'mapping') and isinstance(attr.mapping, MethodMapper) return hasattr(attr, 'mapping') and isinstance(attr.mapping, MethodMapper)
def _check_attr_name(func, name):
assert func.__name__ == name, (
f'Expected function (`{func.__name__}`) to match its attribute name '
f'(`{name}`). If using a decorator, ensure the inner function is '
f'decorated with `functools.wraps`, or that `{func.__name__}.__name__` '
f'is otherwise set to `{name}`.')
return func
class ViewSetMixin: class ViewSetMixin:
""" """
This is the magic. This is the magic.
@ -164,7 +173,9 @@ class ViewSetMixin:
""" """
Get the methods that are marked as an extra ViewSet `@action`. Get the methods that are marked as an extra ViewSet `@action`.
""" """
return [method for _, method in getmembers(cls, _is_extra_action)] return [_check_attr_name(method, name)
for name, method
in getmembers(cls, _is_extra_action)]
def get_extra_action_url_map(self): def get_extra_action_url_map(self):
""" """

View File

@ -1,4 +1,5 @@
from collections import OrderedDict from collections import OrderedDict
from functools import wraps
import pytest import pytest
from django.conf.urls import include, url from django.conf.urls import include, url
@ -34,6 +35,7 @@ class Action(models.Model):
def decorate(fn): def decorate(fn):
@wraps(fn)
def wrapper(self, request, *args, **kwargs): def wrapper(self, request, *args, **kwargs):
return fn(self, request, *args, **kwargs) return fn(self, request, *args, **kwargs)
return wrapper return wrapper
@ -222,9 +224,35 @@ class GetExtraActionsTests(TestCase):
'detail_action', 'detail_action',
'list_action', 'list_action',
'unresolvable_detail_action', 'unresolvable_detail_action',
'wrapped_detail_action',
'wrapped_list_action',
] ]
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
def test_attr_name_check(self):
def decorate(fn):
def wrapper(self, request, *args, **kwargs):
return fn(self, request, *args, **kwargs)
return wrapper
class ActionViewSet(GenericViewSet):
queryset = Action.objects.all()
@action(detail=False)
@decorate
def wrapped_list_action(self, request, *args, **kwargs):
raise NotImplementedError
view = ActionViewSet()
with pytest.raises(AssertionError) as excinfo:
view.get_extra_actions()
assert str(excinfo.value) == (
'Expected function (`wrapper`) to match its attribute name '
'(`wrapped_list_action`). If using a decorator, ensure the inner '
'function is decorated with `functools.wraps`, or that '
'`wrapper.__name__` is otherwise set to `wrapped_list_action`.')
@override_settings(ROOT_URLCONF='tests.test_viewsets') @override_settings(ROOT_URLCONF='tests.test_viewsets')
class GetExtraActionUrlMapTests(TestCase): class GetExtraActionUrlMapTests(TestCase):