diff options
| author | Alex Burgel | 2013-07-15 18:35:13 -0400 | 
|---|---|---|
| committer | Alex Burgel | 2013-07-15 18:35:13 -0400 | 
| commit | eaae8fb2d973769a827214e0606a7e41028d5d34 (patch) | |
| tree | 9c863b99152f016ece5577b11629b9122644d5c5 | |
| parent | ca7ba07b4e42bd1c7c6bb8088c0c5a2c434b56ee (diff) | |
| download | django-rest-framework-eaae8fb2d973769a827214e0606a7e41028d5d34.tar.bz2 | |
Combined link_* and action_* decorators into detail_route and list_route, marked the originals as deprecated.
| -rw-r--r-- | docs/api-guide/routers.md | 16 | ||||
| -rw-r--r-- | docs/api-guide/viewsets.md | 16 | ||||
| -rw-r--r-- | docs/tutorial/6-viewsets-and-routers.md | 8 | ||||
| -rw-r--r-- | rest_framework/decorators.py | 19 | ||||
| -rw-r--r-- | rest_framework/routers.py | 14 | ||||
| -rw-r--r-- | rest_framework/tests/test_routers.py | 40 | 
6 files changed, 59 insertions, 54 deletions
| diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index 86582905..f196dc3c 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -35,12 +35,12 @@ The example above would generate the following URL patterns:  * URL pattern: `^accounts/$`  Name: `'account-list'`  * URL pattern: `^accounts/{pk}/$`  Name: `'account-detail'` -### Extra link and actions +### Registering additional routes -Any methods on the viewset decorated with `@link` or `@action` will also be routed. +Any methods on the viewset decorated with `@detail_route` or `@list_route` will also be routed.  For example, a given method like this on the `UserViewSet` class: -    @action(permission_classes=[IsAdminOrIsSelf]) +    @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf])      def set_password(self, request, pk=None):          ... @@ -52,7 +52,7 @@ The following URL pattern would additionally be generated:  ## SimpleRouter -This router includes routes for the standard set of `list`, `create`, `retrieve`, `update`, `partial_update` and `destroy` actions.  The viewset can also mark additional methods to be routed, using the `@link` or `@action` decorators. +This router includes routes for the standard set of `list`, `create`, `retrieve`, `update`, `partial_update` and `destroy` actions.  The viewset can also mark additional methods to be routed, using the `@detail_route` or `@list_route` decorators.  <table border=1>      <tr><th>URL Style</th><th>HTTP Method</th><th>Action</th><th>URL Name</th></tr> @@ -62,8 +62,8 @@ This router includes routes for the standard set of `list`, `create`, `retrieve`      <tr><td>PUT</td><td>update</td></tr>      <tr><td>PATCH</td><td>partial_update</td></tr>      <tr><td>DELETE</td><td>destroy</td></tr> -    <tr><td rowspan=2>{prefix}/{lookup}/{methodname}/</td><td>GET</td><td>@link decorated method</td><td rowspan=2>{basename}-{methodname}</td></tr> -    <tr><td>POST</td><td>@action decorated method</td></tr> +    <tr><td rowspan=2>{prefix}/{lookup}/{methodname}/</td><td>GET</td><td>@detail_route decorated method</td><td rowspan=2>{basename}-{methodname}</td></tr> +    <tr><td>POST</td><td>@detail_route decorated method</td></tr>  </table>  By default the URLs created by `SimpleRouter` are appending with a trailing slash. @@ -86,8 +86,8 @@ This router is similar to `SimpleRouter` as above, but additionally includes a d      <tr><td>PUT</td><td>update</td></tr>      <tr><td>PATCH</td><td>partial_update</td></tr>      <tr><td>DELETE</td><td>destroy</td></tr> -    <tr><td rowspan=2>{prefix}/{lookup}/{methodname}/[.format]</td><td>GET</td><td>@link decorated method</td><td rowspan=2>{basename}-{methodname}</td></tr> -    <tr><td>POST</td><td>@action decorated method</td></tr> +    <tr><td rowspan=2>{prefix}/{lookup}/{methodname}/[.format]</td><td>GET</td><td>@detail_route decorated method</td><td rowspan=2>{basename}-{methodname}</td></tr> +    <tr><td>POST</td><td>@detail_route decorated method</td></tr>  </table>  As with `SimpleRouter` the trailing slashs on the URL routes can be removed by setting the `trailing_slash` argument to `False` when instantiating the router. diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 6d6bb133..7a8d5979 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -92,15 +92,15 @@ The default routers included with REST framework will provide routes for a stand          def destroy(self, request, pk=None):              pass -If you have ad-hoc methods that you need to be routed to, you can mark them as requiring routing using the `@link`, `@action`, `@list_link`, or `@list_action` decorators.  The `@link` and `@list_link` decorators will route `GET` requests, and the `@action` and `@list_action` decorators will route `POST` requests. +If you have ad-hoc methods that you need to be routed to, you can mark them as requiring routing using the `@detail_route` or `@list_route` decorators. -The `@link` and `@action` decorators contain `pk` in their URL pattern and are intended for methods which require a single instance. The `@list_link` and `@list_action` decorators are intended for methods which operate on a list of objects. +The `@detail_route` decorator contains `pk` in its URL pattern and is intended for methods which require a single instance. The `@list_route` decorator is intended for methods which operate on a list of objects.  For example:      from django.contrib.auth.models import User      from rest_framework import viewsets -    from rest_framework.decorators import action, list_link +    from rest_framework.decorators import detail_route, list_route      from rest_framework.response import Response      from myapp.serializers import UserSerializer, PasswordSerializer @@ -111,7 +111,7 @@ For example:          queryset = User.objects.all()          serializer_class = UserSerializer -        @action() +        @detail_route(methods=['post'])          def set_password(self, request, pk=None):              user = self.get_object()              serializer = PasswordSerializer(data=request.DATA) @@ -123,7 +123,7 @@ For example:                  return Response(serializer.errors,                                  status=status.HTTP_400_BAD_REQUEST) -        @list_link() +        @list_route()          def recent_users(self, request):              recent_users = User.objects.all().order('-last_login')              page = self.paginate_queryset(recent_users) @@ -132,13 +132,13 @@ For example:  The decorators can additionally take extra arguments that will be set for the routed view only.  For example... -        @action(permission_classes=[IsAdminOrIsSelf]) +        @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf])          def set_password(self, request, pk=None):             ... -The `@action` and `@list_action` decorators will route `POST` requests by default, but may also accept other HTTP methods, by using the `methods` argument.  For example: +By default, the decorators will route `GET` requests, but may also accept other HTTP methods, by using the `methods` argument.  For example: -        @action(methods=['POST', 'DELETE']) +        @detail_route(methods=['post', 'delete'])          def unset_password(self, request, pk=None):             ...  --- diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index f16add39..f126ba04 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -25,7 +25,7 @@ Here we've used `ReadOnlyModelViewSet` class to automatically provide the defaul  Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighlight` view classes.  We can remove the three views, and again replace them with a single class. -    from rest_framework.decorators import link +    from rest_framework.decorators import detail_route      class SnippetViewSet(viewsets.ModelViewSet):          """ @@ -39,7 +39,7 @@ Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighl          permission_classes = (permissions.IsAuthenticatedOrReadOnly,                                IsOwnerOrReadOnly,) -        @link(renderer_classes=[renderers.StaticHTMLRenderer]) +        @detail_route(renderer_classes=[renderers.StaticHTMLRenderer])          def highlight(self, request, *args, **kwargs):              snippet = self.get_object()              return Response(snippet.highlighted) @@ -49,9 +49,9 @@ Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighl  This time we've used the `ModelViewSet` class in order to get the complete set of default read and write operations. -Notice that we've also used the `@link` decorator to create a custom action, named `highlight`.  This decorator can be used to add any custom endpoints that don't fit into the standard `create`/`update`/`delete` style. +Notice that we've also used the `@detail_route` decorator to create a custom action, named `highlight`.  This decorator can be used to add any custom endpoints that don't fit into the standard `create`/`update`/`delete` style. -Custom actions which use the `@link` decorator will respond to `GET` requests.  We could have instead used the `@action` decorator if we wanted an action that responded to `POST` requests. +Custom actions which use the `@detail_route` decorator will respond to `GET` requests.  We can use the `methods` argument if we wanted an action that responded to `POST` requests.  ## Binding ViewSets to URLs explicitly diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 92f551db..1ca176f2 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -3,13 +3,14 @@ The most important decorator in this module is `@api_view`, which is used  for writing function-based views with REST framework.  There are also various decorators for setting the API policies on function -based views, as well as the `@action` and `@link` decorators, which are +based views, as well as the `@detail_route` and `@list_route` decorators, which are  used to annotate methods on viewsets that should be included by routers.  """  from __future__ import unicode_literals  from rest_framework.compat import six  from rest_framework.views import APIView  import types +import warnings  def api_view(http_method_names): @@ -111,6 +112,8 @@ def link(**kwargs):      """      Used to mark a method on a ViewSet that should be routed for detail GET requests.      """ +    msg = 'link is pending deprecation. Use detail_route instead.' +    warnings.warn(msg, PendingDeprecationWarning, stacklevel=2)      def decorator(func):          func.bind_to_methods = ['get']          func.detail = True @@ -123,6 +126,8 @@ def action(methods=['post'], **kwargs):      """      Used to mark a method on a ViewSet that should be routed for detail POST requests.      """ +    msg = 'action is pending deprecation. Use detail_route instead.' +    warnings.warn(msg, PendingDeprecationWarning, stacklevel=2)      def decorator(func):          func.bind_to_methods = methods          func.detail = True @@ -131,21 +136,21 @@ def action(methods=['post'], **kwargs):      return decorator -def list_link(**kwargs): +def detail_route(methods=['get'], **kwargs):      """ -    Used to mark a method on a ViewSet that should be routed for GET requests. +    Used to mark a method on a ViewSet that should be routed for detail requests.      """      def decorator(func): -        func.bind_to_methods = ['get'] -        func.detail = False +        func.bind_to_methods = methods +        func.detail = True          func.kwargs = kwargs          return func      return decorator -def list_action(methods=['post'], **kwargs): +def list_route(methods=['get'], **kwargs):      """ -    Used to mark a method on a ViewSet that should be routed for POST requests. +    Used to mark a method on a ViewSet that should be routed for list requests.      """      def decorator(func):          func.bind_to_methods = methods diff --git a/rest_framework/routers.py b/rest_framework/routers.py index b8f19b66..b761ba9a 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -91,7 +91,7 @@ class SimpleRouter(BaseRouter):              initkwargs={'suffix': 'List'}          ),          # Dynamically generated list routes. -        # Generated using @list_action or @list_link decorators +        # Generated using @list_route decorator          # on methods of the viewset.          DynamicListRoute(              url=r'^{prefix}/{methodname}{trailing_slash}$', @@ -111,7 +111,7 @@ class SimpleRouter(BaseRouter):              initkwargs={'suffix': 'Instance'}          ),          # Dynamically generated detail routes. -        # Generated using @action or @link decorators on methods of the viewset. +        # Generated using @detail_route decorator on methods of the viewset.          DynamicDetailRoute(              url=r'^{prefix}/{lookup}/{methodname}{trailing_slash}$',              name='{basename}-{methodnamehyphen}', @@ -148,7 +148,7 @@ class SimpleRouter(BaseRouter):          known_actions = flatten([route.mapping.values() for route in self.routes if isinstance(route, Route)]) -        # Determine any `@action` or `@link` decorated methods on the viewset +        # Determine any `@detail_route` or `@list_route` decorated methods on the viewset          detail_routes = []          list_routes = []          for methodname in dir(viewset): @@ -157,8 +157,8 @@ class SimpleRouter(BaseRouter):              detail = getattr(attr, 'detail', True)              if httpmethods:                  if methodname in known_actions: -                    raise ImproperlyConfigured('Cannot use @action, @link, @list_action ' -                                               'or @list_link decorator on method "%s" ' +                    raise ImproperlyConfigured('Cannot use @detail_route or @list_route ' +                                               'decorators on method "%s" '                                                 'as it is an existing route' % methodname)                  httpmethods = [method.lower() for method in httpmethods]                  if detail: @@ -169,7 +169,7 @@ class SimpleRouter(BaseRouter):          ret = []          for route in self.routes:              if isinstance(route, DynamicDetailRoute): -                # Dynamic detail routes (@link or @action decorator) +                # Dynamic detail routes (@detail_route decorator)                  for httpmethods, methodname in detail_routes:                      initkwargs = route.initkwargs.copy()                      initkwargs.update(getattr(viewset, methodname).kwargs) @@ -180,7 +180,7 @@ class SimpleRouter(BaseRouter):                          initkwargs=initkwargs,                      ))              elif isinstance(route, DynamicListRoute): -                # Dynamic list routes (@list_link or @list_action decorator) +                # Dynamic list routes (@list_route decorator)                  for httpmethods, methodname in list_routes:                      initkwargs = route.initkwargs.copy()                      initkwargs.update(getattr(viewset, methodname).kwargs) diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index 39310176..c3597e38 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -4,7 +4,7 @@ from django.test import TestCase  from django.core.exceptions import ImproperlyConfigured  from rest_framework import serializers, viewsets, permissions  from rest_framework.compat import include, patterns, url -from rest_framework.decorators import link, action, list_link, list_action +from rest_framework.decorators import detail_route, list_route  from rest_framework.response import Response  from rest_framework.routers import SimpleRouter, DefaultRouter  from rest_framework.test import APIRequestFactory @@ -18,23 +18,23 @@ class BasicViewSet(viewsets.ViewSet):      def list(self, request, *args, **kwargs):          return Response({'method': 'list'}) -    @action() +    @detail_route(methods=['post'])      def action1(self, request, *args, **kwargs):          return Response({'method': 'action1'}) -    @action() +    @detail_route(methods=['post'])      def action2(self, request, *args, **kwargs):          return Response({'method': 'action2'}) -    @action(methods=['post', 'delete']) +    @detail_route(methods=['post', 'delete'])      def action3(self, request, *args, **kwargs):          return Response({'method': 'action2'}) -    @link() +    @detail_route()      def link1(self, request, *args, **kwargs):          return Response({'method': 'link1'}) -    @link() +    @detail_route()      def link2(self, request, *args, **kwargs):          return Response({'method': 'link2'}) @@ -175,7 +175,7 @@ class TestActionKeywordArgs(TestCase):          class TestViewSet(viewsets.ModelViewSet):              permission_classes = [] -            @action(permission_classes=[permissions.AllowAny]) +            @detail_route(methods=['post'], permission_classes=[permissions.AllowAny])              def custom(self, request, *args, **kwargs):                  return Response({                      'permission_classes': self.permission_classes @@ -196,14 +196,14 @@ class TestActionKeywordArgs(TestCase):  class TestActionAppliedToExistingRoute(TestCase):      """ -    Ensure `@action` decorator raises an except when applied +    Ensure `@detail_route` decorator raises an except when applied      to an existing route      """      def test_exception_raised_when_action_applied_to_existing_route(self):          class TestViewSet(viewsets.ModelViewSet): -            @action() +            @detail_route(methods=['post'])              def retrieve(self, request, *args, **kwargs):                  return Response({                      'hello': 'world' @@ -220,20 +220,20 @@ class DynamicListAndDetailViewSet(viewsets.ViewSet):      def list(self, request, *args, **kwargs):          return Response({'method': 'list'}) -    @list_action() -    def list_action(self, request, *args, **kwargs): +    @list_route(methods=['post']) +    def list_route_post(self, request, *args, **kwargs):          return Response({'method': 'action1'}) -    @action() -    def detail_action(self, request, *args, **kwargs): +    @detail_route(methods=['post']) +    def detail_route_post(self, request, *args, **kwargs):          return Response({'method': 'action2'}) -    @list_link() -    def list_link(self, request, *args, **kwargs): +    @list_route() +    def list_route_get(self, request, *args, **kwargs):          return Response({'method': 'link1'}) -    @link() -    def detail_link(self, request, *args, **kwargs): +    @detail_route() +    def detail_route_get(self, request, *args, **kwargs):          return Response({'method': 'link2'}) @@ -241,11 +241,11 @@ class TestDynamicListAndDetailRouter(TestCase):      def setUp(self):          self.router = SimpleRouter() -    def test_link_and_action_decorator(self): +    def test_list_and_detail_route_decorators(self):          routes = self.router.get_routes(DynamicListAndDetailViewSet)          decorator_routes = [r for r in routes if not (r.name.endswith('-list') or r.name.endswith('-detail'))]          # Make sure all these endpoints exist and none have been clobbered -        for i, endpoint in enumerate(['list_action', 'list_link', 'detail_action', 'detail_link']): +        for i, endpoint in enumerate(['list_route_get', 'list_route_post', 'detail_route_get', 'detail_route_post']):              route = decorator_routes[i]              # check url listing              if endpoint.startswith('list_'): @@ -255,7 +255,7 @@ class TestDynamicListAndDetailRouter(TestCase):                  self.assertEqual(route.url,                                   '^{{prefix}}/{{lookup}}/{0}{{trailing_slash}}$'.format(endpoint))              # check method to function mapping -            if endpoint.endswith('action'): +            if endpoint.endswith('_post'):                  method_map = 'post'              else:                  method_map = 'get' | 
