今日內容概要 昨日回顧 反序列化類校驗部分源碼解析(瞭解) 斷言 drf之請求(配置解析類) drf之響應(配置響應類,Resposne的源碼的屬性) 視圖組件介紹及兩個視圖基類與五個視圖擴展類的用法介紹 . . . . 昨日回顧 # 1 序列化類的常用欄位 -CharField 。。。。 -Lis ...
今日內容概要
- 昨日回顧
- 反序列化類校驗部分源碼解析(瞭解)
- 斷言
- drf之請求(配置解析類)
- drf之響應(配置響應類,Resposne的源碼的屬性)
- 視圖組件介紹及兩個視圖基類與五個視圖擴展類的用法介紹
.
.
.
.
昨日回顧
# 1 序列化類的常用欄位
-CharField
。。。。
-ListField
-DictField
-------------------------------
# 2 欄位參數
-max_length。。。
-min_value。。。
-required,default。。。
-read_only,write_only
-------------------------------
# 3 定製序列化欄位(改名字)
-source這個欄位參數
-如果是表模型自己的欄位,直接寫
-如果是關聯表欄位,通過外鍵.的方式 拿到
-------------------------------
# 4 定製序列化欄位(格式更豐富)
-在序列化類中使用:SerializerMethodField,配合一個 get_欄位名的方法,方法返回什麼,這個欄位就是什麼----》這種欄位以後不能用來,做反序列化了!!!
-在表模型中使用,寫方法的方式,方法返回什麼,這個欄位前端顯示的就是啥,序列化類中需要配合ListField,DictField----》這種欄位以後不能用來,做反序列化了!!!
-方法可以包裝成數據屬性加@property,也可以不加@property
-------------------------------
# 5 反序列化校驗幾層
-1 欄位自己的校驗規則:序列化類欄位參數控制的
-2 每個欄位單獨設置校驗方法(瞭解)
-3 局部鉤子
-4 全局鉤子
ser調用is_valid方法的時候,就會將4層校驗從上往下依次執行
ser調用save方法,觸發的是create或者update方法的執行!!
-------------------------------
# 6 ModelSerializer:繼承自Serializer
class BookSerializer(ModelSerializer):
# 如果欄位是映射過來的,也會把欄位屬性[反序列化校驗規則],也會映射過來,有可能會校驗失敗,不能寫入
# 咱們重寫這個欄位,不加任何規則,取消掉它
name = serializer.CharField()
class Meta:
model=Author
# fields="__all__"
# 只要是序列化的欄位和反序列化的任何欄位,都要在這註冊
# 序列化的欄位,可能不是表模型的欄位,是在表模型中寫的方法名
# 序列化的欄位,也可能是在序列化類裡面的,寫的定製化欄位,SerializerMethodField,也要註冊
fields=['name','photo','gender','addr']
extra_kwargs={'name':{'max_length': 8}} # 給欄位類增加屬性,read_only 和write_only用的多
gender=serializer.CharField()
addr=serializer.CharField()
# 局部鉤子,全局鉤子 和原來的用法完全一樣
# 在序列化類裡面寫定製化欄位方法的SerializerMethodField的用法和 原來也一樣
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass
-------------------------------
# 7 反序列化的欄位,一定跟表模型的欄位是對應的嗎? 不一定,隨意寫。
# 因為反序列化的欄位,可能是要存到兩張表裡面去的。
註意因為現在BookSerializer類繼承了ModelSerializer,
前端提交過來的數據包含了兩個表裡面的數據,但是序列化類只和Book模型表建立了關聯
只要反序列化,就必須要在fields裡面註冊一下。
但是在將book表裡面的欄位映射的序列化表裡面的時候,是不可能將另一個表裡面的欄位也映射過來的。
所以需要在序列化表裡面,把該欄位重寫一下就行了,如果需要欄位校驗也一樣在括弧裡面寫欄位的校驗參數,如果需要其他的邏輯校驗,就寫鉤子函數。
最後我們需要在序列化類裡面要重寫create方法,因為ModelSerializer封裝了create方法,但是該方法只能適用於前端傳的是關聯表裡面的欄位數據,會自動將前端的數據利用ORM寫到資料庫裡面去。
但是現在前端傳的是兩個表裡面的數據,所以我們要重寫create方法,自己將兩個表裡面的數據從validated_data裡面拿出來,利用ORM分別寫到兩張表裡面去!!!!!!
.
.
.
.
.
1 反序列化類校驗部分源碼解析(瞭解)
反序列化校驗,什麼時候開始執行校驗?
視圖類中的調用 ser.is_valid(), 就會執行校驗,校驗通過返回True,不通過返回False
----------------------------------------
ser是我們自己寫的BookSerializer序列化類的對象
入口:ser.is_valid() ------BookSerializer類裡面沒有is_valid------繼續找最終找到了父類-----BaseSerializer中的is_valid
註意視圖類post函數裡面 if ser.is_valid(): 括弧裡面寫raise_exception=True,就會直接拋異常!!
-----------------------------------------
-----------------------------------------
def is_valid(self, *, raise_exception=False):
if not hasattr(self, '_validated_data'):
try:
# self序列化類的對象,屬性中沒有_validated_data,一定會走下麵這句【核心】
# 這句走完後,對象就有該屬性了,下次對象再點is_valid方法時,下麵這句話肯定就不走了
# 所以is_valid被對象調用一次,與被對象調用多次的效果是一樣的!,只有第一次的時候會校驗
self._validated_data = self.run_validation(self.initial_data)
except ValidationError as exc:
self._validated_data = {}
self._errors = exc.detail
else:
self._errors = {}
if self._errors and raise_exception:
raise ValidationError(self.errors)
return not bool(self._errors)
-----------------------------------------
-----------------------------------------
# self._validated_data = self.run_validation(self.initial_data) 這行代碼的self是序列化類的對象
-切記一定不要按住ctrl鍵點擊名字,因為系統預設是基於當前名字所屬的類查找及往上找!!
-哪個對象調用的該方法,那麼該方法裡面self就是哪個對象,和self當前所在的函數所屬的類無關!!!
-所以真正的查找順序是,從self所指代的這個對象開始從下往上找,找不到,再往上,知道找到該方法!!!
-最終從Serializer類中找到了run_validation,而不是Field中的run_validation !!
-----------------------------------------
-----------------------------------------
def run_validation(self, data=empty):
# 執行欄位自己的,及validates方法,也就是第一層與第二層的校驗
(is_empty_value, data) = self.validate_empty_values(data)
if is_empty_value:
return data
# 局部鉤子----【局部鉤子】
value = self.to_internal_value(data)
try:
self.run_validators(value)
# 全局鉤子--》如果在BookSerializer中寫了全局鉤子validate,優先走它,非常簡單
value = self.validate(value)
except (ValidationError, DjangoValidationError) as exc:
raise ValidationError(detail=as_serializer_error(exc))
return value
-----------------------------------------
-----------------------------------------
# 局部鉤子 self.to_internal_value(data) ---》self是BookSerializer的對象,從根上找
# 最後在Serializer類中找到了
def to_internal_value(self, data):
ret = OrderedDict()
errors = OrderedDict()
fields = self._writable_fields
# fields 就是寫在序列化類中一個個欄位類的對象!!!
for field in fields:
# self 是BookSerializer的對象
# 還是用反射,反射出BookSerializer對象中的局部鉤子函數的函數名字
validate_method = getattr(self, 'validate_' + field.field_name, None)
try:
# 執行BookSerializer類中的鉤子函數方法,傳入了要校驗的數據
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
.
.
.
.
2 斷言
# 源碼中大量使用try和斷言
# 關鍵字assert ,有什麼作用,斷定你是什麼,如果是沒事,如果不是就拋異常
--------------------------------------------
name = 'zzz'
if name == 'lqz':
pass
else:
raise Exception('名字不為lqz,不能繼續走了')
print('後續代碼')
---------------------
上面代碼可以寫成這樣
name = 'zzz'
assert name=='lqz' # 斷定是,沒問題,代碼繼續往下走,如果不是,就主動拋異常!!
print('後續代碼')
----------------------------------------------
def is_valid(self, *, raise_exception=False):
# 斷言對象裡面有'initial_data'屬性,如果有就繼續往下走,如果沒有就主動拋異常報錯了
assert hasattr(self, 'initial_data')
if not hasattr(self, '_validated_data'):
try:
self._validated_data = self.run_validation(self.initial_data)
except ValidationError as exc:
self._validated_data = {}
self._errors = exc.detail
else:
self._errors = {}
# 判斷後面的代碼的結果的布爾值,True就正常繼續往下走,False就主動拋異常,不讓代碼往下走了!!! 斷言的主要目的就是如果是False,不讓代碼往下走了
--------------------------------------------------
.
.
.
.
3 drf之請求
3.1 Request能夠解析的前端傳入的編碼格式-------配置解析類
# 需求是該介面只能接收某種編碼格式,比如json格式,不能接收其他格式
---------------------------------------------
方式一,在繼承自APIView及其子類的的視圖類中配置(局部配置,只對該視圖類有效)
# 總共有三個:
from rest_framework.parsers import JSONParser,FormParser,MultiPartParser
class BookView(APIView):
parser_classes = [JSONParser,]
# 這樣一配置,以後該視圖類只能夠解析,用json格式編碼傳入的文件了!!用其他格式編碼傳入的文件就解析不了了!!!
# parser_classes 解析類 意思是能夠解析前端傳入的上面編碼格式(預設情況下配了3個解析類)
# drf解析類已經幫我們寫好了,我們只需要導入即可!
----------------------------------------------
方式二:在配置文件中配置(影響所有,全局配置)
-django有套預設配置,每個項目也有個配置
-drf也有套預設配置,每個項目也有個配置(就是django的配置文件)
------------------
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
# 'rest_framework.parsers.FormParser',
# 'rest_framework.parsers.MultiPartParser',
],
}
# 這樣在django的配置文件中寫一下,假如上面的視圖類裡面不配置解析類,在這裡配置的就對整個django項目生效了!!!
------------------------------------------------
方式三:全局配了1個解析類,某個視圖類想要配3個解析類,怎麼配?
-只需要在視圖類,配置3個即可
-因為:先從視圖類自身找,找不到,再去項目的drf配置(django項目配置)中找,再找不到,去drf預設的配置找
如果是json格式編碼,request.data就是普通dict
.
如果urlencoded,或form-data編碼,就是querydict
.
.
DRF的配置文件查找
lib---site-packges---rest-framework---settings.py文件裡面
.
.
.
這樣在django的配置文件中寫一下,在這裡配置的就對整個django項目生效了!!!
.
.
.
3.2 Request類有哪些屬性和方法(學過)
# 視圖類方法中的request
data
__getattr__
query_params
.
.
.
4 drf之響應
4.1 Response能夠響應的編碼格式-------配置響應類
drf 是djagno的一個app,所以要註冊!!!
drf的響應,如果使用瀏覽器和postman訪問同一個介面,返回格式是不一樣的
drf做了個判斷,如果是瀏覽器,好看一些,如果是postman只要核心的json數據
------------------------------------------
------------------------------------------
# 需求:現在想讓瀏覽器也返回的也像postman一樣,就返回核心的json數據,怎麼辦?
# 兩個響應類------drf的預設配置文件中找------兩個類
方式一:在視圖類中寫(局部配置,只對視圖類有效)
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
class BookView(APIView):
renderer_classes=[JSONRenderer,]
----------------------------------------------
方式二:在項目配置文件中寫(全局配置,整個項目有效)
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
}
------------------------------------------
方式三:使用順序(一般就用內置的即可)
優先使用視圖類中的配置,其次使用項目配置文件中的配置,最後使用內置的
drf 是djagno的一個app,所以要註冊!!!
.
.
.
.
4.2 Resposne的源碼屬性或方法
# drf 的Response 源碼分析
from rest_framework.response import Response
視圖類的方法返回時,retrun Response ,走它的 __init__
研究 init中可以傳什麼參數
------------------------------------------
# Response init 可以傳的參數
def __init__(self,
data=None,
status=None,
template_name=None,
headers=None,
exception=False,
content_type=None)
# data:之前咱們寫的ser.data 可以是字典或列表或字元串---》序列化後返回給前端---》前端在響應體中看到的就是這個
# status:http響應的狀態碼,預設是200,你可以改。
drf在status包下,把所有http響應狀態碼都寫了一遍,是個常量
from rest_framework.status import HTTP_200_OK
Response('dddd',status=status.HTTP_200_OK)
Response('dddd',status=200) # 這兩句話一個意思
# template_name:瞭解即可,修改響應模板的樣子,BrowsableAPIRenderer定死的樣子,後期公司可以自己定製
# headers:響應頭,http響應的響應頭,可以通過傳參的形式headers={'name': 'lqz'} 往響應頭裡面塞東西!!!
思考,原生djagno如何用4板斧向響應頭中加東西 ?
# 四件套 render,redirect,HttpResponse,JsonResponse
obj = HttpResponse('dddd')
obj['age'] = '18'
obj['name'] = 'lqz'
return obj
# content_type :響應編碼格式,一般不動
-----------------------------------------------
# 重點:data,status,headers
from rest_framework.status import HTTP_200_OK
.
用DRF的Response 往響應頭裡面加東西
.
用django的四板斧 往響應頭裡面加東西
.
.
.
.
5 視圖組件介紹 及 兩個視圖基類 與 五個視圖擴展類的 用法介紹
# 之前學過APIView,是drf的基類,是drf提供的最頂層的類
------------------------------------------
# APIView跟之前的View區別:
-傳入到視圖方法中的是REST framework的Request對象,而不是Django的HttpRequeset對象
-視圖方法可以返回 REST framework的Response對象
-任何APIException異常都會被捕獲到,並且處理成合適的響應信息
-在進行dispatch()分發前,會對請求進行身份認證、許可權檢查、頻率流量控制
------------------------------------------
# drf的 兩個視圖基類
APIVIew 與 GenericAPIView
------------------------------------------
# APIVIew的類裡面屬性:
renderer_classes # 響應格式類
parser_classes # 能夠解析的請求格式類
authentication_classes # 認證類
throttle_classes # 頻率類
permission_classes # 許可權類
.
.
.
.
5.1 第一層 用APIView + ModelSerializer + Resposne 寫5個介面
# 視圖類代碼
from .models import Book
from .serializer import BookSerializer
class BookView(APIView):
def get(self, request):
books = Book.objects.all()
ser = BookSerializer(instance=books, many=True)
return Response(ser.data)
def post(self, request):
ser = BookSerializer(data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '新增成功', 'result': ser.data})
# restful規範裡面要求把新增對策數據返回,怎麼返回新增的數據 ?
# 我們現在只有ser序列化類的對象,能不能拿到新增的對象?可以,ser.save方法運行會觸發create方法運行,create方法的返回值就是新增的對象
# 所以ser.data該方法的作用:就是將create方法的返回值也就是新增的表模型對象,再序列化成字典給前端
# 所以說雖然post方法整體是將前端的字元串轉化為對象的反序列化的過程,但是該方法最後還是有一步將對象序列化成字典的過程!!
# 所以序列化類中的create方法一定要返回新增的對象,否則ser.data方法沒拿到新增的對象,序列化也拿不到新增對象的數據!!!
else:
return Response({'code': 101, 'msg': ser.errors})
class BookDetailView(APIView):
def get(self, request, pk):
books = Book.objects.filter(pk=pk).first()
ser = BookSerializer(instance=books)
# 所以說ser=BookSerializer(XXX)只是一個生成對象給對象添加屬性的過程,正真將表模型對象轉成字典是ser.data在做!!!
return Response(ser.data)
def put(self, request, pk):
books = Book.objects.filter(pk=pk).first()
ser = BookSerializer(instance=books, data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '修改成功', 'result': ser.data})
else:
return Response({'code': 101, 'msg': ser.errors})
def delete(self, request, pk):
Book.objects.filter(pk=pk).delete()
return Response({'code': 100, 'msg': '刪除成功'})
-----------------------------------------------
-----------------------------------------------
-----------------------------------------------
# 序列化類
### ModelSerializer的使用
class BookSerializer(serializers.ModelSerializer):
# 跟表有關聯
class Meta:
model = Book
fields = ['name', 'price', 'publish_detail', 'author_list', 'publish', 'authors']
extra_kwargs = {'name': {'max_length': 8},
'publish_detail': {'read_only': True},
'author_list': {'read_only': True},
'publish': {'write_only': True},
'authors': {'write_only': True},
}
# ModelSerializer序列化類幫我們幹了好多事,原來用Serializer序列化類時,序列化欄位與反序列化欄位都要我們自己寫,現在用fields配一下,全部將模型表裡面的欄位都映射過來了
# 而且最騷的是模型表裡面的一對多的外鍵欄位映射過來自動變成了Integerfield,多對多的外鍵欄位映射過來自動變成了ListField的了!!
# 而且原來我們要自己寫create與update方法,將校驗過後的數據利用ORM語句寫入資料庫,現在全被ModelSerializer序列化類自動幫我們幹了!!!
-----------------------------------------------
-----------------------------------------------
-----------------------------------------------
# 路由
urlpatterns = [
path('admin/', admin.site.urls),
path('books/', views.BookView.as_view()),
path('books/<int:pk>/', views.BookDetailView.as_view()),
]
.
註意前端傳數據反序列新增或者修改的時候,用postman在請求體裡面寫數據的時候有點不一樣
比如json格式我們這樣寫
{"name":"紅樓","price":199,"publish":1,"authors":[1,3]}
但是如果用urlencode或者form-data格式寫的時候,對於多對多的authors外鍵欄位有點不一樣,
authors鍵對應的框裡面不能直接填一個[1,3],而是要把authors寫兩遍,一個對應1,一個對應3
這麼來操作
.
.
.
.
5.2 第二層 基於 GenericAPIView + ModelSerializer 寫5個介面
# 研究 如果我們要寫publish的5個介面,只要複製之前的代碼,改一部分,就可以快速寫出publish的5個介面,區別就只在於表模型與序列化類
# 可以通過繼承,少些代碼,GenericAPIView 繼承了APIView 並擴展了新的屬性與方法!!
'''
GenericAPIView 的屬性和方法:
屬性:
queryset: 要序列化或反序列化的表模型對象
serializer_class: 使用的序列化類
lookup_field : 查詢單條的 路由匹配的轉換器裡面的分組名
filter_backends: 過濾類的配置
pagination_class: 分頁類的配置
方法:
get_queryset() 獲取序列化的對象
get_object() 獲取單個對象
get_serializer() 獲取序列化對象
filter_queryset() 跟後續的排序由關係
'''
from rest_framework.generics import GenericAPIView
class BookView(GenericAPIView):
queryset = Book.objects.all()
# 原來我們是寫在函數裡面的,現在寫外面,在函數裡面通過self.queryset拿表模型對象
serializer_class = BookSerializer
def get(self, request):
# objs = self.queryset
# 這裡可以拿到,但是不要這麼用,GenericAPIView提供了另一個方法
objs = self.get_queryset()
# get_queryset()函數的返回值就是self.queryset,相當於套了一層殼
# 那不是多此一舉嗎?那你就淺了,這樣操作有個好處,可以在序列化類裡面重寫該get_queryset方法
# 然後在self.queryset裡面加點其他數據,或者剔除掉一些數據,那序列化的對象是不是就不一樣了!所以直接拿就寫死了,擴展性差了!!
# 調用get_queryset()前,就可以在拿前先重寫get_queryset()方法,對queryset對象先處理一下!!!
ser = self.get_serializer(instance=objs, many=True)
# 同理也是給self.serializer_class() 套了一層殼!!後期可以重寫get_serializer_class方法,通過返回值來控制所使用的序列化類
return Response(ser.data)
def post(self, request):
ser = self.get_serializer(data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '新增成功', 'result': ser.data})
else:
return Response({'code': 101, 'msg': ser.errors})
class BookDetailView(GenericAPIView):
queryset = Book.objects.all() # 原來我們是寫在函數裡面的,現在寫外面,在函數裡面通過self.queryset拿表模型對象
serializer_class = BookSerializer
# lookup_field = 'pk' # 如果路由匹配的轉換器裡面的分組名不是pk,要手動在這改一下,如果是pk這句話可以不寫!
def get(self, request, pk):
obj = self.get_object() # 獲取表模型的單條對象
ser = self.get_serializer(instance=obj)
return Response(ser.data)
def put(self, request, pk):
obj = self.get_object()
ser = self.get_serializer(instance=obj, data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '修改成功', 'result': ser.data})
else:
return Response({'code': 101, 'msg': ser.errors})
def delete(self, request, pk):
self.get_object().delete()
return Response({'code': 100, 'msg': '刪除成功'})
.
.
.
.
.
5.3 第三層 基於 5個視圖擴展類 + ModelSerializer 寫5個介面
# 視圖類代碼
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListModelMixin
# 基於GenericAPIView + 5個視圖擴展類寫介面
class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
class BookDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
# def get(self, request, pk):
# return self.retrieve(request,pk) 可以直接寫pk 也可以像下麵一樣寫,都行
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
-----------------------------------------------
-----------------------------------------------
-----------------------------------------------
# 序列化類代碼
class BookSerializer(serializers.ModelSerializer):
# 跟表有關聯
class Meta:
model = Book
fields = ['name', 'price', 'publish_detail', 'author_list', 'publish', 'authors']
extra_kwargs = {'name': {'max_length': 8},
'publish_detail': {'read_only': True},
'author_list': {'read_only': True},
'publish': {'write_only': True},
'authors': {'write_only': True},
}
-----------------------------------------------
-----------------------------------------------
-----------------------------------------------
# 路由
urlpatterns = [
path('admin/', admin.site.urls),
path('books/', views.BookView.as_view()),
path('books/<int:pk>/', views.BookDetailView.as_view()),
]
.
.
.
.
作業
# 1 研究反序列化源碼
# 2 整理斷言的使用
# 3 整理drf的請求與響應
# 4 基於APIView寫5個介面
基於GenericAPIView
基於GenericAPIView+5個視圖擴展類
# 5 剩下兩層
-第一層
class PublishView(CreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
class BookDetailView(DestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
-第二層帶5個介面
class PublishView(CreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 周六周天:刷 面向對象視頻