drf序列化
在前後端不分離的專案中,可以使用Django
自帶的forms
元件進行資料驗證,也可以使用Django
自帶的序列化元件對模型表資料進行序列化。
那麼在前後端分離的專案中,drf
也提供了資料驗證與序列化,相比於Django
原生的序列化它更加強大與易用。
準備工作
註冊drf
首先第一步,我們需要在專案全域性資料夾中註冊drf
INSTALLED_APPS = [
'app01.apps.App01Config',
'rest_framework', # 註冊drf
]
模型表
下面是專案中的模型表。
學生和班級是一對多
班級和老師是多對多
班級和班主任是一對一
from django.db import models
# Create your models here.
class Student(models.Model):
student_id = models.AutoField(primary_key=True, verbose_name="學生編號")
student_name = models.CharField(max_length=8, verbose_name="學生姓名")
student_gender = models.BooleanField(
choices=([0, "male"], [1, "female"]), default=0, verbose_name="學生性別")
student_class = models.ForeignKey(
to="Classes", verbose_name="所屬班級", on_delete=models.CASCADE) # 一個班級多個學生
def __str__(self):
return self.student_name
class Meta:
db_table = ''
managed = True
verbose_name = 'Student'
verbose_name_plural = 'Students'
class Classes(models.Model):
class_id = models.AutoField(primary_key=True, verbose_name="班級編號")
class_name = models.CharField(max_length=8, verbose_name="班級名稱")
class_teacher = models.OneToOneField(
to="Teacher", verbose_name="班主任", on_delete=models.CASCADE) # 一個班級只有一個班主任
def __str__(self):
return self.class_name
class Meta:
db_table = ''
managed = True
verbose_name = 'Classes'
verbose_name_plural = 'Classess'
class Teacher(models.Model):
teacher_id = models.AutoField(primary_key=True, verbose_name="教師編號")
teacher_name = models.CharField(max_length=8, verbose_name="教師姓名")
teacher_class = models.ManyToManyField(
to="Classes", verbose_name="任教班級") # 一個班級中可有多個老師,一個老師也可以在多個班級中任教
def __str__(self):
return self.teacher_name
class Meta:
db_table = ''
managed = True
verbose_name = 'Teacher'
verbose_name_plural = 'Teachers'
資料展示
學生資訊如下:
mysql> select * from app01_student;
+------------+--------------+----------------+------------------+
| student_id | student_name | student_gender | student_class_id |
+------------+--------------+----------------+------------------+
| 1 | 學生1 | 1 | 1 |
| 2 | 學生2 | 1 | 2 |
| 3 | 學生3 | 1 | 2 |
| 4 | 學生4 | 0 | 1 |
+------------+--------------+----------------+------------------+
教師資訊如下:
mysql> select * from app01_teacher;
+------------+--------------+
| teacher_id | teacher_name |
+------------+--------------+
| 1 | 王老師 |
| 2 | 李老師 |
| 3 | 張老師 |
+------------+--------------+
班級資訊如下:
mysql> select * from app01_classes;
+----------+--------------+------------------+
| class_id | class_name | class_teacher_id |
+----------+--------------+------------------+
| 1 | 高一一班 | 1 |
| 2 | 高二一班 | 2 |
+----------+--------------+------------------+
教師與班級關係如下:
mysql> select * from app01_teacher_teacher_class;
+----+------------+------------+
| id | teacher_id | classes_id |
+----+------------+------------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 2 | 2 |
| 5 | 3 | 1 |
| 6 | 3 | 2 |
+----+------------+------------+
url書寫
接下來書寫url
,以查詢學生模型表為例,為了符合framework
規範,所以要有一個有名分組來接收可能獲取/修改/刪除的編號。
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/students/(?P<sid>\d+)?', views.Student.as_view()),
]
# 新增問號,代表可有,可沒有
自定義序列器
使用自定義序列器。可以更加靈活的使用序列化及反序列化。也是推薦使用的方式。
建立序列類
序列類的作用如下:
- 序列化時,可選擇序列化的模型表欄位
- 反序列化時,可選擇對資料驗證的規則,類似於forms元件
from .models import Student
from rest_framework import serializers
class StudentSerializer(serializers.Serializer):
student_id = serializers.CharField()
student_name = serializers.CharField()
student_gender = serializers.BooleanField()
student_class = serializers.CharField()
進行序列化
接下來進行序列化,首先要書寫檢視函式。
由於我們每次都需要返回一個狀態碼,以及內容包裝,所以可以建立一個構建返回內容的類。
第一步要將序列化類匯入,在進行序列化的時候將需要序列化的資料物件進行傳入。
當序列化完成後,得到一個序列化物件,它有一個data
屬性,是已經序列化完成的一個字典。
把字典返回,如果不使用rest_framework
提供的Response
,就得使用JsonResponse
。
序列化單個物件,不需要在序列化時指定引數many
序列化多個物件,需要在序列化時指定引數many
from rest_framework.views import APIView
from rest_framework.views import Request
from rest_framework.views import Response # 通過Response返回
from app01 import models
from app01.drf_ser import StudentSerializer
class returnData(object):
# 構建返回的資訊
def __init__(self):
self.status = 100 # 100代表成功,200代表失敗
self.message = None # 返回的資訊
self.data = None # 序列化後的結果
def get_dict(self):
return self.__dict__
@property
def data(self):
return self.data
@data.setter
def data(self, value):
self.__dict__["data"] = value
class Student(APIView):
def get(self, request, sid=None):
# 初始化返回資訊
res = returnData()
if sid:
# 代表獲取單個
obj = models.Student.objects.filter(pk=sid).first()
if obj:
serializer_result = StudentSerializer(obj)
res.data = serializer_result.data
else:
res.message = "沒有該學生"
res.status = 200
return Response(res.get_dict())
else:
# 代表獲取所有
obj_queryset = models.Student.objects.all()
if obj_queryset:
serializer_result = StudentSerializer(
obj_queryset, many=True) # many=True代表獲取多條
res.data = serializer_result.data
else:
res.message = "暫時沒有任何學生"
res.status = 200
return Response(res.get_dict())
def post(self, request, sid):
# 新增
pass
def delete(self, request, sid):
# 刪除
pass
def patch(self, request, sid):
# 修改
pass
序列化時,只關心怎樣將資料返回給頁面。
所以我們只做GET
部分,注意用if
判斷來判定是獲取所有,還是獲取單個。
以下是獲取全部的結果:
# http://127.0.0.1:8000/api/students/
{
"status": 100,
"message": null,
"data": [
{
"student_id": "1",
"student_name": "學生1",
"student_gender": true,
"student_class": "高一一班"
},
{
"student_id": "2",
"student_name": "學生2",
"student_gender": true,
"student_class": "高二一班"
},
{
"student_id": "3",
"student_name": "學生3",
"student_gender": true,
"student_class": "高二一班"
},
{
"student_id": "4",
"student_name": "學生4",
"student_gender": false,
"student_class": "高一一班"
}
]
}
以下是獲取單條的結果:
{
"status": 100,
"message": null,
"data": {
"student_id": "1",
"student_name": "學生1",
"student_gender": true,
"student_class": "高一一班"
}
}
當獲取出錯時,message
中就會存在錯誤資訊:
{
"status": 200,
"message": "沒有該學生",
"data": null
}
反序列化引數介紹
反序列化通常是使用POST/PATCH
插入或更新資料時使用,收集到頁面傳遞的資料,進行反序列化後寫入資料庫中。
當進行反序列化時,可以在序列類中指定一些引數,對將要反序列化寫入模型表的欄位進行檢查。
引數 | 描述 |
---|---|
max_length | 最大長度 |
min_lenght | 最小長度 |
allow_blank | 是否允許為空 |
trim_whitespace | 是否截斷空白字元 |
max_value | 最小值 |
min_value | 最大值 |
required | 表明該欄位在反序列化時必須輸入,預設True |
default | 反序列化時使用的預設值 |
allow_null | 表明該欄位是否允許傳入None,預設False |
validators | 該欄位使用的驗證器 |
error_messages | 包含錯誤編號與錯誤資訊的字典 |
初此之外,還有兩個比較特殊的引數:
引數 | 描述 |
---|---|
read_only | 表明該欄位僅用於序列化輸出,預設False,如果設定成True,postman中可以看到該欄位,修改時,不需要傳該欄位 |
write_only | 表明該欄位僅用於反序列化輸入,預設False,如果設定成True,postman中看不到該欄位,修改時,該欄位需要傳 |
反序列化鉤子
validate_欄位名
是區域性鉤子
validate
是全域性鉤子
如果要在鉤子中丟擲異常,則需要匯入異常模組。
from rest_framework import exceptions
# raise exceptions.ValidationError('異常了')
如下是區域性鉤子的使用示例,因為頁面提交過來的資料關於一對多中的班級欄位是字串,所以我們需要將字串變為模型表物件,方便後面的建立以及更新。
def validate_student_class(self, data):
# data是提交過來的這一個欄位的資料
class_obj = Classes.objects.filter(class_name=data).first()
if not class_obj:
raise exceptions.ValidationError("班級不存在")
data = class_obj # 將字串替換為物件
return data
全域性鉤子使用也是一樣。如下,驗證學生名和班級名是否相同,如果相同則丟擲異常
def validate(self, validate_data):
student_name = validate_data.get("student_name")
class_obj = validate_data.get("student_class") # 由於區域性鉤子中,這裡被替換成了物件,所以我們拿到物件不能直接作比較
if student_name == class_obj.class_name:
raise exceptions.ValidationError("學生名不能和班級名相同")
return validate_data
create&update
在進行反序列化時,必須在序列類中覆寫create()
方法以及update()
方法。
其中create()
方法針對的是新增使用者,而update()
方法針對的是更新使用者。
如果不進行覆寫,則會丟擲異常,這是因為作者在原始碼中做了介面約束的設定:
def update(self, instance, validated_data):
raise NotImplementedError('`update()` must be implemented.')
def create(self, validated_data):
raise NotImplementedError('`create()` must be implemented.')
反序列化案例
瞭解了上面反序列化需要注意的事項後,開始書寫檢視函式中的POST/PATCH
方法。
下面是建立一個學生的例子:
def post(self, request):
# 初始化返回資訊
res = returnData()
serializer_result = StudentSerializer(data=request.data) # 傳入request.data即可。這裡一定要使用關鍵字傳參!!!
if serializer_result.is_valid():
# 驗證通過了
serializer_result.save() # 儲存序列化例項類
res.data = serializer_result.data # 遵循規範,返回新增的資料
else:
# 驗證沒通過
res.status = 200
res.message = "資料校驗失敗"
res.data = serializer_result.errors # 新增錯誤資訊
return Response(res.get_dict())
重寫create()
方法並返回:
def create(self, validated_data):
instance = Student.objects.create(**validated_data)
return instance # 這裡返回的資訊會返回到序列類物件的data屬性中
下面是修改一個學生的例子:
def patch(self, request, sid):
# 初始化返回資訊
res = returnData()
obj = models.Student.objects.filter(pk=sid).first()
if obj:
serializer_result = StudentSerializer(instance=obj, data=request.data) # 需要傳入的引數,記錄本身,新的資料
if serializer_result.is_valid():
# 驗證通過了
serializer_result.save() # 儲存序列化例項類
res.data = serializer_result.data # 遵循規範,返回修改的資料
else:
# 驗證沒通過
res.status = 200
res.message = "資料校驗失敗"
res.data = serializer_result.errors # 新增錯誤資訊
else:
res.status = 200
res.message = "使用者不存在"
return Response(res.get_dict())
重寫update()
方法並返回:
def update(self, instance, validated_data):
# 對資料做更新後再返回
# validated_data中取出str的鍵,然後用反射進行設定
for k, v in validated_data.items():
setattr(instance, k, v)
instance.save()
return instance
全部程式碼
views.py
from rest_framework.views import APIView
from rest_framework.views import Request
from rest_framework.views import Response # 通過Response返回
from app01 import models
from app01.drf_ser import StudentSerializer # 匯入自定義序列類
class returnData(object):
# 構建返回的資訊
def __init__(self):
self.status = 100 # 100代表成功,200代表失敗
self.message = None # 返回的資訊
self.data = None # 序列化後的結果
def get_dict(self):
return self.__dict__
@property
def data(self):
return self.data
@data.setter
def data(self, value):
self.__dict__["data"] = value
class Student(APIView):
def get(self, request, sid=None):
# 初始化返回資訊
res = returnData()
if sid:
# 代表獲取單個
obj = models.Student.objects.filter(pk=sid).first()
if obj:
serializer_result = StudentSerializer(obj)
res.data = serializer_result.data
else:
res.message = "沒有該學生"
res.status = 200
return Response(res.get_dict())
else:
# 代表獲取所有
obj_queryset = models.Student.objects.all()
if obj_queryset:
serializer_result = StudentSerializer(
obj_queryset, many=True) # many=True代表獲取多條
res.data = serializer_result.data
else:
res.message = "暫時沒有任何學生"
res.status = 200
return Response(res.get_dict())
def post(self, request):
# 初始化返回資訊
res = returnData()
serializer_result = StudentSerializer(
data=request.data) # 傳入request.data即可
if serializer_result.is_valid():
# 驗證通過了
serializer_result.save() # 儲存序列化例項類
res.data = serializer_result.data # 遵循規範,返回新增的資料
else:
# 驗證沒通過
res.status = 200
res.message = "資料校驗失敗"
res.data = serializer_result.errors # 新增錯誤資訊
return Response(res.get_dict())
def delete(self, request):
# 刪除
pass
def patch(self, request, sid):
# 初始化返回資訊
res = returnData()
obj = models.Student.objects.filter(pk=sid).first()
if obj:
serializer_result = StudentSerializer(instance=obj, data=request.data) # 需要傳入的引數,記錄本身,新的資料
if serializer_result.is_valid():
# 驗證通過了
serializer_result.save() # 儲存序列化例項類
res.data = serializer_result.data # 遵循規範,返回修改的資料
else:
# 驗證沒通過
res.status = 200
res.message = "資料校驗失敗"
res.data = serializer_result.errors # 新增錯誤資訊
else:
res.status = 200
res.message = "使用者不存在"
return Response(res.get_dict())
自定義序列類
from .models import Student
from .models import Classes
from rest_framework import serializers
from rest_framework import exceptions
class StudentSerializer(serializers.Serializer):
student_id = serializers.CharField(read_only=True) # 建立/修改時不用傳該欄位,但是頁面可以看見
# 相反的,如果是wrtie_only則代表頁面看不見,但是你要傳
student_name = serializers.CharField(max_length=8, min_length=3)
student_gender = serializers.BooleanField()
student_class = serializers.CharField()
def validate_student_class(self, data):
class_obj = Classes.objects.filter(class_name=data).first()
if not class_obj:
raise exceptions.ValidationError("班級不存在")
data = class_obj # 將字串替換為物件
return data
def validate(self, validate_data):
student_name = validate_data.get("student_name")
class_obj = validate_data.get("student_class")
if student_name == class_obj.class_name:
raise exceptions.ValidationError("學生名不能和班級名相同")
return validate_data
def create(self, validated_data):
instance = Student.objects.create(**validated_data)
return instance # 這裡返回的資訊會返回到序列類物件的data屬性中
def update(self, instance, validated_data):
# 對資料做更新後再返回
# validated_data中取出str的鍵,然後用反射進行設定
for k, v in validated_data.items():
setattr(instance, k, v)
instance.save()
return instance
模型表序列器
模型表的序列器定製化比較低,但是使用較為方便。
能夠非常快速的開發介面。
建立序列類
建立序列器:
# 模型表序列器
class StudentModelSerializer(serializers.ModelSerializer):
student_id = serializers.CharField(read_only=True) # 建立/修改時不用傳該欄位,但是頁面可以看見
student_name = serializers.CharField(max_length=8, min_length=3)
student_gender = serializers.BooleanField()
student_class = serializers.CharField()
class Meta:
model = Student # 對應上models.py中的模型
fields = '__all__' # 序列化所有欄位
# fields=('student_id','student_name') # 只序列化指定的欄位
# exclude=('student_id',) # 跟fields不能同時都寫,寫誰,就表示排除誰
# read_only_fields=('student_id',)
# write_only_fields=('student_class',) # 棄用了,使用extra_kwargs
# extra_kwargs = { # 類似於這種形式 student_name=serializers.CharField(max_length=16,min_length=4)
# 'student_name': {'write_only': True},
# }
def validate_student_class(self, data):
class_obj = Classes.objects.filter(class_name=data).first()
if not class_obj:
raise exceptions.ValidationError("班級不存在")
data = class_obj # 將字串替換為物件
return data
def validate(self, validate_data):
student_name = validate_data.get("student_name")
class_obj = validate_data.get("student_class")
print(class_obj)
if student_name == class_obj.class_name:
raise exceptions.ValidationError("學生名不能和班級名相同")
return validate_data
基本使用
其他使用一模一樣,注意在反序列化時不需要重寫create()
和updata()
方法了。
以下是檢視API
介面中的程式碼,只需要把原本使用自定義序列器的地方修改成使用模型表序列器即可。
from rest_framework.views import APIView
from rest_framework.views import Request
from rest_framework.views import Response # 通過Response返回
from app01 import models
# from app01.drf_ser import StudentModelSerializer
from app01.drf_ser import StudentModelSerializer
class returnData(object):
# 構建返回的資訊
def __init__(self):
self.status = 100 # 100代表成功,200代表失敗
self.message = None # 返回的資訊
self.data = None # 序列化後的結果
def get_dict(self):
return self.__dict__
@property
def data(self):
return self.data
@data.setter
def data(self, value):
self.__dict__["data"] = value
class Student(APIView):
def get(self, request, sid=None):
# 初始化返回資訊
res = returnData()
if sid:
# 代表獲取單個
obj = models.Student.objects.filter(pk=sid).first()
if obj:
serializer_result = StudentModelSerializer(obj)
res.data = serializer_result.data
else:
res.message = "沒有該學生"
res.status = 200
return Response(res.get_dict())
else:
# 代表獲取所有
obj_queryset = models.Student.objects.all()
if obj_queryset:
serializer_result = StudentModelSerializer(
obj_queryset, many=True) # many=True代表獲取多條
res.data = serializer_result.data
else:
res.message = "暫時沒有任何學生"
res.status = 200
return Response(res.get_dict())
def post(self, request):
# 初始化返回資訊
res = returnData()
serializer_result = StudentModelSerializer(
data=request.data) # 傳入request.data即可
if serializer_result.is_valid():
# 驗證通過了
serializer_result.save() # 儲存序列化例項類
res.data = serializer_result.data # 遵循規範,返回新增的資料
else:
# 驗證沒通過
res.status = 200
res.message = "資料校驗失敗"
res.data = serializer_result.errors # 新增錯誤資訊
return Response(res.get_dict())
def delete(self, request):
# 刪除
pass
def patch(self, request, sid):
# 初始化返回資訊
res = returnData()
obj = models.Student.objects.filter(pk=sid).first()
if obj:
serializer_result = StudentModelSerializer(instance=obj, data=request.data) # 需要傳入的引數,記錄本身,新的資料
if serializer_result.is_valid():
# 驗證通過了
serializer_result.save() # 儲存序列化例項類
res.data = serializer_result.data # 遵循規範,返回修改的資料
else:
# 驗證沒通過
res.status = 200
res.message = "資料校驗失敗"
res.data = serializer_result.errors # 新增錯誤資訊
else:
res.status = 200
res.message = "使用者不存在"
return Response(res.get_dict())
多關係子序列化
source引數
source
引數的使用:
- 可以改欄位名字 xxxx=serializers.CharField(source='student_name')
- 可以
.
跨表 cls=serializers.CharField(source='student_class.name') # 相當於 student_student_class__name進行資料獲取 - 可以執行方法pub_date=serializers.CharField(source='test') test是Student表模型中的方法(可忽略,這個相當於驗證方法。還不如使用鉤子函式)
該引數最重要的兩點,source
中寫的是什麼,就從哪裡取資料,即展示的就是什麼。當反序列化時,不再按照序列器類的欄位名進行反序列化,而是按照該引數進行反序列化填值。
看一下,我要讓student_name
顯示的不是學生的名字,而是班主任的名字,就用到了第一條和第二條,跨表,顯示資料,可以這樣設定。
class StudentSerializer(serializers.Serializer):
student_id = serializers.CharField(read_only=True) # 建立/修改時不用傳該欄位,但是頁面可以看見
student_name = serializers.CharField(max_length=8, min_length=3,source="student_class.class_teacher.teacher_name") # 相當於:Student.objects.filter(pk=傳入的id).values_list("student_class__class_teacher__teacher_name")[0][0]
student_gender = serializers.BooleanField(source="student_gender")
student_class = serializers.CharField()
當進行GET
請求後,將會看到下面的結果:
# http://127.0.0.1:8000/api/students/5/
{
"status": 100,
"message": null,
"data": {
"student_id": "5",
"student_name": "王老師", # 所以說,該引數後面寫的是什麼,展示的就是什麼。
"student_gender": false,
"student_class": "高一一班"
}
}
示例演示,我們通常會將展示的資料名字進行重新命名,區分開與資料庫儲存的欄位名,這樣做更加安全,所以可以進行如下設定:
from .models import Student
from .models import Classes
from rest_framework import serializers
from rest_framework import exceptions
class StudentSerializer(serializers.Serializer):
sid = serializers.CharField(read_only=True,source="student_id") # 建立/修改時不用傳該欄位,但是頁面可以看見
name = serializers.CharField(max_length=8, min_length=3,source="student_name")
gender = serializers.BooleanField(source="student_gender")
classes = serializers.CharField(source="student_class") # source中寫的是什麼,就從哪裡取資料
def validate_classes(self, data):
# data是提交過來的這一個欄位的資料
class_obj = Classes.objects.filter(class_name=data).first()
if not class_obj:
raise exceptions.ValidationError("班級不存在")
data = class_obj # 將字串替換為物件
return data
def create(self, validated_data):
instance = Student.objects.create(**validated_data)
return instance # 這裡返回的資訊會返回到序列類物件的data屬性中
def update(self, instance, validated_data):
# 對資料做更新後再返回
# validated_data中取出str的鍵,然後用反射進行設定
for k, v in validated_data.items():
setattr(instance, k, v)
instance.save()
return instance
{
"status": 100,
"message": null,
"data": {
"sid": "5",
"name": "修改學生5",
"gender": false,
"classes": "高一一班"
}
}
SerializerMethodField欄位
它需要有個配套方法,方法名叫get_欄位名
,返回值就是要顯示的東西。
比如,我們想檢視每個學生都有那些老師在教授,就可以使用該引數:
class StudentSerializer(serializers.Serializer):
sid = serializers.CharField(read_only=True,source="student_id") # 建立/修改時不用傳該欄位,但是頁面可以看見
name = serializers.CharField(max_length=8, min_length=3,source="student_name")
gender = serializers.BooleanField(source="student_gender")
classes = serializers.CharField(source="student_class") # 現在,我要讓他顯示的是班級編號,而不再是班級名稱了
students = serializers.SerializerMethodField()
def get_students(self,instance):
teacher_queryset = instance.student_class.teacher_set.values("pk","teacher_name")
return teacher_queryset
最後的結果如下:
# http://127.0.0.1:8000/api/students/5/
{
"status": 100,
"message": null,
"data": {
"sid": "5",
"name": "修改學生5",
"gender": false,
"classes": "高一一班",
"students": [
{
"pk": 1,
"teacher_name": "王老師"
},
{
"pk": 2,
"teacher_name": "李老師"
},
{
"pk": 3,
"teacher_name": "張老師"
}
]
}
}