7.管理功能开发
模型关联关系
用例和项目之间的关联可以直接通过 config,因为 config 是套件和用例的扩展,相当于用例和套件本身 。
一、 模块的包管理
1. 创建模型包
如果模型过多时,需要把模型分别存储。
在应用中创建一个名为 models 的包
,将所有的模型文件全部存放在 该文件夹下, 删除原来的 models.py
文件。
2. 创建 模型文件
在 __init__.py
文件中,要使用 显示明确的方式导入每个模型,这样不会让命名混淆,便于可读。
# sqpt/models/__init__.py
from .hr3 import Request, Case, Config, Suite, Step
from .auth import User
from .base import CommonInfo
from .mgr import Environment, Project
hr3.py 存放所有核心数据模型
mgr.py 存放所有项目管理相关模型
# mgr.py
from django.db import models
from .base import CommonInfo
from django.conf import settings # 指向 django 项目的配置模块 settings.py
# 项目
class Project(CommonInfo):
PRO_STATUS = (
(0, '开发中'),
(1, '维护中'),
(2, '稳定运行'),
)
admin = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, null=True, related_name='admin_pros', verbose_name='管理员')
members = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='member_pros', verbose_name='成员')
name = models.CharField('项目名称', max_length=256, unique=True)
status = models.SmallIntegerField('项目状态', choices=PRO_STATUS, default=0)
version = models.CharField('项目版本', default='v0.1', max_length=10)
class Meta(CommonInfo.Meta): # 需要显示继承元类,才会继承相关属性(abstract 除外)
verbose_name = '项目表'
# 测试环境
class Environment(CommonInfo):
service_type = (
(0, 'web服务器'),
(1, '数据库服务器'),
(2, '缓存服务器'),
)
service_os = (
(0, 'windows'),
(1, 'linux'),
)
service_status = (
(0, 'active'), # 可用状态
(1, 'disable'), # 不可以状态
)
project = models.ForeignKey(Project, on_delete=models.CASCADE, verbose_name='所属项目')
# django ORM 提供 GenericIPAddressField 专门用于存储 IP 字段
ip = models.GenericIPAddressField('IP地址', default='127.0.0.1')
port = models.CharField('端口号', max_length=5, default='8080')
category = models.SmallIntegerField('服务器类型', choices=service_type, default=0)
os = models.SmallIntegerField('操作系统', choices=service_os, default=1)
status = models.SmallIntegerField('服务器状态', choices=service_status, default=0)
class Meta(CommonInfo.Meta):
verbose_name = '测试环境表'
二、 模型继承
1. 抽象类
通友字段
用一个 抽象模型类
来存放,然后其他模型继承该抽象模型类,例如:创建时间、更新时间等
在元类中使用 abstract = True
定义 抽象类
,抽象类 不会创建 数据库表
auto_now_add
: 第 1 次创建数据时自动添加当前时间
auto_now
:每次更新数据时自动添加当前时间
# sqpt/models/base.py
from django.db import models
from django.conf import settings
class CommonInfo(models.Model):
creator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, null=True,related_name='%(app_label)s_%(class)s_create')
updater = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, null=True,related_name='%(app_label)s_%(class)s_update')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间', null=True)
decs = models.CharField('描述', max_length=1024, null=True, blank=True)
class Meta:
abstract = True # 定义为抽象模型,不会创建数据库
ordering = ['id']
子类继承父类:
# mgr.py
class Project(CommonInfo):
...
class Environment(CommonInfo):
...
2. 抽象基类(父类) 的 Meta 数据
如果子类没有声明自己的 Meta 类,那么它将自动继承抽象基类的 Meta 类。
如果子类要设置自己的 Meta 属性,则需要扩展基类的 Meta:
from django.db import models
class CommonInfo(models.Model):
# ...
class Meta:
abstract = True
ordering = ['name']
class Student(CommonInfo):
# ...
class Meta(CommonInfo.Meta): # 注意这里有个继承关系
db_table = 'student_info'
这里有几点要特别说明:
- 抽象基类中有元数据,子模型没有的话,直接继承;
- 抽象基类中有元数据,子模型也有的话,直接覆盖;
- 子模型可以额外添加元数据;
- 抽象基类中的 abstract=True 这个元数据不会被继承。
- 也就是说如果想让一个抽象基类的子模型,同样成为一个抽象基类,那你必须显式的在该子模型的 Meta 中同样声明一个 abstract =True ;
- 有一些元数据对抽象基类无效,比如 db_table ,首先是抽象基类本身不会创建数据表,其次它的所有子类也不会按照这个元数据来设置表名。
- 由于 Python 继承的工作机制,如果子类继承了多个抽象基类,则默认情况下仅继承第一个列出的基 类的 Meta 选项。如果要从多个抽象基类中继承 Meta 选项,必须显式地声明 Meta 继承。例如:
from django.db import models
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
ordering = ['name']
class Unmanaged(models.Model):
class Meta:
abstract = True
managed = False
class Student(CommonInfo, Unmanaged):
home_group = models.CharField(max_length=5)
class Meta(CommonInfo.Meta, Unmanaged.Meta):
pass
# hr3.py
from django.db import models
from .base import CommonInfo
from .mgr import Project
class Config(models.Model):
project = models.ForeignKey(Project, on_delete=models.DO_NOTHING, null=True, verbose_name='所属项目')
name = models.CharField('名称', max_length=128)
# 可 null 可空白
base_url = models.CharField('IP/域名', max_length=512, null=True, blank=True)
variables = models.JSONField('变量', null=True)
parameters = models.JSONField('参数', null=True) # 用于参数化
verify = models.BooleanField('https校验', default=False)
export = models.JSONField('用例返回值', null=True)
def __str__(self):
return self.name
# 模型的元类,可以为模型添加额外的信息,比如 数据排序方式,模型对应表名
class Meta:
ordering = ['id'] # 根据 ID 正向排序(反向排序 ['-id'])
# db_table = ['conf_tb']
class Step(models.Model):
# 同模型中,两个字段关联一个模型时,需要指定 related_name, 且名字不能相同
# 属于哪个用例
belong_case = models.ForeignKey('Case', on_delete=models.CASCADE, related_name='test_steps')
# 引用那条用例
linked_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
class Meta:
ordering = ['id']
class Request(CommonInfo):
method_choices = ( # method 可选字段, 二维元组
(0, 'GET'), # 参数 1 : 保存在数据库中的值 ,参数 2 : 对位显示的值
(1, 'POST'),
(2, 'PUT'),
(3, 'DELETE'),
)
step = models.OneToOneField(Step, on_delete=models.CASCADE, null=True, related_name='testrequest')
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
class Meta(CommonInfo.Meta):
ordering = ['id']
verbose_name = '接口请求表'
# 用例
class Case(CommonInfo):
config = models.OneToOneField(Config, on_delete=models.DO_NOTHING)
suite = models.ForeignKey('Suite', on_delete=models.DO_NOTHING, null=True)
file_path = models.CharField('用例文件路径', max_length=1000, default='demo_case.json')
def __str__(self):
return self.config.name
class Meta(CommonInfo.Meta):
ordering = ['id']
verbose_name = '用例表'
# 套件
class Suite(CommonInfo):
config = models.OneToOneField(Config, on_delete=models.DO_NOTHING)
file_path = models.CharField('套件文件路径', max_length=1000, default='demo_suite.json')
def __str__(self):
return self.config.name
class Meta(CommonInfo.Meta):
ordering = ['id']
verbose_name = '套件表'
3. 警惕 related_name 和 related_query_name 参数
如果 抽象类中 存在 ForeignKey
或者 ManyToManyField
字段,那么使用 related_name
或者 related_query_name
参数的值就不能固定,否则在正向查询或反向查询时会出现错误。
解决办法:
related_name
或者 related_query_name
的值中包含 %(app_label)s
和 %(class)s
部分
%(class)s
: 用字段所属子类的小写名替换%(app_label)s
: 用子类所属 app 的小写名替换
# sqpt/models/base.py
from django.db import models
from django.conf import settings
class CommonInfo(models.Model):
creator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, null=True,related_name='%(app_label)s_%(class)s_create')
updater = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, null=True,related_name='%(app_label)s_%(class)s_update')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间', null=True)
decs = models.CharField('描述', max_length=1024, null=True, blank=True)
class Meta:
abstract = True # 定义为抽象模型,不会创建数据库
ordering = ['id']
- common.ChildA.m2m 字段的 reverse name (反向关系名)应该是 common_childa_related ;reverse query name (反向查询名)应该是 common_childas 。
- common.ChildB.m2m 字段的反向关系名应该是 common_childb_related ;反向查询名应该是 common_childbs 。
- rare.ChildB.m2m 字段的反向关系名应该是 rare_childb_related ;反向查询名应该是 rare_childbs 。
三、 用户管理
1. 自定义用户模型
当我们执行 migrate 创建数据库表时,就会为我们创建 用户表 auth_user ,如下所示
如果模型中的字段不符合我们的需求时,通过继承 django.contrib.auth.models
里面的 AbstractUser
类的方式,在你项目文件的 models
里面进行如下定义
# models/auth.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
USER_TYPE = (
(0, '开发'),
(1, '测试'),
(2, '运维'),
(3, '项目经理'),
)
realname = models.CharField('姓名', max_length=32, null=True)
phone = models.CharField('手机号', max_length=11, unique=True)
user_type = models.SmallIntegerField('用户类型', choices=USER_TYPE, default=1)
这样就在原来的 contrib.auth 里面的 user 表的基础上,新增了 usertype
、realname
、phone
、 这些字段
在 settings.py
文件中 设置,使 Django 以该表作为 user 表
# settings.py
# 自定义用户模型
AUTH_USER_MODEL = 'sqpt.User'
同步数据库模型前,需要:
1.删除 migrations 目录及所有文件,
2.删除数据库并重写创建。
3.再执行数据库同步。
2. 引用 User 模型
直接引用 User 模型,那么以后我们的项目改成不同的用户模型时,就无法自动切到 AUTH_USER_MODEL 配置的用户模型 ,因此应该直接引用 AUTH_USER_MODEL
配置的用户模型
# mgr.py
from django.db import models
from .base import CommonInfo
from django.conf import settings # 指向 django 项目的配置模块 settings.py
# 项目
class Project(CommonInfo):
PRO_STATUS = (
(0, 'developing'),
(1, 'operating'),
(2, 'stable'),
)
admin = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, null=True, related_name='admin_pros', verbose_name='%(app_label)s_%(class)s_admin')
members = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='member_pros',verbose_name='%(app_label)s_%(class)s_members')
name = models.CharField('项目名称', max_length=256, unique=True)
status = models.SmallIntegerField('项目状态', choices=PRO_STATUS, default=0)
version = models.CharField('项目版本', default='v0.1', max_length=10)
class Meta(CommonInfo.Meta): # 需要显示继承元类,才会继承相关属性(abstract 除外)
verbose_name = '项目表'
# base.py
from django.db import models
from django.conf import settings
class CommonInfo(models.Model):
creator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, null=True, verbose_name='创建者',
related_name='%(app_label)s_%(class)s_create')
updater = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, null=True, verbose_name='更新者',
related_name='%(app_label)s_%(class)s_update')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间', null=True)
decs = models.CharField('描述', max_length=1024, null=True, blank=True)
class Meta:
abstract = True # 定义为抽象模型,不会创建数据库
ordering = ['id']
同步数据库:
python manage.py makemigrations sqpt
python manage.py migrate
四、 视图开发
1. 创建序列化器
# serializers.py
# 项目
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = '__all__'
# 测试环境
class EnvironmentSerializer(serializers.ModelSerializer):
class Meta:
model = Environment
fields = '__all__'
# 用户
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
2. 创建视图
# views.py
# 项目
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
# 环境
class EnvironmentViewSet(viewsets.ModelViewSet):
queryset = Environment.objects.all()
serializer_class = EnvironmentSerializer
# 查询所有用户
@api_view(['GET'])
def user_lest(request):
query_set = User.objects.all()
serializer = UserSerializer(query_set, many=True)
return Response(serializer.data)
# 查询单个用户
@api_view(['GET'])
def user_detail(request, _id):
try:
user_obj = User.objects.get(pk=_id)
except User.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND) # 如果 ID 不正确则返回异常
serializer = UserSerializer(user_obj) # 通过用户数据对象构造序列化器
return Response(serializer.data)
3. 创建路由
# urls.py
urlpatterns = [
# 引用相关路由
path('', include(router.urls)),
path('users/', views.user_lest),
path('users/<int:_id>', views.user_detail),
]