啥是序列化器?
数据库的数据 ---------> 前端用的数据 (序列化)
- 在序列化输出、 如【列出】、【获取详情】,可控制输出哪些字段
- 和反序列化输入时,如【新增】、【修改】,设置一层校验规则,校验前端传递的参数是否符合要求
序列化器练习的准备
添加注册应用、创建模型
# 创建(练习应用)
startapp project_interface
注册
INSTALLED_APPS = [
"project_interface",
]
创建model
Projects与Interfaces表,一对多关系
from django.db import models
# Create your models here.
class Projects_test(models.Model):
name = models.CharField(max_length=20, verbose_name='项目名称', help_text='项目名称',
unique=True)
leader = models.CharField(max_length=10, verbose_name='项目负责人', help_text='项目负责人')
is_execute = models.BooleanField(verbose_name='是否启动项目', help_text='是否启动项目',
default=True)
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间', help_text='创建时间')
desc = models.TextField(verbose_name='项目描述信息', help_text='项目描述信息',
null=True, blank=True, default='')
class Meta:
# i.db_table指定创建的数据表名称
db_table = 'test_projects'
# 为当前数据表设置中文描述信息
verbose_name = '项目表'
verbose_name_plural = '项目表'
ordering = ['id']
def __str__(self):
return f"Projects({self.name})"
class Interfaces_test(models.Model):
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间', help_text='创建时间')
name = models.CharField(verbose_name='接口名称',help_text='接口名称',max_length=20,unique=True)
tester = models.CharField(verbose_name='测试人员', help_text='测试人员', max_length=10)
projects = models.ForeignKey(Projects_test, on_delete=models.CASCADE,
verbose_name='所属项目', help_text='所属项目')
class Meta:
# db_table指定创建的数据表名称
db_table = 'test_interfaces'
# 为当前数据表设置中文描述信息
verbose_name = '接口表'
verbose_name_plural = '接口表'
ordering = ['id']
def __str__(self):
return f"Interfaces({self.name})"
生成表:
makemigrations 和 migrate # 如果是第一次,就先migrate一次整体,生成用户表等
makemigrations project_interface
migrate project_interface 创建表
数据库表的解析
项目表 举例如下 :
这些限制,是数据库限制的,那使用了序列化器后,比如在反序列化输入时,就要提前校验。
- name 是唯一的
- desc可以为null可以为空str
- leader的长度不能超过10
准备数据
py文件测试django数据的方式,会直接往数据库里添加数据
(以下方式,和django shell 中写的效果是一样的), 这种的话方便复制
项目下新增一个test文件, 新增ready_data.py ,名字任意
import os
import random
import django
"""
运行该文件,需要把pycharm的运行模式换成unittest (settings 中 tools 中 Python integrated tools)
该文件,像shell环境一样,可以操作模型, 会往数据库里添加数据
"""
# 设置环境变量
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sq_tp.settings") # sq_tp 表示项目名字
django.setup()
# 以下代码就像shell中一样使用
from project_interface.models import Projects_test, Interfaces_test
# 新增项目数据
p1 = Projects_test.objects.create(name='项目1', leader='小涛')
p2 = Projects_test.objects.create(name='项目2', leader='王爽')
print(p1,p2)
# 新增接口数据
i1= Interfaces_test.objects.create(name='接口1',projects=p1)
print(i1)
使用序列化器进行序列化输出、反序列化输入
安装并注册应用
安装pip install djangorestframework
pip install markdown 浏览器显示的时候,支持markdown
注册时,要写到其他应用名之上?
定义序列化器
一般是在【应用】中新建py文件,推荐命名serializers.py
一般情况下, models.py中,一个模型类,定义一个序列化器,也可以定义多个
类
要继承Serializer类类名字
可以自定义- 每个
字段名
命名要和模型类字段名
一致 (一般)
我们在练习时,就直接也在test文件中(或者应用下),新建一个serializers.py (暗示
序列化器的定义实际上和模型关系不大)
from rest_framework import serializers
# from rest_framework.validators import UniqueValidator
from .models import Projects_test
class TestSerilizer1(serializers.Serializer):
name = serializers.CharField(label='项目名称', help_text='项目名称', max_length=20, min_length=5,
# validators=[UniqueValidator(
# queryset=Projects_test.objects.all(),
# message="项目名称重复了00")
# ]
)
leader = serializers.CharField(label='项目负责人', help_text='项目负责人', default='阿名')
is_execute = serializers.BooleanField()
# interface_set = serializers.PrimaryKeyRelatedField()
class TestSerilizer2(serializers.Serializer):
name = serializers.CharField(label='项目名称', help_text='项目名称', max_length=20, min_length=5)
# leader1 = serializers.CharField(label='项目负责人', help_text='项目负责人', default='阿名')
is_execute = serializers.CharField()
class InterfacesSerilizer(serializers.Serializer):
name = serializers.CharField(label='接口名称', help_text='项目名称')
tester = serializers.CharField(label='测试人员', help_text='测试人员')
序列化器类字段_补充1 方法字段 SerializerMethodField
SerializerMethodField(), 参数为 method_name 固定的,后面为函数名称
- 定义的xxx字段默认是只读的 (可以是对原来的覆盖,也可以新字段)
- 通常:函数名称取成 get_xxx , 好看一点
class xxxSerializer(serializers.ModelSerializer):
xxx = serializers.SerializerMethodField(method_name='函数名称')
def 函数名称(self,instance):
....
return xxxx
举例
:当一个模型序列化器返回的东西不够时,就可以这么增加一个字段,
比如数据库里 存的 性别sex 为 01, 我想多返回一个 sex_value ,就可以这样写,在函数里转化成 “男女”
使用序列化器(返回数据-序列化时)
写法:
serializer= ProjectSerilizer(instance=queryset, many=True)
或
serializer = ProjectSerilizer(instance=Projects.objects.get(id=1))
那么:serializer.data 就是序列化后的数据
一般在views.py使用, 我们验证时,在test文件下新增一个out.py来验证
import os
import random
import django
# 设置环境变量
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sq_tp.settings") # sq_tp 表示项目名字
django.setup()
# 以下代码就像shell中一样使用
from project_interface.models import Projects_test, Interfaces_test
# 不使用序列化器,项目模型的所有数据都会返回
plist = Projects_test.objects.values()
print(plist) # 格式为列表套字典
print("------------------------------------------------------------------------------------")
# 使用序列化器,详情
from serializers import TestSerilizer1,TestSerilizer2
p_obj=Projects_test.objects.get(pk=1)
s1=TestSerilizer1(instance=p_obj)
s2=TestSerilizer2(instance=p_obj)
"""
s2.data -------特点1:没有leader数据------特点2:is_execut为字符串
"""
print(s1.data)
print(s2.data)
print("------------------------------------------------------------------------------------")
# 使用序列化器, 列表(要多加一个many参数)
s1 = TestSerilizer1(instance=plist,many=True)
print(s1.data) # 格式为列表套字典
序列化输出有两个特点(见下图文字说明)
- 不使用序列化器,只能把模型的所有字段都返回
- 如果使用的序列化器,可以选择性返回字段
- 可以决定返回字段的类型
instance参数,如下参考图(这里是在views中定义了 两个视图函数,且配置关联了url、相关代码在最后
)
- 如果是一个,就返回字典,
- 如果是多个,就是列表套字典,
使用序列化器(新增数据-反序列化时)
在test文件下新增一个文件in.py来验证,依次传不同的body,感受一下反序列化的使用。
import os
import random
import django
"""
反序列化时的使用
1. 获取序列化器对象 s=TestSerilizer1(data=body)
2. 校验 s.is_valid() # 校验成功返回True,校验失败返回False, 可以添加參數 raise_exception=True,校验失败时,会直接报错
3. is_valid() 为True时,获取校验后的数据 s.validated_data
4. is_valid() 为False时,获取校验失败的原因 s.errors
"""
# 设置环境变量
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "autotp_test1.settings") # sq_tp 表示项目名字
django.setup()
# 以下代码就像shell中一样使用
from project_interface.models import Projects_test, Interfaces_test
# 不使用序列化器,新增时会,如果数据不对,会直接报错,就不举例了
# 使用序列化器,新增
from project_interface.serializers import TestSerilizer1,TestSerilizer2
body={"name":"名字长度符合要求的","leader":"小高","is_execute":True}
body1={"name":"名字长度符合要求的","leader":"小高","is_execute":True} # 校验成功,但是新增会报错,因为name不能重复,序列化器name字段现在没有加重复的校验(后面可以用模型生成序列化器)
body2={"name":"名称短的","leader":"小高","is_execute":True} # 校验失败,name长度不够
body3={"name":"名称符合要求呢","is_execute":True} # 校验成功, 序列化器中,leader设置了默认值
body4={"name":"名称符合要求呢呀呀呀"} # 校验失败,序列化器中,is_execute没有设置默认值
"""本文档使用:依次修改body的值,查看校验结果"""
serializers_obj= TestSerilizer1(data=body2)
if serializers_obj.is_valid(): # 可以添加參數 raise_exception=True,校验失败时,会直接报错
print("校验成功,开始新增")
Projects_test.objects.create(**serializers_obj.validated_data)
"""説明,如过是body1时,虽然校验成功了,但是由于数据库里面已经有了name已经有了,所以会报错"""
else:
print("校验失败,原因如下")
print(serializers_obj.errors)
序列化器校验的作用
序列化器,判断body2时
要想知道有没有通过,还需要让序列化器做一个事情,即(要让它在已有的数据里判断一下)
就要再序列化器中加上对name重复的校验,看下面的validators的介绍, 如下:添加validators参数…
from rest_framework.validators import UniqueValidator
from project_interface.models import Projects_test
class TestSerilizer1(serializers.Serializer):
name = serializers.CharField(label='项目名称', help_text='项目名称', max_length=20, min_length=5,
validators=[UniqueValidator(
queryset=Projects_test.objects.all(),
message="项目名称重复了00")]
)
补充:
serializers_obj.errors 的格式也是字典: 会把每个字段不通过的原因给出来:
如:
同样用接口举例,代码也在后面:
接口举例的代码
1、project_interface应用的views.py中
(补充说明:APIView是django RF里最基础的视图类,里面可以重写get post等方法)
import json
from django.shortcuts import render
# Create your views here.
from rest_framework.views import APIView
from .models import Projects_test,Interfaces_test
from .serializers import TestSerilizer1,TestSerilizer2,InterfacesSerilizer # 在project_interface 的应用下新建文件serializers,把它复制过来
from rest_framework.response import Response
class ProjectsView(APIView):
def get(self,request,*args,**kwargs):
s1 = TestSerilizer1(instance=Projects_test.objects.all(), many=True)
print(s1.data)
return Response(s1.data)
def post(self, request):
# 1、获取json参数并转化为python中的数据类型(字典)
try:
python_data = json.loads(request.body)
except Exception as e:
return Response({'msg': '参数有误'}, status=400)
serializerObj = TestSerilizer1(data=python_data)
if not serializerObj.is_valid():
return Response(serializerObj.errors, status=400)
# 新增
Projects_test.objects.create(**serializerObj.validated_data)
return Response(serializerObj.validated_data, status=201)
class ProjectsDetailView(APIView):
def get(self,request,*args,**kwargs):
p_obj = Projects_test.objects.get(pk=1)
s1 = TestSerilizer1(instance=p_obj)
return Response(s1.data)
# 更新 略
def put(self):
pass
class InterfacesView(APIView):
# 接口列表
def get(self, request, *args, **kwargs):
s1 = InterfacesSerilizer(instance=Interfaces_test.objects.all(), many=True)
print(s1.data)
return Response(s1.data)
# 新增接口
def post(self, request):
# 1、获取json参数并转化为python中的数据类型(字典)
try:
python_data = json.loads(request.body)
except Exception as e:
return Response({'msg': '参数有误'}, status=400)
serializerObj = InterfacesSerilizer(data=python_data)
if not serializerObj.is_valid():
return Response(serializerObj.errors, status=400)
# 新增
interface_obj = Interfaces_test.objects.create(**serializerObj.validated_data)
# 再使用一次序列化器返回
serializerObj2 = InterfacesSerilizer(instance=interface_obj)
return Response(serializerObj2.data, status=201)
#
class InterfacesDetailView(APIView):
# 获取接口详情
def get(self, request, *args, **kwargs):
p_obj = Interfaces_test.objects.get(pk=1)
s1 = InterfacesSerilizer(instance=p_obj)
return Response(s1.data)
2、应用中urls.py
# 外层urls中
path("",include('project_interface.urls'))
# 应用内新增urls.py,
from django.urls import path
from project_interface import views
urlpatterns = [
path("projects/", views.ProjectsView.as_view()),
path("projects/<int:pk>/", views.ProjectsDetailView.as_view()),
path("interfaces/", views.InterfacesView.as_view()),
path("interfaces/<int:pk>/", views.InterfacesDetailView.as_view()),
]
3、把test中的serizlizer.py复制到应用中
一些校验规则、和一些参数说明
校验参数
原理(以下的校验规则可以逐个尝试):
一般:在序列化器中,写了哪些字段,就会校验哪些字段
-
默认必传,如果加上
required=False
,就可以不传了,如:可实现非必填,但填就要校验长度的需求
-
默认必传,如果加
default
参数,也可以不传且有默认值,serializerObj.validated_data,校验通过的数据,会拿到序列化器中定义的默认值
-
label
和help_text
为描述信息,(暂不知道哪里会用到) -
max_value
和min_value
针对IntegerField 的 为最大值,和最小值 (没试过) -
max_length
和min_length
为 CharField 的最长,和最短 (针对字符串) -
allow_null=True
请求的那个参数可以传 null
-
allow_blank=True
# 允许为空字符串 ,基本同上 -
write_only =True
和read_only =True
-
write_only =True
可以实现,只校验,但是不返回的需求
-
接收前端传入的参数时(如新增、更新数据):
- serializer_obj = CountrySerilizer(data=body) #data接收输入的数据
- serializer_obj.is_valid() 判断时,会对输入做校验(判断长度是否正常,必填项有没有传),
还是会对body中的每个字段进行校验
- serializer_obj.validated_data 因为有校验,所以校验后,还是会有 校验后的这个字段。
校验后的data,中还是有该字段
-
序列化输出时(返回数据,给前端, 查询全部、查询一个)
- serialiser_obj=CountrySerilizer(instance=Country.objects.all(),many=True)
- serialiser_obj=CountrySerilizer(instance=Country.objects.get(id=pk))
- serializer_obj.data: 输出内容,无该字段。
输出内容里没有该字段
-
-
read_only =True
可以实现,不校验参数,但会返回的需求
- 和上面相反。 1对请求参数不校验, 但是2 查询时会输出
功能参数
时间字段-序列化输出,时间格式化
可以加上format 参数,如:
update_time = serializers.DateTimeField(label='更新时间',help_text='更新时间',format='%Y年%m月%d日 %H:%M:%S' ,read_only=True)
补充:时间格式化 drf全局配置
REST_FRAMEWORK = {
# 0、api返回时日期时间格式配置
'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S",
'DATE_FORMAT': "%Y-%m-%d",
}
修改报错信息,加参数 error_messages
:
serializerObj.is_valid() 当他为False时, 报错信息是这样的
某个校验项: 重写的原因
name = serializers.CharField( max_length=20, min_length=5,
error_messages={'min_length': '项目名称不能少于5位',
'max_length': '项目名称不能多余20位',
'required': "项目名称必填哈[这是重写的错误原因]"})
效果是这样的
获取关联表数据
项目表和 接口表 (1对多)的关系如下:
1234,每一种都是要加参数的
,不然会报下面这个报错, 都需要传 这个两个参数的,(不能同时存在)
Relational field must provide a queryset
argument, override get_queryset
, or set read_only=True
.
- 参数1:
queryset
- 可以接受 请求参数去查 ,不想返回时,要加
read_only=False
- 又可以返回
- 可以接受 请求参数去查 ,不想返回时,要加
- 参数2:
read_only=True
表示 只做返回
举例(接口列表中展示项目)
- 如返回的
接口信息
中要包含项目信息
,如下图
- 要修改的 接口序列化器, – 定义字段时用models.py中 接口模型类中一样的字段名称,里面叫
projects
1、serializers.PrimaryKeyRelatedField()
·这个修改的是接口序列化器,加一个
projects = serializers.PrimaryKeyRelatedField(queryset=Projects_test.objects.all(),label='所属项目', help_text='所属项目',write_only=True)
输出的接口列表里包含有项目id,
当write_only=True 不写时,即为False, 响应里才展示 项目id
新增接口时,也需要传一个projects 参数,里面放id
2、StringRelatedField()
这种只能输出(即,可以实现接口列表,和 接口详情)
这种输出时: 返回的是,模型类中定义的 __str__ 字符串
默认read_only = True了,所以这个就不用在指定了
"""再写一个序列化器,该序列化器用到 接口列表 和 接口详情"""
class InterfacesSerilizer2(serializers.Serializer):
name = serializers.CharField(label='接口名称', help_text='项目名称')
tester = serializers.CharField(label='测试人员', help_text='测试人员')
# projects名称要和models中的一致,也叫projects
projects = serializers.StringRelatedField() # StringRelatedField 它的read_only默认就为True
views中,这个两个方法改一下,就有如下效果
3、SlugRelatedField()
·这个修改的是接口序列化器
输入和输出可以 用 slug_field 参数来指定一个 有唯一约束的字段名
。
必须指定read_only=True, or 指定queryset参数来接收输入
"""使用SlugRelatedField,自定义指定一个 项目表中的有唯一值的字段"""
class InterfacesSerilizer3(serializers.Serializer):
name = serializers.CharField(label='接口名称', help_text='项目名称')
tester = serializers.CharField(label='测试人员', help_text='测试人员')
# projects名称要和models中的一致,也叫projects
projects = serializers.SlugRelatedField(slug_field="name", queryset=Projects_test.objects.all(),label='所属项目', help_text='所属项目')
可以输出:
响应中的projects,就返回的是项目表的name
可以进行输入,请求输入时:
4、用关联表的序列化器
这种只能输出(接口列表,接口详情)
"""用关联表的序列化器"""
class InterfacesSerilizer4(serializers.Serializer):
name = serializers.CharField(label='接口名称', help_text='项目名称')
tester = serializers.CharField(label='测试人员', help_text='测试人员')
# projects名称要和models中的一致,也叫projects
projects = TestSerilizer1(read_only=True)
项目字段里要展示哪些东西,也可以进行设置
这种也可以做新增
写法如下
页面上和接口是这种,即在新建用例时,需要填写config的信息
用例新增和用例列表的效果如下
举例(项目列表中展示接口)
- 如果是项目列表,就要修改项目序列化器,–定义字段时表名 – 用关联模型小写名_set这里是interfaces_set 字段, 当然也可以用在定义外键时定义的related_name,如下图
- 因为接口 的数据时,多方, 在定义时,下方的1234,都要写
many =True
1、同上,效果如图
基本相同,就是名称需要是 接口模型小写名_set 或者related_name
2、同上,效果如下
3、同上,效果如下
4、同上,效果如下
额外的校验
validators
validators中结合模型类的校验(校验某字段是否重复)
如项目表中name字段有唯一约束, 怎么把唯一约束放到序列化器中,做校验
添加validator 参数. 创建校验器
。
如下: 会去判断 Projects模型类中的name 是否有重复的, 如果有,就把message
UniqueValidator 中,必须要指定 queryset参数。
from rest_framework.validators import UniqueValidator
name = serializers.CharField(label='项目名称',
validators=[UniqueValidator(
queryset=Projects.objects.all(),
message="项目名称重复了00"
)])
validators 中使用自定义校验方法
举例: 如在新增项目名时,假设 名字需要 后两位是 ”项目“ 结尾
自定义一个方法,然后把方法名写在validators=[], 后面的列表中,方法名后不打括号
在自定义方法中,
- 第一个参数为待校验的值,写法如下,
- 需要
有抛出 serializers.ValidationError 的地方
, 且抛的异常只能是这种。 - 方法名 是 放在validators 中的 列表参数里面
# 自定义方法
def is_contain_keyword(value):
if '项目' not in value:
raise serializers.ValidationError('项目名称中必须要包含“项目”') # 必须是抛出这种异常
# 序列化器中使用
name = serializers.CharField(label='项目名称', help_text='项目名称', max_length=20, min_length=5,
error_messages={'min_length': '项目名称不能少于5位',
'max_length': '项目名称不能多余20位',
'required': "项目名称必填哈[这是重写的错误原因]"},
validators=[UniqueValidator(queryset=Projects.objects.all(), message="项目名称重复了00"),
is_contain_keyword] # 方法名不用打括号
)
validate_name()单字段校验方法
这个定义在序列化器中
如def validate_name() 方法,首先会去校验如下的 name是否有不通过项,如果没有才走这里的代码
特点
- 方法名为validate_字段名
- 第一参数为,待校验的值
- 最后需要返回 待校验的值 (往往需要?)
- 上面的通过了, 代码才会走到 validate_name 方法这里, 如果上面有一个不通过,就不会走这里
单字段校验方法
- 也需要
有抛出 serializers.ValidationError 的地方
, 且抛的异常只能是这种
def validate_name(self,attr:str):
# 对name再次进行校验 (name定义的其他校验项通过,才走这里)
if not attr.endswith('项目'):
raise serializers.ValidationError("项目名称中,必须以“项目 结尾")
return attr
validate() 多字段进行联合校验
定义这种方法,
- 方法名是固定的
- 参数是
字典
的形式,可以获取到每个字段的数据,然后进行判断, 最后需要返回 - 也是需要抛异常
- 这个方法,是 单字段校验方法,后再校验的
def validate(self,attrs):
attrs.get('leader')
attrs.get('name')
pass # 判断每个字段长度 等等
return attrs
校验的总结 and 校验顺序
- 单个字段中:先校验它的【validator列表中的校验规则】, 上面的validators部分
- 再校验【本身的校验参数如max_length=20】等
- 【单字段校验】方法 def validate_name()
- 【多字段联合校验】方法 def validate()