diff options
| -rw-r--r-- | docs/api-guide/viewsets.md | 10 | ||||
| -rw-r--r-- | rest_framework/decorators.py | 16 | ||||
| -rw-r--r-- | rest_framework/routers.py | 31 | ||||
| -rw-r--r-- | rest_framework/tests/test_routers.py | 24 | 
4 files changed, 41 insertions, 40 deletions
| diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index e83487fb..6d6bb133 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`, `@collection_link`, or `@collection_action` decorators.  The `@link` and `@collection_link` decorators will route `GET` requests, and the `@action` and `@collection_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 `@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. -The `@link` and `@action` decorators contain `pk` in their URL pattern and are intended for methods which require a single instance. The `@collection_link` and `@collection_action` decorators are intended for methods which operate on a collection of objects. +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.  For example:      from django.contrib.auth.models import User      from rest_framework import viewsets -    from rest_framework.decorators import action +    from rest_framework.decorators import action, list_link      from rest_framework.response import Response      from myapp.serializers import UserSerializer, PasswordSerializer @@ -123,7 +123,7 @@ For example:                  return Response(serializer.errors,                                  status=status.HTTP_400_BAD_REQUEST) -        @collection_link() +        @list_link()          def recent_users(self, request):              recent_users = User.objects.all().order('-last_login')              page = self.paginate_queryset(recent_users) @@ -136,7 +136,7 @@ The decorators can additionally take extra arguments that will be set for the ro          def set_password(self, request, pk=None):             ... -The `@action` and `@collection_action` decorators will route `POST` requests by default, but may also accept other HTTP methods, by using the `methods` argument.  For example: +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:          @action(methods=['POST', 'DELETE'])          def unset_password(self, request, pk=None): diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index dacd380f..92f551db 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -109,11 +109,11 @@ def permission_classes(permission_classes):  def link(**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 GET requests.      """      def decorator(func):          func.bind_to_methods = ['get'] -        func.collection = False +        func.detail = True          func.kwargs = kwargs          return func      return decorator @@ -121,35 +121,35 @@ def link(**kwargs):  def action(methods=['post'], **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 detail POST requests.      """      def decorator(func):          func.bind_to_methods = methods -        func.collection = False +        func.detail = True          func.kwargs = kwargs          return func      return decorator -def collection_link(**kwargs): +def list_link(**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.collection = True +        func.detail = False          func.kwargs = kwargs          return func      return decorator -def collection_action(methods=['post'], **kwargs): +def list_action(methods=['post'], **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.collection = True +        func.detail = False          func.kwargs = kwargs          return func      return decorator diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 541df4a9..c8f711e9 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -89,8 +89,8 @@ class SimpleRouter(BaseRouter):              name='{basename}-list',              initkwargs={'suffix': 'List'}          ), -        # Dynamically generated collection routes. -        # Generated using @collection_action or @collection_link decorators +        # Dynamically generated list routes. +        # Generated using @list_action or @list_link decorators          # on methods of the viewset.          Route(              key='collection', @@ -114,7 +114,7 @@ class SimpleRouter(BaseRouter):              name='{basename}-detail',              initkwargs={'suffix': 'Instance'}          ), -        # Dynamically generated routes. +        # Dynamically generated detail routes.          # Generated using @action or @link decorators on methods of the viewset.          Route(              key='dynamic', @@ -157,27 +157,28 @@ class SimpleRouter(BaseRouter):          known_actions = flatten([route.mapping.values() for route in self.routes])          # Determine any `@action` or `@link` decorated methods on the viewset -        collection_routes = [] -        dynamic_routes = [] +        detail_routes = [] +        list_routes = []          for methodname in dir(viewset):              attr = getattr(viewset, methodname)              httpmethods = getattr(attr, 'bind_to_methods', None) -            collection = getattr(attr, 'collection', False) +            detail = getattr(attr, 'detail', True)              if httpmethods:                  if methodname in known_actions: -                    raise ImproperlyConfigured('Cannot use @action or @link decorator on ' -                                               'method "%s" as it is an existing route' % methodname) +                    raise ImproperlyConfigured('Cannot use @action, @link, @list_action ' +                                               'or @list_link decorator on method "%s" ' +                                               'as it is an existing route' % methodname)                  httpmethods = [method.lower() for method in httpmethods] -                if collection: -                    collection_routes.append((httpmethods, methodname)) +                if detail: +                    detail_routes.append((httpmethods, methodname))                  else: -                    dynamic_routes.append((httpmethods, methodname)) +                    list_routes.append((httpmethods, methodname))          ret = []          for route in self.routes:              if route.key == 'dynamic': -                # Dynamic routes (@link or @action decorator) -                for httpmethods, methodname in dynamic_routes: +                # Dynamic detail routes (@link or @action decorator) +                for httpmethods, methodname in detail_routes:                      initkwargs = route.initkwargs.copy()                      initkwargs.update(getattr(viewset, methodname).kwargs)                      ret.append(Route( @@ -188,8 +189,8 @@ class SimpleRouter(BaseRouter):                          initkwargs=initkwargs,                      ))              elif route.key == 'collection': -                # Dynamic routes (@collection_link or @collection_action decorator) -                for httpmethods, methodname in collection_routes: +                # Dynamic list routes (@list_link or @list_action decorator) +                for httpmethods, methodname in list_routes:                      initkwargs = route.initkwargs.copy()                      initkwargs.update(getattr(viewset, methodname).kwargs)                      ret.append(Route( diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index e0a7e292..39310176 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, collection_link, collection_action +from rest_framework.decorators import link, action, list_link, list_action  from rest_framework.response import Response  from rest_framework.routers import SimpleRouter, DefaultRouter  from rest_framework.test import APIRequestFactory @@ -216,39 +216,39 @@ class TestActionAppliedToExistingRoute(TestCase):              self.router.urls -class CollectionAndDynamicViewSet(viewsets.ViewSet): +class DynamicListAndDetailViewSet(viewsets.ViewSet):      def list(self, request, *args, **kwargs):          return Response({'method': 'list'}) -    @collection_action() -    def collection_action(self, request, *args, **kwargs): +    @list_action() +    def list_action(self, request, *args, **kwargs):          return Response({'method': 'action1'})      @action() -    def dynamic_action(self, request, *args, **kwargs): +    def detail_action(self, request, *args, **kwargs):          return Response({'method': 'action2'}) -    @collection_link() -    def collection_link(self, request, *args, **kwargs): +    @list_link() +    def list_link(self, request, *args, **kwargs):          return Response({'method': 'link1'})      @link() -    def dynamic_link(self, request, *args, **kwargs): +    def detail_link(self, request, *args, **kwargs):          return Response({'method': 'link2'}) -class TestCollectionAndDynamicRouter(TestCase): +class TestDynamicListAndDetailRouter(TestCase):      def setUp(self):          self.router = SimpleRouter()      def test_link_and_action_decorator(self): -        routes = self.router.get_routes(CollectionAndDynamicViewSet) +        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(['collection_action', 'collection_link', 'dynamic_action', 'dynamic_link']): +        for i, endpoint in enumerate(['list_action', 'list_link', 'detail_action', 'detail_link']):              route = decorator_routes[i]              # check url listing -            if endpoint.startswith('collection_'): +            if endpoint.startswith('list_'):                  self.assertEqual(route.url,                                   '^{{prefix}}/{0}{{trailing_slash}}$'.format(endpoint))              else: | 
