mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-10-30 23:47:53 +03:00 
			
		
		
		
	Merge branch 'static-routes' into 0.4.0
This commit is contained in:
		
						commit
						db25aaff3a
					
				|  | @ -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, given a 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): | ||||
|         ... | ||||
| 
 | ||||
|  | @ -48,22 +48,24 @@ The following URL pattern would additionally be generated: | |||
| 
 | ||||
| * URL pattern: `^users/{pk}/set_password/$`  Name: `'user-set-password'` | ||||
| 
 | ||||
| For more information see the viewset documentation on [marking extra actions for routing][route-decorators]. | ||||
| 
 | ||||
| # API Guide | ||||
| 
 | ||||
| ## 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> | ||||
|     <tr><td rowspan=2>{prefix}/</td><td>GET</td><td>list</td><td rowspan=2>{basename}-list</td></tr></tr> | ||||
|     <tr><td>POST</td><td>create</td></tr> | ||||
|     <tr><td>{prefix}/{methodname}/</td><td>GET, or as specified by `methods` argument</td><td>`@list_route` decorated method</td><td>{basename}-{methodname}</td></tr> | ||||
|     <tr><td rowspan=4>{prefix}/{lookup}/</td><td>GET</td><td>retrieve</td><td rowspan=4>{basename}-detail</td></tr></tr> | ||||
|     <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>{prefix}/{lookup}/{methodname}/</td><td>GET, or as specified by `methods` argument</td><td>`@detail_route` decorated method</td><td>{basename}-{methodname}</td></tr> | ||||
| </table> | ||||
| 
 | ||||
| By default the URLs created by `SimpleRouter` are appended with a trailing slash. | ||||
|  | @ -82,12 +84,12 @@ This router is similar to `SimpleRouter` as above, but additionally includes a d | |||
|     <tr><td>[.format]</td><td>GET</td><td>automatically generated root view</td><td>api-root</td></tr></tr> | ||||
|     <tr><td rowspan=2>{prefix}/[.format]</td><td>GET</td><td>list</td><td rowspan=2>{basename}-list</td></tr></tr> | ||||
|     <tr><td>POST</td><td>create</td></tr> | ||||
|     <tr><td>{prefix}/{methodname}/[.format]</td><td>GET, or as specified by `methods` argument</td><td>`@list_route` decorated method</td><td>{basename}-{methodname}</td></tr> | ||||
|     <tr><td rowspan=4>{prefix}/{lookup}/[.format]</td><td>GET</td><td>retrieve</td><td rowspan=4>{basename}-detail</td></tr></tr> | ||||
|     <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>{prefix}/{lookup}/{methodname}/[.format]</td><td>GET, or as specified by `methods` argument</td><td>`@detail_route` decorated method</td><td>{basename}-{methodname}</td></tr> | ||||
| </table> | ||||
| 
 | ||||
| As with `SimpleRouter` the trailing slashes on the URL routes can be removed by setting the `trailing_slash` argument to `False` when instantiating the router. | ||||
|  | @ -144,3 +146,4 @@ If you want to provide totally custom behavior, you can override `BaseRouter` an | |||
| You may also want to override the `get_default_base_name(self, viewset)` method, or else always explicitly set the `base_name` argument when registering your viewsets with the router. | ||||
| 
 | ||||
| [cite]: http://guides.rubyonrails.org/routing.html | ||||
| [route-decorators]: viewsets.html#marking-extra-actions-for-routing | ||||
|  | @ -61,7 +61,7 @@ There are two main advantages of using a `ViewSet` class over using a `View` cla | |||
| 
 | ||||
| Both of these come with a trade-off.  Using regular views and URL confs is more explicit and gives you more control.  ViewSets are helpful if you want to get up and running quickly, or when you have a large API and you want to enforce a consistent URL configuration throughout. | ||||
| 
 | ||||
| ## Marking extra methods for routing | ||||
| ## Marking extra actions for routing | ||||
| 
 | ||||
| The default routers included with REST framework will provide routes for a standard set of create/retrieve/update/destroy style operations, as shown below: | ||||
| 
 | ||||
|  | @ -92,14 +92,16 @@ 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` or `@action` decorators.  The `@link` decorator will route `GET` requests, and the `@action` decorator 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 `@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 import status | ||||
|     from rest_framework.decorators import action | ||||
|     from rest_framework import viewsets | ||||
|     from rest_framework.decorators import detail_route, list_route | ||||
|     from rest_framework.response import Response | ||||
|     from myapp.serializers import UserSerializer, PasswordSerializer | ||||
| 
 | ||||
|  | @ -110,7 +112,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) | ||||
|  | @ -122,15 +124,22 @@ For example: | |||
|                 return Response(serializer.errors, | ||||
|                                 status=status.HTTP_400_BAD_REQUEST) | ||||
| 
 | ||||
| The `@action` and `@link` decorators can additionally take extra arguments that will be set for the routed view only.  For example... | ||||
|         @list_route() | ||||
|         def recent_users(self, request): | ||||
|             recent_users = User.objects.all().order('-last_login') | ||||
|             page = self.paginate_queryset(recent_users) | ||||
|             serializer = self.get_pagination_serializer(page) | ||||
|             return Response(serializer.data) | ||||
| 
 | ||||
|         @action(permission_classes=[IsAdminOrIsSelf]) | ||||
| The decorators can additionally take extra arguments that will be set for the routed view only.  For example... | ||||
| 
 | ||||
|         @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf]) | ||||
|         def set_password(self, request, pk=None): | ||||
|            ... | ||||
| 
 | ||||
| The `@action` decorator will route `POST` requests by default, but may also accept other HTTP methods, by using the `method` 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): | ||||
|            ... | ||||
| --- | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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): | ||||
|  | @ -107,12 +108,40 @@ def permission_classes(permission_classes): | |||
|     return decorator | ||||
| 
 | ||||
| 
 | ||||
| def 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 = methods | ||||
|         func.detail = True | ||||
|         func.kwargs = kwargs | ||||
|         return func | ||||
|     return decorator | ||||
| 
 | ||||
| 
 | ||||
| def list_route(methods=['get'], **kwargs): | ||||
|     """ | ||||
|     Used to mark a method on a ViewSet that should be routed for list requests. | ||||
|     """ | ||||
|     def decorator(func): | ||||
|         func.bind_to_methods = methods | ||||
|         func.detail = False | ||||
|         func.kwargs = kwargs | ||||
|         return func | ||||
|     return decorator | ||||
| 
 | ||||
| # These are now pending deprecation, in favor of `detail_route` and `list_route`. | ||||
| 
 | ||||
| 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 | ||||
|         func.kwargs = kwargs | ||||
|         return func | ||||
|     return decorator | ||||
|  | @ -120,10 +149,13 @@ 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. | ||||
|     """ | ||||
|     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 | ||||
|         func.kwargs = kwargs | ||||
|         return func | ||||
|     return decorator | ||||
|     return decorator | ||||
|  | @ -26,6 +26,8 @@ from rest_framework.urlpatterns import format_suffix_patterns | |||
| 
 | ||||
| 
 | ||||
| Route = namedtuple('Route', ['url', 'mapping', 'name', 'initkwargs']) | ||||
| DynamicDetailRoute = namedtuple('DynamicDetailRoute', ['url', 'name', 'initkwargs']) | ||||
| DynamicListRoute = namedtuple('DynamicListRoute', ['url', 'name', 'initkwargs']) | ||||
| 
 | ||||
| 
 | ||||
| def replace_methodname(format_string, methodname): | ||||
|  | @ -88,6 +90,14 @@ class SimpleRouter(BaseRouter): | |||
|             name='{basename}-list', | ||||
|             initkwargs={'suffix': 'List'} | ||||
|         ), | ||||
|         # Dynamically generated list routes. | ||||
|         # Generated using @list_route decorator | ||||
|         # on methods of the viewset. | ||||
|         DynamicListRoute( | ||||
|             url=r'^{prefix}/{methodname}{trailing_slash}$', | ||||
|             name='{basename}-{methodnamehyphen}', | ||||
|             initkwargs={} | ||||
|         ), | ||||
|         # Detail route. | ||||
|         Route( | ||||
|             url=r'^{prefix}/{lookup}{trailing_slash}$', | ||||
|  | @ -100,13 +110,10 @@ class SimpleRouter(BaseRouter): | |||
|             name='{basename}-detail', | ||||
|             initkwargs={'suffix': 'Instance'} | ||||
|         ), | ||||
|         # Dynamically generated routes. | ||||
|         # Generated using @action or @link decorators on methods of the viewset. | ||||
|         Route( | ||||
|         # Dynamically generated detail routes. | ||||
|         # Generated using @detail_route decorator on methods of the viewset. | ||||
|         DynamicDetailRoute( | ||||
|             url=r'^{prefix}/{lookup}/{methodname}{trailing_slash}$', | ||||
|             mapping={ | ||||
|                 '{httpmethod}': '{methodname}', | ||||
|             }, | ||||
|             name='{basename}-{methodnamehyphen}', | ||||
|             initkwargs={} | ||||
|         ), | ||||
|  | @ -139,25 +146,42 @@ class SimpleRouter(BaseRouter): | |||
|         Returns a list of the Route namedtuple. | ||||
|         """ | ||||
| 
 | ||||
|         known_actions = flatten([route.mapping.values() for route in self.routes]) | ||||
|         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 | ||||
|         dynamic_routes = [] | ||||
|         # Determine any `@detail_route` or `@list_route` decorated methods on the viewset | ||||
|         detail_routes = [] | ||||
|         list_routes = [] | ||||
|         for methodname in dir(viewset): | ||||
|             attr = getattr(viewset, methodname) | ||||
|             httpmethods = getattr(attr, 'bind_to_methods', None) | ||||
|             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 @detail_route or @list_route ' | ||||
|                                                'decorators on method "%s" ' | ||||
|                                                'as it is an existing route' % methodname) | ||||
|                 httpmethods = [method.lower() for method in httpmethods] | ||||
|                 dynamic_routes.append((httpmethods, methodname)) | ||||
|                 if detail: | ||||
|                     detail_routes.append((httpmethods, methodname)) | ||||
|                 else: | ||||
|                     list_routes.append((httpmethods, methodname)) | ||||
| 
 | ||||
|         ret = [] | ||||
|         for route in self.routes: | ||||
|             if route.mapping == {'{httpmethod}': '{methodname}'}: | ||||
|                 # Dynamic routes (@link or @action decorator) | ||||
|                 for httpmethods, methodname in dynamic_routes: | ||||
|             if isinstance(route, DynamicDetailRoute): | ||||
|                 # Dynamic detail routes (@detail_route decorator) | ||||
|                 for httpmethods, methodname in detail_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), | ||||
|                         name=replace_methodname(route.name, methodname), | ||||
|                         initkwargs=initkwargs, | ||||
|                     )) | ||||
|             elif isinstance(route, DynamicListRoute): | ||||
|                 # Dynamic list routes (@list_route decorator) | ||||
|                 for httpmethods, methodname in list_routes: | ||||
|                     initkwargs = route.initkwargs.copy() | ||||
|                     initkwargs.update(getattr(viewset, methodname).kwargs) | ||||
|                     ret.append(Route( | ||||
|  |  | |||
|  | @ -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 | ||||
| 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' | ||||
|  | @ -214,3 +214,49 @@ class TestActionAppliedToExistingRoute(TestCase): | |||
| 
 | ||||
|         with self.assertRaises(ImproperlyConfigured): | ||||
|             self.router.urls | ||||
| 
 | ||||
| 
 | ||||
| class DynamicListAndDetailViewSet(viewsets.ViewSet): | ||||
|     def list(self, request, *args, **kwargs): | ||||
|         return Response({'method': 'list'}) | ||||
| 
 | ||||
|     @list_route(methods=['post']) | ||||
|     def list_route_post(self, request, *args, **kwargs): | ||||
|         return Response({'method': 'action1'}) | ||||
| 
 | ||||
|     @detail_route(methods=['post']) | ||||
|     def detail_route_post(self, request, *args, **kwargs): | ||||
|         return Response({'method': 'action2'}) | ||||
| 
 | ||||
|     @list_route() | ||||
|     def list_route_get(self, request, *args, **kwargs): | ||||
|         return Response({'method': 'link1'}) | ||||
| 
 | ||||
|     @detail_route() | ||||
|     def detail_route_get(self, request, *args, **kwargs): | ||||
|         return Response({'method': 'link2'}) | ||||
| 
 | ||||
| 
 | ||||
| class TestDynamicListAndDetailRouter(TestCase): | ||||
|     def setUp(self): | ||||
|         self.router = SimpleRouter() | ||||
| 
 | ||||
|     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_route_get', 'list_route_post', 'detail_route_get', 'detail_route_post']): | ||||
|             route = decorator_routes[i] | ||||
|             # check url listing | ||||
|             if endpoint.startswith('list_'): | ||||
|                 self.assertEqual(route.url, | ||||
|                                  '^{{prefix}}/{0}{{trailing_slash}}$'.format(endpoint)) | ||||
|             else: | ||||
|                 self.assertEqual(route.url, | ||||
|                                  '^{{prefix}}/{{lookup}}/{0}{{trailing_slash}}$'.format(endpoint)) | ||||
|             # check method to function mapping | ||||
|             if endpoint.endswith('_post'): | ||||
|                 method_map = 'post' | ||||
|             else: | ||||
|                 method_map = 'get' | ||||
|             self.assertEqual(route.mapping[method_map], endpoint) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user