不會DRF?原始碼都分析透了確定不來看?

HammerZe發表於2022-03-29

不會DRF?原始碼都分析透了確定不來看?

image

可以更方便的使用django寫出符合restful規範的介面

下載安裝

  • pip3 install djangorestframework
  • pycharm下載

注意

  • rest_framework是一個app需要註冊!

?官網:https://www.django-rest-framework.org/

image

  • drf安裝預設安裝的最新版本,如果django版本過低會自動升級到3.x版本

  • 版本支援對應關係

image

示例

'''views.py'''
from rest_framework.views import APIView
from rest_framework.response import Response


class Test(APIView):
    def get(self,request):
        data = {'status':200,'msg':'success'}
        return Response(data)
    def post(self,request):
        data = {'status': 200, 'msg': 'success'}
        return Response(data)
    
'''urls.py'''    
path('test/', views.Test.as_view()),
    
'''settings.py'''
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
    'rest_framework'
]

get請求

image

post請求

image

注意:前後端分離csrf已經沒有用了,檢視一下原始碼

def as_view(cls, **initkwargs)
	return csrf_exempt(view)

我們發現返回值是csrf_exempt(view)區域性不驗證,在Django總結到了不懂可以看一下:CSRF 跨站請求偽造


快速使用DRF寫出介面

序列化和反序列化

API介面開發,最核心最常見的一個過程就是序列化,所謂序列化就是把資料轉換格式,序列化可以分兩個階段:

  • 序列化:把我們語言識別的資料轉換成指定的格式提交給別人(前端)
    • 比如python中的字典,列表,物件等轉json,xml,prop····
  • 反序列化:把別人提供的資料轉換成我們所需的格式
    • 最常見的比如我們使用json模組來對資料進行處理····

在Djangorestframework中的序列化反序列化又是如何?

  • 序列化: 在Django中獲取到的資料預設是模型物件(QuerySet物件),但是模型物件資料無法直接提供給前端或別的平臺使用,我們需要把資料進行序列化,變成字串或json資料提供給前端或其他平臺;
  • 反序列化: 前端或其他平臺傳入資料到後臺,比如是json格式字串,後端需要存入資料庫,需要轉換成python中的物件,然後處理存入資料庫;
  • 總結:
    • python物件 -----》json格式字串 (序列化)
    • json格式字串 -----》 python 物件 (反序列化)

如何使用DRF快速寫出增刪查改的介面?

drf快速使用

快速寫5個介面

使用Django寫五個介面得配5個路由,5個檢視函式去處理,現在使用drf不需要了,如下:

-查詢所有---》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的時候,地址嚴格寫,不能缺少/
'''

views.py

from rest_framework.viewsets import ModelViewSet
from app import models
from app.serializer import  BookSerializer
class BookView(ModelViewSet):
    serializer_class =BookSerializer
    queryset = models.Book.objects.all()

serializer.py

from rest_framework.serializers import ModelSerializer
from app import models
class BookSerializer(ModelSerializer):
    class Meta:
        model = models.Book
        fields = '__all__'

urls.py

from rest_framework.routers import SimpleRouter
from app import views

router = SimpleRouter()
router.register('books', views.BookView)
'''
register(prefix, viewset, base_name)
prefix 該檢視集的路由字首
viewset 檢視集
base_name 路由名稱的字首
'''
urlpatterns = [
    path('admin/', admin.site.urls),
]
urlpatterns += router.urls

# 千萬注意別把註釋寫到urlpatterns列表中,那樣就不是註釋了,成字串了!!!

在settings的app中註冊

INSTALLED_APPS = [
    'rest_framework'
]

models.py

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=5,decimal_places=2)
    authors = models.CharField(max_length=32)

postman測試

獲取所有

image

獲取一條

image

新增一條資料

image

全部修改:修改id為1的資料,這裡儘量在body體裡寫全引數

image

區域性修改:修改id為1的資料,修改啥寫啥

image

刪除一條資料:刪除id為2的資料

image

image


CBV原始碼流程分析

因為DRF框架裡大部分都是基於CBV(檢視類)寫,所以流程是什麼,如何執行需要了解,同時也方便理解APIView,順便提一嘴APIView也是繼承了View ----> class APIView(View)

這裡需要強調一下,CBV路由歸根結底還是FBV都是函式的記憶體地址,比如views.類.as_view()底層仍然是函式的記憶體地址

CBV原始碼執行流程

'''views.py'''
from django.views import View
from django.http import HttpResponse

class TestView(View):
    def get(self,request):
        return HttpResponse('get請求')
    # post csrf認證註釋掉就好了
	def post(self,request):
        return HttpResponse('post請求')
'''urls.py'''
path('test/',views.TestView.as_view())

寫一個檢視類,寫了get方法和post方法,來了get請求就走get方法,來了post請求就走post方法,過程如何?且看分析原始碼執行過程~

'''請求來了在不考慮中介軟體的情況下,從路由的匹配關係和檢視函式來看'''
1、cbv路由寫法:path('test/', views.TestView.as_view())
# 第二個引數是函式記憶體地址,CBV的底層也是FBV,as_view是類的繫結方法,自己的類中沒有去父類(View)找,as_view()執行完,也是一個記憶體地址,記憶體地址是誰的?是閉包函式view的如下原始碼?
@classonlymethod
    def as_view(cls, **initkwargs):
        ···
        def view(request, *args, **kwargs):
            ···
            return self.dispatch(request, *args, **kwargs)
        ···
        return view
# @classonlymethod通過描述符自定義裝飾器

2、請求來了,路由也匹配成功,執行上邊返回的view(requets),加括號呼叫,並且傳入了當次請求的request物件

3、然後又返回了當前物件的dispatch方法,自己的名稱空間內沒有,那麼去父類中找,然後發現父類(View)的dispatch方法返回了return handler(request, *args, **kwargs)

4、dispatch方法處理請求,什麼請求對應什麼方法
# 父類dispatch方法:
    def dispatch(self, request, *args, **kwargs):
        # 請求方法小寫如果在當前物件的http_method_names中(八個請求方法)
    '''
        http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
        '''
        if request.method.lower() in self.http_method_names:
            # 如果成立,那麼執行下面的反射,從當前物件(檢視類的物件)拿到請求方法,如果是get請求就拿到get方法,post請求就拿到post方法,然後賦給handler,handler就是相應請求對應的方法,最後返回handler(request),本質其實是get(request)
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            # 不成立,也就是沒有該請求對應的方法,執行http_method_not_allowed方法,彈出警告
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)

 


# http_method_not_allowed原始碼  
def http_method_not_allowed(self, request, *args, **kwargs):
    logger.warning(
        'Method Not Allowed (%s): %s', request.method, request.path,
        extra={'status_code': 405, 'request': request}
    )
    return HttpResponseNotAllowed(self._allowed_methods())
  • getattr(obj,pro,None):按pro判斷是否有無obj.pro屬性,沒有返回None,None可以自定製
  • 反射忘記的可以看俺的這篇部落格:python 多型、組合、反射

Django View和DRF APIView的小插曲

  • DRF中最“牛逼”的檢視類是APIView,不過也繼承了View

  • 匯入:from rest_framework.views import APIView

  • Django中最“牛逼”的檢視類是View

  • 匯入:

    from django.views import View # 最簡
    from django.views.generic import View  
    from django.views.generic.base import View  # 最完整
    
    '''這三個匯入的結果都是一個View,想知道緣由還得看原始碼和目錄結構,如下:'''
    # 目錄結構
    -django
    -···  # 此處省略n個包
    -views
    -- __init__.py
    -- generic
    ---- __init__.py
    ---- base.py
    # 原始碼分析
    '''問題:為什麼上述不同的py檔案都可以指向同一個類?'''
    我的思路是羊毛出在羊身上,既然都指向類,那麼從結果出發是不是更好
    1、View類在base.py中,那麼顯而易見最完整的匯入是沒問題的,重點是為什麼簡寫也可以
    2、base.py所在的包generic,如果從包中匯入py檔案我們知道可以通過“白名單”__all__來指定誰可以被其他檔案匯入,我們發現generic包中指定了:
    from django.views.generic.base import RedirectView, TemplateView, View和__all__ = [ 'View',·····],到此第二個匯入解決
    
    # 那麼第一個這麼短這麼簡單又是為啥?
    3、同樣在views包的__init__.py檔案中發現:
    from django.views.generic.base import View
    __all__ = ['View']
    View類被__all__變數包裹了
    

ps:不管是DRF中的APIView還是亂七八糟的xxView,最後只要繼承了Django中的View就是檢視類

DRF之APIView和Request物件分析

APIView的執行流程

# 同樣和Django中一樣寫一個檢視類,只不過DRF中用APIView底層還是View
'''views.py'''
from rest_framework.response import Response
class Test(APIView):
    def get(self, request):
        data = {'status': 200, 'msg': 'success'}
        return Response(data)

    def post(self, request):
        data = {'status': 200, 'msg': 'success'}
        return Response(data)
'''urls.py'''
path('test/', views.Test.as_view())

APIView流程原始碼分析

1、路由:path('test/', views.Test.as_view()),path第二個引數任然返回函式記憶體地址,也還是類繫結方法,Test沒有as_view方法,去繼承的APIView中找,這次不需要和Django一樣去View中找了,慶幸的是APIView中有as_view方法,核心原始碼如下:

@classmethod
    def as_view(cls, **initkwargs):
        # 校驗反射的結果
         if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
            def force_evaluation():
                raise RuntimeError(
                    'Do not evaluate the `.queryset` attribute directly, '····
                )
           ···
        
        # 核心程式碼
        # 這裡呼叫了父類的as_view,檢視原始碼發現又回到了Django中的View類,所以本質還是和上面一樣,用到了閉包返回的view
        view = super().as_view(**initkwargs)
		···
        # 區域性去掉了csrf校驗和加裝飾器的效果是一樣的
        return csrf_exempt(view)

2、view = super().as_view(**initkwargs),這裡跳轉了一下,其實看了父類(View)的原始碼是和上面Django中寫檢視類繼承的View是一樣的,這裡的(APIView)的as_view只是進行了簡單處理和去掉了csrf中介軟體校驗,真實使用的還是View類中的as_view
3、然後還是閉包函式的返回值view加括號呼叫,傳入了當前物件的request,繼續執行了self.dicpatch(),當前類(Test)沒有去父類(APIview)找dispatch方法,發現APIView類中有,千萬注意了這裡可不是View中的dispatch方法了
4、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是一個,因為放到了self中
        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了,是新的
            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
    
    
總結:
1、路由:path('test/', views.Test.as_view())
2、APIView類的as_view執行,最終使用View類的as_view
3、執行閉包返回view加括號呼叫到此就是as_view加括號呼叫
4、呼叫執行了view()返回dispatch,但是這裡的父類不是View,是APIview所以執行的dispatch是APIView中的dispatch方法
5、dispatch方法中包裝了新的Request物件,以後檢視類中的方法傳入的request都是新的,無論三大認證還是檢視函式的方法,執行過程中出了異常,都會被處理掉
6、dispatch執行完畢返回reponse物件,跳轉回進入檢視函式繼續執行as_view去掉了csrf校驗

如何包裝了新的request?

# APIView中的dispatch方法處理老的request
request = self.initialize_request(request, *args, **kwargs)

# initialize_request方法,當前類(自己寫的)沒有去父類找,還是APIView
 def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        return Request(
            request, # 老的request,傳入的非處理的,返回後就是新的了
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
注意:上面返回的Request物件是rest_framework匯入的,然後例項化後返回的,例項化就少不了__init__建構函式
from rest_framework.request import Request
'''Request類'''
class Request:
    # 這裡即將要初始化的request是傳入的老的request
     def __init__(self, request, parsers=None, authenticators=None,negotiator=None, parser_context=None):
     ··· 
     # 初始化,將傳入的老的request放入新的_request中,所以request._request是老的,self.request._request等價於request._request
	    self._request = request
      ···
    
# 驗證一下新老request
class Test(APIView):
    def get(self, request):
        data = {'status': 200, 'msg': 'success'}
        # 新的request
        print(type(request)) # <class 'rest_framework.request.Request'>
        print(type(self.request))# <class 'rest_framework.request.Request'>
        # 老的request
        print(type(self.request._request)) # <class 'django.core.handlers.wsgi.WSGIRequest'>
        print(type(request._request)) # <class 'django.core.handlers.wsgi.WSGIRequest'>
        return Response(data)
    
新的:<class 'rest_framework.request.Request'>
老的:<class 'django.core.handlers.wsgi.WSGIRequest'>

三大認證如何執行?

# APIView類處理三大認證
self.initial(request, *args, **kwargs)
# 核心原始碼
def initial(self, request, *args, **kwargs):
    ····
    self.perform_authentication(request) # 認證
    self.check_permissions(request) # 許可權
    self.check_throttles(request) # 頻率
    ···
# 經過了三大認證才進入了檢視函式
中介軟體---路由---···---三大認證---檢視函式····
# 類似二次校驗

Request物件分析

這裡是經過包裝後的request

rest_framework.request.Request常用屬性和方法

這裡的request和原來的Django使用request一樣,只是多了一個request.data

  • request.data前端POST提交的資料,可以處理多種格式的資料,無論前端傳什麼編碼post提交的資料都在data中

    ps:原來提交的資料在request.POST裡,有侷限性只能處理urlencoded和formdata編碼格式,json格式不能處理,是沒有request.data的,request其餘使用方法的都一樣

# 如果用過新包裝過的request來呼叫原來的方法呢?這樣給使用者的感覺確實沒什麼變化原始碼如下:
'''重寫了getattr'''
    def __getattr__(self, attr):
        try:
            return getattr(self._request, attr)
        except AttributeError:
            return self.__getattribute__(attr)

重寫getattr的結果是,新的request.method執行的時候本質是request._request.method執行了原來老的request的方法,通過這句getattr(self._request, attr)反射,所以才一樣

總結新的request當老的用即可,只是多了個data前端post請求傳入的資料,三種編碼格式都可以獲取

驗證處理三種編碼格式

  • json格式,只有request.data可以獲取,結果是字典
  • form-data格式和urlencode格式都可以獲取並且是QueryDict物件
from rest_framework.response import Response
class Test(APIView):
    def post(self, request):
        data = {'status': 200, 'msg': 'success'}
        print(request.POST)
        print(request._request.POST)
        print(request.data)
        return Response(data)

驗證

# form-data格式結果
<QueryDict: {'name': ['hammer']}>
<QueryDict: {'name': ['hammer']}>
<QueryDict: {'name': ['hammer']}>
# urlencode格式結果
<QueryDict: {'name': ['hammer']}>
<QueryDict: {'name': ['hammer']}>
<QueryDict: {'name': ['hammer']}>
# json格式結果
<QueryDict: {}>
<QueryDict: {}>
{'name': 'Hammer'}

官網的一些解釋

image

原來的django中沒有request.data,造一個!

# 原來的django的request物件中沒有data,使得request.data-->無論什麼編碼格式,post提交資料,data都有值
from django.views import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
from functools import wraps
def outter(func):
    @wraps(func)
    def inner(request,*args,**kwargs):
        import json
        if request.POST:
            request.data = request.POST
        else:
            # 將request.body從json物件轉換為字典
            request.data = json.loads(request.body)
        res = func(request,*args,**kwargs)
        return res
    return inner

@method_decorator(outter,name='post')
class TestView(View):
    def get(self, request):
        return HttpResponse('get請求')

    
    def post(self, request):
   		# 只測試了json格式其餘都可以也測了
        print(request.data) # {'name': 'Hammer'}
        print(request.POST)  # <QueryDict: {}>
        return HttpResponse('post請求')

靈感來源:傳送門

相關文章