不會DRF?原始碼都分析透了確定不來看?
可以更方便的使用django寫出符合restful規範的介面
下載安裝
pip3 install djangorestframework
- pycharm下載
注意
rest_framework
是一個app需要註冊!
?官網:https://www.django-rest-framework.org/
-
drf安裝預設安裝的最新版本,如果django版本過低會自動升級到3.x版本
-
版本支援對應關係
示例
'''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請求
post請求
注意:前後端分離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測試
獲取所有
獲取一條
新增一條資料
全部修改:修改id為1的資料,這裡儘量在body體裡寫全引數
區域性修改:修改id為1的資料,修改啥寫啥
刪除一條資料:刪除id為2的資料
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'}
官網的一些解釋
原來的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請求')
靈感來源:傳送門