Django REST Framework 结合来自不同应用程序的路由器

Posted

技术标签:

【中文标题】Django REST Framework 结合来自不同应用程序的路由器【英文标题】:Django REST Framework combining routers from different apps 【发布时间】:2015-10-07 14:45:46 【问题描述】:

我有一个跨越多个应用程序的项目:

./project/app1
./project/app2
./project/...

每个应用程序都有一个用于 Django REST 框架的路由器,用于合并该应用程序提供的 API 部分:

from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from .views import ThingViewSet

router = DefaultRouter()
router.register(r'things', ThingViewSet, base_name='thing')

urlpatterns = [
    url(r'^', include(router.urls)),
]

因为应用程序是独立的,所以我的*** URL 文件 (./project/urls.py) 包含来自不同应用程序的每个 URL 文件:

url(r'^api/app1/', include('app1.urls', namespace='a1')),
url(r'^api/app2/', include('app2.urls', namespace='a2')),

这意味着 Django REST Framework 为每个应用程序显示一个单独的 API 根。然而,我想要的是一个统一的 API 结构,因此如果我导航到 http://example.com/api/,我会看到在该层次结构级别上可用的所有 URL 的完整列表。

我认为有一种方法可以将每个应用程序的单独 urls.py 文件中定义的所有单独路由器包含到单个路由器中,但我找不到有关如何执行此操作的文档。我错过了什么明显的东西吗?

【问题讨论】:

【参考方案1】:

我最终在 urls_api_v1.py 中创建了一个包含我想要的所有路由的 URLs 文件:

router = DefaultRouter()
router.register(r'app1/foos', FooViewSet, base_name='foo')
router.register(r'app2/bars', BarViewSet, base_name='bar')
router.register(r'app2/bazs', BazViewSet, base_name='baz')

作为一个副作用,这让我可以删除每个应用程序中的所有单独的 urls.py 文件,这是您通常想要的,但在这种情况下,整个应用程序集合需要一个统一的 URL 结构,因此删除更多明智的。

然后我从urls.py引用它:

import api_v1
urlpatterns = patterns('',
    ...,
    url(r'^api/v1/', include(api_v1, namespace='api-v1')),
)

现在,如果我想更改 v2 的路由,我也可以包含一个 v2 URLs 文件并最终弃用 v1 文件。

【讨论】:

但是你的应用是独立的,所以它们应该有自己的 urls.py。这也是我的情况,我想避免将所有网址移动到一个集中的网址,这是没有意义的。如何统一 URL 并同时保持应用程序分离?肯定有一种方法可以包含来自不同应用程序的现有 urls.py 吗? 我还没有找到这样的方法,以为我在想了这么多之后就不再关注了。我相信这是一种合理的方法,因为 apps 是分开的,但 API 不是。通过让 REST 框架逐个应用程序计算 URL 结构,我所做的就是将它们组合成一个不特定于任何应用程序的单一顶层安排。这与拥有一个将每个单独应用程序的 urls.py 文件汇集到它们自己的命名空间中的*** urls.py 并没有太大区别。 我很不同意:API 组织是每个应用程序的责任,***应用程序不需要了解每个应用程序的 URL 树。这就是 API 可发现性(和 api_root)的意义所在。***项目只会在它选择的***端点中插入它认为合适的不同子 API。然后一切都应该正常工作,包括从 api 根目录开始浏览整个 API 的选项。 我明白你在说什么,但我认为这不是普遍正确的。例如,就我而言,我有多个松散耦合的应用程序。一些客户只花时间在一个或另一个领域,但一些客户出于需要跨越边界。从开发的角度来看,应用程序是独立的或至多是松耦合的;这有助于更轻松地开发、测试和管理数据。每个应用程序都提供少量的 ViewSet,单个 urls.py 文件将整个代码库捆绑到单个客户端可见的 API 中。 如果它们是真正独立的应用程序,那么我会毫不犹豫地对 API 拥有真正独立的视图,每个应用程序一个视图。这将通过以传统方式简单地为每个应用程序导入 urls.py 来处理。【参考方案2】:

这将获取基本 API URL 上列出的所有 ViewSet 路由。

它将路由定义为相应包含的 app.urls 中的列表,以便它们可以在其他地方注册。

将它们包含在基本 urls.py 中后,将构建列表的嵌套列表并循环访问以在 API 中注册同一级别的所有路由

# foo.urls
routeList = (
    (r'foos', FooViewSet),
)

# barBaz.urls
routeList = (
    (r'bars', BarViewSet),
    (r'bazs', BazViewSet),
)

# urls
from rest_framework import routers
from foo import urls as fooUrls
from barBaz import urls as barBazUrls

routeLists = [
    fooUrls.routeList,
    barBazUrls.routeList,
]

router = routers.DefaultRouter()
for routeList in routeLists:
    for route in routeList:
        router.register(route[0], route[1])

结果:


    "foo": "http://localhost:8000/foos/",
    "bar": "http://localhost:8000/bars/",
    "baz": "http://localhost:8000/bazs/",

这在每个文件中的重复也更少,并且可以说更容易阅读。

此外,它仍然完全解耦。

如果包含的应用程序在其他地方使用,则可以在内部使用相同的方法来注册它自己的路由,而不会被包含在任何地方。

只需放下外部循环

routeList = (
    (r'bars', BarViewSet),
    (r'bazs', BazViewSet),
)

router = routers.DefaultRouter()
for route in routeList:
    router.register(route[0], route[1])

【讨论】:

好主意。这里唯一需要改进的地方是我认为这可能存在名称冲突(例如,如果两个应用程序尝试注册“bars”名称)。但作为一个起点,这是一个很好的方法。谢谢! 确实如此。将是一个简单的改变。您可以将路由命名为 (r'foos', FooViewSet),,而不是 (r'foo/route1', FooViewSet),,然后只要您的应用名称是唯一的,就不会发生冲突。【参考方案3】:

另一种解决方案是使用SimpleRouter 为各个应用定义路由器。然后,使用自定义的DefaultRouter 来包含应用特定的路由。这样,所有应用特定的 url 定义都将保留在相应的应用中。

假设您有两个名为“app1”和“app2”的应用程序,每个应用程序都有一个名为“api”的目录,在此目录中有一个名为“urls”的文件,其中包含您的所有路由定义。

├── project/ │ ├── api_urls.py │ ├── app1 │ │ ├── api │ │ │ ├── urls.py │ ├── app2 │ │ ├── api │ │ │ ├── urls.py │ ├── patches │ │ ├── routers.py

使用patches/router.py 定义一个名为DefaultRouter 的类,该类继承自rest_framework.routers.DefaultRouter

from rest_framework import routers

class DefaultRouter(routers.DefaultRouter):
    """
    Extends `DefaultRouter` class to add a method for extending url routes from another router.
    """
    def extend(self, router):
        """
        Extend the routes with url routes of the passed in router.

        Args:
             router: SimpleRouter instance containing route definitions.
        """
        self.registry.extend(router.registry)

用路由定义填充您的 api url,例如

"""
URL definitions for the api.
"""
from patches import routers

from app1.api.urls import router as app1_router
from app2.api.urls import router as app2_router

router = routers.DefaultRouter()
router.extend(app1_router)
router.extend(app2_router)

【讨论】:

或者更简单的是,您可以为特定应用程序导入您的路由器from app1.urls import router as app1router,然后在主 urls.py 文件router.registry.extend(app1router.registry) 中使用您的默认路由器注册此路由器,然后您就没有子类化任何东西。 确实如此。好评论。无需子类化。 也推荐此评论的解决方案。 第二段代码应该去哪里?在主项目 URL 文件或单个应用程序 URL 中?另外,我看到 DefaultRouter 在没有导入的情况下被使用。不清楚。 @abicrazieeee 第二段代码应该放在主项目urls.py 文件中。【参考方案4】:

如果您正在实现 SimpleRouter,您只需将其 url 与 urlpatterns 列表连接起来

router = SimpleRouter()
router.register(r'import-project', ImportProject, base_name='di-integ')

在主 urls.py 文件中

from di_apiary_integration.urls import router as di_integration_routers

要注册 URL,您可以:

url(r'^di-integration/', include(di_integration_routers.urls)),

urlpatterns += di_integ_router.urls

两者都可以!

重要

ImportProject 需要是 ModelViewSet 或 ViewSet,如果您将其创建为简单的 APIView,则需要使用 as_view() 将其注册为普通 CBV 视图

【讨论】:

【参考方案5】:

@Colton Hicks评论

假设我们在“应用程序文件夹”中有 2 个应用程序(权限、用户)。然后我们可以这样做:

from apps.users.api.urls import router as users_router
from apps.permissions.api.urls import router as permissions_router


router = DefaultRouter()
router.registry.extend(users_router.registry)
router.registry.extend(permissions_router.registry)

【讨论】:

extend() 是新的吗?在随意的谷歌搜索中,我在文档中看不到任何关于它的内容。 extend 在 python std 库中,其工作方式与两个列表中的+ 相同【参考方案6】:

可以使用router.registry.extend(app_router.registry),总例:

from django.urls import path, include
from rest_framework import routers
from app1.rest import router as app1_router
from app2.rest import router as app2_router

router = routers.DefaultRouter()
router.registry.extend(app1_router.registry)
router.registry.extend(app2_router.registry)

urlpatterns = [
    path('', include(router.urls)),
]

【讨论】:

以上是关于Django REST Framework 结合来自不同应用程序的路由器的主要内容,如果未能解决你的问题,请参考以下文章

Django Rest Framework 和 django Rest Framework simplejwt 两因素身份验证

django-rest-framework 是不是提供管理站点来管理模型?

使用 Django Rest Framework 返回当前用户

Django Rest Framework

Django Rest Framework:非模型服务

Django-rest-framework 和 django-rest-framework-jwt APIViews and validation Authorization headers