序列化和反序列化
api介面開發,最核心最常見的一個過程就是序列化,所謂序列化就是把資料轉換格式,序列化可以分兩個階段:序列化、反序列化
序列化:把我們語言識別的資料轉換成指定的格式提供給別人。
字典,列表,物件 ---> json/xml/prop,massagepack ---> 將json格式的資料提供給別人(前端或其他服務)
'''
序列化和反序列化的格式不僅僅用json格式。
json格式的可讀性太高,安全性不足。可以採用prop、massagepack格式等。
'''
反序列化:把別人提供的資料轉換/還原成我們需要的格式。
我們在django中獲取到的資料預設是模型物件(qreryset物件),但是模型物件資料無法直接提供給前端或別的平臺使用,所以我們需要把資料進行序列化,變成字串或者json資料,提供給別人,這個轉換過程稱為 ---> '序列化過程'
前端傳入到後臺的資料 ---> json格式字串 ---> 後端將資料存到資料庫中,需要將資料轉成python中的物件 ---> 把json格式字串轉成python物件存到資料庫的過程稱為 ---> '反序列化'
drf介紹和安裝
使用原生django寫介面
原生django,不使用任何其他模組,也可以寫出符合resful規範的介面,就是寫起來麻煩一些。
# 查詢所有圖書
地址:127.0.0.1:8080/books
路由:path('/books',views.books)
檢視函式中:
1. 透過orm查出所有圖書物件(qreryset)
2. 序列化(for迴圈取出資料自己拼成列表套字典):
[{name:西遊記,price:99},{name:紅樓夢,price:99}]
3. JsonResponse返回給前端
django DRF安裝
# 定義
drf是django的一個app。
# 作用
幫助程式設計師快速在django上寫符合restful規範的介面
# 安裝:
pip install djangorestframework
# 安裝的注意事項:
1.django的最新版本當前為 4.x , 一般我們將django升級到 3.x 或者 2.x
2.drf的最新版本最低支援django 2.2及以上;
'''
如果你的版本低於2.2:
當你安裝最新版drf的時候, 會把你老版本的django解除安裝,給你裝上最新版,導致原來的django專案出現問題,執行不了
'''
# 建議:
django 2.2 以下版本 ---> 使用低版本的drf
django 3.x ---> 使用最新版本的drf
drf快速使用
# 針對於一個表,通常需要寫哪些介面?
通常需要寫5個介面,以後看到的所有介面都是這5個介面的變形。
# 五個基本介面:
'需求名' '請求方式' '訪問的路由'
-查詢所有 ---> get -> http://127.0.0.1:8000/books/
-查詢一個 ---> get -> http://127.0.0.1:8000/books/1/
-新增一個 ---> post -> http://127.0.0.1:8000/books/
請求體body中攜帶新增的資料
-修改 ---> put,patch
(實際編碼中,基本都用put)
-> http://127.0.0.1:8000/books/1/
請求體body中傳入修改的資料
-刪除一個 ---> delete -> http://127.0.0.1:8000/books/1/
# 註冊、登入介面的本質:
登陸介面 ---> 本質其實是查詢一個
註冊介面 ---> 本質是新增一個
# postman測試介面的特點:
postman的介面測試是嚴格的,對於一個路由地址,斜槓有和沒有是有區別的。
postman不會像瀏覽器一樣,自動補全斜槓再請求一次。
模型
在模型層建立一個簡單的只有3個欄位的圖書類。
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.DecimalField(decimal_places=2, max_digits=5)
author = models.CharField(max_length=32)
序列化類
新建一個py檔案編寫序列化類BookSerializer。
from .models import Book
from rest_framework import serializers
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
檢視
在檢視層使用相對匯入,匯入剛剛建立的圖書類。(也可以使用絕對匯入,但是推薦使用相對匯入)
匯入ModelViewSet模組,自己寫一個類繼承這個模組。
類中屬性serializer_class使用我們剛剛建立的序列化類。
from rest_framework.viewsets import ModelViewSet
from .models import Book # 模組匯入推薦使用相對匯入
# from app01.models import Book # 使用絕對匯入
from .serializer import BookSerializer
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
路由
from rest_framework.routers import SimpleRouter
from app01 import views
router = SimpleRouter()
router.register('books', views.BookView, 'books')
urlpatterns = [
path('admin/', admin.site.urls),
]
urlpatterns += router.urls
datagrip
pycharm是java寫的 django連結資料庫需要java連結資料庫的驅動
類似navicat。datagrip是pycharm公司寫的連結資料的軟體。
使用postman測試介面
使用get請求獲取所有圖書:
使用put請求修改圖書:
使用patch請求修改圖書:
可以只區域性修改一部分。
CBV原始碼分析
1. cbv路由寫法:
path('test/', views.TestView.as_view())
2. path的第二個引數實際是函式記憶體地址
3. as_view()執行完,實際是閉包函式view的記憶體地址
4. 當請求來了,路由匹配成功,會執行view(request),傳入當次請求的request物件
5. view函式的返回值:return self.dispatch(request, *args, **kwargs)
6. View類中找dispatch
7. 如果是get請求就會執行檢視類中的get方法,如果是post請求,就會執行檢視類的post方法
# as_view 類的繫結方法--->View類的方法-->@classonlymethod
# dispatch核心程式碼--->getattr反射--->從當前物件(檢視類的物件)--->如果是get請求-->會拿到get方法--->handler就是get方法--->handler(request)本質就是--->get(request)
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
return handler(request, *args, **kwargs)
# 透過描述符自己寫裝飾器來裝飾類---》完成類似於property,classmethod。
檢視原始碼推薦pycharm配置:
開啟這個show Members,可以檢視py檔案裡面的變數名。
classonlymethod:
classonlymethod繼承於classmethod.
相當於django開發人員自定義的classmethod。
推薦閱讀:
https://liuqingzheng.top/python/物件導向高階/5-描述符(get%E5%92%8C__set__%E5%92%8C__delete__)/
drf之APIView分析
View類的匯入方式
以後使用drf寫介面,在檢視層都是寫檢視類
drf最頂層的檢視類是APIView,是drf中所有檢視類的父類。
APIView又繼承了django中的View類:
這個View是這樣匯入的:
from django.view.genenic import View
不對呀,我們之前寫檢視類,是這樣匯入的:
from django import view
然後我們的類繼承 view.View
為什麼這兩種方式都會匯入同一個View類?
不論是genenic還是view都是包名。
在包內的__init__
檔案註冊View類就可以實現匯入了。
# View類真實路徑
from django.views.generic.base import View
# 因為在generic包的init裡註冊了
from django.views.generic import View
# 因為在views包的init裡註冊了
from django.views import View
繼承了django View類的類,就是檢視類。
所以繼承APIView的類,也是檢視類。
路由層寫法和以前一樣:
這裡如果路由衝突了,會怎麼樣?(有兩個檢視類對應test/路由)
這裡底層是for迴圈,將列表中的路由一個一個取出匹配,如果上面的匹配成功,for迴圈就break退出,不會繼續匹配,所以下面這個檢視類永遠都不會執行。
APIView的執行流程
# APIView的執行流程
路由 path('order/', views.OrderView.as_view())---》第二個引數是函式記憶體地址---》APIView的as_view的執行結果---》本質還是用了View類的as_view內的view閉包函式,去掉了csrf認證----》當請求來了---》觸發view閉包函式執行,並且傳入request--->呼叫了self.dispatch-->self是檢視類的物件,從OrderView中找dispatch,但是找不到----》父類APIView中有---》本質執行是APIView的dispatch----》
APIView的as_view方法
# APIView的as_view方法
view = super().as_view(**initkwargs) # 呼叫APIView的父類(View)的as_view方法
return csrf_exempt(view) # 去掉csrf_exempt的認證,以後只要繼承了APIView後,csrf就無效了,無論中介軟體是否註釋掉
# crsf的區域性禁用--》在檢視函式上加裝飾器---》csrf_exempt裝飾器---》裝飾器本質就是一個函式
@csrf_exempt # 裝飾器的@ 是一個語法糖(特殊語法)-->把下面的函式當引數傳入裝飾器中並且把返回結果賦值給函式名:index=csrf_exempt(index)
def index(request)
pass
跟 csrf_exempt(index) 一毛一樣
路由 path('order/', views.OrderView.as_view())
:
path第二個引數是函式記憶體地址:
我們自己的檢視類OrderView裡面沒有as_view方法,所以回去父類APIView找as_view方法,
由於APIView裡面有as_view,所以不會去APIView的父類View找。
APIView裡的as_view:
重要的兩行程式碼:
view = super().as_view(**initkwargs) # 呼叫APIView的父類(View)的as_view方法
return csrf_exempt(view) # 去掉csrf_exempt的認證,以後只要繼承了APIView後,csrf就無效了,無論中介軟體是否註釋掉
父類(View)的as_view方法最終會拿到 ---> 我們自己編寫的檢視類中的方法 :
如果來了一個get請求 dispatch方法會透過反射從我們檢視類產生的物件中獲取方法:
如何理解這行程式碼return csrf_exempt(view)
?
@csrf_exempt
def index(request):
pass
跟 csrf_exempt(index) 一模一樣
# 因為裝飾器的本質是:
index = csrf_exempt(index)
CBV本質上就是FBV。
傳送請求到後端實際上就是執行了我們檢視類中的一個函式。
如果檢視層有一個檢視函式:
def index(request):
return Httpresponse('你好')
正常情況下,我們給FBV新增裝飾器語法糖實際上是執行了:index = csrf_exempt(index)
。
@csrf_exempt
def index(request):
return Httpresponse('你好')
當路由匹配成功時,看起來是執行index函式,實際是執行crsf_exempt(index)
。
FBV路由層看起來是這樣子:
path('index/', views.index)
實際是:
path('index/', csrf_exempt(index))
CBV路由層看起來是這樣子:
path('index/', views.TestView.as_view())
實際是:
path('index/', crsf_exempt(View))
這個View最終是我們檢視類中的一個方法的函式地址(透過反射拿到的)
總結:APIView的as_view的作用只是給你自己寫的檢視類加了個crsf_exempt裝飾器(去掉了crsf認證)
APIView的dispatch方法
# APIView的dispatch
def dispatch(self, request, *args, **kwargs):
# request是新的drf提供的request,它是由老的django的request得到的
# 透過老request,生成一個新request,drf的Request的物件
request = self.initialize_request(request, *args, **kwargs)
# 把新的request,放到了我們自己的檢視類物件中
self.request = request
try:
# 執行了三大認證(認證,許可權,頻率)
self.initial(request, *args, **kwargs)
# self.http_method_names是個列表
if request.method.lower() in self.http_method_names:
# 原來dispatch的核心程式碼
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed) # 透過反射獲取我們檢視類中的方法
else:
handler = self.http_method_not_allowed
# 這裡也是原來dispatch寫的程式碼,但是request已經不是老request了,是上面生成的新request
response = handler(request, *args, **kwargs) # 執行檢視函式的方法
except Exception as exc:
# 無論在三大認證過程中還是執行檢視函式方法過程中,只要拋了異常,都會被捕獲到
# 處理全域性異常
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
# dispatch方法總結
請求來了之後,dispatch方法,先處理request產生新的request物件,將這個新的request物件放入我們自己檢視類產生的物件中。
再進行三大認證,認證完了之後獲取我們類中的方法並執行,最後dispatch方法返回一個返回值。
# APIView執行流程
1 包裝了新的Request物件,以後檢視類中的方法中傳入的request物件都是新的
2 在進入檢視函式之前,執行了三大認證
3 無論三大認證還是檢視函式的方法,執行過程中出了異常,都會被處理掉
把新的request,放到了我們自己的檢視類物件中:
http_method_names
列表:
列表裡面放了八大請求方法。
drf之Request物件分析
如何包裝的新的request
initialize_request方法如何將django產生的老request包裝成新的request?
# 如何包裝的新的request
request = self.initialize_request(request, *args, **kwargs)---》由於我們的物件裡沒有initialize_request方法,所以去APIView找initialize_request方法---》核心程式碼
# initialize_request方法核心程式碼
from rest_framework.request import Request # 匯入drf新Request類
return Request(
request, # 老的request
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
新的Request類中的__init__方法有如下程式碼:
self._request = request ---》新的request._request是老的request
新的:<class 'rest_framework.request.Request'>
老的:<class 'django.core.handlers.wsgi.WSGIRequest'>
APIView中的initialize_request方法:
drf 新Request類:
Request類__init__
:
在我們寫的檢視類中檢視老的request:
執行get方法裡面的程式碼之前,我們的request已經被換成了新的request。get函式里面的request是drf產生的新request。
更多示例:
request._request
和self.request._request
存放的都是django產生的老request。
檢視request的型別:
三大認證執行順序
# 三大認證是如何走的
self.initial(request, *args, **kwargs)---》APIView的
核心程式碼:
self.perform_authentication(request) # 認證
self.check_permissions(request) #許可權
self.check_throttles(request) # 頻率
# 三大認證執行順序
認證 ---> 許可權 ---> 頻率
# 總結
'''
路由匹配成功 ---三大認證---> 執行檢視類中方法
三大認證有點類似於中介軟體:
請求到達後端伺服器 ---中介軟體---> 路由匹配
'''
新Request常用屬性和方法
request.data
# rest_framework.request.Request --->常用屬性和方法
# request.data定義
新的request有一個data方法(此方法被偽裝成屬性),前端post請求傳入的資料都在equest.data裡面。
# 與老的request.POST的區別
request.POST:
只能處理urlencoded和formdata編碼格式。
request.data:
無論前端用什麼編碼格式的post提交的資料,都可以從request.data中獲取。
未改變的方法
新的request也有一些方法和老request使用方式相同:
request.files # 新的request.files也是獲取上傳的檔案物件
以後直接使用新的request.method request.path 拿到的就是老的request.method... # 跟之前用法相同
使用getattr呼叫老的request的方法
如何使用新request呼叫老request中的方法:
# 原理
物件.呼叫屬性或方法會觸發 魔法方法 __getattr__
原因在於新的request類重寫了__getattr__,以後新的request.method用的時候本質就是request._request.method
# 程式碼
1.使用新的request方法 ---> 執行request.method
2.當新request類中沒有method這個名字時,觸發新request類中的__getattr__方法
def __getattr__(self, attr): # 傳入字串'method'
try:
return getattr(self._request, attr) # 透過反射去老的裡面取 self._request存的是老的request
except AttributeError:
return self.__getattribute__(attr)
3. 新的request.method用的時候本質就是:
request._request.method
# 總結:新的request當老的用即可,只是多了個data屬性,存放前端post請求傳入的資料,三種編碼格式都可以存放在data中。
新的request.data和老的request.POST的區別:
傳送formdata編碼格式:
傳送json編碼格式:
練習
1 APIView和Request原始碼部分
2 原來的django的request物件中沒有data,寫個裝飾器,裝在檢視函式上(中介軟體),使得request.data-->無論什麼編碼格式,post提交資料,data都有值
def before(func):
def inner(request,*args,**kwargs):
request.data=request.POST
res=func(request,*args,**kwargs)
return res
return inner
@before
def test(request):
print(request.data)
return HttpResponse('ok')