def test_list_route_and_detail_route_with_exact_names(self): class BasicViewSet(viewsets.ViewSet): @list_route(url_path='action-one') def action1(self, request, *args, **kwargs): pass @detail_route(url_path='action-one') def action1_detail(self, request, *args, **kwargs): pass routes = self.router.get_routes(BasicViewSet) action1_list_route = self.get_dynamic_route_by_def_name( 'action1', routes) action1_detail_route = self.get_dynamic_route_by_def_name( 'action1_detail', routes) self.assertEqual(action1_list_route.mapping, {'get': 'action1'}) self.assertEqual( action1_list_route.url, add_trailing_slash_if_needed(u'^{prefix}/action-one/$')) self.assertEqual(action1_detail_route.mapping, {'get': 'action1_detail'}) self.assertEqual( action1_detail_route.url, add_trailing_slash_if_needed(u'^{prefix}/{lookup}/action-one/$'))
def test_actions__for_list_and_detail_with_exact_names(self): class BasicViewSet(viewsets.ViewSet): @action(is_for_list=True, endpoint='action-one') def action1(self, request, *args, **kwargs): pass @action(endpoint='action-one') def action1_detail(self, request, *args, **kwargs): pass routes = self.router.get_routes(BasicViewSet) action1_list_route = self.get_dynamic_route_by_def_name('action1', routes) action1_detail_route = self.get_dynamic_route_by_def_name('action1_detail', routes) self.assertEqual(action1_list_route.mapping, {'post': 'action1'}) self.assertEqual(action1_list_route.url, add_trailing_slash_if_needed(u'^{prefix}/action-one/$')) self.assertEqual(action1_detail_route.mapping, {'post': 'action1_detail'}) self.assertEqual(action1_detail_route.url, add_trailing_slash_if_needed(u'^{prefix}/{lookup}/action-one/$'))
def test_action__for_list__and__with_endpoint(self): class BasicViewSet(viewsets.ViewSet): @action(is_for_list=True, endpoint='action-one') def action1(self, request, *args, **kwargs): pass routes = self.router.get_routes(BasicViewSet) action1_route = self.get_dynamic_route_by_def_name('action1', routes) msg = '@action with is_for_list=True and endpoint route should map methods to "endpoint"' self.assertEqual(action1_route.mapping, {'post': 'action1'}, msg) msg = '@action with is_for_list=True and endpoint route should use url in list scope with "endpoint" value' self.assertEqual(action1_route.url, add_trailing_slash_if_needed(u'^{prefix}/action-one/$'), msg)
def test_link_endpoint(self): class BasicViewSet(viewsets.ViewSet): @link(endpoint='link-one') def link1(self, request, *args, **kwargs): pass routes = self.router.get_routes(BasicViewSet) link1_route = self.get_dynamic_route_by_def_name('link1', routes) msg = '@link with endpoint route should map methods to endpoint if it is specified' self.assertEqual(link1_route.mapping, {'get': 'link1'}, msg) msg = '@link with endpoint route should use url with detail lookup' self.assertEqual(link1_route.url, add_trailing_slash_if_needed(u'^{prefix}/{lookup}/link-one/$'), msg)
def test_list_route(self): class BasicViewSet(viewsets.ViewSet): @list_route() def action1(self, request, *args, **kwargs): pass routes = self.router.get_routes(BasicViewSet) action1_route = self.get_dynamic_route_by_def_name('action1', routes) msg = '@list_route should map methods to def name' self.assertEqual(action1_route.mapping, {'get': 'action1'}, msg) msg = '@list_route should use url in list scope' self.assertEqual(action1_route.url, add_trailing_slash_if_needed(u'^{prefix}/action1/$'), msg)
def test_detail_route__with_methods__and__with_url_path(self): class BasicViewSet(viewsets.ViewSet): @detail_route(methods=['post'], url_path='action-one') def action1(self, request, *args, **kwargs): pass routes = self.router.get_routes(BasicViewSet) action1_route = self.get_dynamic_route_by_def_name('action1', routes) msg = '@detail_route should map methods to "url_path"' self.assertEqual(action1_route.mapping, {'post': 'action1'}, msg) msg = '@detail_route should use url with detail lookup and "url_path" value' self.assertEqual(action1_route.url, add_trailing_slash_if_needed(u'^{prefix}/{lookup}/action-one/$'), msg)
def test_detail_route__with_methods(self): class BasicViewSet(viewsets.ViewSet): @detail_route(methods=['post']) def action1(self, request, *args, **kwargs): pass routes = self.router.get_routes(BasicViewSet) action1_route = self.get_dynamic_route_by_def_name('action1', routes) msg = '@detail_route should map methods to def name' self.assertEqual(action1_route.mapping, {'post': 'action1'}, msg) msg = '@detail_route should use url with detail lookup' self.assertEqual( action1_route.url, add_trailing_slash_if_needed(u'^{prefix}/{lookup}/action1/$'), msg)
def test_list_route__with_methods__and__with_url_path(self): class BasicViewSet(viewsets.ViewSet): @list_route(methods=['post'], url_path='action-one') def action1(self, request, *args, **kwargs): pass routes = self.router.get_routes(BasicViewSet) action1_route = self.get_dynamic_route_by_def_name('action1', routes) msg = '@list_route should map methods to "url_path"' self.assertEqual(action1_route.mapping, {'post': 'action1'}, msg) msg = '@list_route should use url in list scope with "url_path" value' self.assertEqual( action1_route.url, add_trailing_slash_if_needed(u'^{prefix}/action-one/$'), msg)
def test_action__for_list__and__with_endpoint(self): class BasicViewSet(viewsets.ViewSet): @action(is_for_list=True, endpoint='action-one') def action1(self, request, *args, **kwargs): pass routes = self.router.get_routes(BasicViewSet) action1_route = self.get_dynamic_route_by_def_name('action1', routes) msg = '@action with is_for_list=True and endpoint route should map methods to "endpoint"' self.assertEqual(action1_route.mapping, {'post': 'action1'}, msg) msg = '@action with is_for_list=True and endpoint route should use url in list scope with "endpoint" value' self.assertEqual( action1_route.url, add_trailing_slash_if_needed(u'^{prefix}/action-one/$'), msg)
def test_link_endpoint(self): class BasicViewSet(viewsets.ViewSet): @link(endpoint='link-one') def link1(self, request, *args, **kwargs): pass routes = self.router.get_routes(BasicViewSet) link1_route = self.get_dynamic_route_by_def_name('link1', routes) msg = '@link with endpoint route should map methods to endpoint if it is specified' self.assertEqual(link1_route.mapping, {'get': 'link1'}, msg) msg = '@link with endpoint route should use url with detail lookup' self.assertEqual( link1_route.url, add_trailing_slash_if_needed(u'^{prefix}/{lookup}/link-one/$'), msg)
class ExtendedBulkRouter(ExtendedDefaultRouter): """ Map http methods to actions defined on the bulk mixins. """ routes = copy.deepcopy(ExtendedDefaultRouter.routes) routes[0].mapping.update({ 'put': 'bulk_update', 'patch': 'partial_bulk_update', 'delete': 'bulk_destroy', }) routes.append( # Single route. Route(url=add_trailing_slash_if_needed(r'^{prefix}/$'), mapping={ 'get': 'retrieve_single', 'put': 'update_single', 'patch': 'partial_update_single', 'delete': 'destroy_single' }, name='{basename}-single', initkwargs={'suffix': 'Instance'}), ) _routs = routes[2:4] + routes[4:5] + routes[:2]
class ExtendedActionLinkRouterMixin(object): routes = [ # List route. Route(url=add_trailing_slash_if_needed(r'^{prefix}/$'), mapping={ 'get': 'list', 'post': 'create' }, name='{basename}-list', initkwargs={'suffix': 'List'}), # Detail route. Route(url=add_trailing_slash_if_needed(r'^{prefix}/{lookup}/$'), mapping={ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' }, name='{basename}-detail', initkwargs={'suffix': 'Instance'}), # Dynamically generated routes. # Generated using @list_route or @detail_route decorators on methods of the viewset. # List Route(url=add_trailing_slash_if_needed(r'^{prefix}/{methodname}/$'), mapping={ '{httpmethod}': '{methodname}', }, name='{basename}-{methodnamehyphen}-list', initkwargs={}), # Detail Route(url=add_trailing_slash_if_needed( r'^{prefix}/{lookup}/{methodname}/$'), mapping={ '{httpmethod}': '{methodname}', }, name='{basename}-{methodnamehyphen}', initkwargs={}), ] _routs = routes[ 2: 4] + routes[: 2] # first routes should be dynamic (because of urlpatterns position matters) # left self.routs for backward def get_routes(self, viewset): """ Augment `self.routes` with any dynamically generated routes. Returns a list of the Route namedtuple. """ # Determine any `@list_route` or `@detail_route` decorated methods on the viewset dynamic_routes = self.get_dynamic_routes(viewset) ret = [] for route in self._routs: if self.is_dynamic_route(route): # Dynamic routes (@list_route or @detail_route decorator) if self.is_list_dynamic_route(route): ret += self.get_dynamic_routes_instances( viewset, route, self._filter_by_list_dynamic_routes(dynamic_routes)) else: ret += self.get_dynamic_routes_instances( viewset, route, self._filter_by_detail_dynamic_routes(dynamic_routes)) else: # Standard route ret.append(route) return ret def _filter_by_list_dynamic_routes(self, dynamic_routes): return [i for i in dynamic_routes if i[3]] def _filter_by_detail_dynamic_routes(self, dynamic_routes): return [i for i in dynamic_routes if not i[3]] def get_dynamic_routes(self, viewset): known_actions = self.get_known_actions() dynamic_routes = [] for methodname in dir(viewset): attr = getattr(viewset, methodname) httpmethods = getattr(attr, 'bind_to_methods', None) if httpmethods: endpoint = getattr(attr, 'endpoint', methodname) is_for_list = getattr(attr, 'is_for_list', not getattr(attr, 'detail', True)) if endpoint in known_actions: raise ImproperlyConfigured( 'Cannot use @detail_route or @list_route decorator on ' 'method "%s" as %s is an existing route' % (methodname, endpoint)) httpmethods = [method.lower() for method in httpmethods] dynamic_routes.append( (httpmethods, methodname, endpoint, is_for_list)) return dynamic_routes def get_dynamic_route_viewset_method_name_by_endpoint( self, viewset, endpoint): for dynamic_route in self.get_dynamic_routes(viewset=viewset): if dynamic_route[2] == endpoint: return dynamic_route[1] def get_known_actions(self): return flatten([route.mapping.values() for route in self.routes]) def is_dynamic_route(self, route): return route.mapping == {'{httpmethod}': '{methodname}'} def is_list_dynamic_route(self, route): return route.name == '{basename}-{methodnamehyphen}-list' def get_dynamic_routes_instances(self, viewset, route, dynamic_routes): dynamic_routes_instances = [] for httpmethods, methodname, endpoint, is_for_list in dynamic_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) url_path = initkwargs.pop('url_path', endpoint) dynamic_routes_instances.append( Route( url=replace_methodname(route.url, url_path), mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), name=replace_methodname(route.name, url_path), initkwargs=initkwargs, )) return dynamic_routes_instances
class ExtendedActionLinkRouterMixin: routes = [ # List route. Route( url=add_trailing_slash_if_needed(r"^{prefix}/$"), mapping={ "get": "list", "post": "create" }, name="{basename}-list", initkwargs={"suffix": "List"}, ), # Detail route. Route( url=add_trailing_slash_if_needed(r"^{prefix}/{lookup}/$"), mapping={ "get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destroy", }, name="{basename}-detail", initkwargs={"suffix": "Instance"}, ), # Dynamically generated routes. # Generated using @list_route or @detail_route decorators on methods # of the viewset. # List Route( url=add_trailing_slash_if_needed(r"^{prefix}/{methodname}/$"), mapping={ "{httpmethod}": "{methodname}", }, name="{basename}-{methodnamehyphen}-list", initkwargs={}, ), # Detail Route( url=add_trailing_slash_if_needed( r"^{prefix}/{lookup}/{methodname}/$"), mapping={ "{httpmethod}": "{methodname}", }, name="{basename}-{methodnamehyphen}", initkwargs={}, ), ] # first routes should be dynamic (because of urlpatterns position matters) # left self.routs for backward _routs = routes[2:4] + routes[:2] def get_routes(self, viewset): """ Augment `self.routes` with any dynamically generated routes. Returns a list of the Route namedtuple. """ # Determine any `@list_route` or `@detail_route` decorated methods on # the viewset dynamic_routes = self.get_dynamic_routes(viewset) ret = [] for route in self._routs: if self.is_dynamic_route(route): # Dynamic routes (@list_route or @detail_route decorator) if self.is_list_dynamic_route(route): ret += self.get_dynamic_routes_instances( viewset, route, self._filter_by_list_dynamic_routes(dynamic_routes), ) else: ret += self.get_dynamic_routes_instances( viewset, route, self._filter_by_detail_dynamic_routes(dynamic_routes), ) else: # Standard route ret.append(route) return ret def _filter_by_list_dynamic_routes(self, dynamic_routes): return [i for i in dynamic_routes if i[3]] def _filter_by_detail_dynamic_routes(self, dynamic_routes): return [i for i in dynamic_routes if not i[3]] def get_dynamic_routes(self, viewset): known_actions = self.get_known_actions() dynamic_routes = [] for methodname in dir(viewset): attr = getattr(viewset, methodname) httpmethods = getattr(attr, "bind_to_methods", None) if httpmethods: endpoint = getattr(attr, "endpoint", methodname) is_for_list = getattr(attr, "is_for_list", not getattr(attr, "detail", True)) if endpoint in known_actions: raise ImproperlyConfigured( "Cannot use @detail_route or @list_route decorator on " 'method "%s" as %s is an existing route' % (methodname, endpoint)) httpmethods = [method.lower() for method in httpmethods] dynamic_routes.append( (httpmethods, methodname, endpoint, is_for_list)) return dynamic_routes def get_dynamic_route_viewset_method_name_by_endpoint( self, viewset, endpoint): for dynamic_route in self.get_dynamic_routes(viewset=viewset): if dynamic_route[2] == endpoint: return dynamic_route[1] def get_known_actions(self): return flatten([route.mapping.values() for route in self.routes]) def is_dynamic_route(self, route): return route.mapping == {"{httpmethod}": "{methodname}"} def is_list_dynamic_route(self, route): return route.name == "{basename}-{methodnamehyphen}-list" def get_dynamic_routes_instances(self, viewset, route, dynamic_routes): dynamic_routes_instances = [] for httpmethods, methodname, endpoint, is_for_list in dynamic_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) url_path = initkwargs.pop("url_path", endpoint) dynamic_routes_instances.append( Route( url=replace_methodname(route.url, url_path), mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), name=replace_methodname(route.name, url_path), initkwargs=initkwargs, )) return dynamic_routes_instances