【Django Ninja】获取所有定义的路径

首先

正准备写一篇关于「获取使用Django和Django REST framework定义的所有端点」的文章时,我偶然发现了Django Ninja。
截至2023年6月,Django Ninja尚未发布主要版本。
但是,我被它的酷炫程度所吸引,并且对于想要尝试的兴趣胜过了在Django Ninja或DRF中选择哪个来写文章的疑虑,所以我将继续写关于「如何获取使用Django Ninja定义的所有端点」的部分。
希望能得到您的支持。

目录结构

基本上,我們將根據 Django Ninja 的教程進行定義。
這次我們定義了兩個抽象的應用程式,即帳號和項目,以及一個名為 all_url 的應用程式,用於獲取所有端點。

我用詩的方式創造了環境。

.
├── django_project
│   ├── apps
│   │   ├── __init__.py
│   │   ├── account
│   │   │   ├── __init__.py
│   │   │   ├── apps.py
│   │   │   ├── schema.py
│   │   │   └── views.py
│   │   ├── all_url
│   │   │   ├── __init__.py
│   │   │   ├── apps.py
│   │   │   └── views.py
│   │   └── item
│   │       ├── __init__.py
│   │       ├── apps.py
│   │       ├── schema.py
│   │       └── views.py
│   ├── django_project
│   │   ├── __init__.py
│   │   ├── asgi.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── manage.py
├── poetry.lock
└── pyproject.toml

端点定义

我们分别定义了views。这次不深入讨论返回值或请求体等任何细节。

在获得所有终端节点时,我需要url名称,因此我定义了url_name。(参考)
在Django中,由于django.urls.path的参数中有name,所以我在这里进行定义。

    account.views
from ninja import Router

from .schema import Account

router = Router()

@router.get('/', url_name='account_list')
def account_list(request):
    return [
        'account_1',
        'account_2',
        'account_3'
    ]

@router.get('/{int:account_id}', url_name='account_detail')
def account_detail(request, account_id: int):
    return {'item_id': account_id}

@router.post('/', url_name='account_create')
def create(request, item: Account):
    return item
    item.views
from ninja import Router

from .schema import Item

router = Router()

@router.get('/', url_name='item_list')
def item_list(request):
    return [
        'item_1',
        'item_2',
        'item_3'
    ]

@router.get('/{int:item_id}', url_name='item_detail')
def item_detail(request, item_id: int):
    return {'item_id': item_id}

@router.post('/', url_name='item_create')
def create(request, item: Item):
    return item

而这次关键的是获取全部终点的API。
由于响应中不能包含对象,因此我返回的是url_name。

    all_url.view
from ninja import Router
from django.urls import get_resolver, URLResolver, URLPattern

router = Router()

@router.get('/', url_name='all_url_list')
def all_url_list(request):
    targets = [
        url
        for url in get_resolver().url_patterns
    ]

    all_url = list()

    def dfs_urls(url_patterns):
        for url in url_patterns:
            if isinstance(url, URLResolver):
                dfs_urls(url.url_patterns)
            elif isinstance(url, URLPattern):
                all_url.append(url.name)

    dfs_urls(targets)

    return all_url

通过使用 get_resolver() 函数,我们可以获取有关解析端点的相关信息,然后通过 url_patterns 列表获取所有端点的详细信息。其中,URLResolver 表示通过 include 定义的解析器,URLPattern 表示通过 path 定义的解析器。通过使用深度优先搜索的递归方式,我们可以获取所有的端点信息。

现在API已经全部定义完毕。
接下来我们将把它们注册到urlpatterns中。

路径模式

我把它写在django_project.urls.py中。
我按照URL的反向解析写了这个url。

from django.contrib import admin
from django.urls import path
from ninja import NinjaAPI
from apps.item.views import router as item_router
from apps.account.views import router as account_router
from apps.all_url.views import router as all_url_router

api = NinjaAPI()

api.add_router('/items/', item_router)
api.add_router('/accounts/', account_router)
api.add_router('/endpoint/', all_url_router)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', api.urls),
]

试试动一动

poetry shell
cd path/to/dir
python manage.py runserver 8080
    response
[
	"index",
	"login",
	"logout",
	"password_change",
	"password_change_done",
	"autocomplete",
	"jsi18n",
	"view_on_site",
	"auth_group_changelist",
	"auth_group_add",
	"auth_group_history",
	"auth_group_delete",
	"auth_group_change",
	null,
	"auth_user_password_change",
	"auth_user_changelist",
	"auth_user_add",
	"auth_user_history",
	"auth_user_delete",
	"auth_user_change",
	null,
	"app_list",
	null,
	"openapi-json",
	"openapi-view",
	"item_list",
	"item_create",
	"item_detail",
	"account_list",
	"account_create",
	"account_detail",
	"all_url_list",
	"api-root"
]

在下方可以看到用view定义的url_name。
但是有太多多余的东西。

刪除掉多餘的東西

将 if url.app_name not in [‘admin’] 追加到 all_url.view 中。

    all_url.view
from ninja import Router
from django.urls import get_resolver, URLResolver, URLPattern

router = Router()

@router.get('/', url_name='all_url_list')
def all_url_list(request):
    targets = [
        url
        for url in get_resolver().url_patterns
+       if url.app_name not in ['admin']
    ]

    all_url = list()

    def dfs_urls(url_patterns):
        for url in url_patterns:
            if isinstance(url, URLResolver):
                dfs_urls(url.url_patterns)
            elif isinstance(url, URLPattern):
                all_url.append(url.name)

    dfs_urls(targets)

    return all_url

相当清爽
url.app_name 是 Django 中定义的 URL 名称空间。
由于 urlpatterns 中的 path(‘admin/’, admin.site.urls) 是在名称空间 admin 中定义的,所以可以在列表推导式中排除它。

[
	"openapi-json",
	"openapi-view",
	"item_list",
	"item_create",
	"item_detail",
	"account_list",
	"account_create",
	"account_detail",
	"all_url_list",
	"api-root"
]

我还有一个尚未定义的。由于Django Ninja可以很好地生成Open API,所以它看起来很不错。

顺便说一句,不需要使用类似Django Ninja这样的库,只要使用path和include就可以获取由我定义的所有path_name。即使路由是嵌套的也没有问题。

在这一点上,可以认为ninja还为URL命名空间定义了一个ninja。让我们来确认一下。

    all_url.view
from ninja import Router
from django.urls import get_resolver, URLResolver, URLPattern

router = Router()

@router.get('/', url_name='all_url_list')
def all_url_list(request):
    targets = [
        url
        for url in get_resolver().url_patterns
        if url.app_name not in ['admin']
    ]

    all_url = list()

+   print([t.app_name for t in targets])

    def dfs_urls(url_patterns):
        for url in url_patterns:
            if isinstance(url, URLResolver):
                dfs_urls(url.url_patterns)
            elif isinstance(url, URLPattern):
                all_url.append(url.name)

    dfs_urls(targets)

    return all_url
    実行結果
["ninja"]

原来如此。我会试着弹奏忍者。

    all_url.view
from ninja import Router
from django.urls import get_resolver, URLResolver, URLPattern

router = Router()

@router.get('/', url_name='all_url_list')
def all_url_list(request):
    targets = [
        url
        for url in get_resolver().url_patterns
+       if url.app_name not in ['admin', 'ninja']
    ]

    all_url = list()

    def dfs_urls(url_patterns):
        for url in url_patterns:
            if isinstance(url, URLResolver):
                dfs_urls(url.url_patterns)
            elif isinstance(url, URLPattern):
                all_url.append(url.name)

    dfs_urls(targets)

    return all_url
    実行結果
[]

什么都消失了。
由于路由器的路径定义与Django Ninja结合在一起,所以当然会变成这样了呢。

我试图以某种方式完美地提取出来,但当我去阅读源代码时,发现是不可能的。

    • NinjaAPI.urls

 

    django.contrib.admin(比較用)

顺便提一下,openapi-***是在openAPI路径中不可避免出现的,默认会生成,但也可以关闭。

    api-docs

最后

不需要使用类似Django Ninja的东西,只要使用路径(path)和包含(include)就可以在这里很好地完成。
所以,这个的用途是什么呢?可能是用在测试之类的吧?
结束。

bannerAds