测试平台开发 - REST 入门

一、 数据库查询强化训练

1. 字段条件查询

字段查询是指如何指定SQL WHERE子句的内容。

它们用作QuerySet的filter(), exclude()get()方法的关键字参数。

其基本格式是:field__lookuptype=value,注意其中是 双下划线

默认查找类型为exact(精确匹配)。

lookuptype的类型有:

Django的数据库API支持20多种查询类型,下表列出了所有的字段查询参数:

字段名说明
exact精准匹配
iexact不区分大小写的精准匹配
contains包含匹配
icontains不区分大小写的包含匹配
in在 。。。之内的匹配
gt大于
gte大于等于
lt小于
lte小于等于
startswith从开头匹配
istartswith不区分大小写的从开头匹配
endswith从结尾处匹配
iendswith不区分大小写的从结尾处匹配
range范围匹配
date日期匹配
year年份匹配
iso_year以 ISO 8601 标准确定的年份
month月份
day日期
week第几周
ios_week_day以 ISO 8601 标准确定的星期几
quarter季度
time时间
hour小时
minute分钟
second
regex区分大小写的正则匹配
iregex不区分大小写的正则匹配
1
2
3
4
5
6
7
8
9
10
# 字段查询
class TestFieldQuery(TestCase):
def setUp(self) -> None:
Request.objects.create(url='/api/request/demo1',data={'name':'小明','age':18,'addr':'nanjing','father':{'name':'xiaogang','age':40}})
def test_contains_query(self):
req = Request.objects.filter(url__contains='demo')
print(req)
def test_in_query(self):
req = Request.objects.filter(url__in=['/api/request/demo1','/api/request/demo2'])
print(req)

执行测试

1
python manage.py test sqtp.tests.TestFieldQuery

2. 夸关系查询

Django提供了强大并且直观的方式解决跨越关联的查询,它在后台自动执行包含JOIN的SQL语句。

要跨越某个关联,只需使用关联的模型字段名称,并使用双下划线分隔,直至你想要的字段(可以链式跨
越,无限跨度).

例如

1
2
# 查找标签是xxx的用例
res1=Case.objects.filter(tags__name='smoketest')

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#跨关系查询
class TestOverRelations(TestCase):
def setUp(self) -> None:
# 创建套件和用例
self.suite_conf = Config.objects.create(name='套件1')
self.suite = Suite.objects.create(config=self.suite_conf)
self.config = Config.objects.create(name='用例1')
self.case = Case.objects.create(config=self.config,suite=self.suite)

# 查找某套件下的用例步骤
def test_case_step(self):
Step.objects.create(belong_case=self.case,name='步骤1')
Step.objects.create(belong_case=self.case,name='步骤2')
Step.objects.create(belong_case=self.case,name='步骤3')
steps=Step.objects.filter(belong_case__suite__config__name='套件1')
print(steps)

二、 接口开发实战 - REST 框架入门

Django REST framework(以下简称 DRF或REST框架)是一个开源的 Django 扩展,提供了便捷的REST API 开发框架,拥有以下特性:

  • 直观的 API web 界面。
  • 多种身份认证和权限认证方式的支持。
  • 内置了 OAuth1 和 OAuth2 的支持。
  • 内置了限流系统。
  • 根据 Django ORM 或者其它库自动序列化。
  • 丰富的定制层级:函数视图、类视图、视图集合到自动生成 API,满足各种需要。
  • 可扩展性,插件丰富。
  • 广泛使用,文档丰富

1. 安装 REST (djangorestframework)框架

1
pip install djangorestframework

2. 配置 REST (djangorestframework)框架

在 settings.py 中 注册 REST 框架

1
2
3
4
5
# settings.py

INSTALLED_APPS = [
'rest_framework'
]

3. REST 运行原理

相比于原生django开发的web应用,多了一层序列化器(Serializer)

序列化器和表单都是基于Field进行字段验证,而Field都来自于rest_framework.fields模块,相当于把django的封装了一层。

三、 REST 框架基本组件 —- Serializer

Serializer 的作用:实现序列化和 反序列化

  • 序列化 : 就是将 python 数据对象 转化 成方便传输的文本格式, 如 json、xml等
  • 反序列化 : 就是将 文本格式的 数据 转化为 python 数据对象

序列化 通常定义在 应用程序下单独文件中 serializer.py

1. 模型改造,添加元类

模型的元数据 : 是指除了字段为的所有内容,比如 排序方式,数据库名等等

增加元数据的方法 : 在模型类中添加 子类, 名字固定为 Meta, 然后在这个 子类 下 增加各种元数据选项。

1
2
3
4
5
6
from django.db import models
class Request(models.Model):
...
class Meta: # 模型元类的作用:为模型增加额外的信息,如模型对应表名,
ordering = ['id'] # 数据根据ID排序
db_table = ['reqquest'] # 模型对应的数据库表名,不设置则默认为app名_模型名

2. 创建 序列化类

开发我们的Web API的第一件事是为我们的Web API提供一种将代码片段实例序列化和反序列化为诸如json 之类的表示形式的方式 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# serializer.py

from rest_framework import serializers
from sqtp.models import Step, Request


class RequestSerializer(serializers.Serializer):
method_choices = ( # method可选字段,二维元组
(0, 'GET'), # 参数1:保存在数据库中的值,参数2:对外显示的值
(1, 'POST'),
(2, 'PUT'),
(3, 'DELETE'),
)
step = serializers.RelatedField(queryset=Step.objects.all(),allow_null=True)
method = serializers.ChoiceField(choices=method_choices, default=0)
url = serializers.CharField()
params = serializers.JSONField(allow_null=True)
headers = serializers.JSONField(allow_null=True)
cookies = serializers.JSONField(allow_null=True)
data = serializers.JSONField(allow_null=True)
json = serializers.JSONField(allow_null=True)

def create(self, validated_data):
"""
根据提供的验证过的数据创建并返回一个新的`Snippet`实例。
"""
return Request.objects.create(**validated_data)

def update(self, instance, validated_data):
"""
根据提供的验证过的数据创建并返回一个新的`Snippet`实例。
"""
instance.step =validated_data.get('step',instance.step)
instance.method =validated_data.get('method',instance.method)
instance.url =validated_data.get('url',instance.url)
instance.params =validated_data.get('params',instance.params)
instance.headers =validated_data.get('headers',instance.headers)
instance.cookies =validated_data.get('cookies',instance.cookies)
instance.data =validated_data.get('data',instance.data)
instance.json =validated_data.get('json',instance.json)

序列化器类的第一部分定义了序列化/反序列化的字段。

create() 和 update() 方法定义了在调用serializer.save() 时如何创建和修改完整的实例

3. 使用序列化类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# sqtp/test_serializers.py

from django.test import TestCase
from sqtp.models import Step, Request
from sqtp.serializers import RequestSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser


class TestRequestSerializer(TestCase):
req1=Request.objects.create(url='/demo1',data={'name':'小明','age':18,'addr':'nanjing'})
req2=Request.objects.create(url='/demo1',params={'name':'小明','age':18,'addr':'nanjing'})

# 序列化:数据对象-->>python原生数据类型
req1_serializer = RequestSerializer(req1)
print(req1_serializer.data) # 序列化数据存储在序列化对象的data属性中

# 序列化:python原生数据类型-->>json
content = JSONRenderer().render(req1_serializer.data)
print(content)

执行测试

1
python manage.py test sqtp.test_serializers

返回

1
2
{'step': None, 'method': 0, 'url': '/demo1', 'params': None, 'headers': None,'cookies': None, 'data': {'name': '小明', 'age': 18, 'addr': 'nanjing'}, 
'json': None}b'{"step":null,"method":0,"url":"/demo1","params":null,"headers":null,"cookies":null,"data":{"name":"\xe5\xb0\x8f\xe6\x98\x8e","age":18,"addr":"nanjing"},"json":null}'

反序列化是类似的。测试类再新增以下代码,实现反序列化

1
2
3
4
5
6
7
8
9
10
11
# 反序列化1: 将流(stream)解析为Python原生数据类型.
import io

stream = io.BytesIO(content) # 构建1个steam流
data = JSONParser().parse(stream) # 转成python原生数据类型

# 反序列化2: Python原生数据类型恢复成正常的对象实例。
serializer = RequestSerializer(data=data) # 序列化器
print(serializer.is_valid()) # 校验数据是否合法
print(serializer.validated_data) # 查看数据对象
serializer.save() # 保存数据

执行测试

1
python manage.py test sqtp.test_serializers

返回

1
2
3
4
...
True
OrderedDict([('step', None), ('method', 0), ('url', '/demo1'), ('params', None),
('headers', None), ('cookies', None), ('data', {'name': '小明', 'age': 18, 'addr': 'nanjing'}), ('json', None)])

除了序列化模型实例,序列化器还可以序列化查询结果集(querysets),只需要为serializer添加一个
many=True 标志。

1
2
3
# 序列化查询结果集(querysets)
serializers= RequestSerializer(Request.objects.all(),many=True)
print(serializers.data)

4. 改进 序列化器(重点)

ModelSerializer 代替 Serializer 类作为自定义序列化器的父类,减少重复代码的编写

使用 ModelSerializer 重构序列化类

1
2
3
4
5
6
7
8
9
10
from rest_framework import serializers
from sqpt.models import Request, Step, Config, Case


# 请求模型的序列化器
class RequestSerializer(serializers.ModelSerializer):
class Meta:
model = Request # 指定对应的模型
# 定义可以显示和操作的字段
fields = ['step', 'method', 'url', 'params', 'headers', 'data', 'json']

打印序列化器类实例的结构(representation)查看它的所有字段

1
2
3
4
# test_serializers.py

# 在测试类中添加代码,查看序列化器的内部结构
print(repr(serializer))

返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
RequestSerializer(data={'id': 13, 'step': None, 'method': 0, 'url': '/demo1',
'params': None, 'headers': None,
'data': {'name': '小明', 'age': 18, 'addr': 'nanjing'}, 'json': None}):
id = IntegerField(label='ID', read_only=True)
step = PrimaryKeyRelatedField(allow_null=True, queryset=Step.objects.all(),
required=False, validators=[<Un
iqueValidator(queryset=Request.objects.all())>])
method = ChoiceField(choices=((0, 'GET'), (1, 'POST'), (2, 'PUT'), (3,
'DELETE')), label='请求方法', requir
ed=False, validators=[<django.core.validators.MinValueValidator object>,
<django.core.validators.MaxValueValida
tor object>])
url = CharField(label='请求路径', max_length=1000, required=False)
params = JSONField(allow_null=True, decoder=None, encoder=None, label='Url参
数', required=False, style={'ba
se_template': 'textarea.html'})
headers = JSONField(allow_null=True, decoder=None, encoder=None, label='请求
头', required=False, style={'ba
se_template': 'textarea.html'})
data = JSONField(allow_null=True, decoder=None, encoder=None, label='Data参
数', required=False, style={'bas
e_template': 'textarea.html'})
json = JSONField(allow_null=True, decoder=None, encoder=None, label='Json参
数', required=False, style={'bas
e_template': 'textarea.html'})

ModelSerializer 类并不会做任何特别神奇的事情,它们只是创建序列化器类的快捷方式:

  • 一组自动确定的字段。
  • 默认简单实现的 create() 和 update() 方法

5. 视图编写

编辑 sqtp/views.py,导入以下库

1
2
3
4
5
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.parsers import JSONParser
from sqtp.models import Request
from sqtp.serializers import RequestSerializer

编写一个视图可以返回所有的请求数据

1
2
3
4
5
def request_list(request):
if request.method == 'GET':
serializer= RequestSerializer(Request.objects.all(),many=True)
# safe=False是为了支持{}以外的python对象转json
return JsonResponse(serializer.data, safe=False)

创建 sqtp/urls.py, 写入路由

1
2
3
4
5
6
from django.urls import path
from sqtp import views

urlpatterns = [
path('requests/',views.request_list)
]

总路由引入子路由 autotpsite/urls.py

1
2
3
4
5
from django.urls import path,include

urlpatterns = [
path('',include('sqtp.urls'))
]

启动server服务并测试

1
python manage.py runserver

6. 接口开发-DRF-进阶

接口开发本质上是处理请求和响应,包括了处理请求参数,判断请求方法,处理响应字段,响应码等

第一个工具,函数视图 装饰器 @api_view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.http import HttpResponse, JsonResponse
from rest_framework.decorators import api_view
from sqtp.models import Request
from rest_framework.response import Response
from sqtp.serializers import RequestSerializer

@api_view(['GET','POST']) # 允许的请求方法
def request_list(request, format=None):
if request.method == 'GET':
# 构造序列化器
serializer = RequestSerializer(Request.objects.all(), many=True)
# 返回 json 格式数据
# safe=False 是为了支持 {} 以外的 python 对象转 json
return Response(serializer.data) # 将 python 原生格式转成 json 数据

装饰器的作用是 确保你在视图中接收到 Request 实例,并将上下文添加到 Response ,以便可以执行内容协商 .

包装器还提供了诸如在适当时候返回 405 Method Not Allowed 响应,并处理在使用格式错误的输入来访问 request.data 时发生的任何 ParseError 异常

1
2
3
4
5
6
7
8
@api_view(['GET'])
def request_detail(request, _id, format=None):
try:
req_obj = Request.objects.get(id=_id) # 根据 ID 查找单个数据
except Exception:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = RequestSerializer(req_obj)
return Response(serializer.data)

6. 请求与响应

在纯django中,request和response只能实现有限的功能。REST框架对其作了扩展 :

请求对象(Request objects)

REST框架引入了一个扩展了常规 HttpRequest 的 Request 对象,并提供了更灵活的请求解析。

Request 对象的核心功能是 request.data 属性,它与 request.POST 类似,但对于使用Web API更为有用。

1
2
request.POST   # 只处理表单数据 只适用于'POST'方法
request.data # 处理任意数据 适用于'POST','PUT'和'PATCH'方法

响应对象(Response objects)

REST框架还引入了一个 Response 对象,这是一种获取未渲染(unrendered)内容的TemplateResponse 类型,并使用内容协商来确定返回给客户端的正确内容类型。

1
return Response(data)     # 渲染成客户端请求的内容类型。

默认情况下,Response把响应内容嵌套在默认的模板中返回出去,所以我们看到的内容是网页形式,而不是单纯的数据。

响应数据格式 - 扩展

为API路径添加对格式后缀 如http://example.com/api/items/4.json之类的URL

视图中添加一个 format 关键字参数。

1
2
3
4
5
6
@api_view(['GET'])
def request_list(request,format=None):
if request.method == 'GET':
serializer = RequestSerializer(Request.objects.all(), many=True)
return Response(serializer.data)
# safe=False是为了支持{}以外的python对象转json

修改路由文件 sqtp/urls.py

1
2
3
4
5
6
7
8
from django.urls import path
from sqtp import views
from rest_framework.urlpatterns import format_suffix_patterns

urlpatterns = [
path('requests/',views.request_list)
]
urlpatterns = format_suffix_patterns(urlpatterns)

访问http://127.0.0.1:8000/requests.json ,返回了json格式的数据

查询单个数据 - 扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
@api_view(['GET'])
def request_detail(request,_id,format=None):
"""
获取,更新或删除一个req实例。
"""
try:
req_obj = Request.objects.get(pk=_id)
except Request.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND) # status提供常用http状态

if request.method == 'GET':
serializer = RequestSerializer(req_obj)
return Response(serializer.data)

更新路由:sqtp/urls.py

1
2
3
4
5
urlpatterns = [
path('requests/', views.request_list),
path('requests/<int:_id>', views.request_detail)
]
urlpatterns = format_suffix_patterns(urlpatterns)

访问http://127.0.0.1:8000/requests/2

http://127.0.0.1:8000/requests/2.json

附录

可用的Meta选项:模型 Meta 选项 | Django 文档 | Django (djangoproject.com)