def get_routes(self, viewset): """ Augment `self.routes` with any dynamically generated routes. Returns a list of the Route namedtuple. """ # converting to list as iterables are good for one pass, known # host needs to be checked again and again for # different functions. known_actions = list( flatten([route.mapping.values() for route in self.custom_routes if isinstance(route, Route)]) ) extra_actions = viewset.get_extra_actions() # checking action names against the known actions list not_allowed = [action.__name__ for action in extra_actions if action.__name__ in known_actions] if not_allowed: msg = "Cannot use the @action decorator on the following " "methods, as they are existing routes: %s" raise ImproperlyConfigured(msg % ", ".join(not_allowed)) # partition detail and list actions detail_actions = [action for action in extra_actions if action.detail] list_actions = [action for action in extra_actions if not action.detail] routes = [] for route in self.custom_routes: if isinstance(route, DynamicRoute) and route.detail: routes += [self._get_dynamic_route(route, action) for action in detail_actions] elif isinstance(route, DynamicRoute) and not route.detail: routes += [self._get_dynamic_route(route, action) for action in list_actions] else: routes.append(route) return routes
def get_routes(self, viewset): """ allow links and actions to be bind to a list view """ known_actions = flatten([route.mapping.values() for route in self.routes]) list_routes = [] for methodname in dir(viewset): attr = getattr(viewset, methodname) httpmethods = getattr(attr, 'list_bind_to_methods', None) 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) httpmethods = [method.lower() for method in httpmethods] list_routes.append((httpmethods, methodname)) ret = [] for route in self.routes: if route.mapping == {'{httpmethod}': '{listmethodname}'}: # Dynamic routes (@link or @action decorator) for httpmethods, methodname in list_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) ret.append(Route( url=replace_listmethodname(route.url, methodname), mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), name=replace_listmethodname(route.name, methodname), initkwargs=initkwargs, )) # list methods goes first on the url definition return ret + super(LinkHeaderRouter, self).get_routes(viewset)
def get_routes(self, viewset): """ Augment `self.routes` with any dynamically generated routes. Returns a list of the Route namedtuple. """ known_actions = routers.flatten([route.mapping.values() for route in self.routes]) # Determine any `@action` or `@link` decorated methods on the viewset dynamic_route_mappings = [] for methodname in dir(viewset): attr = getattr(viewset, methodname) httpmethods = getattr(attr, 'bind_to_methods', None) 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) httpmethods = [method.lower() for method in httpmethods] dynamic_route_mappings.append((httpmethods, methodname)) dynamic_routes = [] standard_routes = [] dyn_url_pattern = r'^{prefix}/{methodname}{trailing_slash}$' for route in self.routes: if route.mapping == {'{httpmethod}': '{methodname}'}: # Dynamic routes (@link or @action decorator) for httpmethods, methodname in dynamic_route_mappings: initkwargs = route.initkwargs.copy() view_fn = getattr(viewset, methodname) initkwargs.update(view_fn.kwargs) # Does the dynamic route have a pk argument? pk_in_args = 'pk' in inspect.getargspec(view_fn).args if pk_in_args: # Default DRF behavior: prefix/pk/methodname dynamic_routes.append(routers.Route( url=routers.replace_methodname(route.url, methodname), mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), name=routers.replace_methodname(route.name, methodname), initkwargs=initkwargs, )) else: # No pk? Then map to prefix/methodname dynamic_routes.append(routers.Route( url=routers.replace_methodname(dyn_url_pattern, methodname), mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), name=routers.replace_methodname('{basename}-{methodname}', methodname), initkwargs=initkwargs, )) else: # Standard route standard_routes.append(route) return dynamic_routes + standard_routes
def get_routes(self, viewset): """ Augment `self.routes` with any dynamically generated routes. Returns a list of the Route namedtuple. """ # converting to list as iterables are good for one pass, known host needs to be checked again and again for # different functions. known_actions = list( flatten([ route.mapping.values() for route in self.routes if isinstance(route, Route) ])) extra_actions = self._get_viewset_extra_actions(viewset) # checking action names against the known actions list not_allowed = [ action.__name__ for action in extra_actions if action.__name__ in known_actions ] if not_allowed: msg = ('Cannot use the @action decorator on the following ' 'methods, as they are existing routes: %s') raise ImproperlyConfigured(msg % ', '.join(not_allowed)) # partition detail and list actions detail_actions = [action for action in extra_actions if action.detail] list_actions = [ action for action in extra_actions if not action.detail ] routes = [] dynamic_routes = {} for route in self.routes: if is_dynamic_detail_route(route): for action in detail_actions: r = self._get_dynamic_route(route, action, known_routes=dynamic_routes) dynamic_routes[r.url] = r elif is_dynamic_list_route(route): for action in list_actions: r = self._get_dynamic_route(route, action, known_routes=dynamic_routes) dynamic_routes[r.url] = r else: routes.append(route) for full_url in dynamic_routes: routes.insert(0, dynamic_routes[full_url]) return routes
def get_routes(self, viewset): """ Augment `self.routes` with any dynamically generated routes. Returns a list of the Route namedtuple. """ known_actions = flatten([route.mapping.values() for route in self.routes]) # Determine any `@action` or `@link` decorated methods on the viewset dynamic_routes = [] for methodname in dir(viewset): attr = getattr(viewset, methodname) httpmethods = getattr(attr, 'bind_to_methods', None) 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) httpmethods = [method.lower() for method in httpmethods] dynamic_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: method_kwargs = getattr(viewset, methodname).kwargs url_path = method_kwargs.pop("url_path", None) or methodname initkwargs = route.initkwargs.copy() initkwargs.update(method_kwargs) if getattr(getattr(viewset, methodname), 'for_list', False): ret.insert(0, Route( url=replace_methodname(route.url, url_path).replace('{lookup}/', ''), mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), name=replace_methodname(route.name, url_path), initkwargs=initkwargs, )) else: ret.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, )) else: # Standard route ret.append(route) return ret
def get_routes(self, viewset): """ allow links and actions to be bound to a collection view """ known_actions = flatten([route.mapping.values() for route in self.routes]) dynamic_routes = [] collection_dynamic_routes = [] for methodname in dir(viewset): attr = getattr(viewset, methodname) bind = getattr(attr, 'bind_to_methods', None) httpmethods = getattr(attr, 'collection_bind_to_methods', bind) if httpmethods: if methodname in known_actions: msg = ('Cannot use @action or @link decorator on method "%s" ' 'as it is an existing route' % methodname) raise ImproperlyConfigured(msg) httpmethods = [method.lower() for method in httpmethods] if bind: dynamic_routes.append((httpmethods, methodname)) else: collection_dynamic_routes.append((httpmethods, methodname)) ret = [] for route in self.routes: # Dynamic routes (@link or @action decorator) if route.mapping == {'{httpmethod}': '{methodname}'}: replace = replace_methodname routes = dynamic_routes elif route.mapping == {'{httpmethod}': '{collectionmethodname}'}: replace = replace_collectionmethodname routes = collection_dynamic_routes else: ret.append(route) continue for httpmethods, methodname in routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) ret.append(Route( url=replace(route.url, methodname), mapping={ httpmethod: methodname for httpmethod in httpmethods }, name=replace(route.name, methodname), initkwargs=initkwargs, )) return ret
def get_routes(self, viewset): """ Augment `self.routes` with any dynamically generated routes. Returns a list of the Route namedtuple. """ known_actions = flatten([ route.mapping.values() for route in self.routes if isinstance(route, Route) ]) # 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 @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: detail_routes.append((httpmethods, methodname)) else: list_routes.append((httpmethods, methodname)) def _get_dynamic_routes(route, dynamic_routes): ret = [] for httpmethods, methodname in dynamic_routes: method_kwargs = getattr(viewset, methodname).kwargs initkwargs = route.initkwargs.copy() initkwargs.update(method_kwargs) url_path = initkwargs.pop("url_path", None) or methodname ret.append( Route( url=replace_methodname(route.url, url_path), mapping={ httpmethod: methodname for httpmethod in httpmethods }, name=replace_methodname(route.name, url_path), initkwargs=initkwargs, )) return ret ret = [] for route in self.routes: if isinstance(route, DynamicDetailRoute): # Dynamic detail routes (@detail_route decorator) ret += _get_dynamic_routes(route, detail_routes) elif isinstance(route, DynamicListRoute): # Dynamic list routes (@list_route decorator) ret += _get_dynamic_routes(route, list_routes) else: # Standard route ret.append(route) return ret
def get_routes(self, viewset): """ Augment `self.routes` with any dynamically generated routes. Returns a list of the Route namedtuple. """ known_actions = flatten([route.mapping.values() for route in self.routes if isinstance(route, Route)]) # Determine any `@detail_route` or `@list_route` decorated methods on the viewset detail_routes = [] nested_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) nested_detail = getattr(attr, 'nested_detail', False) # Changed if httpmethods: if methodname in known_actions: 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 nested_detail: nested_detail_routes.append((httpmethods, methodname)) elif detail: detail_routes.append((httpmethods, methodname)) else: list_routes.append((httpmethods, methodname)) def _get_dynamic_routes(route, dynamic_routes): ret = [] for httpmethods, methodname in dynamic_routes: method_kwargs = getattr(viewset, methodname).kwargs initkwargs = route.initkwargs.copy() initkwargs.update(method_kwargs) url_path = initkwargs.pop("url_path", None) or methodname ret.append(Route( url=replace_methodname(route.url, url_path), mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), # As a class cannot have two identically named methods but the url_path should remain equal, # set the name of the route (to be reversed) to the methodname if it's a nested route. name=replace_methodname(route.name, methodname if 'nested_lookup' in route.url else url_path), initkwargs=initkwargs )) return ret ret = [] for route in self.routes: if isinstance(route, DynamicDetailRoute): # Dynamic detail routes (@detail_route decorator) ret += _get_dynamic_routes(route, detail_routes) elif isinstance(route, DynamicNestedDetailRoute): # Dynamic detail routes (@nested_detail_route decorator) ret += _get_dynamic_routes(route, nested_detail_routes) elif isinstance(route, DynamicListRoute): # Dynamic list routes (@list_route decorator) ret += _get_dynamic_routes(route, list_routes) else: # Standard route ret.append(route) return ret
def get_routes(self, viewset): """ Augment `self.routes` with any dynamically generated routes. Returns a list of the Route namedtuple. """ def get_extra_actions(cls): """ Get the methods that are marked as an extra ViewSet `@action`. """ return [ method for _, method in getmembers( cls, lambda attr: hasattr(attr, 'bind_to_methods')) ] def get_detail(action): """ Return the possible detail attr of an action Used for backwards compatibility between decorator of 3.8.2 and 2.8.12 """ if hasattr(action, 'detail'): return action.detail return True # Default to True as all older routes are detailed # converting to list as iterables are good for one pass, known host needs to be checked again and again for # different functions. known_actions = list( flatten([ route.mapping.values() for route in self.routes if isinstance(route, Route) ])) extra_actions = get_extra_actions(viewset) # checking action names against the known actions list not_allowed = [ action.__name__ for action in extra_actions if action.__name__ in known_actions ] if not_allowed: msg = ('Cannot use the @action decorator on the following ' 'methods, as they are existing routes: %s') raise ImproperlyConfigured(msg % ', '.join(not_allowed)) # partition detail and list actions detail_actions = [] list_actions = [] for action in extra_actions: if get_detail(action): detail_actions.append(action) else: list_actions.append(action) routes = [] for route in self.routes: if isinstance(route, DynamicRoute) and route.detail: routes += [ self._get_dynamic_route(route, action) for action in detail_actions ] elif isinstance(route, DynamicRoute) and not route.detail: routes += [ self._get_dynamic_route(route, action) for action in list_actions ] else: routes.append(route) return routes
def get_routes(self, viewset): """ Augment `self.routes` with any dynamically generated routes. Returns a list of the Route namedtuple. """ known_actions = flatten([ route.mapping.values() for route in self.routes if isinstance(route, Route) ]) # 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 @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: detail_routes.append((httpmethods, methodname)) else: list_routes.append((httpmethods, methodname)) ret = [] for route in self.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( url=replace_methodname(route.url, methodname), mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), name=replace_methodname(route.name, methodname), initkwargs=initkwargs, )) else: # Standard route ret.append(route) return ret
def get_routes(self, viewset): """ Augment `self.routes` with any dynamically generated routes. Returns a list of the Route namedtuple. """ known_actions = flatten([route.mapping.values() for route in self.routes if isinstance(route, Route)]) # 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 @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: detail_routes.append((httpmethods, methodname)) else: list_routes.append((httpmethods, methodname)) ret = [] for route in self.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( url=replace_methodname(route.url, methodname), mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), name=replace_methodname(route.name, methodname), initkwargs=initkwargs, ) ) else: # Standard route ret.append(route) return ret
def known_actions(self): the_known_actions = flatten([route.mapping.values() for route in self.routes]) return the_known_actions
def get_routers(self, nested_view_router, **kwargs): # known actions known_actions = flatten([route.mapping.values() for route in self.routes if isinstance(route, Router)]) # get methods attached to the view detail_router, list_router = [], [] # if @detail @list decorate the method, we reserve it for updating for methodname in dir(nested_view_router): attr = self.get_attr(nested_view_router, methodname) http_methods = getattr(attr, "binding_to_methods", None) detail = getattr(attr, 'detail', True) if http_methods: # user are using decorator to bind methods # we need to modify these codes if methodname in known_actions: raise ImproperlyConfigured('method {0} has already been in router method mapping') if detail: detail_router.append(({http_action:methodname for http_action in http_methods}, methodname)) else: list_router.append(({http_action:methodname for http_action in http_methods}, methodname)) def _get_query_router(router): return [Router( url_regex_tpl = router.url_regex_tpl, mapping=router.mapping, name_tpl=router.name_tpl, init_kwargs=router.init_kwargs )] def _get_detail_router(router, detail_ret): ret = [] for _mapping, methodname in detail_ret: ret.append(DyDetailRouter( url_regex_tpl=router.url_regex_tpl.format(methodname=methodname), mapping=router.mapping.update(_mapping), name_tpl=router.name_tpl.format(method=methodname), init_kwargs=router.init_kwargs) ) return ret def _get_list_router(router, list_ret): ret = [] for _mapping, methodname in list_ret: ret.append(DyListRouter( url_regex_tpl=router.url_regex_tpl.format(methodname=methodname), mapping=router.mapping.update(_mapping), name_tpl=router.name_tpl.format(method=methodname), init_kwargs=router.init_kwargs) ) return ret def _compose_nested_router(router, names): ret = [] if names is None: # do implementation here pass else: for name in names: _map = {} for k, v in router.mapping.items(): _map[k] = REL_RE.sub(name, v) ret.append(NestedRouter( url_regex_tpl=REL_RE.sub('(?P<resource_name>{name})'.format(name=name)+'/'+RESOURCE_QK_RE, router.url_regex_tpl),# !important mapping=_map, name_tpl=REL_RE.sub(name, router.name_tpl), init_kwargs=router.init_kwargs)) return ret # return routers ret = [] for router in self.routers: # add method routing to every router if isinstance(router, NestedRouter): ret += _compose_nested_router(router, kwargs.get('resource_name', None)) elif isinstance(router, Router): ret += _get_query_router(router) elif isinstance(router, DyDetailRouter): ret += _get_detail_router(router, detail_router) elif isinstance(router, DyListRouter): ret += _get_list_router(router, list_router) # self.logger.info('ret: %s', ret) return ret