def get_lookup_routes(self, viewset): ret = [self.routes[0]] # 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: httpmethods = [method.lower() for method in httpmethods] dynamic_routes.append((httpmethods, methodname)) for route in self.lookups_routes: if route.mapping == {"{httpmethod}": "{methodname}"}: # Dynamic routes (@link or @action decorator) for httpmethods, methodname in dynamic_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) mapping = dict((httpmethod, methodname) for httpmethod in httpmethods) name = routers.replace_methodname(route.name, methodname) if "extra_lookup_fields" in initkwargs: uri = route.url[1] uri = routers.replace_methodname(uri, methodname) ret.append( routers.Route(url=uri, mapping=mapping, name="%s-extra" % name, initkwargs=initkwargs) ) uri = routers.replace_methodname(route.url[0], methodname) ret.append(routers.Route(url=uri, mapping=mapping, name=name, initkwargs=initkwargs)) else: # Standard route ret.append(route) return ret
def get_routes(self, viewset): ret = super(APIRouter, self).get_routes(viewset) toplevel_dynamic_routes = [] for methodname in dir(viewset): attr = getattr(viewset, methodname) httpmethods = getattr(attr, 'toplevel_bind_to_methods', None) if httpmethods: httpmethods = [method.lower() for method in httpmethods] toplevel_dynamic_routes.append((httpmethods, methodname)) extra_ret = [] for route in self.routes: if route.mapping == {'{httpmethod}': '{toplevelmethodname}'}: for httpmethods, methodname in toplevel_dynamic_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) extra_ret.append(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, )) extra_ret.extend(ret) return extra_ret
def compute_routes(self, wrapper, method_name, route): httpmethods = wrapper.bind_to_methods dynamic_routes = [] if httpmethods: if method_name in self.known_actions(): raise ImproperlyConfigured('Cannot use @rest_method decorator on ' 'method "%s" as it is an existing route' % method_name) httpmethods = [method.lower() for method in httpmethods] dynamic_routes.append((httpmethods, method_name)) # Dynamic routes (@link or @action decorator) instance_routes = [] for httpmethods, method_name in dynamic_routes: initkwargs = route.initkwargs.copy() # initkwargs.update(getattr(viewset, method_name).kwargs) instance_routes.append(Route( url=replace_methodname(route.url, method_name), mapping=dict((httpmethod, method_name) for httpmethod in httpmethods), name=replace_methodname(route.name, method_name), initkwargs=initkwargs, )) return instance_routes
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_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) dynamic_routes_instances.append(Route( url=replace_methodname(route.url, endpoint), mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), name=replace_methodname(route.name, endpoint), initkwargs=initkwargs, )) return dynamic_routes_instances
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): """Return a list of routers.Route namedtuples that correspond to the routes for the given viewset. """ answer = super(Router, self).get_routes(viewset) # Iterate over the methods on the viewset and look for any # base methods. # # Note: We have a confusing situation here because the use of the # term "method" is overloaded; it refers both to HTTP methods # (e.g. GET, POST) and class/instance methods in Python. In order # to try and gain some clarity, the former are consistently referred # to in this code block as "HTTP methods". for method_name in dir(viewset): method = getattr(viewset, method_name) # Sanity check: If this viewset method doesn't have a # `base_http_methods` attribute, we're done. # Must support recent versions of DRF bind_methods = getattr(method, 'base_http_methods', getattr(method, 'bind_to_methods', None)) if bind_methods is None: continue # Determine the HTTP methods, URL pattern, and name pattern. http_methods = [i.lower() for i in bind_methods] url_format = r'^{prefix}/{methodname}{trailing_slash}$' url = routers.replace_methodname(url_format, method_name) name_format = '{basename}-{methodnamehyphen}' name = routers.replace_methodname(name_format, method_name) # Create the actual Route object. route = routers.Route( url=url, mapping=dict([(i, method_name) for i in http_methods]), name=name, initkwargs=copy(method.kwargs), ) # Append the route to the answer. answer.append(route) # Done! return answer
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
def get_lookup_routes(self, viewset): ret = [self.routes[0]] # 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: httpmethods = [method.lower() for method in httpmethods] dynamic_routes.append((httpmethods, methodname)) for route in self.lookups_routes: if route.mapping == {'{httpmethod}': '{methodname}'}: # Dynamic routes (@link or @action decorator) for httpmethods, methodname in dynamic_routes: initkwargs = route.initkwargs.copy() initkwargs.update(getattr(viewset, methodname).kwargs) mapping = dict( (httpmethod, methodname) for httpmethod in httpmethods) name = routers.replace_methodname(route.name, methodname) if 'extra_lookup_fields' in initkwargs: uri = route.url[1] uri = routers.replace_methodname(uri, methodname) ret.append( routers.Route( url=uri, mapping=mapping, name='%s-extra' % name, initkwargs=initkwargs, )) uri = routers.replace_methodname(route.url[0], methodname) ret.append( routers.Route( url=uri, mapping=mapping, name=name, initkwargs=initkwargs, )) else: # Standard route ret.append(route) return ret
def get_routes(self, viewset): """Return a list of routers.Route namedtuples that correspond to the routes for the given viewset. """ answer = super(Router, self).get_routes(viewset) # Iterate over the methods on the viewset and look for any # base methods. # # Note: We have a confusing situation here because the use of the # term "method" is overloaded; it refers both to HTTP methods # (e.g. GET, POST) and class/instance methods in Python. In order # to try and gain some clarity, the former are consistently referred # to in this code block as "HTTP methods". for method_name in dir(viewset): method = getattr(viewset, method_name) # Sanity check: If this viewset method doesn't have a # `base_http_methods` attribute, we're done. if not hasattr(method, 'base_http_methods'): continue # Determine the HTTP methods, URL pattern, and name pattern. http_methods = [i.lower() for i in method.base_http_methods] url_format = r'^{prefix}/{methodname}{trailing_slash}$' url = routers.replace_methodname(url_format, method_name) name_format = '{basename}-{methodnamehyphen}' name = routers.replace_methodname(name_format, method_name) # Create the actual Route object. route = routers.Route( url=url, mapping=dict([(i, method_name) for i in http_methods]), name=name, initkwargs=copy(method.kwargs), ) # Append the route to the answer. answer.append(route) # Done! return answer
def _get_dynamic_route(self, route, action, known_routes={}): initkwargs = route.initkwargs.copy() initkwargs.update(action.kwargs) url_path = escape_curly_brackets(action.url_path) if helpers.rest_framework_version >= (3, 9, 0): route_kwargs = { 'url': route.url.replace('{url_path}', url_path), 'mapping': action.mapping, 'name': route.name.replace('{url_name}', action.url_name), 'trailing_slash': initkwargs.pop("trailing_slash", None), 'detail': route.detail, 'initkwargs': initkwargs } else: # DEPRECATED: drf < 3.9 route_kwargs = { 'url': replace_methodname(route.url, url_path), 'mapping': action.mapping, 'name': replace_methodname(route.name, action.url_name), 'trailing_slash': initkwargs.pop("trailing_slash", None), 'detail': action.detail, 'initkwargs': initkwargs, } # сверяемся, чтоб не было дубликатов в router if not known_routes.get(route_kwargs['url'], None): return RouteDrfs(**route_kwargs) known_mapping = known_routes[route_kwargs['url']]['mapping'] for httpmethod in [ 'get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace' ]: if not isinstance(route_kwargs['mapping'].get(httpmethod, None), str): continue if isinstance(known_mapping.get(httpmethod, None), str): raise ImproperlyConfigured( 'Cannot map to url "%s" method "%s". Already exists. Try another url or method' % (route_kwargs['url'], httpmethod)) return RouteDrfs(**route_kwargs)
def get_relation_routes(self, viewset): """ Generate routes to serve relational objects. This method will add a sub-URL for each relational field. e.g. A viewset for the following serializer: class UserSerializer(..): events = DynamicRelationField(EventSerializer, many=True) groups = DynamicRelationField(GroupSerializer, many=True) location = DynamicRelationField(LocationSerializer) will have the following URLs added: /users/<pk>/events/ /users/<pk>/groups/ /users/<pk>/location/ """ routes = [] if not hasattr(viewset, 'serializer_class'): return routes serializer = viewset.serializer_class() fields = getattr(serializer, 'get_link_fields', lambda: [])() route_name = '{basename}-{methodnamehyphen}' for field_name, field in six.iteritems(fields): has_list_related = hasattr(viewset, 'list_related') has_create_related = hasattr(viewset, 'create_related') url = ( r'^{prefix}/{lookup}/(?P<field_name>%s)' '{trailing_slash}$' % field_name ) mapping = {} if has_list_related: mapping['get'] = 'list_related' if has_create_related: mapping['post'] = 'create_related' if mapping: routes.append(Route( url=url, mapping=mapping, name=replace_methodname(route_name, field_name), initkwargs={} )) return routes
def get_routes(self, viewset): ret = [] dynamic_routes = [] for methodname in dir(viewset): attr = getattr(viewset, methodname) httpmethods = getattr(attr, 'bind_to_collection_methods', None) if httpmethods: httpmethods = [method.lower() for method in httpmethods] dynamic_routes.append((httpmethods, methodname)) route = self.collection_route for httpmethods, methodname in dynamic_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, )) ret.extend(super(CollectionMethodRouterMixin, self).get_routes(viewset)) return ret
def get_relation_routes(self, viewset): """ Generate routes to serve relational objects. This method will add a sub-URL for each relational field. e.g. A viewset for the following serializer: class UserSerializer(..): events = DynamicRelationField(EventSerializer, many=True) groups = DynamicRelationField(GroupSerializer, many=True) location = DynamicRelationField(LocationSerializer) will have the following URLs added: /users/<pk>/events/ /users/<pk>/groups/ /users/<pk>/location/ """ routes = [] if not hasattr(viewset, 'serializer_class'): return routes if not hasattr(viewset, 'list_related'): return routes serializer = viewset.serializer_class() fields = getattr(serializer, 'get_link_fields', lambda: [])() route_name = '{basename}-{methodnamehyphen}' if drf_version >= (3, 8, 0): route_compat_kwargs = {'detail': False} else: route_compat_kwargs = {} for field_name, field in iter(fields.items()): methodname = 'list_related' url = (r'^{prefix}/{lookup}/(?P<field_name>%s)' '{trailing_slash}$' % field_name) routes.append( Route(url=url, mapping={'get': methodname}, name=replace_methodname(route_name, field_name), initkwargs={}, **route_compat_kwargs)) return routes
def get_relation_routes(self, viewset): """ Generate routes to serve relational objects. This method will add a sub-URL for each relational field. e.g. A viewset for the following serializer: class UserSerializer(..): events = DynamicRelationField(EventSerializer, many=True) groups = DynamicRelationField(GroupSerializer, many=True) location = DynamicRelationField(LocationSerializer) will have the following URLs added: /users/<pk>/events/ /users/<pk>/groups/ /users/<pk>/location/ """ routes = [] if not hasattr(viewset, 'serializer_class'): return routes if not hasattr(viewset, 'list_related'): return routes serializer = viewset.serializer_class() fields = getattr(serializer, 'get_link_fields', lambda: [])() route_name = '{basename}-{methodnamehyphen}' for field_name, field in six.iteritems(fields): methodname = 'list_related' url = ( r'^{prefix}/{lookup}/(?P<field_name>%s)' '{trailing_slash}$' % field_name ) routes.append(Route( url=url, mapping={'get': methodname}, name=replace_methodname(route_name, field_name), initkwargs={} )) return routes
def replace_listmethodname(format_string, methodname): ret = replace_methodname(format_string, methodname) ret = ret.replace('{listmethodname}', methodname) 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 replace_collectionmethodname(format_string, methodname): ret = replace_methodname(format_string, methodname) ret = ret.replace('{collectionmethodname}', methodname) 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