DRF的序列化組件
首先我們要知道序列化是干嘛的,在此之前我們應該知道json格式的數據,一般在前后端交互或者是跨平台交互的時候,會默認使用Json格式拉進行數據的傳輸,所以當我們把普通的數據轉換成json格式的時候就會使用序列化組件,將其序列化成json格式,然后前端接收到json格式之后,再反序列化把json格式數據轉換成普通格式的數據,再進行邏輯運算,判斷以及渲染頁面等.
而DRF的序列化組件,所完成的功能也是如此,負責將對象數據序列化前台所需要的數據,或者反序列化前台的數據,進行校驗,來確保數據的安全.
下面我們就介紹三種DRF中最常用的序列化組件,Serializer,ModelSerializer,以及ListModelSerializer.
Serializer組件
在使用Serializer組件之前,我們要先生成一個序列化器,在我們django的項目名(這里我們定義項目名為api)下面新建一個serializer.py文件,在里面繼承serializers,生成序列器,供我們在視圖函數里面調用:
#/api/models.py
from django.db import models
class User(models.Model):
CHOICES_SEX = ((0, '男'), (1, '女'))
name = models.CharField(max_length=64)
pwd = models.CharField(max_length=64, null=True)
age = models.IntegerField(default=0)
height = models.DecimalField(max_digits=5, decimal_places=2, default=0)
icon = models.ImageField(upload_to='icon', default='default.png')
sex = models.IntegerField(choices=CHOICES_SEX, default=0)
#/api/serializers.py,在這里生成序列化器
from rest_framework import serializers
class UserSerializer(serializers.Serializer):
'''
這里是聲明序列化類,都是models.py已有的字段
如果要參與序列化,這里的字段名字一定要和models.py里面的屬性同名,如果不參與序列化,就不要在這里聲明字段
'''
name = serializers.CharField()
age = serializers.IntegerField()
height = serializers.DecimalField(max_digits=5, decimal_places=2)
'''
下面是自定義序列化字段,序列化的屬性值由方法來提供,
方法的名字:固定為 get_屬性名,
方法的參數:self為序列化對象,obj為序列化的model對象
注意 : 建議自定義序列化字段名不要與model已有的屬性名重名,否則會覆蓋model已有字段的聲明
注意 : 自定義序列化字段要用SerializerMethodField()作為字段類型
'''
gender = serializers.SerializerMethodField()
def get_gender(self, obj):
return obj.get_sex_display()
在完成以上序列化器生成之后,我們就可以在views.py里面去定義我們的方法,從而來使用序列化器,當然,在views.py定義之前,我們需要在urls.py里面寫入路由匹配關系,實例如下,分為序列化和反序列化:
序列化
序列化數據通常是通get請求里面取出來的,常用來查詢數據庫
# api/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^v1/users/$', views.UserAPIView.as_view()),
url(r'^v1/users/(?P<pk>\d+)/$', views.UserAPIView.as_view()),
]
# api/views.py,
'''在視圖函數里寫我們的業務邏輯,大致分三步:
1. 通過ORM操作數據庫取到前端需要的數據
2. 將數據序列化
3. 將序列化后的數據返回給前端
'''
from rest_framework.views import APIView
from rest_framework.response import Response
from django.conf import settings
from . import models
class UserAPIView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
# 這里是單查的寫法,即前端發來的url里面有拼接參數的時候,會走if里面的流程,即我們有匹配條件,通過這些條件去數據庫里面查詢相應的數值
if pk:
# 1. 通過ORM取數據
user_obj = models.User.objects.filter(pk=pk).first()
if not user_obj:
return Response({
'status': 1,
'msg': '單查 error'
})
# 2. 將數據序列化
# 序列化時里面除了需要序列化的對象以外,還有一個many參數,該參數的意義是: 如果要序列化的數據是單個對象,many=False,如果要序列化的對象數據是多個對象,many=True
user_ser = serializers.UserSerializer(user_obj, many=False)
user_data = user_ser.data
# class MySerializer: pass
# user_data = MySerializer(user_obj)
# 3. 把序列化后的數據返回給前端
return Response({
'status': 0,
'msg': '單查 ok',
'results': user_data
})
# 群查,即前端發來的url連接不攜帶拼接參數,會將所有的數據都查出來
# 1. 通過ORM取數據
user_query = models.User.objects.all()
# 2. 將數據序列化
user_list_data = serializers.UserSerializer(user_query, many=True).data
# 3. 把序列化后的數據返回給前端
return Response({
'status': 0,
'msg': '群查 ok',
'results': user_list_data
})
反序列化
反序列化我們需要生成另一個序列化生成器,反序列化的數據一般是從post請求里面取出來的,且通常用來增加數據庫里面的數據
# /api/serializers.py,生成反序列化生成器
class UserDeserializer(serializers.Serializer):
'''
反序列器的生成要注意以下幾點:
1. 系統的字段,可以在Field類型中設置系統校驗規則,比如(name=serializers.CharField(min_length=3))
2. required校驗規則絕對該字段是必校驗還是可選校驗字段(默認required為True,數據庫字段有默認值或可以為空的字段required可以賦值為False)
3. 自定義的反序列字段,設置系統校驗規則同系統字段,但是需要在自定義校驗規則中(局部、全局鈎子)將自定義反序列化字段取出(返回剩余的數據與數據庫交互)
4. 局部鈎子的方法命名 validate_屬性名(self, 屬性的value),校驗規則為 成功返回屬性的value 失敗拋出校驗錯誤的異常
5. 全局鈎子的方法命名 validate(self, 所有屬性attrs),校驗規則為 成功返回attrs 失敗拋出校驗錯誤的異常
'''
name = serializers.CharField(min_length=3, max_length=64, error_messages={
'required': '姓名必填',
'min_length': '太短',
})
pwd = serializers.CharField(min_length=3, max_length=64)
# 系統可選的反序列化字段:沒有提供不進行校驗(數據庫中有默認值或可以為空),提供了就進行校驗
age = serializers.IntegerField(min_value=0, max_value=150, required=False)
# 自定義反序列化字段:一定參與校驗,且要在校驗過程中,將其從入庫的數據中取出,剩余與model對應的數據才會入庫
re_pwd = serializers.CharField(min_length=3, max_length=64)
# 自定義校驗規則:局部鈎子,全局鈎子
# 局部鈎子:validate_字段名(self, 字段值)
# 規則:成功返回value,失敗拋異常
def validate_aaa(self, value):
if 'g' in value.lower():
raise serializers.ValidationError('名字中不能有g')
return value
# 全局鈎子:validate(self, 所有校驗的數據字典)
# 規則:成功返回attrs,失敗拋異常
def validate(self, attrs):
# 取出聯合校驗的字段們:需要入庫的值需要拿到值,不需要入庫的需要從校驗字段中取出
pwd = attrs.get('pwd')
re_pwd = attrs.pop('re_pwd')
if pwd != re_pwd:
raise serializers.ValidationError({'re_pwd': '兩次密碼不一致'})
return attrs
# create重寫,完成入庫
def create(self, validated_data):
return models.User.objects.create(**validated_data)
# /api/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django.conf import settings
from . import models
class UserAPIView(APIView):
def post(self, request, *args, **kwargs):
'''
反序列化通常也有三步操作:
1. 從請求對象中拿到前台的數據
2. 校驗前台數據是否合法
3. 反序列化成后台Model對象與數據庫交互
'''
request_data = request.data
# 反序列化時里面除了需要反序列化的對象以外,同樣一個many參數,如果要序列化的數據是單個對象,many=False,如果要序列化的對象數據是多個對象,many=True,不寫的話默認是False
user_ser = serializers.UserDeserializer(data=request_data)
# 調用反序列化的校驗規則有兩種,即系統規則和自定義規則(局部鈎子,全局鈎子)
result = user_ser.is_valid()
if result:
# 校驗通過,可以與數據庫進行交互:增(create),改(update)
user_obj = user_ser.save()
return Response({
'status': 0,
'msg': 'ok',
'results': serializers.UserSerializer(user_obj).data
})
else:
# 校驗失敗,返回錯誤信息
return Response({
'status': 1,
'msg': user_ser.errors
}, status=status.HTTP_400_BAD_REQUEST)
以上就是Serializer組件序列化以及反序列化的用法和注意事項,實際上這是一種較底層的用法,所以我們在生產中常用ModelSerializer來完成數據的序列化和反序列化,整個的代碼量會更少,開發效率更高.
ModelSerializer組件
ModelSerializer組件除了Serializer組件所有的功能以外,另外提供了一些功能,比如:
- 可以基於模型類自動生成一系列字段,無需手動生成
- 可以基於模型類自動為Serializer生成validators,比如uinque_together
- ModelSerializer組件會自動實現默認的create()和update()功能,無需手動去實現
序列化和反序列化
# ModelSerializer可以將序列化和反序列化的功能整合成一個類,這個類繼承自rest_framework.serializers.ModelSerializer
# api/serializers.py
from rest_framework.serializers import ModelSerializer
from . import models
class UserModelSerializer(ModelSerializer):
'''
該生成器包括三個部分:
1. Meta子類:
里面用model來綁定關聯model表
用fields來設置所有的序列化反序列化字段
用extra_kwargs來設置系統的校驗規則,比如長短,報錯信息提示等
2. 局部鈎子
3. 全局鈎子
'''
'''
該生成器里可以完成的事情:
1. 將序列化類與Model類進行綁定
2. 設置序列化與反序列化所有字段(並划分序列化字段與反序列化字段)
3. 設置反序列化的局部鈎子與全局鈎子
'''
# 自定義反序列化字段,校驗規則只能在聲明自定義反序列化字段時設置,且一定是write_only
re_pwd = serializers.CharField(min_length=3, max_length=64, write_only=True)
class Meta:
model = models.User
fields = ['name', 'age', 'height', 'gender', 'pwd', 're_pwd']
extra_kwargs = {
'name': {
'required': True,
'min_length': 3,
'error_messages': {
'min_length': '太短'
}
},
'age': {
'required': True, # 數據庫有默認值或可以為空字段,required默認為False
'min_value': 0
},
'pwd': {
'required': True,
'write_only': True, # 只參與反序列化,這里注意,required不能和read_only一起使用,規則會沖突
},
'gender': {
'read_only': True, # 只參與序列化
},
}
def validate_name(self, value):
if 'g' in value.lower():
raise serializers.ValidationError('名字中不能有g')
return value
def validate(self, attrs):
pwd = attrs.get('pwd')
re_pwd = attrs.pop('re_pwd')
if pwd != re_pwd:
raise serializers.ValidationError({'re_pwd': '兩次密碼不一致'})
return attrs
# 項目名/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^v2/users/$', views.UserV2APIView.as_view()),
url(r'^v2/users/(?P<pk>\d+)/$', views.UserV2APIView.as_view()),
]
#/api/views.py
from rest_framework.views import APIView
class UserV2APIView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
# 單查,即url鏈接有拼接內容
if pk:
user_obj = models.User.objects.filter(pk=pk).first()
if not user_obj:
return Response({
'status': 1,
'msg': '單查 error'
})
# 完成序列化
user_data = serializers.UserModelSerializer(user_obj, many=False).data
return Response({
'status': 0,
'msg': '單查 ok',
'results': user_data
})
# 群查,url鏈接沒有拼接內容,全查
user_query = models.User.objects.all()
# 完成序列化
user_list_data = serializers.UserModelSerializer(user_query, many=True).data
return Response({
'status': 0,
'msg': '群查 ok',
'results': user_list_data
})
# 單增
def post(self, request, *args, **kwargs):
request_data = request.data
user_ser = serializers.UserModelSerializer(data=request_data)
# 校驗失敗,直接拋異常,反饋異常信息給前台,只要校驗通過,代碼才會往下執行
result = user_ser.is_valid()
if result:
user_obj = user_ser.save()
return Response({
'status': 0,
'msg': 'ok',
'results': serializers.UserModelSerializer(user_obj).data
})
else:
# 校驗失敗,返回錯誤信息
return Response({
'status': 1,
'msg': user_ser.errors
}, status=status.HTTP_400_BAD_REQUEST)
自定義Response方法
在上面的序列化與反序列化方法中,我們多次用到return Response({})返回值,且里面的內容有諸多相似,經常都有status,msg,results等,所以其實我們可以自定義一個Response方法,繼承自原來DRF的response方法,並對其做二次封裝,類似於我們之前所了解的把相似類提取出來生成一個基類,別的都繼承自該基類即可.
所以我們在應用名下面新建一個response.py文件,用以寫我們二次封裝的Response方法,如下:
# api/response.py
from rest_framework.response import Response # 這里導入DRF原本的Response,並在下面作為父類導入,繼承
class APIResponse(Response):
'''
這里我們繼承自父類的init,然后重寫其中的__init__,其中status和msg給默認值,然后其余默認值均為None,最后的**kwargs可以接收多余的所有鍵值對並傳給前端
'''
def __init__(self,status=0,msg='ok',results=None,http_status=None,headers=None,exception=False,**kwargs):
# data里面所寫的是response的基礎數據狀態碼和數據狀態信息
data = {
'status':status,
'msg':msg
}
# results是后端傳給前端的數據,如果有的話,就在data里面添加,一起返回給前端,如果沒有就不添加
if results is not None:
data['results']=results
# 更新**kwargs里面接收到的所有鍵值對,並添加到data里面
data.update(**kwargs)
# 下面是直接調用父類的init方法,然后把相應的數據賦值進去
super().__init__(data=data,status=http_status,headers=headers,exception=exception)
# 在封裝完我們自己的response之后,我們就可以在View.py方法里面導入然后直接使用了
# /api/views.py
from .response import APIResponse
from rest_framework import status
return Response({
'status': 1,
'msg': user_ser.errors
}, status=status.HTTP_400_BAD_REQUEST)
# 上面這個就可以改成下面這種一行的形式,可以極大簡化代碼量.
return APIResponse(1,user_ser.errors,http_status=status.HTTP_400_BAD_REQUEST)
基表相關
基表和基類的概念比較相似,其實就是定義一個繼承自models.Model的類,然后用到的表類里面都繼承該基表,從而減少代碼量,節省操作.
基表配置的關鍵屬性是abstract=True,且要定義在內部的Meta類里面,實例如下:
# api/models.py
class BaseModel(models.Model):
create_time = models.DateTimeField(auto_now_add=True)
class Meta:
# 該屬性就表示該表是基表,可以供普通Model類繼承使用
# 另外還有一點就是,設置了abstract的表類不會在執行數據庫遷移命令(makemigration | migrate)的時候新建表
abstract = True
DRF中ORM的多表關聯操作
外鍵設計
跳出DRF這個概念來說的話,ORM的多表關聯我們應該是知道一些的,多表關系一共三種,其外鍵常存在的位置也不盡相同,如下:
- 一對多關系:外鍵放在多的一方,也就是一對多關系中多的那一方
- 多對多關系:外鍵放在常用的一方
- 一對一關系:外鍵放在不常用的一方
而跨表操作我們通常也有一個口訣,就是正向跨表直接點屬性,反向跨表表名小寫加屬性.
DRF中的跨表操作有些區別,使用起來更加簡單,不過models里面定義的時候需要加上related_name反向查詢字段,實例如下:
# api/models.py
class Author(BaseModel):
name = models.CharField(max_length=16)
class AuthorDetail(BaseModel):
mobile = models.CharField(max_length=11)
# 這里正向查詢點Author,反向查詢直接點detail即可,
author = models.OneToOneField(to='Author',related_name='detail')
斷級聯
級聯我們都知道是什么,級聯的意義在於兩個有級聯關系的表,一旦一個數據被刪除了,跟其級聯相關的另一個表的數據也會被刪除,實際上這對於數據的增刪來說是非常麻煩的一件事,所以在DRF中我們要執行斷級聯這種操作,斷級聯的優點在於:
- 表與表之間不會再有外鍵關聯,但是有邏輯關聯
- 斷級聯之后不會影響數據庫查詢表的效率,但是會極大的提高數據庫的增刪改的效率
- 斷級聯之后一定要通過邏輯代碼來保證表與表之間數據的安全,而且有特定的關鍵字
on_delete來表示其不同的級聯關系
DRF的models.py里面斷級聯的參數為db_constraint,我們將其設置為False即可斷級聯,不同級聯關系的表示方式如下:
# /api/models.py
'''
我們假設四種情況,可以用四種on_delete級聯方式:
'''
# 1. on_delete=models.CASCADE,作者和作者詳情表是一對一級聯,且詳情表會隨着作者表的刪除而刪除,就需要在詳情表里面的author字段里加入該屬性
class AuthorDetail(BaseModel):
author=models.OneToOneField(to='Author',db_constraint=False,on_delete=models.CASCADE)
# 2. on_delet=models.DO_NOTHING,假設有出版社表和圖書表,是一對多的關系,且圖書不會隨着出版社的刪除而隨之刪除,那么就可以設置on_delete=models.DO_NOTHING
class Book(BaseModel):
publish = models.ForeignKey(to='Publish', related_name='books', db_constraint=False, on_delete=models.DO_NOTHING)
# 3. null=True,on_delete=models.SET_NULL,假設有部門表和員工表,是一對多的關系,員工沒有部門,可以為空
class Employee(BaseModel):
section = models.ForeignKey(to='Section',related_name='employee',db_constraint=False,null=True,on_delete=models.SET_NULL)
# 4. default=0,on_delete=models.SET_DEFAULT,一樣有部門表和員工表,在部門表刪除的時候,員工不會為空部門,而是自動進入一個默認的部門,即default值
class Employee(BaseModel):
section = models.ForeignKey(to='Section',related_name='employee',db_constraint=False,default=0,on_delete=models.SET_DEFAULT)
注意一點的是,只有一對一和一對多關系的表才有on_delete字段,多對多關系的表沒有這個字段,要注意.
