defaction(methods=None, detail=None, url_path=None, url_name=None, **kwargs): """ Mark a ViewSet method as a routable action. Set the `detail` boolean to determine if this action should apply to instance/detail requests or collection/list requests. """ methods = ['get'] if (methods isNone) else methods methods = [method.lower() for method in methods]
# name and suffix are mutually exclusive if'name'in kwargs and'suffix'in kwargs: raise TypeError("`name` and `suffix` are mutually exclusive arguments.")
def_is_extra_action(attr): returnhasattr(attr, 'mapping') @classmethod defget_extra_actions(cls): """ Get the methods that are marked as an extra ViewSet `@action`. """ return [method for _, method in getmembers(cls, _is_extra_action)]
defget_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 ifisinstance(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 ifnot action.detail]
routes = [] for route in self.routes: ifisinstance(route, DynamicRoute) and route.detail: routes += [self._get_dynamic_route(route, action) for action in detail_actions] elifisinstance(route, DynamicRoute) andnot route.detail: routes += [self._get_dynamic_route(route, action) for action in list_actions] else: routes.append(route)