【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)就可以在这里很好地完成。
所以,这个的用途是什么呢?可能是用在测试之类的吧?
结束。