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
Example #2
0
 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
Example #5
0
    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
Example #6
0
 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
Example #7
0
    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
Example #8
0
    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
Example #9
0
    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
Example #10
0
    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
Example #11
0
    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
Example #12
0
 def known_actions(self):
     the_known_actions = flatten([route.mapping.values() for route in self.routes])
     return the_known_actions
Example #13
0
    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