前言
Django REST framework ( DRF )是一個強大且靈活的工具包,用於構建 Web API。DRF 有自己的一套路由定義方式,即通過 Router 類型的 register 方法,該方法包含了一個名為 basename 的參數,下面讓我們通過了解這個參數來一窺 DRF 的路由系統吧!
探究哪些問題
為什么要寫這個文章呢,因為在使用 Django REST framework
經常看到在配置路由的時候使用basename
這種用法,類似於 Django
中的 include
函數中的 namespace
參數和 path
中的 name
參數,那真相如何呢?請讓我們帶着下面三個問題來看這篇文章吧!👇
Django REST framework
中的basename
和Django
中的namespace
參數和name
參數有什么關系?- 什么時候需要加
basename
,什么時候不需要加basename
呢? - 如果缺省
basename
參數,那Django REST Framework
又會如何處理呢?
需要的預備知識
根據Django REST framework
官方文檔的介紹:Routers-英文文檔 | Routers-中文文檔
在解答第一個問題的時候,需要你先對router.register有所了解,具體可看這篇文章 使用DRF的時候,選擇router.register還是urlpatterns path ???
作用在哪:
打斷點:
查看變量信息
可以發現,basename
的參數影響的是 URLPattern
的 name
屬性,這個 name
屬性是拼接的
實際應用:
basename 主要是用在反向解析
一般流程是 url 路由->視圖
但有的時候需要在視圖中通過視圖獲取url
比如重定向,這個時候就可以通過 basename 做軟編碼
當 basename 缺省的時候
如果 basename 參數缺省,那么會使用 viewset 參數的 queryset 的屬性,如果即不指定 basename 參數,又不指定 viewset 參數的 queryset 的屬性 ,那么就會報錯!
File "C:\Users\17293\Desktop\Coder\Python\Django\twitter\twitter\urls.py", line 17, in <module>
router.register('/api/accounts/', AccountViewSet)
File "C:\Users\17293\AppData\Local\Programs\Python\Python39\lib\site-packages\rest_framework\routers.py", line 54, in register
basename = self.get_default_basename(viewset)
File "C:\Users\17293\AppData\Local\Programs\Python\Python39\lib\site-packages\rest_framework\routers.py", line 137, in get_default_basename
assert queryset is not None, '`basename` argument not specified, and could ' \
AssertionError: `basename` argument not specified, and could not automatically determine the name from the viewset, as it does not have a `.queryset` attribute.
遇事不決看源碼,一切的不解都可以再源碼中解惑
Django REST framework 中的路由注冊是這樣子的
router = routers.SimpleRouter()
router.register('/api/accounts/', AccountViewSet, basename='accounts')
其中的 basename
參數可寫可不寫,不寫的時候不代表就沒有 basename
了,因為如前面所說,basename
是需要用來反向解析的,因此缺省 basename
會給一個默認值,讓我們來看看 register
的源代碼吧!
可以看到 register
方法的 basename 參數有一個默認值 None,但是這可不是最終的默認值哦!
從源碼中可以看見當我們不自定義該參數的時候,默認為 None,當為 None 的時候,會從 viewset 參數中獲取 basename 的值
class BaseRouter:
def __init__(self):
self.registry = []
def register(self, prefix, viewset, basename=None):
if basename is None:
basename = self.get_default_basename(viewset)
self.registry.append((prefix, viewset, basename))
# invalidate the urls cache
if hasattr(self, '_urls'):
del self._urls
如果你有一個疑問:為什么我們在 urls.py 自定義的路由用的是 SimpleRouter 類的 register,為什么這里展示的源代碼是 BaseRouter 類?答案是因為 SimpleRouter 類的 register 方法是從 BaseRouter 類繼承來的, SimpleRouter 類並沒有重寫 register 方法
接下來看看如何獲取默認的 basename,從源代碼中可以看到 basename = self.get_default_basename(viewset)
是這句語句起了作用,那就觀摩一下 self.get_default_basename
方法 👇
首先來看看 BaseRouter 類的 get_default_basename 方法,emmmm,這是一個抽象方法
def get_default_basename(self, viewset):
""" If `basename` is not specified, attempt to automatically determine it from the viewset. """
raise NotImplementedError('get_default_basename must be overridden')
上面提到了一個概念:抽象方法
關於抽象方法,就是父類定義一個方法,你一調用這個方法就會拋出一個錯誤,哈哈,你是不是很奇怪,為什么要搞一個這個東西惡心人?調一下就報錯?憑什么?為什么?干什么?
先回答為什么要定義這么一個惡心人的抽象方法之前,先來看看怎么用這個方法,抽象方法的正確打開方式是,在子類中繼承父類,並且在子類中重寫父類的這個抽象方法,即在子類中自定義個不會拋出錯誤的方法。
那為什么父類要搞這么一個抽象方法呢?因為這個抽象方法很重要(不是抽象方法很重要,是這個方法,這個被稱為抽象方法,一調用就會拋出的出錯的方法很重要,不是“抽象方法”這四個字或者“抽象方法”這四個字指代的那一類東西很重要!!!),既然是要重寫才能用的,那不如直接不要在父類中定義了,直接寫在子類中不好嗎?也可以!完全可以!那為什么還要定義這個會拋出錯誤的方法呢?答案是占坑位,提醒寫子類的人,別忘了這個重要方法!!!還有一點就是,你不繼承的話,ide,諸如 vscode、pycharm 就會提示語法錯誤,畢竟你在 BaseRouter 類的 register 方法中調用了 get_default_basename 方法,結果你卻沒有定義什么是 get_default_basename ???那這些 ide 就要給你錯誤提示了!!!所以我們需要占一個坑位,定義一個假的 get_default_basename
總結:
- 告訴要寫子類的人,千萬別忘了重寫這個抽象方法(重要)
- 占坑位,保證代碼的完整性(不是那么重要)
再來看看 SimpleRouter 類的 get_default_basename 方法
def get_default_basename(self, viewset):
""" If `basename` is not specified, attempt to automatically determine it from the viewset. """
queryset = getattr(viewset, 'queryset', None)
assert queryset is not None, '`basename` argument not specified, and could ' \
'not automatically determine the name from the viewset, as ' \
'it does not have a `.queryset` attribute.'
return queryset.model._meta.object_name.lower()
getattr(viewset, 'queryset', None)
語句先通過 getattr 函數獲取 viewset 對象中的 queryset 屬性,如果該屬性不存在未返回 None,viewset 對象是什么?往回翻翻!
router.register('/api/accounts/', AccountViewSet, basename='accounts')
def register(self, prefix, viewset, basename=None):
basename = self.get_default_basename(viewset)
所以,viewset 對象是 AccountViewSet 類,這個類呢是不自帶 queryset 屬性的,需要人類,需要程序員,需要坐在你電腦前的你自己定義的
很簡單對吧,但我想相信看這篇文章的有很多菜鳥,因此想講的更入門一點,如果你不知道 getattr 函數是干嘛的?那你確實應該多去引擎搜索一下!什么?你連搜索引擎都不會使用!好吧,讓我來告訴你搜索關鍵字,諸如 “python getattr 用法” 這樣的詞條就可以找你到你想要的答案了
接着往下看到斷言語句 assert ,如果 queryset is not None ,就 pass ,如果 queryset 是 None ,那就要拋出錯誤了!
什么!你連斷言 assert 都不知道!!!你簡直就是一個 🤡
這個時候就很明白了,如果你既沒有自定義 basename 屬性,又不定義 AccountViewSet 類的 queryset 屬性,就會報錯和你說拜拜
那如果是沒有自定義 basename 屬性,但是定義了 AccountViewSet 類的 queryset 屬性呢?這個時候會就使用 queryset 類作為 basename ,但是有一個小問題,就是 basename 是一個字符串,而 queryset 往往是一個 QuerySet 類,或者是一個 models.Model 的子類,顯然類不能當作字符串呀,這個時候就還需要進一步處理!接着往下看吧!
queryset.model._meta.object_name.lower()
可以看下 .lower()
方法,這是字符串類型的內置方法,負責將字符串轉為小寫字符, object_name
就是一個字符串對象,該對象保存着 AccountViewSet
類的名字,在轉成小寫,就變成了 accountviewset
不懂
lower
方法的,請參考
Python3 lower()方法