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

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

image

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

下載安裝

  • pip3 install djangorestframework
  • pycharm下載

注意

  • rest_framework是一個app需要註冊!

👉官網://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->//127.0.0.1:8000/books/
-查詢一個---》get->//127.0.0.1:8000/books/1/
-新增一個---》post->//127.0.0.1:8000/books/  #在body中帶數據
-修改-----》put,patch--->實際編碼中,基本都用put
  	//127.0.0.1:8000/books/1/ body體中傳入修改的數據
-刪除一個---》delete-->//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請求')

靈感來源:傳送門