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, Projecthr3.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),
]