drf快速使用 CBV原始碼分析 drf之APIView分析 drf之Request物件分析

2023-01-01 06:00:36

序列化和反序列化

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設定:

推薦閱讀:
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方法:

在我們寫的檢視類中檢視老的request:

執行get方法裡面的程式碼之前,我們的request已經被換成了新的request。get函數裡面的request是drf產生的新request。
更多範例:

request._requestself.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')