# -*- coding: utf-8 -*-
|
"""
|
@File : serializers.py
|
@Time : 2023/1/14 14:25
|
@Author : geekbing
|
@LastEditTime : -
|
@LastEditors : -
|
@Description : 序列化&反序列化
|
"""
|
|
|
import datetime
|
import json
|
from ast import literal_eval
|
from typing import Union
|
|
from croniter import croniter
|
from django.contrib.auth import get_user_model
|
from django.contrib.auth.models import Group
|
from django.db.models import Q
|
from django_celery_beat.models import PeriodicTask
|
|
from rest_framework import serializers
|
|
from lunarlink import models
|
from lunarlink.utils.parser import Parse
|
from lunarlink.utils.tree import get_tree_relation_name
|
|
|
Users = get_user_model()
|
|
|
class ProjectSerializer(serializers.ModelSerializer):
|
"""项目信息序列化"""
|
|
api_cover_rate = serializers.SerializerMethodField()
|
creator_name = serializers.SlugRelatedField(
|
slug_field="name",
|
source="creator",
|
read_only=True,
|
)
|
updater_name = serializers.SerializerMethodField(read_only=True)
|
groups = serializers.PrimaryKeyRelatedField(
|
many=True,
|
read_only=False,
|
queryset=Group.objects.all(),
|
required=False,
|
allow_null=True
|
)
|
|
class Meta:
|
model = models.Project
|
fields = [
|
"id",
|
"name",
|
"desc",
|
"responsible",
|
"creator",
|
"creator_name",
|
"create_time",
|
"updater",
|
"updater_name",
|
"update_time",
|
"yapi_openapi_token",
|
"yapi_base_url",
|
"api_cover_rate",
|
"jira_project_key",
|
"jira_bearer_token",
|
"groups",
|
]
|
|
def get_updater_name(self, obj):
|
if not hasattr(obj, "updater") or obj.updater is None:
|
return None
|
user = Users.objects.filter(id=obj.updater).first()
|
return user.name if user else None
|
|
def get_api_cover_rate(self, obj) -> str:
|
"""
|
接口覆盖率,百分比保留两位小数
|
:param obj: Project实例对象
|
:return:
|
"""
|
apis = (
|
models.API.objects.filter(project_id=obj.id, is_deleted=False)
|
.filter(~Q(tag=4))
|
.values("url", "method")
|
)
|
api_unique = {f'{api["url"]}_{api["method"]}' for api in apis}
|
case_steps = (
|
models.CaseStep.objects.filter(case__project_id=obj.id)
|
.filter(~Q(method="config"))
|
.values("url", "method")
|
)
|
case_steps_unique = {
|
f'{case_step["url"]}_{case_step["method"]}' for case_step in case_steps
|
}
|
if len(api_unique) == 0:
|
return "0.00"
|
if len(case_steps_unique) > len(api_unique):
|
return "100.00"
|
return "%.2f" % (len(case_steps_unique & api_unique) / len(api_unique) * 100)
|
|
|
class VisitSerializer(serializers.ModelSerializer):
|
"""
|
访问统计序列化
|
"""
|
|
class Meta:
|
model = models.Visit
|
fields = "__all__"
|
|
|
class DebugTalkSerializer(serializers.ModelSerializer):
|
"""
|
驱动代码序列化
|
"""
|
|
class Meta:
|
model = models.Debugtalk
|
fields = "__all__"
|
|
|
class RelationSerializer(serializers.ModelSerializer):
|
"""树形结构序列化"""
|
|
class Meta:
|
model = models.Relation
|
fields = "__all__"
|
|
|
class AssertSerializer(serializers.Serializer):
|
"""API搜索序列化"""
|
|
class Meta:
|
model = models.API
|
|
node = serializers.IntegerField(min_value=0, default=None)
|
project = serializers.IntegerField(required=True, min_value=1)
|
search = serializers.CharField(default="")
|
creator = serializers.CharField(required=False, default="")
|
tag = serializers.ChoiceField(choices=models.API.TAG, default="")
|
rigEnv = serializers.ChoiceField(choices=models.API.ENV_TYPE, default="")
|
is_deleted = serializers.BooleanField(default=False)
|
onlyMe = serializers.BooleanField(default=False)
|
showYAPI = serializers.BooleanField(default=True)
|
start_time = serializers.CharField(required=False, default=None)
|
end_time = serializers.CharField(required=False, default=None)
|
|
|
class CaseSearchSerializer(serializers.Serializer):
|
"""
|
用例反序列化验证器
|
"""
|
|
creator = serializers.CharField(required=False, default="")
|
node = serializers.IntegerField(min_value=0, default=None)
|
project = serializers.IntegerField(required=True, min_value=1)
|
search = serializers.CharField(default="")
|
searchType = serializers.CharField(default="")
|
caseType = serializers.CharField(default="")
|
onlyMe = serializers.BooleanField(default=False)
|
start_time = serializers.CharField(required=False, default=None)
|
end_time = serializers.CharField(required=False, default=None)
|
|
|
class CaseSerializer(serializers.ModelSerializer):
|
"""用例信息序列化"""
|
|
creator_name = serializers.SlugRelatedField(
|
slug_field="name",
|
source="creator",
|
read_only=True,
|
)
|
tag = serializers.CharField(source="get_tag_display")
|
tasks = serializers.ListField(read_only=True) # 包含用例的定时任务
|
updater_name = serializers.SerializerMethodField(read_only=True)
|
|
class Meta:
|
model = models.Case
|
fields = "__all__"
|
|
def get_updater_name(self, obj):
|
if not hasattr(obj, "updater") or obj.updater is None:
|
return None
|
user = Users.objects.filter(id=obj.updater).first()
|
return user.name if user else None
|
|
|
class CaseStepSerializer(serializers.ModelSerializer):
|
"""用例步骤序列化"""
|
|
body = serializers.SerializerMethodField()
|
|
class Meta:
|
model = models.CaseStep
|
fields = [
|
"id",
|
"name",
|
"url",
|
"method",
|
"body",
|
"case",
|
"source_api_id",
|
"creator",
|
"updater",
|
]
|
depth = 1
|
|
def get_body(self, obj):
|
body = literal_eval(obj.body)
|
if "base_url" in body["request"].keys():
|
return {"name": body["name"], "method": "config"}
|
else:
|
parse = Parse(literal_eval(obj.body))
|
parse.parse_http()
|
return parse.testcase
|
|
|
class APIRelatedCaseSerializer(serializers.Serializer):
|
case_name = serializers.CharField(source="case.name")
|
case_id = serializers.CharField(source="case.id")
|
|
class Meta:
|
fields = ["case_id", "case_name"]
|
|
|
class APISerializer(serializers.ModelSerializer):
|
"""
|
接口信息序列化
|
"""
|
|
creator_name = serializers.SlugRelatedField(
|
slug_field="name",
|
source="creator",
|
read_only=True,
|
)
|
body = serializers.SerializerMethodField()
|
tag_name = serializers.CharField(source="get_tag_display")
|
cases = serializers.SerializerMethodField()
|
relation_name = serializers.SerializerMethodField()
|
updater_name = serializers.SerializerMethodField(read_only=True)
|
|
class Meta:
|
model = models.API
|
fields = [
|
"id",
|
"name",
|
"url",
|
"method",
|
"project",
|
"relation",
|
"body",
|
"rig_env",
|
"tag",
|
"tag_name",
|
"update_time",
|
"is_deleted",
|
"creator",
|
"creator_name",
|
"updater",
|
"updater_name",
|
"cases",
|
"relation_name",
|
]
|
|
def get_body(self, obj):
|
parse = Parse(literal_eval(obj.body))
|
parse.parse_http()
|
return parse.testcase
|
|
def get_cases(self, obj):
|
case_steps = models.CaseStep.objects.filter(source_api_id=obj.id)
|
cases = APIRelatedCaseSerializer(many=True, instance=case_steps)
|
return cases.data
|
|
def get_relation_name(self, obj):
|
relation_obj = models.Relation.objects.get(project_id=obj.project_id, type=1)
|
label = get_tree_relation_name(literal_eval(relation_obj.tree), obj.relation)
|
return label
|
|
def get_updater_name(self, obj):
|
if not hasattr(obj, "updater") or obj.updater is None:
|
return None
|
user = Users.objects.filter(id=obj.updater).first()
|
return user.name if user else None
|
|
|
class ConfigSerializer(serializers.ModelSerializer):
|
"""配置信息序列化"""
|
|
body = serializers.SerializerMethodField()
|
|
class Meta:
|
model = models.Config
|
fields = [
|
"id",
|
"base_url",
|
"body",
|
"name",
|
"update_time",
|
"is_default",
|
"creator",
|
"updater",
|
]
|
|
def get_body(self, obj):
|
parse = Parse(literal_eval(obj.body), level="config")
|
parse.parse_http()
|
return parse.testcase
|
|
|
class VariablesSerializer(serializers.ModelSerializer):
|
"""
|
变量信息序列化
|
"""
|
|
key = serializers.CharField(allow_null=False, max_length=100, required=True)
|
value = serializers.CharField(allow_null=False, max_length=1024)
|
description = serializers.CharField(required=False, allow_blank=True)
|
|
class Meta:
|
model = models.Variables
|
fields = "__all__"
|
|
|
class ReportSerializer(serializers.ModelSerializer):
|
"""报告信息序列化"""
|
|
creator = serializers.SlugRelatedField(
|
slug_field="name",
|
read_only=True,
|
)
|
type = serializers.CharField(source="get_type_display")
|
time = serializers.SerializerMethodField()
|
stat = serializers.SerializerMethodField()
|
platform = serializers.SerializerMethodField()
|
success = serializers.SerializerMethodField()
|
ci_job_url = serializers.CharField()
|
|
class Meta:
|
model = models.Report
|
fields = [
|
"id",
|
"name",
|
"type",
|
"time",
|
"stat",
|
"platform",
|
"success",
|
"creator",
|
"updater",
|
"ci_job_url",
|
]
|
|
def get_time(self, obj):
|
return json.loads(obj.summary)["time"]
|
|
def get_stat(self, obj):
|
return json.loads(obj.summary)["stat"]
|
|
def get_platform(self, obj):
|
return json.loads(obj.summary)["platform"]
|
|
def get_success(self, obj):
|
return json.loads(obj.summary)["success"]
|
|
|
def get_cron_next_execute_time(crontab_expr: str) -> int:
|
"""
|
获取下一个符合给定cron表达式的时间,并将其转换为时间戳返回
|
:param crontab_expr: cron表达式时间
|
:return:
|
"""
|
now = datetime.datetime.now()
|
cron = croniter(crontab_expr, now)
|
next_time: datetime.datetime = cron.get_next(datetime.datetime)
|
return int(next_time.timestamp())
|
|
|
class PeriodicTaskSerializer(serializers.ModelSerializer):
|
"""
|
定时任务信息序列化
|
"""
|
|
kwargs = serializers.SerializerMethodField()
|
args = serializers.SerializerMethodField()
|
name = serializers.SerializerMethodField()
|
last_run_at = serializers.SerializerMethodField()
|
|
class Meta:
|
model = PeriodicTask
|
fields = [
|
"id",
|
"name",
|
"args",
|
"kwargs",
|
"enabled",
|
"date_changed",
|
"enabled",
|
"description",
|
"total_run_count",
|
"last_run_at",
|
]
|
|
def get_name(self, obj):
|
"""
|
兼容定时任务名称必须唯一
|
|
:param obj:
|
:return:
|
"""
|
name: str = obj.name
|
return name.split("_")[1]
|
|
def get_kwargs(self, obj):
|
kwargs = json.loads(obj.kwargs)
|
if obj.enabled:
|
kwargs["next_execute_time"] = get_cron_next_execute_time(kwargs["crontab"])
|
|
kwargs["ci_project_ids"] = kwargs.get("ci_project_ids", "")
|
kwargs["ci_env"] = kwargs.get("ci_env", "请选择")
|
kwargs["config"] = kwargs.get("config", "请选择")
|
# False:串行,True:并行
|
kwargs["is_parallel"] = kwargs.get("is_parallel", False)
|
return kwargs
|
|
def get_args(self, obj):
|
case_id_list = json.loads(obj.args)
|
# 数据格式, list of dict: [{"id":case_id, "name"}]
|
return list(
|
models.Case.objects.filter(pk__in=case_id_list).values("id", "name")
|
)
|
|
def get_last_run_at(self, obj) -> Union[str, int]:
|
if obj.last_run_at:
|
return int(obj.last_run_at.timestamp())
|
return ""
|
|
|
class ScheduleDeSerializer(serializers.Serializer):
|
"""
|
定时任务反序列化
|
"""
|
|
switch = serializers.BooleanField(required=True, help_text="定时任务开关")
|
crontab = serializers.CharField(
|
required=True, help_text="定时任务表达式", max_length=100, allow_blank=True
|
)
|
ci_project_ids = serializers.CharField(
|
required=True,
|
allow_blank=True,
|
help_text="Gitlab的项目id,多个用逗号分开,一个项目id对应多个task,但只能在同一个项目中",
|
)
|
strategy = serializers.CharField(required=True, help_text="发送通知策略", max_length=20)
|
receiver = serializers.CharField(
|
required=True, help_text="邮件接收者列表", allow_blank=True, max_length=100
|
)
|
mail_cc = serializers.CharField(
|
required=True, help_text="邮件抄送列表", allow_blank=True, max_length=100
|
)
|
name = serializers.CharField(required=True, help_text="定时任务名称", max_length=100)
|
webhook = serializers.CharField(
|
required=True,
|
help_text="飞书/企微/钉钉webhook url",
|
trim_whitespace=True,
|
allow_blank=True,
|
max_length=500,
|
)
|
updater = serializers.CharField(
|
required=False,
|
help_text="更新人",
|
max_length=20,
|
allow_null=True,
|
allow_blank=True,
|
)
|
creator = serializers.CharField(
|
required=False,
|
help_text="创建人",
|
max_length=20,
|
allow_null=True,
|
allow_blank=True,
|
)
|
data = serializers.ListField(required=True, help_text="用例id")
|
project = serializers.IntegerField(
|
required=True, help_text="测试平台的项目id", min_value=1
|
)
|
|
def validate_crontab(self, value):
|
"""
|
检查cron表达式是否有效
|
"""
|
if not croniter.is_valid(value):
|
raise serializers.ValidationError("无效的 cron 表达式")
|
return value
|
|
def validate_ci_project_ids(self, ci_project_ids):
|
if ci_project_ids:
|
not_allowed_project_ids = set()
|
kwargs_list = PeriodicTask.objects.filter(
|
~Q(description=self.initial_data["project"])
|
).values("kwargs")
|
for kwargs in kwargs_list:
|
not_allowed_project_id: str = json.loads(kwargs["kwargs"]).get(
|
"ci_project_ids", ""
|
)
|
if not_allowed_project_id:
|
not_allowed_project_ids.update(not_allowed_project_id.split(","))
|
|
validation_errors = set()
|
for ci_project_id in ci_project_ids.split(","):
|
if ci_project_id in not_allowed_project_ids:
|
validation_errors.add(ci_project_id)
|
|
if validation_errors:
|
raise serializers.ValidationError(
|
f"{','.join(validation_errors)} 已经在其他项目存在"
|
)
|
|
|
class CISerializer(serializers.Serializer):
|
"""持续集成序列化"""
|
|
ci_job_id = serializers.IntegerField(
|
required=True,
|
min_value=1,
|
help_text="gitlab-ci job id",
|
)
|
ci_job_url = serializers.CharField(required=True, max_length=500)
|
ci_pipeline_id = serializers.IntegerField(required=True)
|
ci_pipeline_url = serializers.CharField(required=True, max_length=500)
|
ci_project_id = serializers.IntegerField(required=True, min_value=1)
|
ci_project_name = serializers.CharField(required=True, max_length=100)
|
ci_project_namespace = serializers.CharField(required=False, max_length=100)
|
start_job_user = serializers.CharField(
|
required=True,
|
max_length=100,
|
help_text="GITLAB_USER_NAME",
|
)
|
|
|
class CIReportSerializer(serializers.Serializer):
|
"""持续集成报告序列化"""
|
|
ci_job_id = serializers.IntegerField(
|
required=True,
|
min_value=1,
|
help_text="gitlab-ci job id",
|
)
|