內容概要
- ApiView+JsonResponse編寫介面
- ApiView+Response編寫介面
- ApiView原始碼解析
- Request物件原始碼分析
- 序列化器介紹和快速使用/反序列化
- 反序列化的校驗
ApiView+JsonResponse編寫介面
我們還是在models模型層中建立一個book類
裡面包含 name/price欄位
我們就使用django自帶的sqlite資料庫即可
'modles.py'
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.CharField(max_length=32)
'views'
from django.shortcuts import render
from rest_framework.views import APIView # 匯入APiView
from django.http import JsonResponse
from app01 import models
class BookView(APIView): # 繼承APIView類
def get(self, request):
book_queryset = models.Book.objects.all()
book_list = []
for book_obj in book_queryset: book_list.append({'name':book_obj.name,'price':book_obj.price})
return JsonResponse(book_list,safe=False)
# 因為在返回列表格式資料的時候會出現問題,需要將safe=False
'urls'
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('book/api/v1/',views.BookView.as_view()),
]
ApiView+Response編寫介面
與JsonResponse基本一致,只是在返回資料時,不需要進行safe=False的操作
def get(self,request):
book_queryset = models.Book.objects.all()
book_list = []
for book_obj in book_queryset:
book_list.append({'name':book_obj.name,'price':book_obj.price})
return Response(book_list)
ApiView原始碼解析
我們接下來分析以下APIView和View到底有什麼區別?
首先是APIView的執行流程:
路由層:path('book/api/v1/',views.BookView.as_view()),
請求來了,執行views.BookView.as_view()()
'as_view()是APIView中的方法,並不是View的了!'
# 呼叫父類的as_view()方法,APIView繼承的是View
view = super().as_view(**initkwargs)
# 拿到父類中的閉包函式view
as_view() 方法 返回了 >>>> return csrf_exempt(view)
# csrf_exempt 排除所有csrf的認證
# 相當於在所有的方法上面加了這個裝飾器
路由匹配成功,執行 csrf_exempt(view)(requets)
相當於回到了View類中,執行閉包函式view,返回的是
return self.dispatch(request, *args, **kwargs)
這個self就是我們檢視函式中編寫的BookView類產生的物件
所以執行物件.dispatch,因為這個BookView繼承了APIView
正好在APIView中也有dispatch方法
def dispatch(self, request, *args, **kwargs):
# request是django原生的request,老的request
request = self.initialize_request(request, *args, **kwargs)
self.request = request
# 把老的request包裝成了新的request,這個是drf提供的Request類的物件
try:
# 執行了三大認證(認證,頻率,許可權)使用新的request
self.initial(request, *args, **kwargs)
接下來跟CBV原始碼都一樣了
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
# 將新的request傳入,檢視類中的get的request也是新的request
response = handler(request, *args, **kwargs)
except Exception as exc:
# 在執行3大認證和檢視類中方法的過程中,如果出了異常,都能捕獲到---》全域性異常捕獲
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
# 總結
1 去除了所有的csrf
2 包裝了新的request,以後在檢視類中用的request是新的request Request類的物件,不是原生的了
-原生的在:新的requets._request
3 在執行檢視類的方法之前,執行了3大認證
4 如果在3大認證或檢視函式方法執行過程中出了錯,會有異常捕獲>>>全域性異常捕獲
5 以後檢視類方法中的request都是新的了
Request原始碼分析
在Request類中有__getattr__魔法方法
如果我們使用新的request.method方法就會觸發Request中的__getattr__方法
def __getattr__(self, attr):
try:
return getattr(self._request, attr) # 會從老的request中獲取method方法
except AttributeError:
return self.__getattribute__(attr)
-request.data--->這是個方法,包裝成了資料屬性
-以後無論post,put 在body中提交的資料,都從request.data中取,取出來就是字典
-無論是那種編碼格式
-request.query_params--->這是個方法,包裝成了資料屬性
-get請求攜帶的引數,以後從這裡面取
-query_params:查詢引數--->restful規範請求地址中帶查詢引數
-request.FILES--->這是個方法,包裝成了資料屬性
-前端提交過來的檔案,從這裡取
它們都攜帶了@property裝飾器,將方法偽裝成了屬性
# Request類總結
-1 新的request用起來,跟之前一模一樣,因為新的取不到,會取老的__getattr__
-2 request.data 無論什麼編碼,什麼請求方式,只要是body中的資料,就從這裡取(字典格式)
-3 request.query_params 就是原來的request._request.GET
-4 上傳的檔案從request.FILES中取
序列化器介紹和快速使用
我們在編寫介面時,經常需要使用到序列化和反序列化
並且在反序列化過程中需要進行資料校驗
drf中直接提供了固定的寫法,我們只需要按照固定寫法編寫即可完成上述需求
drf提供了兩個類 Serializer/ModelSerializer
以後我們只需要編寫自己的類,繼承drf提供的序列化類
使用其中的某些方法即可
序列化基本使用>>>序列化多條資料
**serializer.py >>> BookSerializer類
from rest_framework import serializers
class BookSerializer(serializers.Serializer):
name = serializers.CharField() # 只需要填寫需要序列化的欄位即可
price = serializers.CharField()
views檢視 >>> BookView類
def get(self,request):
book_queryset = models.Book.objects.all()
serializer_obj = serializer.BookSerializer(instance=book_queryset,many=True)
# instance 引數是指定需要序列化的物件,
# many 引數是如果有多個物件就需要將many引數改為True,預設為False
return Response(serializer_obj.data)
urls路由層 >>> urlpatterns
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('book/api/v1/',views.BookView.as_view()),
]
序列化基本使用>>> 反序列化單條資料(新增)
**serializer.py >>> BookSerializer類
from rest_framework import serializers
class BookSerializer(serializers.Serializer):
name = serializers.CharField() # 只需要填寫需要序列化的欄位即可
price = serializers.CharField()
# 新增資料需要我們自己編寫create方法來進行儲存
def create(self, validated_data):
# validated_data >> 校驗完成的資料
book_obj = models.Book.objects.create(**validated_data)
return book_obj
views檢視 >>> BookView類
def post(self,request):
# 因為是新增資料我們需要將前端傳遞來的資料反序列化
serializer_obj = serializer.BookSerializer(data=request.data)
if serializer_obj.is_valid(): # 校驗資料是否合法
# 合法的資料透過ser_obj點save方法自動呼叫序列化檔案中的create方法
serializer_obj.save()
return Response({"code":100,"msg":'新增成功','result':serializer_obj.data})
else:
return Response({"code":101,'msg':serializer_obj.errors})
urls路由層 >>> urlpatterns
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('book/api/v1/',views.BookView.as_view()),
]
序列化基本使用>>> 序列化單條資料(查詢)
**serializer.py >>> BookSerializer類
from rest_framework import serializers
class BookSerializer(serializers.Serializer):
name = serializers.CharField() # 只需要填寫需要序列化的欄位即可
price = serializers.CharField()
views檢視 >>> BookDetailView類
class BookDetailView(APIView):
def get(self, request, pk):
book_obj = models.Book.objects.filter(pk=pk).first()
serializer_obj = serializer.BookSerializer(instance=book_obj)
return Response(serializer_obj.data)
urls路由層 >>> urlpatterns
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('book/api/v1/',views.BookView.as_view()),
path('book/api/v1/<int:pk>/',views.BookDetailView.as_view()),
]
序列化基本使用>>> 反序列化資料(修改單條資料)
**serializer.py >>> BookSerializer類
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.CharField()
def create(self, validated_data):
# validated_data >> 校驗完成的資料
# name = validated_data.get('name')
# price = validated_data.get('price')
book_obj = models.Book.objects.create(**validated_data)
return book_obj
def update(self, instance, validated_data):
# instance就是需要序列化的資料物件
instance.name = validated_data.get('name')
instance.price = validated_data.get('price')
instance.save()
return instance # 返回序列化資料物件
views檢視 >>> BookDetailView類
def put(self,request,pk):
book_obj = models.Book.objects.filter(pk=pk).first()
serializer_obj = serializer.BookSerializer(instance=book_obj,data=request.data)
if serializer_obj.is_valid():
serializer_obj.save() # 會自動呼叫update方法進行修改操作
return Response({"code": 103, "msg": '修改成功', 'result': serializer_obj.data})
else:
return Response({"code": 104, 'msg': serializer_obj.errors})
urls路由層 >>> urlpatterns
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('book/api/v1/',views.BookView.as_view()),
path('book/api/v1/<int:pk>/',views.BookDetailView.as_view()),
]
刪除單條資料 (不需要序列化/反序列化)
views檢視 >>> BookDetailView類
class BookDetailView(APIView):
def delete(self, requset, pk):
Book.objects.filter(pk=pk).delete()
return Response({'code': 100, 'msg': '刪除成功'})
反序列化資料校驗
在我們的serializer.py檔案中編寫反序列化的校驗
# 全域性鉤子 # 校驗過後的資料,價格不能超過90
def validate(self, attrs):
if int(attrs.get('price')) >= 90:
raise ValidationError('書價格不能超過90')
else:
return attrs
# 區域性鉤子 校驗名字不能以1開頭
def validate_name(self, name):
if name.startswith('1'):
raise ValidationError('不能以1開頭')
else:
return name
如果滿足上述函式報錯條件,serializer_obj.is_valid() 不成立!
練習
原生的request 沒有data 屬性 實現一個原生的 request.data
拿出無論什麼編碼格式提交的資料(編寫FBV即可)
views.py
def method(func):
def inner(request,*args,**kwargs):
try:
# 如果報錯說明資料不是Json格式
request.data = request.body
except:
request.data = request.POST
res = func(request)
return res
return inner
@method
def login(request):
print(request.data)
print(request.content_type)
return render(request,'login.html',locals())