From 6de7e20b7927a486714feeaf7d90364cbe0c6a6b Mon Sep 17 00:00:00 2001 From: matklad Date: Fri, 12 Jul 2013 02:34:52 +0400 Subject: [PATCH] add endpoint argument to @action and @link decorators --- docs/api-guide/routers.md | 8 ++++++++ docs/api-guide/viewsets.md | 6 ++++++ rest_framework/decorators.py | 6 ++++-- rest_framework/routers.py | 14 ++++++++------ rest_framework/tests/test_routers.py | 11 ++++++++++- 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index 865829057..460a3c3a1 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -48,6 +48,14 @@ The following URL pattern would additionally be generated: * URL pattern: `^users/{pk}/set_password/$` Name: `'user-set-password'` +You can customize endpoint name by supplying `endpoint` argument like this: + + @action(endpoint='fancy-name') + def do_stuff(self, request, pk=None): + ... + +* URL pattern: `^users/{pk}/fancy-name/$` Name: `'user-do-stuff'` + # API Guide ## SimpleRouter diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 25d11bfb5..9482f5a09 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -131,6 +131,12 @@ The `@action` decorator will route `POST` requests by default, but may also acce @action(methods=['POST', 'DELETE']) def unset_password(self, request, pk=None): ... + +By default endpoint match method name, but this can be overridden with `endpoint` argument. + + @action(endpoint='fancy-name') + def do_stuff(self, request, pk=None): + ... --- # API Reference diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index c69756a43..55072e75a 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -107,23 +107,25 @@ def permission_classes(permission_classes): return decorator -def link(**kwargs): +def link(endpoint=None, **kwargs): """ Used to mark a method on a ViewSet that should be routed for GET requests. """ def decorator(func): func.bind_to_methods = ['get'] func.kwargs = kwargs + func.endpoint = endpoint or func.__name__ return func return decorator -def action(methods=['post'], **kwargs): +def action(methods=['post'], endpoint=None, **kwargs): """ Used to mark a method on a ViewSet that should be routed for POST requests. """ def decorator(func): func.bind_to_methods = methods func.kwargs = kwargs + func.endpoint = endpoint or func.__name__ return func return decorator diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 930011d39..39c08e541 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -147,22 +147,24 @@ class SimpleRouter(BaseRouter): attr = getattr(viewset, methodname) httpmethods = getattr(attr, 'bind_to_methods', None) if httpmethods: - if methodname in known_actions: + endpoint = getattr(attr, 'endpoint') + if endpoint in known_actions: raise ImproperlyConfigured('Cannot use @action or @link decorator on ' - 'method "%s" as it is an existing route' % methodname) + 'method "%s" as %s is an existing route' + % (methodname, endpoint)) httpmethods = [method.lower() for method in httpmethods] - dynamic_routes.append((httpmethods, methodname)) + dynamic_routes.append((httpmethods, methodname, endpoint)) ret = [] for route in self.routes: if route.mapping == {'{httpmethod}': '{methodname}'}: # Dynamic routes (@link or @action decorator) - for httpmethods, methodname in dynamic_routes: + for httpmethods, methodname, endpoint in dynamic_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) ret.append(Route( - url=replace_methodname(route.url, methodname), - mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), + url=replace_methodname(route.url, endpoint), + mapping=dict((httpmethod, endpoint) for httpmethod in httpmethods), name=replace_methodname(route.name, methodname), initkwargs=initkwargs, )) diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index 5fcccb741..5d84963e6 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -30,6 +30,10 @@ class BasicViewSet(viewsets.ViewSet): def action3(self, request, *args, **kwargs): return Response({'method': 'action2'}) + @action(endpoint='action-name') + def action4(self, request, *args, **kwargs): + return Response({'method': 'action4'}) + @link() def link1(self, request, *args, **kwargs): return Response({'method': 'link1'}) @@ -38,6 +42,10 @@ class BasicViewSet(viewsets.ViewSet): def link2(self, request, *args, **kwargs): return Response({'method': 'link2'}) + @link(endpoint='link-name') + def link3(self, request, *args, **kwargs): + return Response({'method': 'link3'}) + class TestSimpleRouter(TestCase): def setUp(self): @@ -47,7 +55,8 @@ class TestSimpleRouter(TestCase): routes = self.router.get_routes(BasicViewSet) decorator_routes = routes[2:] # Make sure all these endpoints exist and none have been clobbered - for i, endpoint in enumerate(['action1', 'action2', 'action3', 'link1', 'link2']): + for i, endpoint in enumerate(['action1', 'action2', 'action3', 'action-name', + 'link1', 'link2', 'link-name']): route = decorator_routes[i] # check url listing self.assertEqual(route.url,