Django JSONField类型操作解析
- 模型代码设计
- 正向查询与反向查询解析
- Json字段操作解析
- 新增
- 查询
- Json条件查询
- 字段条件查询
- 跨关系查询
- 修改
- 删除
接口测试平台核心以Httprunner为接口用例运行框架,要将用例的数据持久化到数据库中,方便读取修改与存储,则需要按照 yaml/json 的数据结构来设计数据库。
数据库模型关系图的总体结构
执行命令 django-admin startproject AutoTpsite
创建一个项目名为 AutoTpsite
的Django项目
再执行python manage.py startapp sqtp
命令创建一个名为 sqtp的应用
模型代码设计
其中,request,step和config参考HR对应部分的字段,由于其中会出现很多嵌套字段,所以1层的字段就用json数据类型来代替。
在models.py
下创建模型
配置config部分
class Config(models.Model):name = models.CharField('测试用例名称',max_length=128)base_url = models.CharField('IP/域名',max_length=256,null=True,blank=True) #可为空或者空白variables = models.JSONField('变量',null=True)parameters = models.JSONField('参数',null=True)export = models.JSONField('用例返回值',null=True)verify = models.BooleanField('https校验',default=False)def __str__(self):return self.name# 以 测试用例名称 对外展示
通过 models.JSONField
可指定此字段为存储类型为JSON格式。null=True
表示此字段可以为空,这个NULL指的是SQL NULL,如果想存储为JsonNULL,则可以使用 Value('null')
来实现
用例Case部分
class Case(models.Model):# 一对一关系指向配置config 关联约束选择 DO_NOTHING 不做删除,保留config = models.OneToOneField(Config,on_delete=models.DO_NOTHING)# 用例文件路径,用于后续数据的导出file_path = models.CharField('用例文件路径',max_length=1000,default='demo_case.json')def __str__(self):return self.config.name# 以 config名称(测试用例名称) 对外展示
测试用例与配置config的对应关系为 一对一 ,通过 models.OneToOneField
配置关联约束
测试步骤Step部分
class Step(models.Model):# related_name 反向查询名称 同个模型中,两个字段关联同1个模型,必须指定related_name ,且名字不能相同# 属于哪条测试用例,属于的用例被删除了,测试步骤也就没有存在的必要了 关联约束选择 CASCADE级联删除belong_case = models.ForeignKey(Case,on_delete=models.CASCADE,related_name='teststeps')# 引用的哪条测试用例,引用的用例被删除了,测试步骤可能还需要存在 关联约束选择 DO_NOTHING 不做删除,保留,或 SET_NULL 允许为nulllinked_case = models.ForeignKey(Case,on_delete=models.SET_NULL,null=True,related_name='linked_steps')name = models.CharField('测试步骤名称', max_length=128)variables = models.JSONField('变量', null=True)extract = models.JSONField('请求返回值', null=True)validate = models.JSONField('校验项', null=True)setup_hooks = models.JSONField('初始化', null=True)teardown_hooks = models.JSONField('清除', null=True)def __str__(self):return self.name# 以 测试步骤名称 对外展示
Httprunner测试框架中,测试步骤可以属于某条测试用例,一条测试用例中可能会存在多条测试用例;而测试步骤也能引用其他测试用例。
在数据存储的观点来看,测试步骤与测试用例之间的关系为 多对一 ,通过外键 ForeignKey 进行关联,但在模型的设计中,需要将这两种关联关系区分开来,所以需要有两个字段。两个字段关联同1个模型,这里需要通过related_name
反向查询名称进行区分,且名字不能相同
请求Request部分
class Request(models.Model):# 一对一关系指向测试步骤Step 关联约束选择 CASCADE级联删除step = models.OneToOneField(Step,on_delete=models.CASCADE,null=True)# method可选字段,二维元组method_choices = ((0, 'GET'), # 参数1:保存在数据库中的值,参数2:对外显示的值(1, 'POST'),(2, 'PUT'),(3, 'DELETE'),)method = models.SmallIntegerField('请求方法',choices=method_choices,default=0)url = models.CharField('请求路径', default='/', max_length=1000)params = models.JSONField('url参数', null=True)headers = models.JSONField('请求头', null=True)cookies = models.JSONField('Cookies', null=True)data = models.JSONField('data参数', null=True)json = models.JSONField('json参数', null=True)def __str__(self):return self.url# 以 请求路径 对外展示
http请求方式通常有GET、POST、PUT、DELETE,像这种数据库存储字段的值有限定内容的时候,可以先创建一个二维元组,参数1表示保存在数据库中的值,参数2表示对外显示的值,通过choices
指定创建的二维元素。数据库中只需要存0、1、2、3这些数字即可,不需要设定为CharField
类型,设定为SmallIntegerField
即可,这种方式可以减少数据存储的压力。
正向查询与反向查询解析
在1对1,1对多,多对多关系中都存在正向查询和反向查询,模型查询其关联的项目叫做正向查询
如多对一关系中,正向查询是多方查询单方,因为外键是定义在多方,通过模型对象的外键 即modelObj.foreigner
进行查询则为正向查询;反向查询是单方查找多方,通过模型对象的外键模型的小写_set
即modelObj.foreigner_set
进行查询
在tests.py文件下进行测试
from django.test import TestCase# Create your tests here.from sqtp.models import Case,Config,Step,Requestclass TestRelatedQuery(TestCase):def setUp(self) -> None:self.config1 = Config.objects.create(name='用例1',base_url='http://127.0.0.1:8080/')self.config2 = Config.objects.create(name='用例2',base_url='http://127.0.0.1:8888/')self.case1 = Case.objects.create(config=self.config1)self.case2 = Case.objects.create(config=self.config2)def test_steps_query(self):step1 = Step.objects.create(belong_case=self.case1, name='步骤1') # 步骤1关联用例1step1.linked_case = self.case2 # 步骤1引用用例1step1.save()step2 = Step.objects.create(belong_case=self.case2, name='步骤2') # 步骤2关联用例2# 正向查询print('===========正向查询===========')print(step1.belong_case) #查看step1所属用例print(step1.linked_case) # 查看step1引用的用例print(step2.belong_case) #查看step2所属的用例# 反向查询print('===========反向查询===========')# related_name代替 step_setprint(self.case1.teststeps.all()) # 查询用例1下面有哪些步骤print(self.case2.linked_steps.all()) # 查询用例2被哪些步骤引用
输出结果
===========正向查询===========
用例1
用例2
用例2
===========反向查询===========
<QuerySet [<Step: 步骤1>]>
<QuerySet [<Step: 步骤1>]>
外键在 Step模型 中定义,与 Case模型 进行关联,而 Case模型 与 Config模型 一对一关联。
通过Step
查询关联的用例,则是正向查询
通过用例查询步骤,则是属于反向查询,正常情况下要通过模型小写_set
(即step_set)进行查询,但是在Step模型 中比较特殊,两个字段关联同1个模型,所以需要用 related_name 代替 step_set
Json字段操作解析
模型中大部分字段都是json类型(Django模型 JSONField类型)存储,以Request(请求信息)模型为例,操作增删改查
在tests.py文件下进行测试
新增
from django.test import TestCase# Create your tests here.from sqtp.models import Case, Config, Step, Request
from django.db.models import Valueclass TestJsonField(TestCase):def setUp(self) -> None:print('====================新增数据======================')req1 = Request.objects.create(method=1,url='/mgr/student/',data={"name":"小明","age":16,"address":"广东广州","school":{"PrimarySchool":"实验小学","SecondarySchool":"第一中学"}})req2 = Request.objects.create(method=0,url='/api/teacher/',data={"name":"小王老师","courses":"英语","address":"广东深圳"})req3 = Request.objects.create(method=3,url='/api/delete/',data={"id":105,"display_idx":1})req = Request.objects.all()print(req)
执行命令python manage.py test sqtp.tests.TestJsonField
进行测试
通过objects.create()
新增数据,通过Request.objects.all()
查询数据,输出结果
====================新增数据======================
<QuerySet [<Request: /mgr/student/>, <Request: /api/teacher/>, <Request: /api/delete/>]>
查询
Json条件查询
class TestJsonField(TestCase):def setUp(self) -> None:print('====================新增数据======================')req1 = Request.objects.create(method=1,url='/mgr/student/',data={"name":"小明","age":16,"address":"广东广州","school":{"PrimarySchool":"实验小学","SecondarySchool":"第一中学"}})req2 = Request.objects.create(method=0,url='/api/teacher/',data={"name":"小王老师","courses":"英语","address":"广东深圳"})req3 = Request.objects.create(method=3,url='/api/delete/',data={"id":105,"display_idx":1})req = Request.objects.all()print(req)def test_json_01(self):print('====================查询数据======================')req1 = Request.objects.all().first()print(req1)print(req1.data) # 查询data数据内容print('====================json条件查询======================')req2 = Request.objects.filter(data__age=16)print(req2)req3 = Request.objects.filter(data__school__PrimarySchool="实验小学")print(req3)
通过Request.objects.all().first()
查询出第一条数据
若想要根据json里的内容筛选数据,可以根据字段名__参数名
(注意是双下划线)查询json字段里的内容,存在多层嵌套,依旧可以查询成功
输出结果
====================查询数据======================
/mgr/student/
{'name': '小明', 'age': 16, 'address': '广东广州', 'school': {'PrimarySchool': '实验小学', 'SecondarySchool': '第一中学'}}
====================json条件查询======================
<QuerySet [<Request: /mgr/student/>]>
<QuerySet [<Request: /mgr/student/>]>
字段条件查询
字段查询是指如何指定 SQL WHERE子句 的内容。它们用作 QuerySet 的 filter()、exclude() 和 get() 方法的关键字参数。 其基本格式是:field__lookuptype=value
(注意其中是双下划线),默认查找类型为exact(精确匹配)。
Django的数据库API支持20多种查询类型:
字段名 | 说明 |
---|---|
exact | 精确匹配 |
iexact | 不区分大小写的精确匹配 |
contains | 包含匹配 |
icontains | 不区分大小写的包含匹配 |
in | 在…之内的匹配 |
gt | 大于 |
gte | 大于等于 |
lt | 小于 |
lte | 小于等于 |
startswith | 从开头匹配 |
istartswith | 不区分大小写从开头匹配 |
endswith | 不区分大小写从结尾处匹配 |
range | 范围匹配 |
date | 日期匹配 |
year | 年份 |
iso_year | 以ISO 8601标准确定的年份 |
month | 月份 |
day | 日期 |
week | 第几周 |
week_day | 周几 |
iso_week_day | 以ISO 8601标准确定的星期几 |
quarter | 季度 |
time | 时间 |
hour | 小时 |
minute | 分钟 |
second | 秒 |
regex | 区分大小写的正则匹配 |
iregex | 不区分大小写的正则匹配 |
以上字段条件查询对非JSONField 类型的模型字段,同样适用
class TestJsonField(TestCase):def setUp(self) -> None:print('====================新增数据======================')req1 = Request.objects.create(method=1,url='/mgr/student/',data={"name":"小明","age":16,"address":"广东广州","school":{"PrimarySchool":"实验小学","SecondarySchool":"第一中学"}})req2 = Request.objects.create(method=0,url='/api/teacher/',data={"name":"小王老师","courses":"英语","address":"广东深圳"})req3 = Request.objects.create(method=3,url='/api/delete/',data={"id":105,"display_idx":1})def test_json_02(self):print('====================字段条件查询======================')req4 = Request.objects.filter(url__contains='mgr')print(req4)req5 = Request.objects.filter(method__in=[0,1,2])print(req5)
输出结果
====================字段条件查询======================
<QuerySet [<Request: /mgr/student/>]>
<QuerySet [<Request: /mgr/student/>, <Request: /api/teacher/>]>
跨关系查询
Django提供了强大并且直观的方式解决跨越关联的查询,它在后台自动执行包含 JOIN 的 SQL语句。要跨越某个关联,只需使用关联的模型字段名称,并使用双下划线分隔,直至你想要的字段(可以链式跨越,无限跨度)
class TestOverRelations(TestCase):def setUp(self) -> None:# 创建用例self.config1 = Config.objects.create(name='用例1', base_url='http://127.0.0.1:8080/')self.config2 = Config.objects.create(name='用例2', base_url='http://127.0.0.1:8888/')self.case1 = Case.objects.create(config=self.config1)self.case2 = Case.objects.create(config=self.config2)def test_step_request(self):# 准备测试数据 步骤和请求step1 = Step.objects.create(belong_case=self.case1,name='步骤1')step2 = Step.objects.create(belong_case=self.case1,name='步骤2')step3 = Step.objects.create(belong_case=self.case2,name='步骤3')req1 = Request.objects.create(method=0,url='/api/teacher1/',data={"name":"小王老师","courses":"英语","address":"广东深圳"},step=step1)req2 = Request.objects.create(method=1,url='/api/teacher2/',data={"name":"小王老师","courses":"英语","address":"广东深圳"},step=step2)req3 = Request.objects.create(method=2,url='/api/teacher3/',data={"name":"小王老师","courses":"英语","address":"广东深圳"},step=step3)print(req1.step.belong_case) # 链式语法 通过请求查询步骤再查询所属的用例(通过模型关联一步步查询 print(Request.objects.filter(step=step1)) # 跨关系查询print(Request.objects.filter(step__belong_case__config__name='用例1')) # 查询 用例1 下的步骤
跨关系查询语法:字段__关联字段
跨关系查询是根据上级数据特征查找下级的结果
用例1
<QuerySet [<Request: /api/teacher1/>]>
<QuerySet [<Request: /api/teacher1/>, <Request: /api/teacher2/>]>
修改
from django.test import TestCase# Create your tests here.from sqtp.models import Case, Config, Step, Request
from django.db.models import Valueclass TestJsonField(TestCase):def setUp(self) -> None:print('====================新增数据======================')req = Request.objects.create(method=1,url='/mgr/student/',data={"name":"小明","age":16,"address":"广东广州","school":{"PrimarySchool":"实验小学","SecondarySchool":"第一中学"}})def test_json_04(self):print('====================查询数据======================')req1 = Request.objects.all().first()print(req1)print(req1.data) # 查询data数据内容print('====================修改整个字段内容======================')req1.data={"name":"小王","age":16,"籍贯":"江西南昌"}req1.save()print(Request.objects.all().first().data) # 查看修改后的内容print('====================修改字段中json局部内容======================')req2 = Request.objects.all().first()print(req2.data['name']) # 修改前req2.data['name'] = '小红'req2.save()print(Request.objects.all().first().data['name']) # 修改后
输出结果
====================查询数据======================
/mgr/student/
{'name': '小明', 'age': 16, 'address': '广东广州', 'school': {'PrimarySchool': '实验小学', 'SecondarySchool': '第一中学'}}
====================修改整个字段内容======================
{'name': '小王', 'age': 16, '籍贯': '江西南昌'}
====================修改字段中json局部内容======================
小王
小红
修改data字段的内容,data字段就变为了{'name': '小王', 'age': 16, '籍贯': '江西南昌'}
;如果只需要修改json数据中的部分内容,只需要对字典中下标名称进行赋值即可,保存后json数据的内容就修改成功了
删除
from django.test import TestCase# Create your tests here.from sqtp.models import Case, Config, Step, Request
from django.db.models import Valueclass TestJsonField(TestCase):def setUp(self) -> None:print('====================新增数据======================')req = Request.objects.create(method=1,url='/mgr/student/',data={"name":"小明","age":16,"address":"广东广州","school":{"PrimarySchool":"实验小学","SecondarySchool":"第一中学"}})def test_json_05(self):print('====================查询数据======================')req1 = Request.objects.all().first()print(req1)print(req1.data) # 查询data数据内容print('====================删除字段局部的内容======================')req1.data.pop('name')req1.save()print(Request.objects.all().first().data)print('====================删除整个字段的内容======================')req1.data = Value('null') # 设置成json的null# req1.data = None # 设置成sql的nullreq1.save()print(Request.objects.all().first().data)
输出结果
====================查询数据======================
/mgr/student/
{'name': '小明', 'age': 16, 'address': '广东广州', 'school': {'PrimarySchool': '实验小学', 'SecondarySchool': '第一中学'}}
====================删除字段局部的内容======================
{'age': 16, 'address': '广东广州', 'school': {'PrimarySchool': '实验小学', 'SecondarySchool': '第一中学'}}
====================删除整个字段的内容======================
None
删除字段json数据中的内容,则是通过pop()
函数完成;删除整个字段的数据,则是字段内容置为null
。