Django 中常用的第三方插件庫介紹
今天我們會(huì)介紹在 Django 生態(tài)中比較火的一些第三方插件。正是這些插件,讓我們開發(fā)網(wǎng)站變得如此簡(jiǎn)單。同時(shí)我會(huì)就一個(gè)熱門的第三方框架談一談如何學(xué)習(xí)該框架的源碼。接下來,讓我們一起開始今天的學(xué)習(xí)吧。
1. Django 中常用的插件庫
1.1 Django Rest Framework
Django Rest Framework 是一個(gè)強(qiáng)大且靈活的工具包,用以快速構(gòu)建 Web API。為啥要使用它來構(gòu)建 Web API呢?除了 DRF 可以在 Django 的基礎(chǔ)上迅速實(shí)現(xiàn) API 外,它自身還帶有 WEB 的測(cè)試頁面,可以方便的測(cè)試自己的 API。這點(diǎn)非常類似于 Java Web 開發(fā)中的 Swagger 插件,對(duì)于我們測(cè)試自己的 API 接口時(shí)非常有幫助的。
我們來總結(jié) DRF 框架具備的一些特點(diǎn):
- 提供了定義序列化器 Serializer 的方法,可以快速根據(jù) Django ORM 或者其他庫自動(dòng)序列化/反序列化;
- 提供了豐富的類視圖 和 Mixin 擴(kuò)展類,可以進(jìn)一步簡(jiǎn)化視圖的編寫;
- 多種身份認(rèn)證和權(quán)限認(rèn)證方式的支持;
- 內(nèi)置了限流系統(tǒng);
- 直觀的 API Web界面;
- 可擴(kuò)展性 , 插件豐富;
- 完備的文檔以及良好的社區(qū)支持
- 諸多大廠(如 Mozilla, Red Hat 等)使用和點(diǎn)贊;
最后作為一個(gè)開源框架,查看下其項(xiàng)目 Github 地址時(shí)十分必要的。一個(gè)原則是:stars 越多,項(xiàng)目越火,可以放心使用。
項(xiàng)目的地址和文檔如下:
可以看到,無論是從 star 數(shù)還是 contributor 人數(shù)上看, DRF 都可以稱為是熱門框架了。大部分做 Django 開發(fā)的都會(huì)使用 DRF 這個(gè)插件,用于開發(fā)高質(zhì)量的 Web API,這個(gè)框架也是我們后面學(xué)習(xí)和研究的重點(diǎn)。
1.2 Django Celery
首先介紹下 Celery 模塊,Celery 是一款非常簡(jiǎn)單、靈活、可靠的分布式系統(tǒng),可用于處理大量消息,并且提供了一整套操作此系統(tǒng)的一系列工具。另外,Celery 是一款消息隊(duì)列工具,可用于處理實(shí)時(shí)數(shù)據(jù)以及任務(wù)調(diào)度。官網(wǎng)給出了 Celery 如下四個(gè)特點(diǎn):
- 簡(jiǎn)單:開箱機(jī)用,維護(hù)簡(jiǎn)單,不需要使用配置文件;
- 高可靠性:如果連接丟失或者出現(xiàn)故障,客戶端進(jìn)程會(huì)自動(dòng)重試。一些 broker 會(huì)以主/主或者主/備的方式維持系統(tǒng)的高可靠性;
- 快速:?jiǎn)蝹€(gè) Celery 進(jìn)程每分鐘內(nèi)可以處理數(shù)百萬個(gè)任務(wù),往返的延遲在毫秒級(jí)別(使用RabbitMQ 做中間件,再加上一些優(yōu)化的設(shè)置);
- 靈活:幾乎 Celery 的每個(gè)部分都可以自行擴(kuò)展,如使用自定義池、序列化器,壓縮方案、日志記錄,調(diào)度程序,消費(fèi)者,生產(chǎn)者,代理傳輸?shù)鹊取?/li>
注意到基于 Django 框架構(gòu)建的 Web 系統(tǒng)實(shí)際上是一個(gè)同步服務(wù)。這意味著當(dāng)客戶端發(fā)起一個(gè)請(qǐng)求時(shí),后端只有在視圖函數(shù)處理完后才會(huì)返回結(jié)果。如果這個(gè)請(qǐng)求背后要做的工作比較耗時(shí),或者因?yàn)槟撤N原因?qū)е路浅:臅r(shí),那么此時(shí)客戶端會(huì)一直等待請(qǐng)求的響應(yīng),這非常影響用戶體驗(yàn)。對(duì)于一個(gè)優(yōu)秀的網(wǎng)站而言,良好的用戶體驗(yàn)十分重要,這也說明了一個(gè)支持異步功能的第三方插件的重要性。為了能讓 Django 搭建的 Web 系統(tǒng)支持這樣的異步功能,于是 django-celery 便應(yīng)運(yùn)而生。
django-celery項(xiàng)目之后也被移到 celery 下進(jìn)行統(tǒng)一管理。它相比原 celery 項(xiàng)目在 star 數(shù)和貢獻(xiàn)者數(shù)上要遜色不少。
可以看到,Celery 模塊火熱程度可以媲美 DRF。而它不僅僅用在 Django 項(xiàng)目中,其它框架甚至個(gè)人項(xiàng)目中也常常使用 Celery 來完成異步場(chǎng)景的需求。
1.3 Django Guardian
上一節(jié)中我們介紹了 Django 的權(quán)限管理, 知道 Django 僅僅提供的是一種全局權(quán)限。這種簡(jiǎn)單的全局權(quán)限控制機(jī)制在很多場(chǎng)景下并不適用,因此需要引入另一種更細(xì)的權(quán)限機(jī)制:對(duì)象權(quán)限 (object permission)。所謂的 Object Permission 其實(shí)是一種對(duì)象顆粒度上的權(quán)限機(jī)制,它允許為每個(gè)具體對(duì)象授權(quán) ,在 Django 中其實(shí)已經(jīng)包含了 object permission 的模塊,但沒有具體實(shí)現(xiàn),必須要使用第三方的插件完成相應(yīng)的功能。django-guardian 是目前比較活躍的一個(gè) django extension,提供了一種有效的 object permission 控制機(jī)制,與 django 原生機(jī)制一脈相承,而且能快速整合到 django-admin 中,十分推薦使用。
接下來繼續(xù)看下它在 Github 上的表現(xiàn):
django-guardian 項(xiàng)目的地址和文檔如下:
從更新的頻率上看,django-guardian 項(xiàng)目還是非?;钴S的,更新速度可能沒有 Django 這樣的大型框架頻繁,但是還是保持著一定的迭代速度,是比較值得使用并花心思研究的。
1.4 Django Xadmin
Django Xadmin 是國(guó)內(nèi)程序員 sshwsfc 基于 Django 打造的一款簡(jiǎn)單易用而且又好看的后臺(tái)管理系統(tǒng)。官網(wǎng)宣傳語便是:打造管理系統(tǒng)從未如此簡(jiǎn)單。其宣稱的幾個(gè)核心特點(diǎn)如下:
- 基于 Bootstrap3,適合多種屏幕顯示;
- 內(nèi)置功能豐富,除了基本的 CURD 功能外,還有豐富的插件功能,如數(shù)據(jù)的導(dǎo)出、書簽、圖表、圖片相冊(cè)等多種擴(kuò)展功能;
- 強(qiáng)大的插件系統(tǒng),通過制作 Xadmin 插件可以擴(kuò)展系統(tǒng)的任何一個(gè)功能點(diǎn);
- 完善的權(quán)限系統(tǒng),可配置、可定制,安全可靠;
來看一看它的 Github 地址, 主要關(guān)注 star 和 contributor 人數(shù)??梢钥吹竭@個(gè)項(xiàng)目的火熱程度和 DRF 項(xiàng)目相比還有點(diǎn)距離,在國(guó)產(chǎn)框架中已經(jīng)算很不錯(cuò)的了。但是截止到2020年5月4日,看到的上次 commit 記錄好像還是2019年4月份,項(xiàng)目似乎是停滯更新了。如果是生產(chǎn)環(huán)境的話,需要慎重考慮,但是作為學(xué)習(xí)使用,還是十分推薦的。
2. 如何深入插件源碼學(xué)習(xí)?
我們以 DRF 框架為例,聊一聊如何深入 DRF 框架的源碼學(xué)習(xí)。首先肯定是下載穩(wěn)定版本為 DRF 源碼到本地,這是為了方便自己閱讀代碼。截止到2020年5月10日,DRF 的 Github 官方地址發(fā)布的最新版本為3.11.0,我們會(huì)用該版本的代碼來進(jìn)行相關(guān)的演示和說明。以下是 DRF-3.11.0 源代碼截圖,里面的代碼量還是比較大的,不過相對(duì)于 Django 的代碼而言就會(huì)少很多,我們前面能學(xué)習(xí)并跟蹤 Django 框架的源碼,拿下 DRF 源碼自然也不在話下。
一般而言,推薦學(xué)習(xí)一個(gè) Django 第三方插件源碼的過程如下:
- 第一步:熟練使用 Django 框架以及熟悉 Django 框架源碼。所有的 Django 第三方插件代碼里會(huì)大量調(diào)用 Django 源碼的類或者方法,并在其基礎(chǔ)上進(jìn)行擴(kuò)展或者進(jìn)一步創(chuàng)新。以我們必須先掌握 Django 的源碼,才能繼續(xù)學(xué)習(xí) DRF 的源碼;
- 第二步:仔細(xì)閱讀官方文檔手冊(cè)進(jìn)行學(xué)習(xí),掌握框架的基本用法;
- 第三步:通過官方文檔,實(shí)戰(zhàn) DRF 框架;每次在用熟練 DRF 提供的類或者方法后,就可以對(duì)應(yīng)地查看源碼,并分析 DRF 背后所做的工作。每掌握一個(gè)模塊的基本用法,就可以深入學(xué)習(xí)對(duì)應(yīng)模塊的源碼,同時(shí)在源碼中我們還可以發(fā)現(xiàn)該模塊中的更多用法,然后再次實(shí)踐,以加深對(duì)源碼的理解。
我們按照上面的過程來簡(jiǎn)單走一遍。首先我們前面對(duì) Django 的幾大模塊的源碼都有涉獵,算是滿足了第一步要求。接下來我們用官方給的快速入門教程完成我們的第一次 Django REST framework 框架的初體驗(yàn)。
-
模型序列化器:給會(huì)員表 member 添加一個(gè)序列化器類,放到新建的 serializers.py 文件中。
from rest_framework import serializers from hello_app.models import Member class MemberSerializer(serializers.ModelSerializer): class Meta: model = Member fields = ("id", "name", "age", "sex", "occupation", "phone_num", "email", "city", "vip_level_id")
-
準(zhǔn)備 View 視圖:添加一個(gè)對(duì)會(huì)員表操作的視圖類,我們用最簡(jiǎn)單的形式即可。
# 代碼位置:hello_app/views.py # ... from rest_framework import viewsets from rest_framework import permissions # ... class MemberViewSet(viewsets.ModelViewSet): # 設(shè)置queryset queryset = Member.objects.all().order_by('-register_date') # 設(shè)置序列化器 serializer_class = MemberSerializer # 設(shè)置認(rèn)證器 permission_classes = [permissions.IsAuthenticated]
-
編寫 URLConf 配置:Django REST framework 框架改良了 URLConf 配置的寫法,后面會(huì)研究這種寫法,先直接使用官方的示例即可。
# 代碼位置:hello_app/urls.py # ... from rest_framework import routers router = routers.DefaultRouter() router.register(r'members', views.MemberViewSet) urlpatterns = [ # ... path('', include(router.urls)) ]
另外,由于我們對(duì)
MemberViewSet
視圖加上了認(rèn)證,所以必須要在入口的 urls.py 中上如下的 URLConf 的配置。# 代碼位置: first_django_app/urls.py # ... urlpatterns = [ # ... path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) ]
注意:不添加和添加這行 URLConf 配置的效果圖如下所示。
-
接下來,最后一步是設(shè)置視圖的相關(guān)配置以及注冊(cè) rest_framework 應(yīng)用。
# 代碼位置:first_django_app/settings.py # ... INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 注冊(cè)第三方應(yīng)用 'rest_framework', # 注冊(cè)應(yīng)用 'hello_app' ] REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 5 } # ...
-
最后我們啟動(dòng)服務(wù),來一起看看效果。我們之前創(chuàng)建過一個(gè)超級(jí)用戶admin/admin.1234!,接下來會(huì)用這個(gè)通過 DRF 的認(rèn)證。
從上面的演示中,我們看到了 Django REST framework 框架給我們做的接口測(cè)試頁面,我們只需要簡(jiǎn)單繼承下MemberViewSet
即可,然后添加相關(guān)屬性即可立即擁有這樣一個(gè)完整的接口測(cè)試頁面。后臺(tái)服務(wù)主要提供接口數(shù)據(jù),我們也可以使用 curl 命令來獲取和操作相應(yīng)的模型表。
[root@server ~]# curl -H 'Accept: application/json; indent=4' -u admin:admin.1234! http://127.0.0.1:8888/hello/members/?page=3
{
"count": 103,
"next": "http://127.0.0.1:8888/hello/members/?page=4",
"previous": "http://127.0.0.1:8888/hello/members/?page=2",
"results": [
{
"id": 9,
"name": "spyinx-5",
"age": "39",
"sex": 0,
"occupation": "product",
"phone_num": "18015702646",
"email": "225@qq.com",
"city": "shanghai",
"vip_level_id": null
},
{
"id": 10,
"name": "spyinx-6",
"age": "26",
"sex": 0,
"occupation": "ops",
"phone_num": "18790082215",
"email": "226@qq.com",
"city": "beijing",
"vip_level_id": null
},
{
"id": 11,
"name": "spyinx-7",
"age": "23",
"sex": 0,
"occupation": "security",
"phone_num": "18354491889",
"email": "227@qq.com",
"city": "guangzhou",
"vip_level_id": null
},
{
"id": 12,
"name": "spyinx-8",
"age": "26",
"sex": 1,
"occupation": "ui",
"phone_num": "18406891676",
"email": "228@qq.com",
"city": "wuhan",
"vip_level_id": null
},
{
"id": 13,
"name": "spyinx-9",
"age": "26",
"sex": 0,
"occupation": "ops",
"phone_num": "18036496230",
"email": "229@qq.com",
"city": "wuhan",
"vip_level_id": null
}
]
}
在上面這個(gè)過程走通之后,我們可以看到其實(shí)這個(gè)例子中已經(jīng)涉及到了 DRF 中的許多類,比如用于序列化的類ModelSerializer
、視圖類 ModelViewSet
、分頁類 PageNumberPagination
等等。從這個(gè)案例中,我們可以找到許多學(xué)習(xí) DRF 源碼的切入點(diǎn)。首先看用到的視圖類 ModelViewSet
:
# 源碼位置:rest_framework/viewsets.py
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
通過學(xué)習(xí) Django 的視圖,我們了解了 Mixin 這個(gè)概念,所以容易理解這里的代碼,視圖繼承 GenericViewSet
,同時(shí)也繼承了數(shù)個(gè) Mixin。這些 Mixin 從命名上就很容易知道其功能用法。進(jìn)一步翻看其實(shí)現(xiàn)類,也能發(fā)現(xiàn)其具體含義 。以 mixins.CreateModelMixin
類為例:
# rest_framework/mixins.py
class CreateModelMixin:
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
CreateModelMixin
的主要功能就是提供了 create()
方法,讓視圖擁有新增記錄的功能。其他的 Mixin 會(huì)提供類似的函數(shù),讓視圖具有某一特定的功能。接下來我們的重點(diǎn)放到 GenericViewSet
類的學(xué)習(xí)上。
# 源碼位置:rest_framework/viewsets.py
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
The GenericViewSet class does not provide any actions by default,
but does include the base set of generic view behavior, such as
the `get_object` and `get_queryset` methods.
"""
pass
這里又是多繼承,一個(gè) ViewSetMixin
類,另一個(gè) generics.GenericAPIView
類。先追后面的 View 類,實(shí)現(xiàn)代碼如下:
從這里我們看到了一些熟悉的屬性,如 queryset
,serializer_class
以及用于分頁的 pagination_class
。這個(gè)繼承的 APIView
類同樣也是 Django REST framework 框架自己定義的類,我們繼續(xù)追進(jìn) APIView
類的實(shí)現(xiàn)代碼:
最后 APIView
這個(gè)類繼承的 View
正是 Django 中我們學(xué)過的 View 視圖類。
from django.views.generic import View
那這樣子,我們也算清楚了一些事情。Django REST framework 框架中定義的視圖是在 Django 的 View 視圖類上封裝和改進(jìn)來的?,F(xiàn)在一個(gè)疑問就來了,看我們前面使用 Django 的視圖中,URLConf 配置如下:
urlpatterns = [
path('test-cbv/', views.TestView.as_view(), name="test-cbv"),
]
我們也分析過對(duì)應(yīng)的 View 類以及 as_view()
方法,它將 GET 請(qǐng)求映射到視圖類的 get()
方法,POST 請(qǐng)求則映射到 post()
方法;然而我們這里一路走下來并有沒有看到對(duì)應(yīng)的 get()
或者 post()
方法。但是視圖類繼承的多個(gè) Mixin 中提供了 create()
、list()
等這樣的方法,那么他們是如何和 URLConf 配置對(duì)應(yīng)上的呢?我們現(xiàn)在要通過代碼去找出前面配置 URLConf 代碼的內(nèi)部原理:
from django.conf.urls import include
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'members', views.MemberViewSet)
urlpatterns = [
# ...
path('', include(router.urls))
]
來看看上面的 URLConf 配置。這個(gè)時(shí)候,我們需要去看 Django REST Framework 中的 DefaultRouter
類,包括注冊(cè)方法 register
() 以及 urls
屬性值的獲取。最后還要看 Django 中的 include() 方法的代碼,才能理清楚 URL 和視圖的映射關(guān)系。
先追蹤 Django REST Framework 中的 DefaultRouter
類實(shí)現(xiàn),該類繼承自 SimpleRouter
,SimpleRouter
又繼承自 BaseRouter
。為了加快速度,我們直接定位到基類 BaseRouter,可以看到 register()
方法和 urls
屬性的定義,如下:
# 源碼位置:rest_framework/routers.py
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
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')
def get_urls(self):
"""
Return a list of URL patterns, given the registered viewsets.
"""
raise NotImplementedError('get_urls must be overridden')
@property
def urls(self):
if not hasattr(self, '_urls'):
self._urls = self.get_urls()
return self._urls
可以看到,在執(zhí)行 router.register(r'members', views.MemberViewSet)
后其實(shí)等同于給 registry
數(shù)組添加一個(gè)元組元素,用于存儲(chǔ)映射關(guān)系。而 urls
屬性值則是調(diào)用 get_urls()
方法得到的。
class DefaultRouter(SimpleRouter):
"""
The default router extends the SimpleRouter, but also adds in a default
API root view, and adds format suffix patterns to the URLs.
"""
# ...
def get_urls(self):
"""
Generate the list of URL patterns, including a default root view
for the API, and appending `.json` style format suffixes.
"""
urls = super().get_urls()
if self.include_root_view:
view = self.get_api_root_view(api_urls=urls)
root_url = url(r'^$', view, name=self.root_view_name)
urls.append(root_url)
if self.include_format_suffixes:
urls = format_suffix_patterns(urls)
return urls
可以看到它先是調(diào)用了父類的 get_urls()
方法,另外又添加了一些映射規(guī)則。我們添加如下一行 print()
語句:
class DefaultRouter(SimpleRouter):
def get_urls(self):
"""
Generate the list of URL patterns, including a default root view
for the API, and appending `.json` style format suffixes.
"""
urls = super().get_urls()
print('父類調(diào)用得到的urls={}'.format(urls))
# ...
然后啟動(dòng)服務(wù),可以看到如下的結(jié)果:
(django-manual) [root@server first_django_app]# python manage.py runserver 0:8888
Watching for file changes with StatReloader
Performing system checks...
父類調(diào)用得到的urls=[<URLPattern '^members/$' [name='member-list']>, <URLPattern '^members/(?P<pk>[^/.]+)/$' [name='member-detail']>]
System check identified no issues (0 silenced).
May 15, 2020 - 13:30:04
Django version 2.2.12, using settings 'first_django_app.settings'
Starting development server at http://0:8888/
Quit the server with CONTROL-C
可以看到,這個(gè) ^members/$
的URL 配置是由父類的 get_urls()
方法得到的。在父類 SimpleRouter
中的get_urls()
方法中,我已經(jīng)做好了相關(guān)的注釋,最關(guān)鍵的代碼就在最后的 append()
部分,那里添加的便是最后 URL 和 視圖函數(shù)的關(guān)系。
class SimpleRouter(BaseRouter):
# ...
def get_urls(self):
# ...
# 前面介紹過這個(gè) registry 屬性,就是通過 register() 方法得到的
for prefix, viewset, basename in self.registry:
# ...
for route in routes:
# Only actions which actually exist on the viewset will be bound
mapping = self.get_method_map(viewset, route.mapping)
if not mapping:
continue
# Build the url pattern
regex = route.url.format(
prefix=prefix,
lookup=lookup,
# 尾部加上"/"
trailing_slash=self.trailing_slash
)
# 處理一些簡(jiǎn)單情況
if not prefix and regex[:2] == '^/':
regex = '^' + regex[2:]
initkwargs = route.initkwargs.copy()
initkwargs.update({
'basename': basename,
'detail': route.detail,
})
# 最最核心的部分代碼,這里得到視圖函數(shù)
view = viewset.as_view(mapping, **initkwargs)
# 視圖名稱
name = route.name.format(basename=basename)
# 添加映射規(guī)則
ret.append(url(regex, view, name=name))
return ret
我們可以看到最后添加的映射規(guī)則就是這一句:ret.append(url(regex, view, name=name))
,我們繼續(xù)看看這個(gè) url()
方法,它調(diào)用的正是 Django 中的 url()
方法,內(nèi)容如下:
# 源碼路徑:django/conf/urls.py
# ...
def url(regex, view, kwargs=None, name=None):
return re_path(regex, view, kwargs, name)
這個(gè) url()
方法和我們之前在 Django 中用 repath()
以及 path()
差不多一致的。第一個(gè)參數(shù)是 url 規(guī)則,第二個(gè)便是視圖函數(shù)。比較重要的就是這里得到 view
的函數(shù)了,它便是真正的視圖函數(shù)。它和前面 Django 中的一樣,通過 as_view()
得到的。那么這個(gè) as_view()
方法在哪呢,通過父類追蹤,可知 Django 的父類中本身就有 as_view()
方法,但是在前一個(gè)繼承的Mixin 中重寫了該方法,因此調(diào)用的便是該 Mixin 中的 as_view()
方法:
class ViewSetMixin:
"""
This is the magic.
Overrides `.as_view()` so that it takes an `actions` keyword that performs
the binding of HTTP methods to actions on the Resource.
For example, to create a concrete view binding the 'GET' and 'POST' methods
to the 'list' and 'create' actions...
view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
"""
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
"""
Because of the way class based views create a closure around the
instantiated view, we need to totally reimplement `.as_view`,
and slightly modify the view function that is created and returned.
"""
# ...
def view(request, *args, **kwargs):
self = cls(**initkwargs)
# We also store the mapping of request methods to actions,
# so that we can later set the action attribute.
# eg. `self.action = 'list'` on an incoming GET request.
self.action_map = actions
# Bind methods to actions
# This is the bit that's different to a standard view
for method, action in actions.items():
handler = getattr(self, action)
setattr(self, method, handler)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
# And continue as usual
return self.dispatch(request, *args, **kwargs)
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
# We need to set these on the view function, so that breadcrumb
# generation can pick out these bits of information from a
# resolved URL.
view.cls = cls
view.initkwargs = initkwargs
view.actions = actions
return csrf_exempt(view)
和 Django 中的一樣,這里最后的 as_view()
方法最后返回的便是視圖函數(shù)。那么對(duì)應(yīng)的 /hello/members/
請(qǐng)求進(jìn)來后,有 view()
方法進(jìn)行處理,最后調(diào)用的和 Django 中的一樣:
return self.dispatch(request, *args, **kwargs)
我們?nèi)?Django 中看這個(gè) dispatch()
方法的源碼:
# 源碼位置:django/views/generic/base.py
class View:
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
"""
# ...
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
那么執(zhí)行 /hello/members/
請(qǐng)求到這里是,handler 是哪個(gè)?我們繼續(xù)翻看前面的 Mixin 類,有這樣一段代碼:
class ViewSetMixin:
# ...
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
def view(request, *args, **kwargs):
# ...
for method, action in actions.items():
handler = getattr(self, action)
setattr(self, method, handler)
# ...
# ...
# ...
這里就非常明顯了,我們大概也能猜到一些。就是設(shè)置 (get|post|put|delete) 請(qǐng)求對(duì)應(yīng)的方法,比較好的方式時(shí)我們?cè)谶@里打印下請(qǐng)求,并在前端進(jìn)行下請(qǐng)求測(cè)試,看看這里到底設(shè)置了啥?
class ViewSetMixin:
# ...
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
def view(request, *args, **kwargs):
# ...
for method, action in actions.items():
print('請(qǐng)求處理view視圖函數(shù):method={}, action={}'.format(method, action))
handler = getattr(self, action)
setattr(self, method, handler)
# ...
# ...
# ...
我們啟動(dòng)服務(wù)請(qǐng)求以下路徑 /hello/members/
,可以得到如下輸出結(jié)果:
(django-manual) [root@server first_django_app]# python manage.py runserver 0:8888
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
May 15, 2020 - 17:07:38
Django version 2.2.12, using settings 'first_django_app.settings'
Starting development server at http://0:8888/
Quit the server with CONTROL-C.
請(qǐng)求處理view視圖函數(shù):method=get, action=list
請(qǐng)求處理view視圖函數(shù):method=post, action=create
[15/May/2020 17:07:43] "GET /hello/members/ HTTP/1.1" 200 14426
結(jié)合 Django 中的 dispatch()
方法,我們終于知道了 get 請(qǐng)求最后會(huì)調(diào)用視圖類中的 list()
方法去處理,而這個(gè) list()
方法正是 ListModelMixin 中的。另外 post 請(qǐng)求則對(duì)應(yīng)著視圖類中的 create()
方法,而這個(gè)屬性則來自 CreateModelMixin。這樣我們總算理解了前面的 URLConf 的映射流程以及對(duì)應(yīng)的真正視圖處理函數(shù)。
帶著問題去追源碼是我比較推薦的一個(gè)學(xué)習(xí)方式。完成一個(gè)模塊的學(xué)習(xí)就要去思考,去追蹤這個(gè)案例背后的執(zhí)行過程,這樣才能更好的掌握這個(gè)模塊。今天的分享就到此結(jié)束了,DRF 中還有很多代碼等著你們?nèi)ヌ剿?,去?shí)踐,祝大家學(xué)習(xí)愉快!
3. 小結(jié)
本小節(jié)中,我們介紹了多種流行的基于 Django 的第三方插件,好用又好玩,而且它們的組合能幫我們迅速開發(fā)一個(gè)完整而又美妙的網(wǎng)站。當(dāng)然,流行的第三方插件遠(yuǎn)不止這些,我們?cè)谡莆蘸靡欢ǖ拈_發(fā)基礎(chǔ)后,也可以自行制作第三方插件。然而長(zhǎng)路漫漫,還需要靜下心來仔細(xì)學(xué)習(xí)和研究代碼,才有可能在未來成為別人眼中的大神。