django 如何使用多个DRF路由器创建API索引?

wecizke3  于 2022-11-19  发布在  Go
关注(0)|答案(1)|浏览(214)

我尝试使用DRF构建一个API,结构如下(示例):

api/
├── v1/
│   ├── foo/
│   │   ├── bar/
│   │   │   └── urls.py # There's one `rest_framework.routers.DefaultRouter` here
│   │   ├── bar2/
│   │   │   └── urls.py # There's one `rest_framework.routers.DefaultRouter` here
│   │   ├── __init__.py
│   │   └── urls.py
│   ├── foo2/
│   │   ├── bar3/
│   │   │   └── urls.py # There's one `rest_framework.routers.DefaultRouter` here
│   │   ├── bar4/
│   │   │   └── urls.py # There's one `rest_framework.routers.DefaultRouter` here
│   │   ├── __init__.py
│   │   └── urls.py
│   ├── __init__.py
│   └── urls.py
├── __init__.py
└── urls.py

直观地说,我的端点应该是

https://api.example.com/v1/foo/bar/...
https://api.example.com/v1/foo/bar2/...
https://api.example.com/v1/foo2/bar3/...
https://api.example.com/v1/foo2/bar4/...

但是我希望Api Root网页可以从https://api.example.com/v1级别访问。

{"foo":"https://api.example.com/v1/foo/","foo2":"https://api.example.com/v1/foo2/"}

和/或其他信息。
话虽如此,我想这样做的方法是以某种方式“合并”这些DefaultRouter
我知道我可以只写router.registry.extend(some_other_router.registry),但这会使所有的都在同一层,我明确地需要它是多层的,如上所示。

owfi6suc

owfi6suc1#

最后我编写了一个IndexRouter类,它可以如下所示使用:

  • 示例1:重用您的urlpatterns
your_old_urlpatterns = []
router = IndexRouter(urlpatterns=your_old_urlpatterns)
urlpatterns = router.to_urlpatterns()
  • 示例2:使用其它DRF路由器:
from my_app.urls import router as my_app_router

router = IndexRouter(routers={"my_app": my_app_router})

您也可以混合它们。此外,还有其他有用的参数,例如:

  • name:设置面包屑导航的页面名称
  • deprecated_func:一个函数,用于告知此端点是否已过时(与drf-yasg兼容)
  • swagger_operation_descriptiondrf-yasg兼容,请参阅其文档
  • swagger_operation_summarydrf-yasg兼容,请参阅其文档

实现本身,虽然不是很优雅,但如下所示:

from collections import OrderedDict
from typing import Any, Callable, Dict, List, Union

from django.urls import include, path, URLPattern, URLResolver
from drf_yasg.utils import swagger_auto_schema
from rest_framework.response import Response
from rest_framework.routers import BaseRouter
from rest_framework.views import APIView

class IndexView(APIView):
    deprecated_func: Callable[[], bool] = None
    ignore_admin: bool = None
    router_names: List[str] = None
    swagger_operation_description: str = (None,)
    swagger_operation_summary: str = (None,)
    urlpatterns: List[Union[URLPattern, URLResolver]] = None
    view_name: str = None

    def __init__(
        self,
        *,
        deprecated_func: Callable[[], bool] = lambda: False,
        ignore_admin: bool = True,
        router_names: List[str] = None,
        swagger_operation_description: str = None,
        swagger_operation_summary: str = None,
        urlpatterns: List[Union[URLPattern, URLResolver]] = None,
        view_name: str = "Index",
        **kwargs,
    ):
        super().__init__(**kwargs)
        self.deprecated_func = deprecated_func
        self.ignore_admin = ignore_admin
        self.router_names = router_names or []
        self.swagger_operation_description = swagger_operation_description
        self.swagger_operation_summary = swagger_operation_summary
        self.urlpatterns = urlpatterns or []
        self.view_name = view_name

        @swagger_auto_schema(
            deprecated=self.deprecated_func(),
            operation_summary=self.swagger_operation_summary,
            operation_description=self.swagger_operation_description,
        )
        def get(request, format=None):
            current_url = request.build_absolute_uri().rstrip("/")
            data = OrderedDict()
            for router_name in self.router_names:
                data[router_name] = f"{current_url}/{router_name}/"
            for urlpatterns in self.urlpatterns:
                if isinstance(urlpatterns, URLResolver):
                    pattern = str(urlpatterns.pattern).rstrip("/")
                    if self.ignore_admin and pattern == "admin":
                        continue
                    data[
                        str(urlpatterns.pattern).rstrip("/")
                    ] = f"{current_url}/{str(urlpatterns.pattern)}"
                elif isinstance(urlpatterns, URLPattern):
                    # Parse the URLPattern to something readable
                    base_pattern = str(urlpatterns.pattern).rstrip("$")
                    base_pattern = base_pattern.rstrip("/")
                    base_pattern = base_pattern.replace("^", "")
                    base_pattern = base_pattern.replace("<", "{")
                    base_pattern = base_pattern.replace(">", "}")
                    # If base_pattern is empty, skip it
                    if not base_pattern:
                        continue
                    # If we want to skip admin, skip it
                    if self.ignore_admin and base_pattern == "admin":
                        continue
                    # If we have a regex group, skip it
                    if "(" in base_pattern or ")" in base_pattern:
                        continue
                    data[base_pattern] = f"{current_url}/{base_pattern}/"
            return Response(data)

        self.get = get

    def get_view_name(self):
        return self.view_name

class IndexRouter:
    def __init__(
        self,
        routers: Dict[str, BaseRouter] = None,
        urlpatterns: List[Any] = None,
        name: str = "Index",
        deprecated_func: Callable[[], bool] = lambda: False,
        swagger_operation_description: str = None,
        swagger_operation_summary: str = None,
    ):
        self.routers = routers or {}
        self.urlpatterns = urlpatterns or []
        self.name = name
        self.deprecated_func = deprecated_func
        self.swagger_operation_description = swagger_operation_description
        self.swagger_operation_summary = swagger_operation_summary
        self._index = IndexView(
            router_names=list(self.routers.keys()),
            urlpatterns=self.urlpatterns,
            view_name=self.name,
            deprecated_func=self.deprecated_func,
            swagger_operation_description=self.swagger_operation_description,
            swagger_operation_summary=self.swagger_operation_summary,
        )

    def to_urlpatterns(self):
        urlpatterns = self.urlpatterns
        urlpatterns.append(
            path(
                "",
                self._index.as_view(
                    router_names=list(self.routers.keys()),
                    urlpatterns=self.urlpatterns,
                    view_name=self.name,
                    deprecated_func=self.deprecated_func,
                    swagger_operation_description=self.swagger_operation_description,
                    swagger_operation_summary=self.swagger_operation_summary,
                ),
                name="index",
            )
        )
        for name, router in self.routers.items():
            urlpatterns.append(path(name + "/", include(router.urls)))
        return urlpatterns

相关问题