initial commit

This commit is contained in:
2025-04-29 18:09:00 +08:00
commit 4faed52de5
690 changed files with 13481 additions and 0 deletions

0
utils/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,18 @@
from apps.project.models import Round
from utils.util import get_dict_info
def create_csx_chapter_dict(one_round: Round):
"""传入轮次对象返回测试项类型字典key的数组/测试项key的dict以便后续使用"""
testType_list = []
last_chapter_items = {}
if one_round:
for csx in one_round.rtField.all():
if csx.testType not in testType_list:
testType_list.append(csx.testType)
# 排序需要查字典里面index来排序
testType_list.sort(key=lambda x: get_dict_info('testType', x)['index'], reverse=False)
for test_type in testType_list:
last_chapter_items[test_type] = []
for csx in one_round.rtField.all():
last_chapter_items[csx.testType].append(csx.key)
return testType_list, last_chapter_items

92
utils/chen_crud.py Normal file
View File

@@ -0,0 +1,92 @@
from django.shortcuts import get_object_or_404
# 1.1)这个函数参数1request、参数2schema或schema.dict()、参数3ORM模型
def create(request, data, model):
if not isinstance(data, dict):
data = data.dict()
query_set = model.objects.create(**data)
return query_set
# 1.2)新增快捷函数直接填入data
def createWithOutRequestParam(data, model):
if not isinstance(data, dict):
data = data.dict()
query_set = model.objects.create(**data)
return query_set
# 2.1)更新便捷函数特别注意参数0为request后面有用参数1id参数2schema参数3ORM模型
def update(request, id, data, model):
dict_data = data.dict()
instance = get_object_or_404(model, id=id)
for attr, value in dict_data.items():
setattr(instance, attr, value)
instance.save()
return instance
# 2.2)更新便捷函数-无request参数 -> data参数为schema对象
def updateWithoutRequestParam(id, data, model):
dict_data = data.model_dump(exclude_none=True)
instance = get_object_or_404(model, id=id)
for attr, value in dict_data.items():
if attr != 'id': # 不对id更新
setattr(instance, attr, value)
instance.save()
return instance
# 多个id删除便捷函数参数1:ids数组参数2ORM模型
def multi_delete(ids, model):
for item in ids:
# 删除多对多关系 - project
instance = get_object_or_404(model, pk=item)
instance.delete()
pass
# project删除对应problem问题单多对多关系也要删除
def multi_delete_project(ids, model):
idents = []
for item in ids:
instance = get_object_or_404(model, pk=item)
# (注意project所属problem全部删除且关联关系删除)
for problem in instance.projField.all():
problem.case.clear()
idents.append(instance.ident)
instance.delete()
return idents
# testDemand多个id删除便捷函数参数1:ids数组参数2ORM模型
def multi_delete_testDemand(ids, model):
for item in ids:
instance = get_object_or_404(model, pk=item)
# 多对多删除case下面的problem关联删除
for case in instance.tcField.all():
case.caseField.clear()
instance.delete()
pass
# dut的删除需要多对多case-problem
def multi_delete_dut(ids, model):
for item in ids:
instance = get_object_or_404(model, pk=item)
# 多对多删除case下面的problem关联删除
for case in instance.ducField.all():
case.caseField.clear()
instance.delete()
pass
# design的多个id删除便捷函数参数1:ids数组参数2ORM模型
def multi_delete_design(ids, model):
for item in ids:
instance = get_object_or_404(model, pk=item)
# 多对多删除case下面problem关联删除
for case in instance.dcField.all():
case.caseField.clear()
instance.delete()
pass
# 由于case有多对多关系所以单独提出来删除
def multi_delete_case(ids, model):
for item in ids:
instance = get_object_or_404(model, pk=item)
instance.caseField.clear()
instance.delete()
pass

45
utils/chen_ninja.py Normal file
View File

@@ -0,0 +1,45 @@
from ninja_extra import NinjaExtraAPI
from django.http import HttpRequest, HttpResponse
from typing import Any
from utils.codes import HTTP_USER_PASSWORD_ERROR_CODE
# 重写ninja返回 - 全局统一视图函数返回如果None则如下返回
class ChenNinjaAPI(NinjaExtraAPI):
def create_response(
self, request: HttpRequest, data: Any, *, status: int = 200, code: int = 200, message: str = "请求成功",
temporal_response: HttpResponse = None,
) -> HttpResponse:
std_data = {
"code": code,
"data": data,
"message": message,
"success": True
}
# 因为extra的APIException会添加到data['detail']里面的,所以做一致性处理转换
if std_data['data'] is not None:
if 'detail' in std_data['data']:
std_data['message'] = std_data['data']['detail']
# ~~~~~~~~~~正常异常,status进行通用处理:TODO:后续规划~~~~~~~~~~
# 1.当状态码为403时候给前端提示message
if status == 403:
std_data['message'] = '您没有权限这样做'
# 2.当状态码为401时候根据APIException处理
elif status == 401:
if (std_data['data']['detail'] == '找不到指定凭据对应的有效用户' or
std_data['data']['detail'] == 'No active account found with the given credentials'):
std_data['message'] = '账号或密码错误,如果是内网登录检查密码是否过期...'
std_data['data']['code'] = HTTP_USER_PASSWORD_ERROR_CODE # TODO:后续单独以枚举方式定义code
else:
std_data['message'] = '您的token已过期请重新登录'
# 3.因为前端是获取message所以这里处理
elif status != 200 and std_data['message'] == '请求成功':
std_data['message'] = '请求失败,请检查'
elif status == 404:
std_data['message'] = '未找到相应的内容,请检查参数'
elif status == 422:
std_data['message'] = '请求的参数或响应数据未通过验证'
content = self.renderer.render(request, std_data, response_status=status)
content_type = "{}; charset={}".format(
self.renderer.media_type, self.renderer.charset
)
return HttpResponse(content, status=status, content_type=content_type)

34
utils/chen_pagination.py Normal file
View File

@@ -0,0 +1,34 @@
from django.db.models import QuerySet
from ninja.pagination import PaginationBase
from ninja.types import DictStrAny
from ninja_schema import Schema
from ninja import Field
from typing import List,Any
# 这个结构是前端要求的,看前端代码可知
class Total(Schema):
total:int
class MyPagination(PaginationBase):
class Input(Schema):
pageSize: int = Field(10, gt=0)
page: int = Field(1, gt=-1)
class Output(Schema):
items: List[Any]
pageInfo: Total
def paginate_queryset(
self,
queryset: QuerySet,
pagination: Input,
**params: DictStrAny,
) -> Any:
offset = pagination.pageSize * (pagination.page - 1)
limit: int = pagination.pageSize
return {
"page": offset,
"limit": limit,
"items": queryset[offset: offset + limit],
"pageInfo": {'total':self._items_count(queryset)},
} # noqa: E203

25
utils/chen_response.py Normal file
View File

@@ -0,0 +1,25 @@
import json
import datetime
from django.http import HttpResponse
# 返回的时间有可能无法序列化,这里单独处理
class DateEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return obj.strftime("%Y-%m-%d")
else:
return json.JSONEncoder.default(self, obj)
## 重写django的HttpResponse注意返回的是axios里面的data!!!
class ChenResponse(HttpResponse):
def __init__(self, data=None, message='success', code=200, *args, **kwargs):
if data is None:
data = {}
std_data = {
"code": code,
"data": data,
"message": message,
"success": True
}
data = json.dumps(std_data, cls=DateEncoder)
super().__init__(data, *args, **kwargs)

7
utils/codes.py Normal file
View File

@@ -0,0 +1,7 @@
# 确定响应codes的枚举类型
# 1.账号账户和密码错误的code -> status:部分401
HTTP_USER_PASSWORD_ERROR_CODE: int = 40001
# 2.传给后端的数据越界
HTTP_INDEX_ERROR: int = 40038
# 3.当右键测试项时,如果测试项下面已经有用例了
HTTP_EXISTS_CASES:int = 40031

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,90 @@
"""
日志中间件
"""
import json
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.utils.deprecation import MiddlewareMixin
from apps.system.models import OperationLog
from apps.user.models import Users
from utils.log_util.request_util import (
get_browser,
get_os,
get_request_data,
get_request_ip,
get_request_path,
)
class ApiLoggingMiddleware(MiddlewareMixin):
"""
用于记录API访问日志中间件
"""
def __init__(self, get_response=None):
super().__init__(get_response)
self.enable = getattr(settings, 'API_LOG_ENABLE', None) or False
self.methods = getattr(settings, 'API_LOG_METHODS', None) or set()
self.operation_log_id = None
@classmethod
def __handle_request(cls, request):
request.request_ip = get_request_ip(request)
request.request_data = get_request_data(request)
request.request_path = get_request_path(request)
def __handle_response(self, request, response):
# request_data,request_ip由PermissionInterfaceMiddleware中间件中添加的属性
body = getattr(request, 'request_data', {})
# 请求含有password则用*替换掉(暂时先用于所有接口的password请求参数)
if isinstance(body, dict) and body.get('password', ''):
body['password'] = '*' * len(body['password'])
if not hasattr(response, 'data') or not isinstance(response.data, dict):
response.data = {}
try:
if not response.data and response.content:
content = json.loads(response.content.decode())
response.data = content if isinstance(content, dict) else {}
except Exception:
return
if getattr(request, 'user', None) is None:
return
user = request.user
if isinstance(user, AnonymousUser):
return
# 如果操作日志在settings.API_OPERATION_EXCLUDE_START记录的路径中则不操作【暂时不记录因为太多了】
for path in settings.API_OPERATION_EXCLUDE_START:
if request.request_path.startswith(path):
return
info = {
'request_username': user.username if isinstance(user, Users) else user['username'],
'request_ip': getattr(request, 'request_ip', 'unknown'),
'creator_id': user.id if isinstance(user, Users) else user['id'],
'request_method': request.method,
'request_path': request.request_path,
'request_body': body,
'response_code': response.data.get('code'),
'request_os': get_os(request),
'request_browser': get_browser(request),
# 'request_msg': request.session.get('request_msg'),
'status': True if response.data.get('code') in [2000, ] else False,
'json_result': {"code": response.data.get('code'), "msg": response.data.get('result')},
}
operation_log, creat = OperationLog.objects.update_or_create(defaults=info, id=self.operation_log_id)
if not operation_log.request_modular and settings.API_MODEL_MAP.get(request.request_path, None):
operation_log.request_modular = settings.API_MODEL_MAP[request.request_path]
operation_log.save()
def process_request(self, request):
self.__handle_request(request)
def process_response(self, request, response):
"""
主要请求处理完之后记录
:param request:
:param response:
:return:
"""
if self.enable:
if self.methods == 'ALL' or request.method in self.methods:
self.__handle_response(request, response)
return response

View File

@@ -0,0 +1,132 @@
"""
request工具类
"""
import json
from apps.system.models import LoginLog
from django.urls.resolvers import ResolverMatch
from user_agents import parse
def get_request_ip(request):
"""
获取请求IP
:param request:
:return:
"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[-1].strip()
return ip
ip = request.META.get('REMOTE_ADDR', '') or getattr(request, 'request_ip', None)
return ip or 'unknown'
def get_request_data(request):
"""
获取请求参数
:param request:
:return:
"""
request_data = getattr(request, 'request_data', None)
if request_data:
return request_data
data: dict = {**request.GET.dict(), **request.POST.dict()}
if not data:
try:
body = request.body
if body:
data = json.loads(body)
except Exception as e:
pass
if not isinstance(data, dict):
data = {'data': data}
return data
def get_request_path(request, *args, **kwargs):
"""
获取请求路径
:param request:
:param args:
:param kwargs:
:return:
"""
request_path = getattr(request, 'request_path', None)
if request_path:
return request_path
values = []
for arg in args:
if len(arg) == 0:
continue
if isinstance(arg, str):
values.append(arg)
elif isinstance(arg, (tuple, set, list)):
values.extend(arg)
elif isinstance(arg, dict):
values.extend(arg.values())
if len(values) == 0:
return request.path
path: str = request.path
for value in values:
path = path.replace('/' + value, '/' + '{id}')
return path
def get_request_canonical_path(request, ):
"""
获取请求路径
:param request:
:param args:
:param kwargs:
:return:
"""
request_path = getattr(request, 'request_canonical_path', None)
if request_path:
return request_path
path: str = request.path
resolver_match: ResolverMatch = request.resolver_match
for value in resolver_match.args:
path = path.replace(f"/{value}", "/{id}")
for key, value in resolver_match.kwargs.items():
if key == 'pk':
path = path.replace(f"/{value}", f"/{{id}}")
continue
path = path.replace(f"/{value}", f"/{{{key}}}")
return path
def get_browser(request, ):
"""
获取浏览器名
:param request:
:param args:
:param kwargs:
:return:
"""
ua_string = request.META['HTTP_USER_AGENT']
user_agent = parse(ua_string)
return user_agent.get_browser()
def get_os(request, ):
"""
获取操作系统
:param request:
:param args:
:param kwargs:
:return:
"""
ua_string = request.META['HTTP_USER_AGENT']
user_agent = parse(ua_string)
return user_agent.get_os()
def save_login_log(request, user):
"""
保存登录日志
:return:
"""
ip = get_request_ip(request=request)
analysis_data = {
'username': user.username,
'ip': ip,
'agent': str(parse(request.META['HTTP_USER_AGENT'])),
'browser': get_browser(request),
'os': get_os(request),
'creator_id': user.id
}
LoginLog.objects.create(**analysis_data)

46
utils/models.py Normal file
View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
from django.apps import apps
from django.db import models
from cdtestplant_v1 import settings
class CoreModel(models.Model):
"""
核心标准抽象模型,可直接继承使用
增加审计字段,覆盖字段时,字段名称请勿修改,必须统一审计字段名称
"""
id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id")
remark = models.CharField(max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述")
update_datetime = models.DateField(auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间")
create_datetime = models.DateField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
verbose_name="创建时间")
sort = models.IntegerField(default=1, null=True, blank=True, verbose_name="显示排序", help_text="显示排序")
class Meta:
abstract = True # 指定为True不会创建表只会当做父类
verbose_name = '核心模型'
verbose_name_plural = verbose_name
def get_all_models_objects(model_name=None):
"""
获取所有 models 对象
:return: {}
"""
settings.ALL_MODELS_OBJECTS = {}
if not settings.ALL_MODELS_OBJECTS:
all_models = apps.get_models()
for item in list(all_models):
table = {
"tableName": item._meta.verbose_name,
"table": item.__name__,
"tableFields": []
}
for field in item._meta.fields:
fields = {
"title": field.verbose_name,
"field": field.name
}
table['tableFields'].append(fields)
settings.ALL_MODELS_OBJECTS.setdefault(item.__name__, {"table": table, "object": item})
if model_name:
return settings.ALL_MODELS_OBJECTS[model_name] or {}
return settings.ALL_MODELS_OBJECTS or {}

11
utils/path_utils.py Normal file
View File

@@ -0,0 +1,11 @@
from apps.project.models import Project
def project_path(id: int):
"""
:param id:传入project_id获取其ident作为路径
:return:
"""
project = Project.objects.filter(id=id).first()
if not project:
return
return project.ident

View File

@@ -0,0 +1,18 @@
"""该文件主要是小工具,简化接口处理函数写的代码太多"""
from ninja import Schema
from django.db.models import QuerySet
def conditionNoneToBlank(condition: Schema):
"""将BaseModel/Schema对象中None变为空字符串"""
for attr, value in condition.__dict__.items():
if getattr(condition, attr) is None:
setattr(condition, attr, '')
def model_retrieve(condition: Schema, qs: QuerySet, exclude_field) -> QuerySet:
"""该函数合并上面功能传入qs以及排除字段全部添加__icontains进行查询"""
conditionNoneToBlank(condition)
search_obj = {}
for k, v in condition.dict(exclude_none=True).items():
if k not in exclude_field:
search_obj["".join([k, "__icontains"])] = v
return qs.filter(**search_obj)

407
utils/util.py Normal file
View File

@@ -0,0 +1,407 @@
from typing import List
from apps.dict.models import DictItem
from apps.project.models import TestDemand
from html.parser import HTMLParser
from django.db.models import QuerySet
# 导入生成文档检查器log - 单例模式
from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
logger = GenerateLogger(model='字典模块')
# 传入一个字符串数字以及字典标志code返回真实的title名字
def get_str_dict(a, dict_code):
dict_obj = DictItem.objects.filter(dict__code=dict_code, key=a).first()
if dict_obj:
return dict_obj.title
else:
# 如果用户没填写内容则记录检查中心log
logger.write_warning_log(fragment='字典数据缺失', message=f'字典数据{dict_code}数据缺失,请检查相应数据是否存在')
return ''
# 传入一个字符串数字以及字典标志code返回字典所属的缩写(show_title)
def get_str_abbr(a, dict_code):
dict_obj = DictItem.objects.filter(dict__code=dict_code, key=a).first()
if dict_obj:
return dict_obj.show_title
else:
logger.write_warning_log(fragment='字典数据缺失',
message=f'查询字段数据缩写问题,字典数据{dict_code}数据可能缺失')
return ""
# 传入一个字符串数组测试项类型字典标志code返回(真实title,sort)
def get_str_dict_plus(a, dict_code):
dict_obj = DictItem.objects.filter(dict__code=dict_code, key=a).first()
if dict_obj:
return dict_obj.title, dict_obj.sort
else:
logger.write_warning_log(fragment='字典数据查询错误', message=f'字典{dict_code}未查询到数据,请检查')
return "", 1
# 找到testType字典中的缩写例如“DC”“FT”
def get_testType(a, dict_code):
dict_obj = DictItem.objects.filter(dict__code=dict_code, key=a).first()
if dict_obj:
return dict_obj.show_title
else:
logger.write_warning_log(fragment='字典数据查询错误',
message=f'查询字段数据缩写问题,字典数据{dict_code}数据可能缺失')
return ""
# 标识处理获取测试需求测试项的生成文档的ident标识
def get_ident(test_item):
# key_index = int(test_item.key.split("-")[-1]) + 1
# test_index = str(key_index).rjust(3, '0')
reveal_ident = "_".join(
["XQ", get_testType(test_item.testType, "testType"), test_item.ident])
return reveal_ident
# 标识处理传入demand的ident以及case返回case的ident
def get_case_ident(demand_ident, case):
key_index = int(case.key.split("-")[-1]) + 1
test_index = str(key_index).rjust(3, '0')
reveal_ident = "_".join([demand_ident.replace("XQ", "YL"), test_index])
return reveal_ident
# 传入字典code以及字符串数组返回一个数组每个数组是dict
def get_list_dict(dict_code: str, str_list: List[str]):
result = []
qss = DictItem.objects.filter(dict__code=dict_code)
for st in str_list:
res = {}
for qs in qss:
if st == qs.key:
res['ident_version'] = qs.title
res['doc_name'] = qs.doc_name
res['publish_date'] = qs.publish_date
res['source'] = qs.source
result.append(res)
return result
# 传入字典code以及字符串数组返回一个列表每个元素是dict包含字典item很多信息
def get_list_dict_info(dict_code, str_list):
"""传入字典的字符串列表输出每个元素为dict的列表信息"""
result = []
qss = DictItem.objects.filter(dict__code=dict_code) # type:ignore
for st in str_list:
res = {}
for qs in qss:
if st == qs.key:
res['title'] = qs.title
res['index'] = qs.sort
result.append(res)
return result
# 传入字典code以及单个字典字符串输出一个dict带信息
def get_dict_info(dict_code, item_str):
"""传入字典的字符串单个表示输出dict包含排序index字段"""
qss = DictItem.objects.filter(dict__code=dict_code)
res = {}
for qs in qss:
if item_str == qs.key:
res['title'] = qs.title
res['index'] = qs.sort
return res
# 简单HTML解析器 - 解析富文本的parser - 复杂的使用apps/createDocument/extensions/parse_rich_text.py
class MyHTMLParser(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.allStrList = []
def error(self, message):
print("HTML解析器出错error信息为", message)
def handle_starttag(self, tag, attrs):
if tag == 'img':
img_base64 = attrs[0][1]
self.allStrList.append(img_base64)
def handle_endtag(self, tag):
pass
def handle_data(self, data):
if data != '\n':
self.allStrList.append(data)
# 不提取图片的HTML解析器
class MyHTMLParser_p(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.allStrList = []
def handle_data(self, data):
if data != '\n':
self.allStrList.append(data)
def create_problem_grade_str(problems):
"""传入问题qs生成到文档的字符串严重问题4个、一般问题10个"""
problem_r1_grade_dict = {}
for problem in problems:
grade_key: str = get_str_dict(problem.grade, 'problemGrade')
if grade_key in problem_r1_grade_dict.keys():
problem_r1_grade_dict[grade_key] += 1
else:
problem_r1_grade_dict[grade_key] = 1
problem_r1_grade_list = []
for key, value in problem_r1_grade_dict.items():
problem_r1_grade_list.append("".join([f"{key}问题", f"{value}"]))
return "".join(problem_r1_grade_list)
def create_problem_type_str(problems):
"""传入问题qs生成到文档的字符串文档问题1个程序问题10个"""
problem_r1_type_dict = {}
for problem in problems:
type_key: str = get_str_dict(problem.type, 'problemType')
if type_key in problem_r1_type_dict.keys():
problem_r1_type_dict[type_key] += 1
else:
problem_r1_type_dict[type_key] = 1
problem_r1_type_list = []
for key, value in problem_r1_type_dict.items():
problem_r1_type_list.append("".join([f"{key}", f"{value}"]))
return "".join(problem_r1_type_list)
def create_str_testType_list(cases):
"""传入用例的qs生成其测试类型的字符串例如包含xxx测试5个xxx测试11个返回的是元组第二个元素表示测试类型个数"""
test_type_set: set = set()
for case in cases:
demand: TestDemand = case.test
test_type_set.add(demand.testType)
test_type_list = get_list_dict_info('testType', list(test_type_set))
test_type_list.sort(key=lambda x: int(x['index']))
round1_testType_list = list(map(lambda x: x['title'], test_type_list))
return round1_testType_list, len(test_type_list)
def util_func(item):
"""辅助函数将下面函数返回的summary_list改造为以需求类型执行统计列表"""
transform_item = {'title': item['title'], 'total_count': 0, 'exe_count': 0, 'not_exe_count': 0, 'pass_count': 0,
'not_pass_count': 0}
for it in item['demand_list']:
transform_item['total_count'] += it['total_count']
transform_item['exe_count'] += it['exe_count']
transform_item['not_exe_count'] += it['not_exe_count']
transform_item['pass_count'] += it['pass_count']
transform_item['not_pass_count'] += it['not_pass_count']
return transform_item
def create_demand_summary(demands: QuerySet, project_ident: str):
"""
[
{
'title':'功能测试_5',
'demand_list':[
{
'name': '测试项1',
'total_count': 1,
'exe_count': 1,
'not_exe_count': 0,
'pass_count': 1,
'not_pass_count': 0,
'conclusion': '通过',
'problems': '/'
}
]
},
]
"""
summary_dict = {} # 后面再变为数组
# 1.首先根据testType字段生成一个一个dict
for demand in demands:
# 取出testType -> '4'(后面查字典)
test_type_num = demand.testType
# 查出需求类型的sort与testType真实名字,组合为一个字符串作为key
testTypeStr, testTypeSort = get_str_dict_plus(test_type_num, 'testType')
testType_info = testTypeStr + '_' + str(testTypeSort)
demand_dict = {
'name': demand.name,
'total_count': 0,
'exe_count': 0,
'not_exe_count': 0,
'pass_count': 0,
'not_pass_count': 0,
'conclusion': '通过',
'problems': "/"
}
# 查询当前测试需求下用例
demand_problem_list = set()
for case in demand.tcField.all():
demand_dict['total_count'] += 1
# 要遍历下面的测试步骤
isPassed = True
isExe = True
for step in case.step.all():
# 所有步骤有一个未通过则是未通过,所有都执行则是已执行
if step.passed == '2':
isPassed = False
isExe = True
break
if step.passed == '3':
isExe = False
if isPassed:
demand_dict['pass_count'] += 1
else:
demand_dict['not_pass_count'] += 1
demand_dict['conclusion'] = '未通过'
if isExe:
demand_dict['exe_count'] += 1
else:
demand_dict['not_exe_count'] += 1
# 查询每个用例下面的问题单将问题单的ident弄出来
for problem in case.caseField.all():
problem_ident = 'PT_' + project_ident + '_' + problem.ident.rjust(3, '0')
demand_problem_list.add(problem_ident)
if len(list(demand_problem_list)) > 0:
demand_dict['problems'] = "\a".join(list(demand_problem_list))
if testType_info in summary_dict.keys():
summary_dict[testType_info].append(demand_dict)
else:
summary_dict[testType_info] = [demand_dict]
summary_list = []
for key, value in summary_dict.items():
one_dict = {
'title': key,
'demand_list': value
}
summary_list.append(one_dict)
# 根据其排序
summary_list.sort(key=lambda x: int(x['title'].split('_')[-1]))
# 然后将_5这种替换掉
for one in summary_list:
one['title'] = one['title'].split("_")[0]
# ~~~~还需要返回一个 -> 测试类型的用例执行情况
demandType_exe_list = list(map(util_func, summary_list))
return summary_list, demandType_exe_list
def create_problem_table(problems):
"""传入问题单qs输出按问题种类分的问题统计表的数据"""
res_list = [{
'name': '一般问题',
'xq_count': 0,
'sj_count': 0,
'wd_count': 0,
'bm_count': 0,
'data_count': 0,
'other_count': 0,
'closed_count': 0,
'non_closed_count': 0,
}, {
'name': '严重问题',
'xq_count': 0,
'sj_count': 0,
'wd_count': 0,
'bm_count': 0,
'data_count': 0,
'other_count': 0,
'closed_count': 0,
'non_closed_count': 0,
}, {
'name': '建议',
'xq_count': 0,
'sj_count': 0,
'wd_count': 0,
'bm_count': 0,
'data_count': 0,
'other_count': 0,
'closed_count': 0,
'non_closed_count': 0,
}, {
'name': '重大问题',
'xq_count': 0,
'sj_count': 0,
'wd_count': 0,
'bm_count': 0,
'data_count': 0,
'other_count': 0,
'closed_count': 0,
'non_closed_count': 0,
}]
for problem in problems:
index = int(problem.grade) - 1
if problem.type == '1':
res_list[index]['other_count'] += 1
elif problem.type == '2':
res_list[index]['wd_count'] += 1
elif problem.type == '3':
res_list[index]['bm_count'] += 1
elif problem.type == '4':
res_list[index]['sj_count'] += 1
elif problem.type == '5':
res_list[index]['xq_count'] += 1
elif problem.type == '6':
res_list[index]['data_count'] += 1
# 是否归零
if problem.status == '1':
res_list[index]['closed_count'] += 1
else:
res_list[index]['non_closed_count'] += 1
return res_list
def create_problem_type_table(problems):
"""传入问题qs解析出按测试类型分的问题统计表格"""
res_list = [{
'name': '一般问题',
'wd_count': 0,
'jt_count': 0,
'dm_count': 0,
'dt_count': 0,
'data_count': 0,
'closed_count': 0,
'non_closed_count': 0,
}, {
'name': '严重问题',
'wd_count': 0,
'jt_count': 0,
'dm_count': 0,
'dt_count': 0,
'data_count': 0,
'closed_count': 0,
'non_closed_count': 0,
}, {
'name': '建议',
'wd_count': 0,
'jt_count': 0,
'dm_count': 0,
'dt_count': 0,
'data_count': 0,
'closed_count': 0,
'non_closed_count': 0,
}, {
'name': '重大问题',
'wd_count': 0,
'jt_count': 0,
'dm_count': 0,
'dt_count': 0,
'data_count': 0,
'closed_count': 0,
'non_closed_count': 0,
}]
for problem in problems:
index = int(problem.grade) - 1
belong_demand: TestDemand = problem.case.all()[0].test
if belong_demand.testType == '8': # 属于文档审查问题
res_list[index]['wd_count'] += 1
elif belong_demand.testType == '15': # 属于静态分析问题
res_list[index]['jt_count'] += 1
elif belong_demand.testType == '2' or belong_demand.testType == '3': # 属于代码审查和走查
res_list[index]['dm_count'] += 1
else: # 属于动态发现问题
res_list[index]['dt_count'] += 1
# 是否归零
if problem.status == '1':
res_list[index]['closed_count'] += 1
else:
res_list[index]['non_closed_count'] += 1
return res_list
def get_demand_testTypes(demand_qs) -> str:
"""传入测试项qs返回字符串类似于“静态分析、代码审查、功能测试等”"""
testType_list = list(demand_qs.values("testType").distinct().order_by('testType'))
t_code_list = [item['testType'] for item in testType_list]
t_code_list = get_list_dict_info('testType', t_code_list)
t_code_list.sort(key=lambda x: int(x['index']))
t_str = [item['title'] for item in t_code_list]
return ''.join(t_str) if len(t_str) > 0 else "测试"