Compare commits

..

10 Commits

Author SHA1 Message Date
beb8c2d25b v0.1.1 2026-01-28 16:50:40 +08:00
f755422cb3 更新项目由uv管理 2025-12-24 16:59:57 +08:00
8ba5d4fd23 新增问题单详情界面 2025-12-23 10:36:55 +08:00
3e048ea876 增加批量增加用例、测试项、设计需求功能 2025-12-19 18:08:19 +08:00
f3806687b0 增加测试AI接口 2025-12-04 10:34:14 +08:00
9db8b28f5b [Update#1]支撑AI生成测试项-接口调整 2025-12-02 18:13:30 +08:00
a396a8fcfa 保存修订 2025-11-18 10:52:10 +08:00
e6c593c920 Crud表格批量修改替换 2025-05-28 18:44:25 +08:00
1b2c3ec3d6 添加test.py 2025-05-17 18:05:23 +08:00
322096c069 log change 2025-05-15 18:53:48 +08:00
426 changed files with 5230 additions and 365 deletions

4
.env
View File

@@ -2,4 +2,6 @@ AUTH_LDAP_SERVER_URI='ldap://dns.paisat.cn:389'
AUTH_LDAP_BIND_DN='CN=Administrator,CN=Users,DC=sstc,DC=ctu'
AUTH_LDAP_BIND_PASSWORD='WXWX2019!!!!!!'
BASE_DN='OU=all,DC=sstc,DC=ctu'
FILTER_STR='(sAMAccountName=%(user)s)'
FILTER_STR='(sAMAccountName=%(user)s)'
AUTH_LDAP_SERVER_URI_IP='ldap://192.168.0.201:389'

View File

@@ -18,7 +18,7 @@
<excludeFolder url="file://$MODULE_DIR$/.venv" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13 (cdtestplant_v1)" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="uv (cdtestplant_v1)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">

2
.idea/misc.xml generated
View File

@@ -3,5 +3,5 @@
<component name="Black">
<option name="sdkName" value="Python 3.8 (cdtestplant_v1)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (cdtestplant_v1)" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="uv (cdtestplant_v1)" project-jdk-type="Python SDK" />
</project>

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.13.11

View File

@@ -3,8 +3,7 @@
## 内外V0.0.1版本
2024年7月3日 - V0.0.1版本首次导入内网并进行数据库迁移和部署
2025年12月22日 - V0.1.1版本导入内网并数据库迁移部署
## 外V0.0.2版本

View File

@@ -3,7 +3,7 @@ from pathlib import Path
from ninja_extra import api_controller, ControllerBase, route
from django.db import transaction
from django.shortcuts import get_object_or_404
from django.db.models import Q
from django.db.models import Q, QuerySet
from docxtpl import DocxTemplate
from typing import Optional
from docx import Document
@@ -16,7 +16,8 @@ from apps.createDocument.extensions import util
from utils.chen_response import ChenResponse
from apps.createDocument.extensions.util import create_bg_docx, get_round1_problem
from utils.util import get_str_dict, get_list_dict, create_problem_grade_str, create_str_testType_list, \
create_demand_summary, create_problem_type_str, create_problem_table, create_problem_type_table, get_str_abbr
create_demand_summary, create_problem_type_str, create_problem_table, create_problem_type_table, \
get_str_abbr
# 根据轮次生成测评内容文档context
from apps.createDocument.extensions.content_result_tool import create_round_context
from apps.createDocument.extensions.zhui import create_bg_round1_zhui
@@ -62,9 +63,11 @@ class GenerateControllerBG(ControllerBase):
'publish_date': timer.dg_cover_time, 'source': project_obj.test_unit}
std_documents.append(dg_duty)
# 需要添加说明、记录
sm_duty = {'doc_name': f'{project_obj.name}软件测试说明', 'ident_version': f'PT-{project_obj.ident}-TD-1.00',
sm_duty = {'doc_name': f'{project_obj.name}软件测试说明',
'ident_version': f'PT-{project_obj.ident}-TD-1.00',
'publish_date': timer.sm_cover_time, 'source': project_obj.test_unit}
jl_duty = {'doc_name': f'{project_obj.name}软件测试记录', 'ident_version': f'PT-{project_obj.ident}-TN',
jl_duty = {'doc_name': f'{project_obj.name}软件测试记录',
'ident_version': f'PT-{project_obj.ident}-TN',
'publish_date': timer.jl_cover_time, 'source': project_obj.test_unit}
# 循环所有轮次,除了第一轮
std_documents.extend([sm_duty, jl_duty])
@@ -144,7 +147,8 @@ class GenerateControllerBG(ControllerBase):
for case in round1_case_qs:
demand: TestDemand = case.test
test_type_set.add(demand.testType)
round1_testType_list = list(map(lambda x: x['ident_version'], get_list_dict('testType', list(test_type_set))))
round1_testType_list = list(
map(lambda x: x['ident_version'], get_list_dict('testType', list(test_type_set))))
# 这里找出第一轮,源代码被测件,并获取版本
so_dut = round1.rdField.filter(type='SO').first()
so_dut_verson = "$请添加第一轮的源代码信息$"
@@ -158,7 +162,8 @@ class GenerateControllerBG(ControllerBase):
# 找所属dut的so-dut
so_dut = r.rdField.filter(type='SO').first()
# 找出上一轮dut的so-dut
last_problem_count = Problem.objects.filter(case__round__key=str(int(r.key) - 1)).distinct().count()
last_problem_count = Problem.objects.filter(
case__round__key=str(int(r.key) - 1)).distinct().count()
current_round_problem_count = Problem.objects.filter(case__round__key=r.key).distinct().count()
if current_round_problem_count > 0:
current_round_description = f'引入新问题{current_round_problem_count}'
@@ -294,14 +299,16 @@ class GenerateControllerBG(ControllerBase):
static_problems = problems_r1.filter(case__test__testType='15')
# 6.第一轮动态测试用例个数(动态测试-非静态分析、文档审查、代码审查、代码走查4个)
case_r1_qs = round1.rcField.filter(~Q(test__testType='2'), ~Q(test__testType='3'), ~Q(test__testType='8'),
case_r1_qs = round1.rcField.filter(~Q(test__testType='2'), ~Q(test__testType='3'),
~Q(test__testType='8'),
~Q(test__testType='15'),
round__key='0') # !warning:中变量-第一轮动态测试用例qs
testType_list, testType_count = create_str_testType_list(case_r1_qs)
## 动态测试(第一轮)各个类型测试用例执行表/各个测试需求表
demand_r1_dynamic_qs = round1.rtField.filter(~Q(testType='2'), ~Q(testType='3'), ~Q(testType='8'),
~Q(testType='15')) # !warning:中变量:第一轮动态测试的测试项
summary_r1_demand_info, summry_r1_demandType_info = create_demand_summary(demand_r1_dynamic_qs, project_ident)
summary_r1_demand_info, summry_r1_demandType_info = create_demand_summary(demand_r1_dynamic_qs,
project_ident)
# N.第一轮所有动态问题统计
problems_dynamic_r1 = problems_r1.filter(~Q(case__test__testType='2'), ~Q(case__test__testType='3'),
@@ -350,7 +357,7 @@ class GenerateControllerBG(ControllerBase):
context = create_round_context(project_obj, round_str)
template_path = Path.cwd() / 'media' / project_path_str / 'form_template' / 'bg' / '测试内容和结果_第二轮次.docx'
doc = DocxTemplate(template_path)
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(
Path.cwd() / "media" / project_path_str / "output_dir/bg" / f"测试内容和结果_第{context['round_id']}轮次.docx")
@@ -450,7 +457,8 @@ class GenerateControllerBG(ControllerBase):
for design in round1_design_yz_qs:
rich_parser2 = RichParser(design.description)
p_list = rich_parser2.get_final_p_list()
design_dict = {'yz_des': "".join([design.chapter, '章节:', design.name, '\a', '\a'.join(p_list)])}
design_dict = {
'yz_des': "".join([design.chapter, '章节:', design.name, '\a', '\a'.join(p_list)])}
# 找出其中所有demand
demand_qs = design.dtField.all()
if not demand_qs.exists():
@@ -545,7 +553,7 @@ class GenerateControllerBG(ControllerBase):
last_dut_so = dut_so
break
# 找出所有被测件协议XY、需求规格说明XQ、设计说明SJ
duties_qs: list[Dut] = project_obj.pdField.filter(Q(type='XQ') | Q(type='SJ') | Q(type='XY'))
duties_qs = project_obj.pdField.filter(Q(type='XQ') | Q(type='SJ') | Q(type='XY'))
# ***Inspect-start***
if not last_dut_so:
self.logger.model = '测评报告'
@@ -621,7 +629,7 @@ class GenerateControllerBG(ControllerBase):
temporary_file = Path.cwd() / 'media' / project_path_str / 'form_template' / 'bg' / 'temporary' / '研总需归追踪_temp.docx'
out_put_file = Path.cwd() / 'media' / project_path_str / 'output_dir' / 'bg' / '研总需归追踪.docx'
doc = DocxTemplate(input_file)
doc.render(context)
doc.render(context, autoescape=True)
doc.save(temporary_file)
# 通过docx合并单元格
if temporary_file.is_file():
@@ -678,7 +686,7 @@ class GenerateControllerBG(ControllerBase):
'data_list': data_list
}
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/bg" / "问题汇总表.docx")
return ChenResponse(status=200, code=200, message="文档生成成功!")
@@ -716,7 +724,7 @@ class GenerateControllerBG(ControllerBase):
context = {
'modi_list': modi_list,
}
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/bg" / "摸底清单.docx")
return ChenResponse(status=200, code=200, message="文档生成成功!")

View File

@@ -1,7 +1,6 @@
from datetime import datetime
from ninja.errors import HttpError
from ninja_extra import ControllerBase, api_controller, route
from ninja_extra.permissions import IsAuthenticated
from ninja_jwt.authentication import JWTAuth
from django.db import transaction
from django.db.models import Q
from docxtpl import DocxTemplate
@@ -13,7 +12,6 @@ from apps.dict.models import Dict
# 导入工具函数
from utils.util import get_str_dict, get_list_dict, get_testType, get_ident, get_str_abbr
from utils.chapter_tools.csx_chapter import create_csx_chapter_dict
from utils.util import MyHTMLParser_p
from django.shortcuts import get_object_or_404
from django.forms.models import model_to_dict
from apps.createDocument.extensions.util import create_dg_docx
@@ -24,6 +22,8 @@ from utils.path_utils import project_path
from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
# 导入mixins-处理文档片段
from apps.createDocument.extensions.mixins import FragementToolsMixin
# 导入工具
from apps.createDocument.extensions.tools import demand_sort_by_designKey
# @api_controller("/generate", tags=['生成大纲文档'], auth=JWTAuth(), permissions=[IsAuthenticated])
@api_controller("/generate", tags=['生成大纲文档'])
@@ -34,7 +34,8 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
@transaction.atomic
def create_testdemand(self, id: int): # type:ignore
"""目前生成第一轮测试项"""
tplTestDemandGenerate_path = Path.cwd() / "media" / project_path(id) / "form_template" / "dg" / "测试项及方法.docx"
tplTestDemandGenerate_path = Path.cwd() / "media" / project_path(
id) / "form_template" / "dg" / "测试项及方法.docx"
doc = DocxTemplate(tplTestDemandGenerate_path)
# 获取指定的项目对象
project_qs = get_object_or_404(Project, id=id)
@@ -45,10 +46,12 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
# 查出第一轮所有testdemand
project_round_one = project_qs.pField.filter(key=0).first()
testDemand_qs = project_round_one.rtField.all()
testDemand_qs = project_round_one.rtField.all().select_related('design')
# 按照自己key排序这样可以按照design的key排序
sorted_demand_qs = sorted(testDemand_qs, key=demand_sort_by_designKey)
# 遍历第一轮测试项默认是ID排序
for single_qs in testDemand_qs:
for single_qs in sorted_demand_qs:
type_index = type_number_list.index(int(single_qs.testType))
# 先查询其testDemandContent信息
content_list = []
@@ -57,6 +60,7 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
"index": index + 1,
"rindex": str(index + 1).rjust(2, '0'),
"subName": content.subName,
"subDescription": content.subDescription,
# 修改遍历content下面的stepcontent变量是TestDemandContent表
"subStep": [
{'index': index + 1, 'operation': step_obj.operation, 'expect': step_obj.expect}
@@ -78,7 +82,6 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
# ***Inspect-end***
html_parser = RichParser(single_qs.design.description)
desc_list = html_parser.get_final_list(doc)
# 查询关联design以及普通design
doc_list = [{'dut_name': single_qs.dut.name, 'design_chapter': single_qs.design.chapter,
'design_name': single_qs.design.name}]
@@ -88,6 +91,7 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
doc_list.append(ddict)
# 组装单个测试项
## 打印本项目是FPGA还是CPU
testdemand_dict = {
"name": single_qs.name,
"key": single_qs.key,
@@ -96,16 +100,13 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
"doc_list": doc_list,
"design_description": desc_list,
"test_demand_content": content_list,
"testMethod": testmethod_str,
"testMethod": testmethod_str.strip(),
"adequacy": single_qs.adequacy.replace("\n", "\a"),
"testDesciption": single_qs.testDesciption.replace("\n", "\a") # 测试项描述
"testDesciption": single_qs.testDesciption.replace("\n", "\a"), # 测试项描述
"testType": get_testType(single_qs.testType, 'testType'),
}
list_list[type_index].append(testdemand_dict)
# 定义渲染context字典
context = {
"project_name": project_qs.name
}
output_list = []
for (index, li) in enumerate(list_list):
@@ -122,8 +123,15 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
# 排序1测试类型排序
output_list = sorted(output_list, key=(lambda x: x["sort"]))
context["data"] = output_list
doc.render(context)
# 定义渲染context字典
context = {
"project_name": project_qs.name,
"is_JD": True if project_qs.report_type == '9' else False,
"data": output_list,
"isFPGA": '1' in project_qs.plant_type
}
doc.render(context, autoescape=True)
try:
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / "测试项及方法.docx")
return ChenResponse(status=200, code=200, message="文档生成成功!")
@@ -148,7 +156,8 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
# 找出所属项目
project_qs = get_object_or_404(Project, id=id)
# 根据项目找出被测件-只找第一轮次
duties_qs = project_qs.pdField.filter(Q(type='XQ') | Q(type='SJ') | Q(type='XY') | Q(type='YZ')).filter(
duties_qs = project_qs.pdField.filter(
Q(type='XQ') | Q(type='SJ') | Q(type='XY') | Q(type='YZ')).filter(
round__key='0')
# 先定义个字典
std_documents = []
@@ -169,7 +178,8 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
# 先找出所属项目
project_qs = get_object_or_404(Project, id=id)
contact_dict = model_to_dict(project_qs,
fields=['entrust_unit', 'entrust_contact', 'entrust_contact_phone', 'dev_unit',
fields=['entrust_unit', 'entrust_contact', 'entrust_contact_phone',
'dev_unit',
'dev_contact', 'dev_contact_phone', 'test_unit', 'test_contact',
'test_contact_phone'])
# 根据entrust_unit、dev_unit、test_unit查找Contact中地址信息
@@ -190,8 +200,18 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
def create_timeaddress(self, id: int):
doc_timer = DocTime(id)
context = doc_timer.dg_address_time()
context = self.change_time_to_another(context, ['beginTime_strf', 'dgCompileStart', 'dgCompileEnd',
'designStart', 'designEnd'])
return create_dg_docx('测评时间和地点.docx', context, id)
# 2025/12/11将20250417格式改为2025年04月17日 - 封装函数,传入字典和键值,修改对应键值信息
def change_time_to_another(self, context: dict, key_list: list[str]):
for key in key_list:
time_val = context.get(key, None)
if time_val:
context[key] = datetime.strptime(time_val, "%Y%m%d").strftime("%Y年%m月%d")
return context
# 生成【主要功能和性能指标】文档片段
@route.get('/create/indicators', url_name='create-indicators')
@transaction.atomic
@@ -255,7 +275,7 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
'md_demand_list': md_demand_list,
'is_has_modi': is_has_modi
}
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '主要功能和性能指标.docx')
return ChenResponse(status=200, code=200, message="文档生成成功!")
@@ -273,7 +293,7 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
"replace": replace, # 指定是否由数据库文档片段进行生成
"user_content": frag and rich_text_list
}
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '测评对象.docx')
return ChenResponse(status=200, code=200, message="文档生成成功!")
@@ -344,7 +364,7 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
"replace": replace, # 指定是否由数据库文档片段进行生成
"user_content": frag and rich_text_list
}
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '静态测试环境说明.docx')
return ChenResponse(status=200, code=200, message="文档生成成功!")
@@ -378,14 +398,16 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
# 动态测评环境说明
@route.get('/create/dynamic_env', url_name='create-dynamic_env')
def create_dynamic_env(self, id: int):
project_obj: Project = get_object_or_404(Project, id=id)
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '动态测试环境说明.docx'
doc = DocxTemplate(input_path)
replace, frag, rich_text_list = self._generate_frag(id, doc, '动态测试环境说明')
context = {
'project_name': project_obj.name,
"replace": replace,
"user_content": frag and rich_text_list
}
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '动态测试环境说明.docx')
return ChenResponse(status=200, code=200, message="文档生成成功!")
@@ -395,10 +417,12 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
# 动态软件项
@route.get('/create/dynamic_soft', url_name='create-dynamic_soft')
def create_dynamic_soft(self, id: int):
project_obj: Project = get_object_or_404(Project, id=id)
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '动态软件项.docx'
doc = DocxTemplate(input_path)
replace, frag, rich_text_list = self._generate_frag(id, doc, '动态软件项')
context = {
'project_name': project_obj.name,
"replace": replace,
"user_content": frag and rich_text_list
}
@@ -462,6 +486,7 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
# 渲染上下文
context = {
'project_name': project_qs.name,
'is_JD': True if project_qs.report_type == '9' else False,
'security_level': security,
'language': "\a".join(language_list),
'version': version,
@@ -492,7 +517,8 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
# 获取所有已录入测试类型
test_types = project_qs.ptField.values("testType").distinct()
# 通过测试类型查询字典中的中文
type_name_list = list(map(lambda qs_item: get_str_dict(qs_item['testType'], 'testType'), test_types))
type_name_list = list(
map(lambda qs_item: get_str_dict(qs_item['testType'], 'testType'), test_types))
# 定义测试类型一览的顺序注意word里面也要一样
word_types = ['文档审查', '静态分析', '代码审查', '逻辑测试', '功能测试', '性能测试', '边界测试',
'恢复性测试', '安装性测试', '数据处理测试', '余量测试', '强度测试', '接口测试',
@@ -503,6 +529,7 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
if exist_type == test_type:
type_index.append(str(index))
context = {
"security_level": get_str_dict(project_qs.security_level, 'security_level'),
"testTypes": "".join(type_name_list),
"project_name": project_qs.name,
"type_index": type_index
@@ -526,15 +553,24 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
# 根据关键等级检查是否有代码审查
security = project_qs.security_level
isDmsc = True if int(security) <= 2 else False
# 获取第一轮所有测试项QuerySet
project_round_one = project_qs.pField.filter(key=0).first()
testDemand_qs = project_round_one.rtField.all()
# grouped_data的键是测试类型名称值为测试项名称数组
grouped_data = {}
for item in testDemand_qs:
grouped_data.setdefault(get_str_dict(item.testType, 'testType'), []).append(item.name)
# 获取当前测试项的测试类型
test_types = project_qs.ptField.values("testType").distinct()
type_name_list = list(map(lambda qs_item: get_str_dict(qs_item['testType'], 'testType'), test_types))
test_types = testDemand_qs.values("testType").distinct()
type_name_list = list(
map(lambda qs_item: get_str_dict(qs_item['testType'], 'testType'), test_types))
context = {
"project_name": project_qs.name,
# 查询关键等级-类似“关键”输出
"security_level_str": get_str_abbr(security, 'security_level'),
"isDmsc": isDmsc,
"test_types": type_name_list
"test_types": type_name_list,
"grouped_data": grouped_data,
}
return create_dg_docx("测试策略.docx", context, id)
@@ -563,6 +599,7 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
type_str_list.append(f"{key}{value}")
context = {
'project_name': project_qs.name,
'test_item_count': testDemands.count(),
'length': length,
'type_str': "".join(type_str_list),
}
@@ -614,30 +651,38 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
# 形成一个数组['1','2','3','4','9']后面用来判断测试项的章节号
project_qs = get_object_or_404(Project, id=id)
design_list = [] # 先按照design的思路进行追踪
# 判断是否为鉴定测评,有则生成该表
if project_qs.report_type == '9':
project_round_one = project_qs.pField.filter(key=0).first()
testType_list, last_chapter_items = create_csx_chapter_dict(project_round_one)
# 找出第一轮的研总
yz_dut = project_round_one.rdField.filter(type='YZ').first()
if yz_dut:
# 查询出验证所有design
yz_designs = yz_dut.rsField.all()
# 遍历所有研总的design
for design in yz_designs:
design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []}
# 获取一个design的所有测试项
test_items = design.dtField.all()
for test_item in test_items:
reveal_ident = "_".join(
["XQ", get_testType(test_item.testType, "testType"), test_item.ident])
# 查字典方式确认章节号最后一位
test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1
test_chapter = ".".join([test_item_prefix, str(testType_list.index(test_item.testType) + 1),
str(test_item_last_chapter)])
test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident}
design_dict['test_demand'].append(test_item_dict)
design_list.append(design_dict)
# 查询第一轮次
project_round_one = project_qs.pField.filter(key=0).first()
testType_list, last_chapter_items = create_csx_chapter_dict(project_round_one)
# 找出第一轮的研总
yz_dut = project_round_one.rdField.filter(type='YZ').first()
if yz_dut:
# 查询出验证所有design
yz_designs = yz_dut.rsField.all()
# 遍历所有研总的design
for design in yz_designs:
design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []}
# 获取一个design的所有测试项
test_items = design.dtField.all()
# 连接两个QuerySet默认去重
test_items = test_items.union(design.odField.all())
for test_item in test_items:
reveal_ident = "_".join(
["XQ", get_testType(test_item.testType, "testType"), test_item.ident])
# 查字典方式确认章节号最后一位
test_item_last_chapter = last_chapter_items[test_item.testType].index(
test_item.key) + 1
test_chapter = ".".join(
[test_item_prefix, str(testType_list.index(test_item.testType) + 1),
str(test_item_last_chapter)])
test_item_dict = {'name': test_item.name, 'chapter': test_chapter,
'ident': reveal_ident}
design_dict['test_demand'].append(test_item_dict)
design_list.append(design_dict)
try:
design_list = sorted(design_list, key=chapter_key)
except Exception as e:
print("研总的追踪排序报错,错误原因:", e)
context = {
'design_list': design_list
}
@@ -671,10 +716,13 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
reveal_ident = "_".join(
["XQ", get_testType(test_item.testType, "testType"), test_item.ident])
# 查字典方式确认章节号最后一位
test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1
test_chapter = ".".join([test_item_prefix, str(testType_list.index(test_item.testType) + 1),
str(test_item_last_chapter)])
test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident}
test_item_last_chapter = last_chapter_items[test_item.testType].index(
test_item.key) + 1
test_chapter = ".".join(
[test_item_prefix, str(testType_list.index(test_item.testType) + 1),
str(test_item_last_chapter)])
test_item_dict = {'name': test_item.name, 'chapter': test_chapter,
'ident': reveal_ident}
design_dict['test_demand'].append(test_item_dict)
design_list.append(design_dict)
@@ -691,13 +739,21 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
reveal_ident = "_".join(
["XQ", get_testType(test_item.testType, "testType"), test_item.ident])
# 查字典方式确认章节号最后一位
test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1
test_chapter = ".".join([test_item_prefix, str(testType_list.index(test_item.testType) + 1),
str(test_item_last_chapter)])
test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident}
test_item_last_chapter = last_chapter_items[test_item.testType].index(
test_item.key) + 1
test_chapter = ".".join(
[test_item_prefix, str(testType_list.index(test_item.testType) + 1),
str(test_item_last_chapter)])
test_item_dict = {'name': test_item.name, 'chapter': test_chapter,
'ident': reveal_ident}
design_dict['test_demand'].append(test_item_dict)
design_list.append(design_dict)
# 根据design的chapter排序-为防止报错崩溃使用try-但难排查
try:
design_list = sorted(design_list, key=chapter_key)
except Exception as e:
print("追踪排序报错,错误原因:", e)
context = {
'design_list': design_list
}
@@ -719,8 +775,9 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
items_list = []
for test_item in test_items:
# 第二个处理被测件为"XQ",第二个处理被测件为'SO'并且为测试项testType为['8', '15', '3', '2']的
if test_item.dut.type == 'XQ' or (test_item.dut.type == 'SO' and test_item.testType in ['8', '15', '3',
'2']):
if test_item.dut.type == 'XQ' or (
test_item.dut.type == 'SO' and test_item.testType in ['8', '15', '3',
'2']):
reveal_ident = "_".join(
["XQ", get_testType(test_item.testType, "testType"), test_item.ident])
# 查字典方式确认章节号最后一位
@@ -782,3 +839,13 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
else:
return ChenResponse(message='未找到源代码被测件', code=400)
return create_dg_docx('代码质量度量分析表.docx', context, id)
# 工具方法-给sorted排序使用-知识点python里面可以元组排序
def chapter_key(item):
big_num = [5000, 5000, 5000, 5000]
if "." in item['chapter']:
# 如果是有章节号的则排序
return [int(part) for part in item['chapter'].split(".")]
if item['test_demand'][0]['name'] in ['文档审查', '静态分析', '代码审查', '代码走查']:
return [0, 0, 0, 0]
return big_num

View File

@@ -87,7 +87,7 @@ class GenerateControllerHJL(ControllerBase):
context_round['version_info'] = version_info
# 开始渲染每个轮次的二级文档
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl' / f"{cname}轮被测软件基本信息.docx"
doc.render(context=context_round)
doc.render(context=context_round, autoescape=True)
try:
doc.save(save_path)
except PermissionError:
@@ -204,7 +204,7 @@ class GenerateControllerHJL(ControllerBase):
context["data"] = output_list
# 最后渲染
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl' / f"{cname}轮测试用例记录.docx"
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(save_path)
except PermissionError:

View File

@@ -1,23 +1,17 @@
import base64
import io
from pathlib import Path
from copy import deepcopy
from typing import Union
from ninja_extra import api_controller, ControllerBase, route
from ninja_extra.permissions import IsAuthenticated
from ninja_jwt.authentication import JWTAuth
from ninja.errors import HttpError
from django.db import transaction
from django.shortcuts import get_object_or_404
from django.db.models import QuerySet, Q
from docxtpl import DocxTemplate, RichText, InlineImage
from docx.shared import Mm
from docxtpl import DocxTemplate
from docx import Document
# 导入模型
from apps.project.models import Project, Round, Dut
from apps.dict.models import Dict, DictItem
from apps.dict.models import Dict
# 导入项目工具
from utils.util import get_list_dict, get_str_dict, MyHTMLParser, get_ident, get_case_ident, get_testType
from utils.util import get_list_dict, get_str_dict, get_ident, get_case_ident, get_testType
from utils.chapter_tools.csx_chapter import create_csx_chapter_dict
from utils.chen_response import ChenResponse
from apps.createDocument.extensions import util
@@ -27,6 +21,8 @@ from apps.createDocument.extensions.parse_rich_text import RichParser
from apps.createDocument.extensions.documentTime import DocTime
# 导入生成日志记录模块
from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
# 导入排序
from apps.createDocument.extensions.tools import demand_sort_by_designKey
chinese_round_name: list = ['', '', '', '', '', '', '', '', '', '']
@@ -103,7 +99,7 @@ class GenerateControllerHSM(ControllerBase):
context_round['version_info'] = version_info
# 开始渲染每个轮次的二级文档
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"{cname}轮被测软件基本信息.docx"
doc.render(context=context_round)
doc.render(context=context_round, autoescape=True)
try:
doc.save(save_path)
except PermissionError:
@@ -141,7 +137,7 @@ class GenerateControllerHSM(ControllerBase):
round_context['last_version'] = so_dut_last.version
round_context['round_chinese'] = cname
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"{cname}轮文档概述.docx"
doc.render(context=round_context)
doc.render(context=round_context, autoescape=True)
try:
doc.save(save_path)
except PermissionError:
@@ -171,9 +167,11 @@ class GenerateControllerHSM(ControllerBase):
'publish_date': timer.dg_cover_time, 'source': project_obj.test_unit}
std_documents.append(dg_duty)
# 需要添加说明、记录
sm_duty = {'doc_name': f'{project_obj.name}软件测试说明', 'ident_version': f'PT-{project_obj.ident}-TD-1.00',
sm_duty = {'doc_name': f'{project_obj.name}软件测试说明',
'ident_version': f'PT-{project_obj.ident}-TD-1.00',
'publish_date': timer.sm_cover_time, 'source': project_obj.test_unit}
jl_duty = {'doc_name': f'{project_obj.name}软件测试记录', 'ident_version': f'PT-{project_obj.ident}-TN',
jl_duty = {'doc_name': f'{project_obj.name}软件测试记录',
'ident_version': f'PT-{project_obj.ident}-TN',
'publish_date': timer.jl_cover_time, 'source': project_obj.test_unit}
std_documents.extend([sm_duty, jl_duty])
@@ -196,7 +194,7 @@ class GenerateControllerHSM(ControllerBase):
'std_documents': std_documents_round
}
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"{cname}轮技术依据文件.docx"
doc.render(context=context)
doc.render(context=context, autoescape=True)
try:
doc.save(save_path)
except PermissionError:
@@ -254,7 +252,7 @@ class GenerateControllerHSM(ControllerBase):
context_round['so_str'] = f"被测软件代码{now_dm_version}版本和{last_dm_version}版本"
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"{cname}轮软件更改部分.docx"
doc.render(context_round)
doc.render(context_round, autoescape=True)
try:
doc.save(save_path)
except PermissionError:
@@ -264,9 +262,7 @@ class GenerateControllerHSM(ControllerBase):
@route.get("/create/hdemand", url_name="create-hdemand")
@transaction.atomic
def create_hdemand(self, id: int):
"""
生成非第一轮的多个测试需求
"""
"""生成非第一轮的多个测试需求"""
project_path_str = project_path(id)
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hsm' / '回归测试需求.docx'
doc = DocxTemplate(tpl_path)
@@ -282,9 +278,13 @@ class GenerateControllerHSM(ControllerBase):
test_type_len = Dict.objects.get(code='testType').dictItem.count()
type_number_list = [i for i in range(1, test_type_len + 1)]
list_list = [[] for j in range(1, test_type_len + 1)]
# 获得本轮次所有testDemand
testDemand_qs = hround.rtField.all()
for demand in testDemand_qs:
testDemand_qs = hround.rtField.all().select_related('design')
# 根据自己key排序
sorted_demand_qs = sorted(testDemand_qs, key=demand_sort_by_designKey)
for demand in sorted_demand_qs:
type_index = type_number_list.index(int(demand.testType))
content_list = []
for (index, content) in enumerate(demand.testQField.all()):
@@ -322,7 +322,7 @@ class GenerateControllerHSM(ControllerBase):
"doc_list": doc_list,
"design_description": parser.get_final_list(doc),
"test_demand_content": content_list,
"testMethod": testmethod_str,
"testMethod": testmethod_str.strip(),
"adequacy": demand.adequacy.replace("\n", "\a"),
"testDesciption": demand.testDesciption.replace("\n", "\a") # 测试项描述
}
@@ -346,7 +346,7 @@ class GenerateControllerHSM(ControllerBase):
output_list = sorted(output_list, key=(lambda x: x["sort"]))
context["data"] = output_list
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"{cname}轮回归测试需求.docx"
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(save_path)
except PermissionError:
@@ -403,7 +403,7 @@ class GenerateControllerHSM(ControllerBase):
output_list = sorted(output_list, key=(lambda x: x["sort"]))
context["data"] = output_list
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"{cname}轮回归测试用例概述.docx"
doc.render(context=context)
doc.render(context=context, autoescape=True)
try:
doc.save(save_path)
except PermissionError:
@@ -494,7 +494,7 @@ class GenerateControllerHSM(ControllerBase):
context["data"] = output_list
context["round_han"] = cname
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"{cname}轮测试用例.docx"
doc.render(context=context)
doc.render(context=context, autoescape=True)
try:
doc.save(save_path)
except PermissionError:
@@ -585,7 +585,7 @@ class GenerateControllerHSM(ControllerBase):
temporary_file = Path.cwd() / 'media' / project_path_str / 'form_template' / 'hsm' / 'temporary' / f'{cname}轮用例追踪_temp.docx'
out_put_file = Path.cwd() / 'media' / project_path_str / 'output_dir' / 'hsm' / f'{cname}轮用例追踪.docx'
doc = DocxTemplate(input_file)
doc.render(context)
doc.render(context, autoescape=True)
doc.save(temporary_file)
# 通过docx合并单元格
if temporary_file.is_file():

View File

@@ -146,7 +146,7 @@ class GenerateControllerJL(ControllerBase):
output_list = sorted(output_list, key=(lambda x: x["sort"]))
context["data"] = output_list
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(Path.cwd() / "media" / project_path_str / "output_dir/jl" / "测试用例记录.docx")
return ChenResponse(status=200, code=200, message="文档生成成功!")

View File

@@ -135,7 +135,7 @@ class GenerateControllerSM(ControllerBase):
# 排序
output_list = sorted(output_list, key=(lambda x: x["sort"]))
context["data"] = output_list
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(Path.cwd() / "media" / project_path_str / "output_dir/sm" / "测试用例.docx")
return ChenResponse(status=200, code=200, message="文档生成成功!")
@@ -265,7 +265,7 @@ class GenerateControllerSM(ControllerBase):
temporary_file = Path.cwd() / 'media' / project_path_str / 'form_template' / 'sm' / 'temporary' / '说明追踪_temp.docx'
out_put_file = Path.cwd() / 'media' / project_path_str / 'output_dir' / 'sm' / '说明追踪.docx'
doc = DocxTemplate(input_file)
doc.render(context)
doc.render(context, autoescape=True)
doc.save(temporary_file)
# 通过docx合并单元格
if temporary_file.is_file():

View File

@@ -147,7 +147,7 @@ class GenerateControllerWtd(ControllerBase):
'project_ident': project_obj.ident,
'problem_list': data_list,
}
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(Path.cwd() / "media" / project_path_str / "output_dir/wtd" / '问题详情表.docx')
return ChenResponse(status=200, code=200, message="文档生成成功!")

View File

@@ -85,11 +85,15 @@ class RichParser:
if oneline.startswith("data:image/png;base64"):
base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", ""))
# ~~~设置了固定宽度、高度~~~
final_list.append(InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=Mm(height)))
final_list.append(
InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=Mm(height)))
else:
final_list.append(oneline)
if len(final_list) <= 0:
final_list.append("")
# 针对tinymce中粘贴表格最后一行显示句号问题这里统一删除
if final_list[-1] == '\xa0':
final_list.pop()
return final_list
# 4.2.最终方法,在上面方法基础上,增加格式,例如<p>增加缩进,图片居中,<p>包含“图x”则居中

View File

@@ -0,0 +1,9 @@
from apps.project.models import TestDemand
def demand_sort_by_designKey(demand_obj: TestDemand) -> tuple[int, ...]:
"""仅限于测试项排序函数传入sorted函数的key里面"""
parts = demand_obj.key.split('-')
sort_tuple = tuple(int(part) for part in parts)
return sort_tuple
__all__ = ['demand_sort_by_designKey']

View File

@@ -18,7 +18,7 @@ def merge_all_cell(table: Table) -> None:
temp_text = cell.text
else:
if cell.text == temp_text:
if cell.text == '': # 不知道什么原因必须这样判断下
if cell.text == '': # 不知道什么原因必须这样判断下
cell.text = '/'
text_temp = cell.text
ce = cell.merge(col_right.cells[index - 1])
@@ -31,7 +31,7 @@ def create_sm_docx(template_name: str, context: dict, id: int) -> ChenResponse:
"""生成最终说明文档工具函数"""
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'sm' / template_name
doc = DocxTemplate(input_path)
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/sm" / template_name)
return ChenResponse(status=200, code=200, message="文档生成成功!")
@@ -42,7 +42,7 @@ def create_dg_docx(template_name: str, context: dict, id: int) -> ChenResponse:
"""生成最终大纲文档工具函数"""
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / template_name
doc = DocxTemplate(input_path)
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / template_name)
return ChenResponse(status=200, code=200, message="文档生成成功!")
@@ -53,7 +53,7 @@ def create_bg_docx(template_name: str, context: dict, id: int) -> ChenResponse:
"""生成最终报告文档工具函数"""
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'bg' / template_name
doc = DocxTemplate(input_path)
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/bg" / template_name)
return ChenResponse(status=200, code=200, message="文档生成成功!")
@@ -64,7 +64,7 @@ def create_wtd_docx(template_name: str, context: dict, id: int) -> ChenResponse:
"""生成最终问题单文档工具函数"""
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'wtd' / template_name
doc = DocxTemplate(input_path)
doc.render(context)
doc.render(context, autoescape=True)
try:
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/wtd" / template_name)
return ChenResponse(status=200, code=200, message="文档生成成功!")

View File

@@ -52,7 +52,7 @@ class GenerateSeitaiController(ControllerBase):
'project_ident': self.project_obj.ident,
'project_name': self.project_obj.name,
'test_purpose': "装备鉴定和列装定型" if is_jd else "软件交付和使用",
'sec_title': sec_title,
'sec_title': '密级:' + sec_title,
'sec': sec_title,
'duty_person': duty_person,
'member': self.project_obj.member[0] if len(
@@ -64,7 +64,8 @@ class GenerateSeitaiController(ControllerBase):
self.get_xq_doc_informations()
result = generate_temp_doc('dg', payload.id, frag_list=payload.frag)
if isinstance(result, dict):
return ChenResponse(status=400, code=400, message=result.get('msg', 'dg未报出错误原因反正在生成文档出错'))
return ChenResponse(status=400, code=400,
message=result.get('msg', 'dg未报出错误原因反正在生成文档出错'))
dg_replace_path, dg_seitai_final_path = result
# ~~~~start2025/04/19-新增渲染单个字段(可能封装为函数-对temp文件下的jinja字段处理~~~~
# 现在已经把alias和stdContent对应起来了
@@ -122,7 +123,8 @@ class GenerateSeitaiController(ControllerBase):
self.project_obj = get_object_or_404(Project, id=payload.id)
# seitai文档所需变量
is_jd = True if self.project_obj.report_type == '9' else False
member = self.project_obj.member[0] if len(self.project_obj.member) > 0 else self.project_obj.duty_person
member = self.project_obj.member[0] if len(
self.project_obj.member) > 0 else self.project_obj.duty_person
self.temp_context = {
'project_name': self.project_obj.name,
'project_ident': self.project_obj.ident,
@@ -164,7 +166,8 @@ class GenerateSeitaiController(ControllerBase):
# 将cname存入一个list以便后续拼接给下载函数
cname_list.append(cname)
is_jd = True if self.project_obj.report_type == '9' else False
member = self.project_obj.member[0] if len(self.project_obj.member) > 0 else self.project_obj.duty_person
member = self.project_obj.member[0] if len(
self.project_obj.member) > 0 else self.project_obj.duty_person
# 回归轮次的标识和版本
so_dut: Dut = hround.rdField.filter(type='SO').first()
if not so_dut:
@@ -216,7 +219,8 @@ class GenerateSeitaiController(ControllerBase):
# 取出当前轮次key减1就是上一轮次
cname = self.chinese_round_name[int(hround.key)] # 输出二、三...
cname_list.append(cname)
member = self.project_obj.member[0] if len(self.project_obj.member) > 0 else self.project_obj.duty_person
member = self.project_obj.member[0] if len(
self.project_obj.member) > 0 else self.project_obj.duty_person
is_jd = True if self.project_obj.report_type == '9' else False
so_dut: Dut = hround.rdField.filter(type='SO').first()
if not so_dut:
@@ -260,7 +264,8 @@ class GenerateSeitaiController(ControllerBase):
"""生成最后的问题单"""
self.project_obj = get_object_or_404(Project, id=payload.id)
# seitai文档所需变量
member = self.project_obj.member[0] if len(self.project_obj.member) > 0 else self.project_obj.duty_person
member = self.project_obj.member[0] if len(
self.project_obj.member) > 0 else self.project_obj.duty_person
is_jd = True if self.project_obj.report_type == '9' else False
self.temp_context = {
"project_name": self.project_obj.name,
@@ -272,7 +277,8 @@ class GenerateSeitaiController(ControllerBase):
} | DocTime(payload.id).wtd_final_time()
result = generate_temp_doc('wtd', payload.id, frag_list=payload.frag)
if isinstance(result, dict):
return ChenResponse(status=400, code=400, message=result.get('msg', 'wtd未报出错误原因反正在生成文档出错'))
return ChenResponse(status=400, code=400,
message=result.get('msg', 'wtd未报出错误原因反正在生成文档出错'))
wtd_replace_path, wtd_seitai_final_path = result
text_frag_name_list, doc_docx = get_jinja_stdContent_element(wtd_replace_path)
# 文本片段操作
@@ -290,7 +296,8 @@ class GenerateSeitaiController(ControllerBase):
self.project_obj = get_object_or_404(Project, id=payload.id)
# seitai文档所需变量
## 1.判断是否为JD
member = self.project_obj.member[0] if len(self.project_obj.member) > 0 else self.project_obj.duty_person
member = self.project_obj.member[0] if len(
self.project_obj.member) > 0 else self.project_obj.duty_person
is_jd = True if self.project_obj.report_type == '9' else False
self.temp_context = {
'project_name': self.project_obj.name,
@@ -306,7 +313,8 @@ class GenerateSeitaiController(ControllerBase):
} | DocTime(payload.id).bg_final_time()
result = generate_temp_doc('bg', payload.id, frag_list=payload.frag)
if isinstance(result, dict):
return ChenResponse(status=400, code=400, message=result.get('msg', 'bg未报出错误原因反正在生成文档出错'))
return ChenResponse(status=400, code=400,
message=result.get('msg', 'bg未报出错误原因反正在生成文档出错'))
bg_replace_path, bg_seitai_final_path = result
text_frag_name_list, doc_docx = get_jinja_stdContent_element(bg_replace_path)
# 文本片段操作
@@ -375,14 +383,16 @@ class CreateFragmentController(ControllerBase):
frags = self.get_fragment_name_by_document_name(id, documentType)
# 如果没有文档片段-说明没有生成二段文档
if not frags:
return ChenResponse(status=500, code=500, message='文档片段还未生成,请关闭后再打开/或者先下载基础文档')
return ChenResponse(status=500, code=500,
message='文档片段还未生成,请关闭后再打开/或者先下载基础文档')
# 到这里说fragments_files数组有值返回文件名数组
return ChenResponse(data=[fragment for fragment in frags], message='返回文档片段成功')
@staticmethod
def get_fragment_name_by_document_name(id: int, document_name: str):
# 1.找到模版的路径 - 不用异常肯定存在
document_path = main_download_path / project_path(id) / 'form_template' / 'products' / f"{document_name}.docx"
document_path = main_download_path / project_path(
id) / 'form_template' / 'products' / f"{document_name}.docx"
# 2.识别其中的文档片段
frag_list = get_frag_from_document(document_path)
# 3.这里处理报告里第十轮次前端展示问题
@@ -402,7 +412,8 @@ class CreateFragmentController(ControllerBase):
filter_frags = list(filter(lambda x: '测试内容和结果' not in x['frag_name'], frag_list))
# 再找到白名单的“测试内容和结果_”的片段
content_and_result_frags = list(
filter(lambda x: '测试内容和结果' in x['frag_name'] and x['frag_name'] in white_list_frag, frag_list))
filter(lambda x: '测试内容和结果' in x['frag_name'] and x['frag_name'] in white_list_frag,
frag_list))
# 再组合起来返回
filter_frags.extend(content_and_result_frags)
return filter_frags

View File

@@ -1,13 +1,87 @@
from datetime import date
from ninja_extra import api_controller, ControllerBase, route
import json
from apps.project.models import Project
from django.db import transaction
from django.contrib.auth import get_user_model
from utils.chen_response import ChenResponse
from django.db.models import Q
from ninja import Schema
Users = get_user_model()
class AIPostSchema(Schema):
question: str
stream: bool
# AI测试接口
@api_controller("/local_doc_qa", tags=['AI测试接口'])
class AITestController(ControllerBase):
"""AI测试接口自定义延迟"""
@route.post("/testing_item")
def ai_return(self, item: AIPostSchema):
import time
time.sleep(2)
res = [
{
"demandDescription": "验证外部32MHz品振时钟和内部10KHZ时钟能否正确布线至FPGA内部相应的全局时钟网络并通过指定缓冲器降低延迟。",
"title": "时钟布线与缓冲功能测试",
"children": [
{
"name": "外部32MHz时钟布线到HCLKBUF级冲测试",
"subDescription": "验证外部32MH布线的测试子项描述",
"subStep": [
{
"operation": "配置FPGA逻辑将外部32MHz晶振输入连接到HCLKBUF缓冲器。",
"expect": "时钟信号成功接入HCLKBUF缓冲器无错误提示。"
}, {
"operation": "使用示波器或时序分析工具检测HCLKBUF输出端的时钟波形。",
"expect": "输出端应稳定输出32MHz时钟信号频率准确目波形无明显失真。"
}, {
"operation": "监测从HCLKBUF到各寄存器的时钟路径延迟。",
"expect": "各路径延迟保持一致目为最小值,满足分布式延迟最低的变求。"
}
]
}, {
"name": "内部10KHz时钟布线到CLKINT缓冲测试",
"subDescription": "验证内部10KHz时钟布线到CLKINT缓冲的测试子项描述",
"subStep": [
{
"operation": "在FPGA中启用内部10KHz时钟源并将其连接至CLKINT缓冲器。",
"expect": "内部时钟信号成功接入CLKINT缓冲器系统无报错。"
}, {
"operation": "测量CLKINT输出端的时钟频率。",
"expect": "输出端应稳定输出10KHz时钟信号频率精度符合设计要求。"
}, {
"operation": "检查CLKINT是否将时钟广播到全局时钟网器",
"expect": "时钟能被正常分发至内部各个需要该时钟的模块。"
}
]
}, {
"name": "异常情况下的时钟处理测试",
"subDescription": "验证异常情况下的时钟处理测试的测试子项描述",
"subStep": [
{
"operation": "断开外部32MHz晶振输入后尝试进行HCLKBUF配置。",
"expect": "系统应报告时钟缺失错误,无法完成正常的时钟分配。"
}, {
"operation": "人为制造内部10KHz时钟不稳定(如干扰)后再送入CLKINT。",
"expect": "CLKINT应拒绝不稳定的时钟或将错误上报给监控机制。"
}, {
"operation": "同时配置两个时钟但未正确绑定各自缓冲器。",
"expect": "系统应阻止非法配置操作,确保每个时钟进入正确的缓冲通道。"
}
]
}
]
}
]
return {
"history": [["我是没有用的", json.dumps(res)]]
}
# 这是其他common内容接口
@api_controller("/system", tags=['通用接口'])
class CommonController(ControllerBase):
@@ -19,7 +93,8 @@ class CommonController(ControllerBase):
item1 = {"title": "测试管理平台V0.0.2测试发布", "created_at": "2023-09-23",
"content": "测试管理平台V0.0.2发布,正在进行内部测试.."}
item_list.append(item1)
item2 = {"title": "测试管理平台更新公共", "created_at": "2024-06-17", "content": "<p>1.修改大纲和报告模版<p><p>2.修复多个bug<p>"}
item2 = {"title": "测试管理平台更新公共", "created_at": "2024-06-17",
"content": "<p>1.修改大纲和报告模版<p><p>2.修复多个bug<p>"}
item_list.append(item2)
return item_list

View File

@@ -8,8 +8,11 @@ from utils.chen_pagination import MyPagination
from django.db import transaction
from django.shortcuts import get_object_or_404
from django.db.models.functions import Replace
from django.db.models import Q, F, Value
from django.db.models import F, Value
from typing import List
from faker import Faker
from datetime import datetime
from django.utils import timezone
from utils.chen_response import ChenResponse
from utils.chen_crud import multi_delete_case
from apps.project.models import Design, Dut, Round, TestDemand, Case, CaseStep, Project, Problem
@@ -20,8 +23,9 @@ from utils.util import get_testType
from utils.codes import HTTP_INDEX_ERROR, HTTP_EXISTS_CASES
from apps.project.tools.copyCase import case_move_to_test, case_copy_to_test, case_to_case_copy_or_move
from utils.smallTools.interfaceTools import conditionNoneToBlank
from apps.project.tool.batchTools import parse_case_content_string
# 导入case的schema
from apps.project.schemas.case import CaseModelOutSchemaWithoutProblem
from apps.project.schemas.case import CaseModelOutSchemaWithoutProblem, BatchCreateCaseInputSchema
@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['测试用例接口'])
class CaseController(ControllerBase):
@@ -120,7 +124,7 @@ class CaseController(ControllerBase):
@transaction.atomic
def create_case(self, payload: CaseCreateInputSchema):
asert_dict = payload.dict(exclude_none=True)
# 构造design_key
# 构造demand_key
test_whole_key = "".join(
[payload.round_key, "-", payload.dut_key, '-', payload.design_key, '-', payload.test_key])
# 查询当前key应该为多少
@@ -157,6 +161,52 @@ class CaseController(ControllerBase):
CaseStep.objects.bulk_create(data_list) # type:ignore
return qs
# 批量新增用例
@route.post("/case/multi_save", url_name="case-batch-create")
@transaction.atomic
def multi_case_save(self, payload: BatchCreateCaseInputSchema):
project_obj = get_object_or_404(Project, id=payload.project_id)
user_name = self.context.request.user.name
keys = []
demands = project_obj.ptField.all() # 当前项目所有测试项
for case_data in payload.cases:
# 解析放在前面防止出错
stepsOrErrorResponse = parse_case_content_string(case_data.test_step)
if isinstance(stepsOrErrorResponse, ChenResponse):
return stepsOrErrorResponse
# 查询当前测试项下case数量以设置case的key
demand_key = case_data.parent_key
demand_obj = demands.filter(key=demand_key).first()
case_count = demand_obj.tcField.count()
key_string = ''.join([demand_key, "-", str(case_count)])
keys.append(key_string)
case_dict = {
"ident": demand_obj.ident,
"name": case_data.name,
"key": key_string,
"initialization": case_data.initialization,
"premise": case_data.premise,
"summarize": case_data.summarize,
"designPerson": user_name,
"testPerson": user_name,
"monitorPerson": user_name,
"project": project_obj,
"round": demand_obj.round,
"dut": demand_obj.dut,
"design": demand_obj.design,
"test": demand_obj,
"exe_time": timezone.now(),
"timing_diagram": case_data.sequence,
"title": case_data.name
}
case_new_obj = Case.objects.create(**case_dict)
case_step_list = []
for step in stepsOrErrorResponse:
case_step_list.append(CaseStep(**{"case": case_new_obj, "operation": step['operation'],
"expect": step['expect']}))
CaseStep.objects.bulk_create(case_step_list)
return ChenResponse(code=60000, status=200, data=keys, message='成功录入用例')
# 更新测试用例
@route.put("/case/update/{id}", response=CaseCreateOutSchema, url_name="case-update")
@transaction.atomic
@@ -301,7 +351,8 @@ class CaseController(ControllerBase):
# 批量更新 operation 和 expect
step_count = caseStep_qs.update(
operation=Replace(F('operation'), Value(payload.originText), Value(payload.replaceText)),
expect=Replace(F('expect'), Value(payload.originText), Value(payload.replaceText))
expect=Replace(F('expect'), Value(payload.originText), Value(payload.replaceText)),
result=Replace(F('result'), Value(payload.originText), Value(payload.replaceText))
)
# 5.提交更新
replace_count = case_qs.update(**replace_kwargs)
@@ -324,6 +375,24 @@ class CaseController(ControllerBase):
@route.post("/case/timeReplace/", url_name='case-time-replace')
@transaction.atomic
def bulk_replace_time(self, payload: ExetimeReplaceSchema):
selected_case_ids = payload.selectRows
if not selected_case_ids:
return ChenResponse(status=500, code=50999, message='未选择行!', data="")
# 随机日期
start, end = payload.exetime
start_date = datetime.strptime(start, "%Y-%m-%d").date()
end_date = datetime.strptime(end, "%Y-%m-%d").date()
# 更新的case的id列表
updated_cases = []
# 替换设计人员
case_qs = Case.objects.filter(id__in=payload.selectRows)
case_qs.update(exe_time=payload.exetime)
faker = Faker()
# 逐个更新
for case in case_qs:
random_date = faker.date_between(start_date=start_date, end_date=end_date)
formatted_date = random_date.strftime("%Y-%m-%d")
case.exe_time = formatted_date
updated_cases.append(case)
Case.objects.bulk_update(updated_cases, ['exe_time'])
return ChenResponse(status=200, code=200, data=len(updated_cases),
message=f"成功更新{len(updated_cases)}个用例执行时间")

View File

@@ -1,3 +1,5 @@
import re
from copy import deepcopy
from ninja_extra import api_controller, ControllerBase, route
from ninja import Query
from ninja_jwt.authentication import JWTAuth
@@ -20,6 +22,8 @@ from apps.project.schemas.design import DeleteSchema, DesignFilterSchema, Design
ReplaceDesignContentSchema
from apps.project.tools.delete_change_key import design_delete_sub_node_key
from utils.smallTools.interfaceTools import conditionNoneToBlank
from apps.project.tools.auto_create_data import auto_create_renji
from apps.project.tool.dragAndDrop import DesignDrapAtoB
@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['设计需求数据'])
class DesignController(ControllerBase):
@@ -63,7 +67,10 @@ class DesignController(ControllerBase):
# 处理树状数据
@route.get("/getDesignDemandInfo", response=List[DesignTreeReturnSchema], url_name="design-info")
def get_design_tree(self, payload: DesignTreeInputSchema = Query(...)):
qs = Design.objects.filter(project__id=payload.project_id, dut__key=payload.key).order_by('id')
qs = Design.objects.filter(
project__id=payload.project_id,
dut__key=payload.key
).select_related('project', 'dut')
return qs
# 添加设计需求
@@ -162,7 +169,7 @@ class DesignController(ControllerBase):
design_delete_sub_node_key(single_qs)
return ChenResponse(message="设计需求删除成功!")
# 给复制功能级联选择器查询所有的设计需求
# 给复制功能级联选择器查询所有的设计需求【这是查项目所有的设计需求】
@route.get("/designDemand/getRelatedDesign", url_name='dut-relatedDesign')
def getRelatedDesign(self, id: int):
project_qs = get_object_or_404(Project, id=id)
@@ -196,3 +203,66 @@ class DesignController(ControllerBase):
# 4.提交更新
replace_count = design_qs.update(**replace_kwargs)
return {'count': replace_count}
# 点击生成人机交互界面测试-注意必须要有界面的软件
@route.get("/create_renji/", url_name='renji')
@transaction.atomic
def create_rj(self, round_id: int, project_id: int):
user_name = self.context.request.user.name # 获取当前用户名
project_obj: Project = get_object_or_404(Project, id=project_id)
dut_qs = Dut.objects.filter(round__key=round_id, project=project_obj, type='XQ').first()
if dut_qs:
auto_create_renji(user_name, dut_qs, project_obj)
return ChenResponse(status=200, message='自动生成人机界面交互测试成功!', data=dut_qs.key)
return ChenResponse(status=402, message='您还未录入需求规格说明文档,请录入后再试')
# 复制design到当前dut下面接口
@route.get("/copy_current", url_name='copy-design-current')
@transaction.atomic
def copy_current(self, dut_id: int, design_id: int):
dut_obj = get_object_or_404(Dut, id=dut_id)
design_obj = get_object_or_404(Design, id=design_id)
# 首先查询该dut下design个数设置为新增设计需求的key末尾
key_index = dut_obj.rsField.count()
new_design_obj = deepcopy(design_obj)
# 修改新design内容
new_design_obj.pk = None
new_design_obj.key = "".join([dut_obj.key, "-", str(key_index)])
new_design_obj.title = "".join([design_obj.title, "(复制)"])
new_design_obj.name = "".join([design_obj.name, "(复制)"])
# ident容错查询是否有拼接的
current_ident = "".join([new_design_obj.ident, "1"])
project_obj = dut_obj.project
exit_ident = project_obj.psField.filter(ident=current_ident).exists()
if exit_ident:
match = re.search(r'(\d+)$', current_ident)
if match:
num = int(match.group(1)) + 1
current_ident = re.sub(r'\d+$', str(num), current_ident)
else:
current_ident = current_ident + "1"
new_design_obj.ident = current_ident
# 最后记得save
new_design_obj.save()
return ChenResponse(status=200, code=200, message='复制当前设计需求成功', data="")
# 拖拽更变desing的key同dut下其他design也变动
@route.get("/switch_position", url_name='design-switch-position')
@transaction.atomic
def switch_position(self, from_key: str, to_key: str, pos: int, project_id: int):
from_key_list = from_key.split("-")
to_key_list = to_key.split("-")
# 如果两个设计需求被测件或轮次不一样则报错
if from_key_list[:-1] != to_key_list[:-1]:
return ChenResponse(status=422, code=40022, message="无法交换不同父节点的设计需求")
# 先查询两个design
from_design_obj: Design = Design.objects.filter(key=from_key, project_id=project_id).first()
to_design_obj: Design = Design.objects.filter(key=to_key, project_id=project_id).first()
if not from_design_obj or not to_design_obj:
return ChenResponse(status=404, code=40004, message="设计需求不存在")
# 获取父节点下所有design
parant_dut = from_design_obj.dut
design_qs = parant_dut.rsField.all()
# 根据pos将from排到后面
return_key = DesignDrapAtoB(from_design_obj, to_design_obj, design_qs, pos)
return ChenResponse(status=200, data=return_key)

View File

@@ -35,19 +35,25 @@ class ProblemController(ControllerBase):
def get_problem_list(self, data: ProblemFilterSchema = Query(...)):
project_id = data.project_id
conditionNoneToBlank(data)
case_key = "".join([data.round_id, '-', data.dut_id, '-', data.design_id, '-', data.test_id, '-', data.case_id])
# 先查询出对应的case
case_obj = Case.objects.filter(project_id=project_id, key=case_key).first()
# 然后进行过滤
qs = case_obj.caseField.filter(project__id=data.project_id,
ident__icontains=data.ident,
name__icontains=data.name,
status__icontains=data.status,
type__icontains=data.type,
grade__icontains=data.grade,
operation__icontains=data.operation,
postPerson__icontains=data.postPerson,
).order_by("id")
# 组装查询条件
query_params = {
"project__id":data.project_id,
"ident__icontains":data.ident,
"name__icontains":data.name,
"status__icontains":data.status,
"type__icontains":data.type,
"grade__icontains":data.grade,
"operation__icontains":data.operation,
"postPerson__icontains":data.postPerson
}
# 如果没有多个key传递则是汇总界面
if data.dut_id and data.design_id and data.test_id and data.case_id:
case_key = "".join(
[data.round_id, '-', data.dut_id, '-', data.design_id, '-', data.test_id, '-', data.case_id])
query_params['case__key'] = case_key
else:
query_params['case__round__key'] = data.round_id
qs = Problem.objects.filter(**query_params).order_by("id")
# 遍历通过代码不通过ORM查询闭环方式-巧妙使用numpy中array对象的in方法来判断
closeMethod1 = self.context.request.GET.get("closeMethod[0]")
@@ -73,9 +79,7 @@ class ProblemController(ControllerBase):
@paginate(MyPagination)
def get_all_problems(self, round_key: Optional[str] = False, data: ProblemFilterWithHangSchema = Query(...)):
project_id = data.project_id
for attr, value in data.__dict__.items():
if getattr(data, attr) is None:
setattr(data, attr, '')
conditionNoneToBlank(data)
# 先查询当前项目
qs = Problem.objects.filter(project__id=data.project_id,
ident__icontains=data.ident,

View File

@@ -1,3 +1,5 @@
from multiprocessing.spawn import old_main_modules
from ninja_extra import api_controller, ControllerBase, route
from ninja import Query
from ninja_jwt.authentication import JWTAuth
@@ -16,14 +18,16 @@ from utils.codes import HTTP_INDEX_ERROR
from apps.project.models import Design, Dut, Round, TestDemand, TestDemandContent, TestDemandContentStep
from apps.project.schemas.testDemand import DeleteSchema, TestDemandModelOutSchema, TestDemandFilterSchema, \
TestDemandTreeReturnSchema, TestDemandTreeInputSchema, TestDemandCreateOutSchema, \
TestDemandCreateInputSchema, ReplaceDemandContentSchema, \
TestDemandRelatedSchema, TestDemandExistRelatedSchema, DemandCopyToDesignSchema
TestDemandCreateInputSchema, ReplaceDemandContentSchema, PriorityReplaceSchema, \
TestDemandRelatedSchema, TestDemandExistRelatedSchema, DemandCopyToDesignSchema, \
TestDemandMultiCreateInputSchema
# 导入ORM
from apps.project.models import Project
# 导入工具
from apps.project.tools.copyDemand import demand_copy_to_design
from apps.project.tools.delete_change_key import demand_delete_sub_node_key
from utils.smallTools.interfaceTools import conditionNoneToBlank
from apps.project.tool.batchTools import parse_test_content_string
@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['测试项接口'])
class TestDemandController(ControllerBase):
@@ -110,8 +114,9 @@ class TestDemandController(ControllerBase):
# ident判重
project_qs = Project.objects.filter(id=payload.project_id).first()
if payload.ident and project_qs:
exists = project_qs.ptField.filter(ident=payload.ident).exists()
if exists:
old_obj = project_qs.ptField.filter(ident=payload.ident).first()
# 2025/06/24修改现在运行不同测试类型有相同的标识
if old_obj and old_obj.testType == payload.testType:
return ChenResponse(code=500, status=500,
message='测试项标识和其他测试项重复,请更换测试项标识!!!')
# 构造design_key
@@ -133,13 +138,13 @@ class TestDemandController(ControllerBase):
asert_dict.pop("dut_key")
asert_dict.pop("design_key")
asert_dict.pop("testContent")
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 创建测试项 - 以及子项/子项步骤
qs = TestDemand.objects.create(**asert_dict)
for item in payload.dict()['testContent']:
content_obj = TestDemandContent.objects.create(
testDemand=qs,
subName=item['subName']
subName=item['subName'],
subDescription=item['subDescription']
)
TestDemandContentStep.objects.bulk_create([
TestDemandContentStep(
@@ -150,6 +155,69 @@ class TestDemandController(ControllerBase):
])
return qs
# 批量新增测试项
@route.post("/testDemand/multi_save", url_name="testDemand-multi-create")
@transaction.atomic
def create_multi_test_demand(self, payload: TestDemandMultiCreateInputSchema):
# 1.首先判断测试项标识是否重复
project_qs = Project.objects.filter(id=payload.project_id).first()
designs = project_qs.psField.all()
## 给返回response的data数据以便前端更新树状目录
keys = []
## 遍历payload.demands数组
for index, demandOne in enumerate(payload.demands):
if demandOne.ident and project_qs:
old_obj = project_qs.ptField.filter(ident=demandOne.ident).first()
if old_obj and old_obj.testType == demandOne.testType:
message_temp = f"{index}个测试项标识重复,请修改"
return ChenResponse(status=200, code=500101, data=index, message=message_temp)
# 标识不重复就开始录入了
for index, demand in enumerate(payload.demands):
create_sub_demands = parse_test_content_string(demand.testContent)
if isinstance(create_sub_demands, ChenResponse):
return create_sub_demands
else:
# 这说明解析成功了
# 首先查询所属design、dut、round方便新增
design_obj: Design = designs.filter(key=demand.parent_key).first() # 因为前端限制必然有
dut_obj = design_obj.dut
round_obj = design_obj.round
test_demand_count = TestDemand.objects.filter(project=project_qs,
design=design_obj).count()
key_string = ''.join([design_obj.key, "-", str(test_demand_count)])
keys.append(key_string)
create_demand_dict = {
'ident': demand.ident,
'name': demand.name,
'adequacy': demand.adequacy,
'priority': demand.priority,
'testType': demand.testType,
'testMethod': demand.testMethod,
'title': demand.name,
'key': key_string,
'project': project_qs,
'round': round_obj,
'dut': dut_obj,
'design': design_obj,
'testDesciption': demand.testDesciption
}
demand_created = TestDemand.objects.create(**create_demand_dict)
# 录入测试子项
for sub in create_sub_demands:
content_obj = TestDemandContent.objects.create(
testDemand=demand_created,
subName=sub['subName'],
subDescription=sub['subDescription']
)
TestDemandContentStep.objects.bulk_create([
TestDemandContentStep(
testDemandContent=content_obj,
**step.dict() if not isinstance(step, dict) else step
)
for step in sub['subStep']
])
return ChenResponse(code=200991, status=200, data=keys, message='成功录入')
# 更新测试项
@route.put("/testDemand/update/{id}", response=TestDemandCreateOutSchema, url_name="testDemand-update")
@transaction.atomic
@@ -161,9 +229,11 @@ class TestDemandController(ControllerBase):
for attr, value in payload.dict().items():
# 判重复
if attr == 'ident':
if testDemand_qs.ident != value: # 如果ident不和原来相等则要判重复
exists = project_qs.ptField.filter(ident=payload.ident).exists()
if exists:
# 先判断是否和原标识一样,且测试类型改变
if payload.dict()['testType'] != testDemand_qs.testType and value == old_ident:
old_obj = project_qs.ptField.filter(ident=payload.ident).first()
# 2025/06/24修改不同类型可以相同
if old_obj and old_obj.testType == payload.dict()['testType']:
return ChenResponse(code=500, status=500, message='更换的标识和其他测试项重复')
if attr == 'project_id' or attr == 'round_key' or attr == 'dut_key' or attr == 'design_key':
continue # 如果发现是key则不处理
@@ -181,7 +251,8 @@ class TestDemandController(ControllerBase):
if item['subName']:
content_obj = TestDemandContent.objects.create(
testDemand=testDemand_qs,
subName=item["subName"]
subName=item["subName"],
subDescription=item["subDescription"]
)
TestDemandContentStep.objects.bulk_create([
TestDemandContentStep(
@@ -221,7 +292,7 @@ class TestDemandController(ControllerBase):
demand_delete_sub_node_key(single_qs) # 删除后需重排子节点
return ChenResponse(message="测试需求删除成功!")
# 查询一个项目的所有测试项
# 查询一个项目的所有测试项【当前轮次】
@route.get("/testDemand/getRelatedTestDemand", url_name="testDemand-getRelatedTestDemand")
@transaction.atomic
def getRelatedTestDemand(self, id: int, round: str):
@@ -233,7 +304,7 @@ class TestDemandController(ControllerBase):
for design in designs:
design_dict = {'label': design.name, 'value': design.id, 'children': []}
for test_item in design.dtField.all():
test_item_dict = {'label': test_item.name, 'value': test_item.id}
test_item_dict = {'label': test_item.name, 'value': test_item.id, 'key': test_item.key}
design_dict['children'].append(test_item_dict)
data_list.append(design_dict)
return ChenResponse(message='获取成功', data=data_list)
@@ -320,3 +391,11 @@ class TestDemandController(ControllerBase):
# 5.提交更新
replace_count = demand_qs.update(**replace_kwargs)
return {'count': replace_count + step_count}
# 批量替换优先级-priority
@route.post("/testDemand/priorityReplace/", url_name='demand-priority-replace')
@transaction.atomic
def multiple_modify_demand_priority(self, payload: PriorityReplaceSchema):
# 替换优先级
demand_qs = TestDemand.objects.filter(id__in=payload.selectRows)
demand_qs.update(priority=payload.priority)

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.3 on 2025-06-20 19:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('project', '0016_dutmetrics'),
]
operations = [
migrations.AlterField(
model_name='testdemandcontentstep',
name='id',
field=models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0 on 2025-12-15 09:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('project', '0017_alter_testdemandcontentstep_id'),
]
operations = [
migrations.AddField(
model_name='testdemandcontent',
name='subDescription',
field=models.CharField(blank=True, max_length=1024, null=True, verbose_name='测试子项一句话描述'),
),
]

View File

@@ -13,12 +13,15 @@ class Project(CoreModel):
objects = models.Manager()
ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="项目标识",
help_text="项目标识", unique=True) # 唯一
name = models.CharField(max_length=100, blank=True, null=True, verbose_name="项目名称", help_text="项目名称")
name = models.CharField(max_length=100, blank=True, null=True, verbose_name="项目名称",
help_text="项目名称")
beginTime = models.DateField(auto_now_add=True, null=True, blank=True, help_text="开始时间",
verbose_name="开始时间")
endTime = models.DateField(auto_now_add=True, null=True, blank=True, help_text="结束时间", verbose_name="结束时间")
endTime = models.DateField(auto_now_add=True, null=True, blank=True, help_text="结束时间",
verbose_name="结束时间")
duty_person = models.CharField(max_length=64, verbose_name="负责人", help_text="负责人")
member = models.JSONField(null=True, blank=True, help_text="项目成员", verbose_name="项目成员", default=create_list)
member = models.JSONField(null=True, blank=True, help_text="项目成员", verbose_name="项目成员",
default=create_list)
# 8月新增字段
quality_person = models.CharField(max_length=64, verbose_name="质量保证员", help_text="质量保证员")
vise_person = models.CharField(max_length=64, verbose_name="质量监督员", help_text="质量监督员")
@@ -30,7 +33,8 @@ class Project(CoreModel):
default=create_list)
plant_type = models.JSONField(null=True, blank=True, help_text="平台类型", verbose_name="平台类型",
default=create_list)
report_type = models.CharField(max_length=64, blank=True, null=True, verbose_name="报告类型", help_text="报告类型")
report_type = models.CharField(max_length=64, blank=True, null=True, verbose_name="报告类型",
help_text="报告类型")
language = models.JSONField(null=True, blank=True, help_text="被测语言", verbose_name="被测语言",
default=create_list)
standard = models.JSONField(null=True, blank=True, help_text="依据标准", verbose_name="依据标准",
@@ -56,10 +60,12 @@ class Project(CoreModel):
help_text="测评中心电话")
test_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心邮箱",
help_text="测评中心邮箱")
step = models.CharField(max_length=8, blank=True, null=True, verbose_name="项目阶段", help_text="项目阶段")
step = models.CharField(max_length=8, blank=True, null=True, verbose_name="项目阶段",
help_text="项目阶段")
abbreviation = models.JSONField(null=True, blank=True, help_text="缩略语", verbose_name="缩略语",
default=create_list)
soft_type = models.SmallIntegerField(verbose_name='软件类型', choices=((1, '新研'), (2, '改造'), (3, '沿用')),
soft_type = models.SmallIntegerField(verbose_name='软件类型',
choices=((1, '新研'), (2, '改造'), (3, '沿用')),
default=1)
runtime = models.CharField(max_length=8, blank=True, null=True, verbose_name="运行环境",
help_text="运行环境")
@@ -85,23 +91,31 @@ class Round(CoreModel):
help_text="轮次名称")
beginTime = models.DateField(auto_now_add=True, null=True, blank=True, help_text="开始时间",
verbose_name="开始时间")
endTime = models.DateField(auto_now_add=True, null=True, blank=True, help_text="结束时间", verbose_name="结束时间")
grade = models.CharField(max_length=64, blank=True, null=True, verbose_name="等级", help_text="等级", default='1')
best_condition_voltage = models.CharField(max_length=64, blank=True, null=True, verbose_name="最优工况电压",
endTime = models.DateField(auto_now_add=True, null=True, blank=True, help_text="结束时间",
verbose_name="结束时间")
grade = models.CharField(max_length=64, blank=True, null=True, verbose_name="等级", help_text="等级",
default='1')
best_condition_voltage = models.CharField(max_length=64, blank=True, null=True,
verbose_name="最优工况电压",
help_text="最优工况电压")
best_condition_tem = models.CharField(max_length=64, blank=True, null=True, verbose_name="最优工况温度",
help_text="最优工况温度")
typical_condition_voltage = models.CharField(max_length=64, blank=True, null=True, verbose_name="典型工况电压",
typical_condition_voltage = models.CharField(max_length=64, blank=True, null=True,
verbose_name="典型工况电压",
help_text="典型工况电压")
typical_condition_tem = models.CharField(max_length=64, blank=True, null=True, verbose_name="典型工况温度",
typical_condition_tem = models.CharField(max_length=64, blank=True, null=True,
verbose_name="典型工况温度",
help_text="典型工况温度")
low_condition_voltage = models.CharField(max_length=64, blank=True, null=True, verbose_name="最低工况电压",
low_condition_voltage = models.CharField(max_length=64, blank=True, null=True,
verbose_name="最低工况电压",
help_text="最低工况电压")
low_condition_tem = models.CharField(max_length=64, blank=True, null=True, verbose_name="最低工况温度",
help_text="最低工况温度")
project = models.ForeignKey(to="Project", db_constraint=False, related_name="pField", on_delete=models.CASCADE,
project = models.ForeignKey(to="Project", db_constraint=False, related_name="pField",
on_delete=models.CASCADE,
verbose_name='归属项目', help_text='归属项目', related_query_name='pQuery')
level = models.CharField(max_length=15, verbose_name='树状级别第一级', help_text="树状级别第一级", default='0')
level = models.CharField(max_length=15, verbose_name='树状级别第一级', help_text="树状级别第一级",
default='0')
key = models.CharField(max_length=15, verbose_name='给前端的树状级别', help_text="给前端的树状级别")
title = models.CharField(max_length=15, verbose_name='给前端的name', help_text="给前端的name")
# 新增执行地点
@@ -120,30 +134,38 @@ class Dut(CoreModel):
objects = models.Manager()
ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="被测件标识",
help_text="被测件标识") # 后面加上unique=True
type = models.CharField(max_length=16, blank=True, null=True, verbose_name="被测件类型", help_text="被测件类型")
name = models.CharField(max_length=64, blank=True, null=True, verbose_name="被测件名称", help_text="被测件名称")
type = models.CharField(max_length=16, blank=True, null=True, verbose_name="被测件类型",
help_text="被测件类型")
name = models.CharField(max_length=64, blank=True, null=True, verbose_name="被测件名称",
help_text="被测件名称")
# 2025年4月28日更新分为总函数、有效代码行数、注释行数
total_lines = models.CharField(max_length=64, blank=True, null=True, verbose_name='总行数')
effective_lines = models.CharField(max_length=64, blank=True, null=True, verbose_name='有效代码行数')
comment_lines = models.CharField(max_length=64, blank=True, null=True, verbose_name='注释行数')
# 更新结束
title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称", help_text="树-名称")
title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称",
help_text="树-名称")
key = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-key", help_text="树-key")
# 被测件添加版本、发布单位、发布时间
version = models.CharField(max_length=64, blank=True, null=True, verbose_name="发布版本", help_text="发布版本")
version = models.CharField(max_length=64, blank=True, null=True, verbose_name="发布版本",
help_text="发布版本")
release_union = models.CharField(max_length=64, blank=True, null=True, verbose_name="发布版本",
help_text="发布版本")
release_date = models.DateField(auto_now_add=True, null=True, blank=True, help_text="发布时间",
verbose_name="发布时间")
# 新增用户文档的编号
ref = models.CharField(max_length=32, blank=True, null=True, verbose_name="文档编号", help_text="文档编号")
ref = models.CharField(max_length=32, blank=True, null=True, verbose_name="文档编号",
help_text="文档编号")
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
level = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-level", help_text="树-level",
level = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-level",
help_text="树-level",
default=1) # 默认为1
project = models.ForeignKey(to="Project", db_constraint=False, related_name="pdField", on_delete=models.CASCADE,
project = models.ForeignKey(to="Project", db_constraint=False, related_name="pdField",
on_delete=models.CASCADE,
verbose_name='归属项目', help_text='归属项目', related_query_name='pdQuery')
round = models.ForeignKey(to="Round", db_constraint=False, related_name="rdField", on_delete=models.CASCADE,
round = models.ForeignKey(to="Round", db_constraint=False, related_name="rdField",
on_delete=models.CASCADE,
verbose_name='归属轮次', help_text='归属轮次', related_query_name='rdQuery')
def __str__(self):
@@ -159,7 +181,8 @@ class DutMetrics(models.Model):
objects = models.Manager()
id = ShortUUIDField(primary_key=True, help_text="id", verbose_name="id")
# 外键Dut一个Dut储存一个指标
dut = models.OneToOneField(Dut, on_delete=models.CASCADE, related_name='metrics', related_query_name='metrics',
dut = models.OneToOneField(Dut, on_delete=models.CASCADE, related_name='metrics',
related_query_name='metrics',
db_constraint=False, verbose_name='归属源代码被测件')
avg_function_lines = models.IntegerField(verbose_name='平均模块大小')
avg_cyclomatic = models.IntegerField(verbose_name='平均圈复杂度')
@@ -173,19 +196,25 @@ class Design(CoreModel):
objects = models.Manager()
ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="设计需求标识",
help_text="设计需求标识")
name = models.CharField(max_length=64, blank=True, null=True, verbose_name="设计需求名称", help_text="设计需求名称")
name = models.CharField(max_length=64, blank=True, null=True, verbose_name="设计需求名称",
help_text="设计需求名称")
demandType = models.CharField(max_length=8, blank=True, null=True, verbose_name="设计需求类型",
help_text="设计需求类型")
description = HTMLField(blank=True, null=True, verbose_name="设计需求描述", help_text="设计需求描述")
title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称", help_text="树-名称")
title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称",
help_text="树-名称")
key = models.CharField(max_length=64, blank=True, null=True, verbose_name="round-dut-designkey",
help_text="round-dut-designkey")
level = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-level", help_text="树-level",
level = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-level",
help_text="树-level",
default=2) # 默认为2
chapter = models.CharField(max_length=64, blank=True, verbose_name="设计需求章节号", help_text="设计需求章节号")
project = models.ForeignKey(to="Project", db_constraint=False, related_name="psField", on_delete=models.CASCADE,
chapter = models.CharField(max_length=64, blank=True, verbose_name="设计需求章节号",
help_text="设计需求章节号")
project = models.ForeignKey(to="Project", db_constraint=False, related_name="psField",
on_delete=models.CASCADE,
verbose_name='归属项目', help_text='归属项目', related_query_name='psQuery')
round = models.ForeignKey(to="Round", db_constraint=False, related_name="dsField", on_delete=models.CASCADE,
round = models.ForeignKey(to="Round", db_constraint=False, related_name="dsField",
on_delete=models.CASCADE,
verbose_name='归属轮次', help_text='归属轮次', related_query_name='rsQuery')
dut = models.ForeignKey(to="Dut", db_constraint=False, related_name="rsField", on_delete=models.CASCADE,
verbose_name='归属轮次', help_text='归属轮次', related_query_name='rsQuery')
@@ -214,29 +243,42 @@ class TestDemand(CoreModel):
"""测试项"""
ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="测试需求标识",
help_text="测试需求标识")
name = models.CharField(max_length=64, blank=True, null=True, verbose_name="测试需求名称", help_text="测试需求名称")
adequacy = models.CharField(max_length=256, blank=True, null=True, verbose_name="充分条件", help_text="充分条件")
priority = models.CharField(max_length=8, blank=True, null=True, verbose_name="优先级", help_text="优先级")
testType = models.CharField(max_length=8, null=True, blank=True, help_text="测试类型", verbose_name="测试类型",
name = models.CharField(max_length=64, blank=True, null=True, verbose_name="测试需求名称",
help_text="测试需求名称")
adequacy = models.CharField(max_length=256, blank=True, null=True, verbose_name="充分条件",
help_text="充分条件")
priority = models.CharField(max_length=8, blank=True, null=True, verbose_name="优先级",
help_text="优先级")
testType = models.CharField(max_length=8, null=True, blank=True, help_text="测试类型",
verbose_name="测试类型",
default="1")
testMethod = models.JSONField(blank=True, help_text="测试方法", verbose_name="测试方法", default=create_list)
title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称", help_text="树-名称")
key = models.CharField(max_length=64, blank=True, null=True, verbose_name="round-dut-designkey-testdemand",
testMethod = models.JSONField(blank=True, help_text="测试方法", verbose_name="测试方法",
default=create_list)
title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称",
help_text="树-名称")
key = models.CharField(max_length=64, blank=True, null=True,
verbose_name="round-dut-designkey-testdemand",
help_text="round-dut-designkey-testdemand")
level = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-level", help_text="树-level",
level = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-level",
help_text="树-level",
default=3) # 默认为3
project = models.ForeignKey(to="Project", db_constraint=False, related_name="ptField", on_delete=models.CASCADE,
project = models.ForeignKey(to="Project", db_constraint=False, related_name="ptField",
on_delete=models.CASCADE,
verbose_name='归属项目', help_text='归属项目', related_query_name='ptQuery')
round = models.ForeignKey(to="Round", db_constraint=False, related_name="rtField", on_delete=models.CASCADE,
round = models.ForeignKey(to="Round", db_constraint=False, related_name="rtField",
on_delete=models.CASCADE,
verbose_name='归属轮次', help_text='归属轮次', related_query_name='dutQuery')
dut = models.ForeignKey(to="Dut", db_constraint=False, related_name="dutField", on_delete=models.CASCADE,
verbose_name='归属被测件', help_text='归属被测件', related_query_name='dtQuery')
design = models.ForeignKey(to="Design", db_constraint=False, related_name="dtField", on_delete=models.CASCADE,
verbose_name='归属设计需求', help_text='归属设计需求', related_query_name='dtQuery')
design = models.ForeignKey(to="Design", db_constraint=False, related_name="dtField",
on_delete=models.CASCADE,
verbose_name='归属设计需求', help_text='归属设计需求',
related_query_name='dtQuery')
otherDesign = models.ManyToManyField(to="Design", db_constraint=False, related_name="odField",
related_query_name='odQuery', blank=True)
# 新模版要求:测试项描述对整个测试项进行描述
testDesciption = models.CharField(max_length=1024, blank=True, null=True, verbose_name='测试项描述', default="",
testDesciption = models.CharField(max_length=1024, blank=True, null=True, verbose_name='测试项描述',
default="",
help_text='测试项描述')
def __str__(self):
@@ -246,11 +288,15 @@ class TestDemandContent(CoreModel):
objects = models.Manager()
"""测试方法中的测试子项内容"""
testDemand = models.ForeignKey(to="TestDemand", db_constraint=False, related_name="testQField",
on_delete=models.CASCADE, verbose_name='归属的测试项', help_text='归属的测试项',
on_delete=models.CASCADE, verbose_name='归属的测试项',
help_text='归属的测试项',
related_query_name='testQField')
# 2025年4月16去掉subDesc、condition、observe
# 4月17修改因为新增步骤所以把operation和expect弄到下面Model里面了新增字段
# 2025/4/16去掉subDesc、condition、observe
# 2025/4/17修改因为新增步骤所以把operation和expect弄到下面Model里面了新增字段
subName = models.CharField(max_length=1024, blank=True, null=True, verbose_name='测试子项名称')
# 2025/12/15修改CPU新增“测试子项描述”FPGA渲染单个
subDescription = models.CharField(max_length=1024, blank=True, null=True,
verbose_name='测试子项一句话描述')
def __str__(self):
return f'测试子项:{self.subName}'
@@ -258,41 +304,57 @@ class TestDemandContent(CoreModel):
# 4月17日新增因为测试项需要测试子项step
class TestDemandContentStep(CoreModel):
objects = models.Manager()
id = ShortUUIDField(primary_key=True, help_text="Id", verbose_name="Id")
operation = models.CharField(max_length=3072, blank=True, null=True, verbose_name='测试子项操作')
expect = models.CharField(max_length=1024, blank=True, null=True, verbose_name='期望')
testDemandContent = models.ForeignKey(to="TestDemandContent", db_constraint=False, related_name="testStepField",
testDemandContent = models.ForeignKey(to="TestDemandContent", db_constraint=False,
related_name="testStepField",
on_delete=models.CASCADE, verbose_name='归属的测试项',
help_text='归属的测试项',
related_query_name='testStepField')
class Case(CoreModel):
objects = models.Manager()
ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="用例标识", help_text="用例标识")
name = models.CharField(max_length=64, blank=True, null=True, verbose_name="用例名称", help_text="用例名称")
ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="用例标识",
help_text="用例标识")
name = models.CharField(max_length=64, blank=True, null=True, verbose_name="用例名称",
help_text="用例名称")
initialization = models.CharField(max_length=128, blank=True, null=True, verbose_name="初始条件",
help_text="初始化条件")
premise = models.CharField(max_length=128, blank=True, null=True, verbose_name="前提和约束", help_text="前提和约束")
summarize = models.CharField(max_length=256, blank=True, null=True, verbose_name="用例综述", help_text="用例综述")
designPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="设计人员", help_text="设计人员")
testPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="测试人员", help_text="测试人员")
premise = models.CharField(max_length=128, blank=True, null=True, verbose_name="前提和约束",
help_text="前提和约束")
summarize = models.CharField(max_length=256, blank=True, null=True, verbose_name="用例综述",
help_text="用例综述")
designPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="设计人员",
help_text="设计人员")
testPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="测试人员",
help_text="测试人员")
monitorPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="审核人员",
help_text="审核人员")
project = models.ForeignKey(to="Project", db_constraint=False, related_name="pcField", on_delete=models.CASCADE,
project = models.ForeignKey(to="Project", db_constraint=False, related_name="pcField",
on_delete=models.CASCADE,
verbose_name='归属项目', help_text='归属项目', related_query_name='pcQuery')
isLeaf = models.BooleanField(default=True, verbose_name="树状图最后一个节点", help_text="树状图最后一个节点")
round = models.ForeignKey(to="Round", db_constraint=False, related_name="rcField", on_delete=models.CASCADE,
isLeaf = models.BooleanField(default=True, verbose_name="树状图最后一个节点",
help_text="树状图最后一个节点")
round = models.ForeignKey(to="Round", db_constraint=False, related_name="rcField",
on_delete=models.CASCADE,
verbose_name='归属轮次', help_text='归属轮次', related_query_name='rcQuery')
dut = models.ForeignKey(to="Dut", db_constraint=False, related_name="ducField", on_delete=models.CASCADE,
verbose_name='归属被测件', help_text='归属被测件', related_query_name='ducQuery')
design = models.ForeignKey(to="Design", db_constraint=False, related_name="dcField", on_delete=models.CASCADE,
verbose_name='归属设计需求', help_text='归属设计需求', related_query_name='dcQuery')
test = models.ForeignKey(to="TestDemand", db_constraint=False, related_name="tcField", on_delete=models.CASCADE,
verbose_name='归属测试需求', help_text='归属测试需求', related_query_name='tcQuery')
title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称", help_text="树-名称")
key = models.CharField(max_length=64, blank=True, null=True, verbose_name="round-dut-designkey-testdemand-case",
design = models.ForeignKey(to="Design", db_constraint=False, related_name="dcField",
on_delete=models.CASCADE,
verbose_name='归属设计需求', help_text='归属设计需求',
related_query_name='dcQuery')
test = models.ForeignKey(to="TestDemand", db_constraint=False, related_name="tcField",
on_delete=models.CASCADE,
verbose_name='归属测试需求', help_text='归属测试需求',
related_query_name='tcQuery')
title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称",
help_text="树-名称")
key = models.CharField(max_length=64, blank=True, null=True,
verbose_name="round-dut-designkey-testdemand-case",
help_text="round-dut-designkey-testdemand-case")
level = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-level", help_text="树-level",
level = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-level",
help_text="树-level",
default=4) # 默认为4
# 2024年5月31日新增属性执行时间
exe_time = models.DateField(blank=True, null=True, verbose_name='执行时间', help_text='执行时间')
@@ -311,14 +373,17 @@ class Case(CoreModel):
class CaseStep(CoreModel):
objects = models.Manager()
operation = HTMLField(blank=True, null=True, verbose_name="测试步骤-操作", help_text="测试步骤-操作")
expect = models.CharField(max_length=3072, blank=True, null=True, verbose_name="用例预期", help_text="用例预期")
expect = models.CharField(max_length=3072, blank=True, null=True, verbose_name="用例预期",
help_text="用例预期")
result = HTMLField(blank=True, null=True, verbose_name="测试步骤-结果", help_text="测试步骤-结果")
passed = models.CharField(max_length=8, null=True, blank=True, help_text="是否通过", verbose_name="是否通过",
passed = models.CharField(max_length=8, null=True, blank=True, help_text="是否通过",
verbose_name="是否通过",
default="3")
# status = models.CharField(max_length=8, null=True, blank=True, help_text="执行状态", verbose_name="执行状态",
# default="3")
case = models.ForeignKey(to="Case", db_constraint=False, related_name="step",
on_delete=models.CASCADE, verbose_name='归属的测试用例', help_text='归属的测试用例',
on_delete=models.CASCADE, verbose_name='归属的测试用例',
help_text='归属的测试用例',
related_query_name='stepQ')
def __str__(self):
@@ -327,34 +392,46 @@ class CaseStep(CoreModel):
class Problem(CoreModel):
objects = models.Manager()
# ident为PT_RXXXX_ident这里需要根据测试项类型进行排序处理
ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="问题单标识", help_text="问题单标识")
name = models.CharField(max_length=64, blank=True, null=True, verbose_name="问题单名称", help_text="问题单名称")
ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="问题单标识",
help_text="问题单标识")
name = models.CharField(max_length=64, blank=True, null=True, verbose_name="问题单名称",
help_text="问题单名称")
# 问题状态1-已闭环 2-开放 3-推迟 4-撤销
status = models.CharField(max_length=8, blank=True, null=True, verbose_name="缺陷状态", help_text="缺陷状态")
status = models.CharField(max_length=8, blank=True, null=True, verbose_name="缺陷状态",
help_text="缺陷状态")
# 问题等级1-一般 2-严重 3-建议 4-重大
grade = models.CharField(max_length=8, blank=True, null=True, verbose_name="缺陷等级", help_text="缺陷等级")
grade = models.CharField(max_length=8, blank=True, null=True, verbose_name="缺陷等级",
help_text="缺陷等级")
# 问题类型1-其他问题 2-文档问题 3-程序问题 4-设计问题 5-需求问题 6-数据问题
type = models.CharField(max_length=8, blank=True, null=True, verbose_name="缺陷类型", help_text="缺陷类型")
type = models.CharField(max_length=8, blank=True, null=True, verbose_name="缺陷类型",
help_text="缺陷类型")
closeMethod = models.JSONField(null=True, blank=True, help_text="闭环方式", verbose_name="闭环方式",
default=create_list_1)
operation = HTMLField(blank=True, null=True, verbose_name="问题描述", help_text="问题描述")
result = HTMLField(blank=True, null=True, verbose_name="问题结果/影响", help_text="问题结果/影响")
postPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="提出人员", help_text="提出人员")
postDate = models.DateField(auto_now_add=True, null=True, blank=True, help_text="提单日期", verbose_name="提单日期")
postPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="提出人员",
help_text="提出人员")
postDate = models.DateField(auto_now_add=True, null=True, blank=True, help_text="提单日期",
verbose_name="提单日期")
designerPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="开发人员",
help_text="开发人员")
designDate = models.DateField(auto_now_add=True, null=True, blank=True, help_text="确认日期",
verbose_name="确认日期")
verifyPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="验证人员", help_text="验证人员")
verifyPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="验证人员",
help_text="验证人员")
verifyDate = models.DateField(auto_now_add=True, null=True, blank=True, help_text="验证日期",
verbose_name="验证日期")
project = models.ForeignKey(to="Project", db_constraint=False, related_name="projField", on_delete=models.CASCADE,
project = models.ForeignKey(to="Project", db_constraint=False, related_name="projField",
on_delete=models.CASCADE,
verbose_name='归属项目', help_text='归属项目', related_query_name='projQuery')
case = models.ManyToManyField(to="Case", db_constraint=False, related_name="caseField", verbose_name='归属测试用例',
case = models.ManyToManyField(to="Case", db_constraint=False, related_name="caseField",
verbose_name='归属测试用例',
help_text='归属测试用例-多对多', related_query_name='caseQuery')
solve = models.TextField(verbose_name='开发人员填写-改正措施',
help_text='开发人员填写-改正措施该字段需要关联“status=1”', blank=True, null=True)
analysis = HTMLField(blank=True, null=True, verbose_name="开发人员填写-原因分析", help_text="开发人员填写-原因分析")
help_text='开发人员填写-改正措施该字段需要关联“status=1”', blank=True,
null=True)
analysis = HTMLField(blank=True, null=True, verbose_name="开发人员填写-原因分析",
help_text="开发人员填写-原因分析")
effect_scope = HTMLField(blank=True, null=True, verbose_name="开发人员填写-影响域分析",
help_text="开发人员填写-影响域分析")
verify_result = HTMLField(blank=True, null=True, verbose_name="回归结果", help_text="回归结果")

View File

@@ -13,9 +13,9 @@ class DeleteSchema(Schema):
# 测试步骤输出schema
class CaseStepSchema(ModelSchema):
class Config:
class Meta:
model = CaseStep
model_fields = ["operation", 'expect', 'result', 'passed', 'case', 'id']
fields = ["operation", 'expect', 'result', 'passed', 'case', 'id']
# 测试用例的步骤输出schema输出isPassed和isExe转换后的
class CaseStepWithTransitionSchema(ModelSchema):
@@ -28,9 +28,9 @@ class CaseModelOutSchemaWithoutProblem(ModelSchema):
testStep: List[CaseStepWithTransitionSchema]
testType: str # 用例额外字段用于测试类型FT的标识给前端
class Config:
class Meta:
model = Case
model_exclude = ['project', 'round', 'dut', 'design', 'test', 'remark', 'sort']
exclude = ['project', 'round', 'dut', 'design', 'test', 'remark', 'sort']
# 输出case关联问题单
class CaseModelOutSchemaOrigin(ModelSchema):
@@ -39,9 +39,9 @@ class CaseModelOutSchemaOrigin(ModelSchema):
# 新增:关联的问题单
problem: Optional[ProblemModelOutSchema] = None
class Config:
class Meta:
model = Case
model_exclude = ['project', 'round', 'dut', 'design', 'test', 'remark', 'sort']
exclude = ['project', 'round', 'dut', 'design', 'test', 'remark', 'sort']
# 输出case关联问题单
class CaseModelOutSchema(ModelSchema):
@@ -52,9 +52,9 @@ class CaseModelOutSchema(ModelSchema):
# 2025年5月10日新增上级字段
test: Optional[TestDemandModelOutSchemaOrigin] = None
class Config:
class Meta:
model = Case
model_exclude = ['project', 'round', 'dut', 'design', 'test', 'remark', 'sort']
exclude = ['project', 'round', 'dut', 'design', 'test', 'remark', 'sort']
# 查询测试项
class CaseFilterSchema(Schema):
@@ -93,9 +93,9 @@ class CaseTreeInputSchema(Schema):
class CaseCreateOutSchema(ModelSchema):
level: Union[str, int]
class Config:
class Meta:
model = Case
model_exclude = ['remark', 'sort', 'project', 'round', 'dut', 'design']
exclude = ['remark', 'sort', 'project', 'round', 'dut', 'design']
# 新增接口schema
class CaseInputSchema(Schema):
@@ -126,6 +126,20 @@ class CaseCreateInputSchema(Schema):
# 新增时序图字段
timing_diagram: str = Field("", alias="timing_diagram")
# 批量新增测试用例
class OneCaseBatchCreateSchema(Schema):
parent_key: str
name: str
summarize: Optional[str] = ""
initialization: Optional[str] = ""
premise: Optional[str] = ""
sequence: Optional[str] = "" # 时序图
test_step: str
class BatchCreateCaseInputSchema(Schema):
project_id: int = Field(..., validation_alias=AliasChoices('project_id', 'projectId'))
cases: List[OneCaseBatchCreateSchema] = []
# 由demand创建case的输入Schema
class DemandNodeSchema(Schema):
project_id: int
@@ -150,7 +164,7 @@ class PersonReplaceSchema(Schema):
testPerson: str
monitorPerson: str
# 事件替换Schema
# 时间替换Schema
class ExetimeReplaceSchema(Schema):
selectRows: List[int] = None
exetime: str
exetime: List[str]

View File

@@ -23,17 +23,17 @@ class DesignFilterSchema(Schema):
# 2025年改为2个输出因为下级需要上级原始不再嵌套上级
class DesignModelOutSchemaOrigin(ModelSchema):
class Config:
class Meta:
model = Design
model_exclude = ['project', 'round', 'dut', 'remark', 'sort']
exclude = ['project', 'round', 'dut', 'remark', 'sort']
class DesignModelOutSchema(ModelSchema):
# 新增字段 - 上级的dut对象
dut: Optional[DutModelOutSchema] = None
class Config:
class Meta:
model = Design
model_exclude = ['project', 'round', 'dut', 'remark', 'sort']
exclude = ['project', 'round', 'dut', 'remark', 'sort']
# 处理树状结构的schema
class DesignTreeReturnSchema(Schema):

View File

@@ -5,9 +5,9 @@ from datetime import date
from pydantic import AliasChoices
class DutModelOutSchema(ModelSchema):
class Config:
class Meta:
model = Dut
model_exclude = ['project', 'round', 'remark', 'sort']
exclude = ['project', 'round', 'remark', 'sort']
class DutFilterSchema(Schema):
project_id: int = Field(None, alias='projectId')
@@ -56,9 +56,9 @@ class DutCreateOutSchema(ModelSchema):
effective_lines: Optional[Union[str, int]] = None
comment_lines: Optional[Union[str, int]] = None
class Config:
class Meta:
model = Dut
model_exclude = ['remark', 'sort', 'project', 'round']
exclude = ['remark', 'sort', 'project', 'round']
# 删除schema
class DeleteSchema(Schema):

View File

@@ -1,3 +1,5 @@
from pydantic import AliasChoices
from apps.project.models import Problem
from ninja import Field, Schema, ModelSchema
from typing import List, Optional
@@ -11,9 +13,9 @@ class ProblemModelOutSchema(ModelSchema):
related: Optional[bool] = Field(False) # 给前端反应是否为关联的问题单
hang: bool = Field(False) # 给前端反应是否是悬挂状态即没有关联case
class Config:
class Meta:
model = Problem
model_exclude = ['case', 'remark', 'sort']
exclude = ['case', 'remark', 'sort']
# 查询问题单
class ProblemFilterSchema(Schema):
@@ -53,13 +55,13 @@ class ProblemTreeInputSchema(Schema):
# 增加问题单
class ProblemCreateOutSchema(ModelSchema):
class Config:
class Meta:
model = Problem
model_exclude = ['remark', 'sort', 'case']
exclude = ['remark', 'sort', 'case']
# 更新新增schema
class ProblemCreateInputSchema(Schema):
project_id: int = Field(..., alias="projectId")
project_id: int = Field(..., validation_alias=AliasChoices('project_id', 'projectId'))
round_key: str = Field(None, alias="round")
dut_key: str = Field(None, alias="dut")
design_key: str = Field(None, alias="designDemand")

View File

@@ -7,9 +7,9 @@ from typing import List, Optional
window_file_str = ['\\', '/', ':', '*', '?', '"', '<', '>', "|"]
class ProjectRetrieveSchema(ModelSchema):
class Config:
class Meta:
model = Project
model_exclude = ['update_datetime', 'create_datetime', 'remark']
exclude = ['update_datetime', 'create_datetime', 'remark']
class ProjectFilterSchema(Schema):
ident: Optional[str] = None
@@ -26,9 +26,9 @@ class ProjectFilterSchema(Schema):
class ProjectCreateInput(ModelSchema):
ident: str
class Config:
class Meta:
model = Project
model_exclude = ['remark', 'update_datetime', 'create_datetime', 'sort', 'id']
exclude = ['remark', 'update_datetime', 'create_datetime', 'sort', 'id']
@field_validator('ident')
@staticmethod

View File

@@ -15,6 +15,7 @@ class TestContentStepSchema(ModelSchema):
fields = ['operation', 'expect']
class TestContentSchema(ModelSchema):
subDescription: Optional[str] = ""
subStep: List[TestContentStepSchema] = [] # 可能为空
class Meta:
@@ -69,13 +70,15 @@ class TestDemandTreeInputSchema(Schema):
class TestDemandCreateOutSchema(ModelSchema):
level: Union[str, int]
class Config:
class Meta:
model = TestDemand
model_exclude = ['remark', 'sort', 'project', 'round', 'dut', 'design']
exclude = ['remark', 'sort', 'project', 'round', 'dut', 'design']
# 新增测试子项单个子项的Schema
class TestContentInputSchema(Schema):
subName: str = None
# 2025/12/15-对CPU增加测试子项描述
subDescription: Optional[str] = "" # 未提供时为空字符串
subStep: Optional[List[TestContentStepSchema]] = []
# 新增/更新测试项Schema
@@ -95,6 +98,22 @@ class TestDemandCreateInputSchema(Schema):
testType: str = Field(None, alias="testType")
testDesciption: str = Field("", alias='testDesciption')
# 批量新增测试项Schema-2个Schema
class TestDemandOneInput(Schema):
parent_key: str # 直接给设计需求的key前端去组装
ident: str
name: str
priority: str = "1"
adequacy: str
testContent: str # 注意这个在接口里面分情况判断
testMethod: List[str] = []
testType: str
testDesciption: Optional[str] = ""
class TestDemandMultiCreateInputSchema(Schema):
project_id: int
demands: List[TestDemandOneInput]
# 处理前端请求-设计需求关联测试需求(测试项)
class TestDemandRelatedSchema(Schema):
data: List[int] = None
@@ -125,3 +144,8 @@ class ReplaceDemandContentSchema(Schema):
replaceText: str
selectRows: List[int]
selectColumn: List[str]
# 优先级替换Schema
class PriorityReplaceSchema(Schema):
selectRows: List[int] = None
priority: str

View File

@@ -0,0 +1,111 @@
import re
from utils.chen_response import ChenResponse
def parse_test_content_string(content: str):
"""
解析前端传来的批量新增测试项testContent字段
"""
# 判断是否为空字符串
if not content or not content.strip():
return []
create_subDemands = [] # 储存测试子项内容
current_subDemand: None | dict = None
lines = content.strip().split("\n")
line_number = 0
for i, line in enumerate(lines):
line_number = i + 1
line = line.strip()
# 跳过空行
if not line:
continue
# 检查是否以^开头
if line.startswith("^"):
# 标识一个测试子项开始了
if current_subDemand:
create_subDemands.append(current_subDemand)
# 解析新测试子项
try:
# 检查是否包含分隔符@,包含则分割
if '@' in line:
[item_name, item_desc] = line.split("@")
else:
item_name = line
item_desc = ""
# 判断名称是否为空
item_name = item_name.replace("^", "", count=1)
if not item_name:
message = f"您字符串中,第{line_number}行没有测试子项名称"
return ChenResponse(status=200, code=500102, data=line_number, message=message)
# 组装一个测试子项
current_subDemand = {
'subName': item_name,
'subDescription': item_desc,
'subStep': []
}
except Exception as e:
message = f"您字符串中,第{line_number}行解析错误,错误原因请检查"
print('解析^行报错,后台详情:', e)
return ChenResponse(status=200, code=500103, data=line_number, message=message)
elif '@' in line and current_subDemand is not None:
try:
[operation, expect] = line.split('@')
current_subDemand['subStep'].append({ # type:ignore
'operation': operation,
'expect': expect
})
except Exception as e:
message = f"{line_number}发现您子项步骤格式有问题,请检查"
print('解析步骤行报错,后台详情:', e)
return ChenResponse(status=200, code=500104, data=line_number, message=message)
else:
# 这里就是即没有^也没有@的情况,直接跳出本次循环即可
continue
# 添加最后一个测试项
if current_subDemand:
create_subDemands.append(current_subDemand)
return create_subDemands
def parse_case_content_string(content: str):
"""
解析前端传来的批量新增测试用例test_step字段
"""
# 如果为空返回空列表-不会引起错误因为前端限制
if not content or not content.strip():
return []
create_step = [] # 储存测试子项内容
current_step: None | dict = None
lines = content.strip().split("\n")
line_number = 0
for i, line in enumerate(lines):
line_number = i + 1
line = line.strip()
# 跳过空行
if not line:
continue
# 判断是否有“@”,如果没有则给用户报错
if "@" not in line:
message = f"{line_number}行没有使用@符号分割,请检查!"
return ChenResponse(status=200, code=60001, data=line_number, message=message)
# 这里必然有@组装current_step
[operation, expect] = line.strip().split("@")
# 错误处理两种情况,操作为空,预期为空
if not operation.strip():
message = f"{line_number}行@符号前面的输入内容为空,请检查!"
return ChenResponse(status=200, code=60002, data=line_number, message=message)
if not expect.strip():
message = f"{line_number}行@符号后面的预期为空,请检查!"
return ChenResponse(status=200, code=60003, data=line_number, message=message)
# 组装当前步骤行
current_step = {
"operation": operation,
"expect": expect
}
if current_step:
create_step.append(current_step)
return create_step

View File

@@ -0,0 +1,43 @@
from django.db.models import QuerySet
from apps.project.models import Design, TestDemand
from typing import Union
def DesignDrapAtoB(a: Design,
b: Design,
origin_qs: QuerySet[Design, Design],
pos: Union[-1 | 1]) -> str:
"""该函数传入拖拽design和释放到的design然后更改排序完成key的重新设置"""
# 判断是移动到b前面还是后面
list_qs = list(origin_qs)
list_qs.remove(a)
b_index = list_qs.index(b)
if pos == -1:
list_qs.insert(b_index, a)
elif pos == 1:
list_qs.insert(b_index + 1, a)
# 重新完成排序后调整key
prefix = "".join([a.dut.key, "-"])
for index, obj in enumerate(list_qs):
obj.key = "".join([prefix, str(index)])
# 需要测试项调整key
designConvertDemadnKey(obj)
obj.save()
return a.key
def designConvertDemadnKey(desgin_obj: Design):
"""传入Design对象集体修改demand和case的key"""
for demand in desgin_obj.dtField.all():
design_key = desgin_obj.key
demand_last_key = demand.key.split("-")[-1]
demand.key = "-".join([design_key, demand_last_key])
demandConvertCaseKey(demand)
demand.save()
def demandConvertCaseKey(demand_obj:TestDemand):
"""传入Demand对象集体修改case的key"""
for case in demand_obj.tcField.all():
demand_key = demand_obj.key
case_last_key = case.key.split("-")[-1]
case.key = "-".join([demand_key, case_last_key])
case.save()

View File

@@ -10,6 +10,9 @@ from apps.project.models import (
CaseStep
)
# 导入人机交互界面固定数据
from apps.project.tools.rj_data_cont import rj_data
def auto_create_jt_and_dm(user_name: str, dut_qs: Dut, project_obj: Project):
"""传入源代码dut以及测试人员名称username自动在dut下面生成静态分析和代码审查设计需求、测试项、用例"""
# 先查询dut_qs下面有多少design以便写里面的key
@@ -188,20 +191,22 @@ def auto_create_wd(user_name: str, dut_qs: Dut, project_obj: Project):
'testMethod': ["3"],
'title': '文档审查',
'testDesciption': '本次文档审查包括的内容如下:\a'
'1软件研制总结报告\a'
'2软件开发计划\a'
'3软件运行方案说明\a'
'4软件接口需求规格说明\a'
'5软件系统设计说明\a'
'6软件接口设计说明\a'
'7软件需求规格说明\a'
'8软件配置项设计说明\a'
'9软件测试说明\a'
'10软件测试报告\a'
'11产品规格说明\a'
'12软件版本说明\a'
'13软件用户手册\a'
'14固件保障手册',
'1软件需求规格说明\a'
'2软件详细设计说明\a'
'3软件开发计划\a'
'4软件配置管理计划\a'
'5软件质量保证计划\a'
'6软件单元测试计划\a'
'7软件单元测试说明\a'
'8软件单元测试报告\a'
'9配置项测试计划\a'
'10配置项测试说明\a'
'11配置项测试报告\a'
'12软件用户手册\a'
'13软件研制总结报告\a'
'14软件版本说明\a'
'15软件产品规格说明\a'
'16固件保障手册',
'key': ''.join([new_wd_design_obj.key, '-', '0']),
'level': '3',
'project': project_obj,
@@ -216,7 +221,10 @@ def auto_create_wd(user_name: str, dut_qs: Dut, project_obj: Project):
operation='根据文档审查表人工逐项检查,检查此项目文档的齐套性、完整性、规范性:\a'
'1使用人工审查方法按照附录A中文档齐套性审查单检查需求类、设计类、用户类、测试类文档是否齐套\a'
'2使用人工审查方法按照附录A中需求规格说明审查单对软件需求规格说明逐项检查\a'
'3使用人工审查方法按照附录A中软件设计文档审查单逐项检查。'
'3使用人工审查方法按照附录A中软件设计文档审查单逐项检查。',
expect='被测软件文档种类齐全,内容完整,描述准确,格式规范;\a'
'2需求文档内容完整描述准确格式规范文档文文一致、文实相符\a'
'3设计说明文档内容完整描述准确格式规范文档文文一致、文实相符。',
)
new_wd_case_obj = Case.objects.create(
ident='WDSC',
@@ -247,3 +255,74 @@ def auto_create_wd(user_name: str, dut_qs: Dut, project_obj: Project):
expect='文档满足完整性、准确性、规范性和一致性的要求',
result='文档检查单全部审查通过,文档内容完整、文档描述准确、'
'文档格式规范、文档文文一致', )
def auto_create_renji(user_name: str, dut_qs: Dut, project_obj: Project):
"""传入用户名、在dut下创建、项目对象自动创建人机交互界面的设计需求、测试项、测试用例"""
# 先查询dut_qs下有多少desgin用于设置key
design_index = dut_qs.rsField.count()
for item in rj_data:
# 创建设计需求
rj_design_create_dict = {
'ident': item['ident'],
'name': item['desgin_name'],
'demandType': '6',
'description': item['xq_desc'],
'title': item['desgin_name'],
'key': ''.join([dut_qs.key, '-', str(design_index)]),
'level': '2',
'chapter': '/',
'project': project_obj,
'round': dut_qs.round,
'dut': dut_qs
}
design_index += 1
new_design_rj: Design = Design.objects.create(**rj_design_create_dict)
# 创建测试项
rj_demand_create_dict = {
'ident': item['ident'],
'name': item['test_item_name'],
'adequacy': item['chongfen'],
'priority': '2',
'testType': '12',
'testMethod': ["4"],
'testDesciption': '在界面进行观察与操作,对照需求规格说明的功能需求进行比对,对照用户手册进行比对',
'title': item['test_item_name'],
'key': ''.join([new_design_rj.key, '-', '0']),
'level': '3',
'project': project_obj,
'round': new_design_rj.round,
'dut': new_design_rj.dut,
'design': new_design_rj,
}
new_demand_rj = TestDemand.objects.create(**rj_demand_create_dict)
new_demand_content_obj = TestDemandContent.objects.create(testDemand=new_demand_rj,
subName=item['test_item_name'])
for operation in item['test_method']:
TestDemandContentStep.objects.create(testDemandContent=new_demand_content_obj,
operation=operation['caozuo']
, expect=operation['yuqi'])
# 创建测试用例
new_case_rj = Case.objects.create(
ident=item['ident'],
name=item['test_item_name'],
initialization='已获取被测件的用户手册',
premise='软件可正常运行,界面初始化完成',
summarize=item['xq_desc'],
designPerson=user_name,
testPerson=user_name,
monitorPerson=user_name,
project=project_obj,
isLeaf=True,
round=new_demand_rj.round,
dut=new_demand_rj.dut,
design=new_demand_rj.design,
test=new_demand_rj,
title=item['test_item_name'],
key=''.join([new_demand_rj.key, '-', '0']),
level='4'
)
for operation in item['test_method']:
CaseStep.objects.create(case=new_case_rj,
operation=operation['caozuo'],
expect=operation['yuqi'],
result='界面操作结果符合预期', )

View File

@@ -0,0 +1,141 @@
# 人机交互界面测试自动生成
rj_data = [
{
"desgin_name": "人机交互完整性",
"test_item_name": "人机交互完整性测试",
"ident": "WZXC",
"xq_desc": "测试软件人机交互界面的完整性",
"chongfen": "测试用例覆盖人机交互界面完整性子项要求的全部内容。\a"
"所有用例执行完毕,对于未执行的用例说明未执行原因。",
"test_method": [
{
"caozuo": "检查界面菜单名称、标签名称、按键名称的完整性",
"yuqi": "名称完整"
},
{
"caozuo": "检查界面显示区内容的完整性",
"yuqi": "显示内容完整"
},
{
"caozuo": "检查界面菜单功能的完整性",
"yuqi": "菜单功能完整"
},
]
},
{
"desgin_name": "人机交互界面一致性",
"test_item_name": "人机交互界面一致性测试",
"ident": "YZXC",
"xq_desc": "测试软件人机交互界面的一致性",
"chongfen": "测试用例覆盖人机交互界面一致性子项要求的全部内容。\a"
"所有用例执行完毕,对于未执行的用例说明未执行原因。",
"test_method": [
{
"caozuo": "检查主界面的标题栏与使用说明书的一致性",
"yuqi": "主界面的标题栏与用户手册一致"
},
{
"caozuo": "检查次级界面、对话框等标题与使用说书的一致性",
"yuqi": "次级界面、对话框等标题与用户手册一致"
},
{
"caozuo": "检查界面用语与使用说明书的一致性",
"yuqi": "界面用语与用户手册一致"
},
{
"caozuo": "检查界面的字体、颜色、位置等是否统一、字体是否对齐",
"yuqi": "界面的字体、颜色、位置等与用户手册一致"
},
{
"caozuo": "检查同类数据的显示精度是否正确、统一",
"yuqi": "同类数据的显示精度与用户手册一致,精确并统一"
},
]
},
{
"desgin_name": "人机交互界面友好性",
"test_item_name": "人机交互界面友好性测试",
"ident": "YHXC",
"xq_desc": "测试软件人机交互界面的友好性",
"chongfen": "测试用例覆盖人机交互界面友好性子项要求的全部内容。\a"
"所有用例执行完毕,对于未执行的用例说明未执行原因。",
"test_method": [
{
"caozuo": "检查界面实时显示信息区是否实时刷新",
"yuqi": "实时刷新"
},
{
"caozuo": "检查提示信息显示位置是否合理、提示框或弹窗是否居中显示",
"yuqi": "合理、弹窗居中"
},
{
"caozuo": "检查界面用语、名称等是否存在错别字、乱码等",
"yuqi": "无错别字或乱码"
},
{
"caozuo": "检查界面字符显示是否清晰",
"yuqi": "字符显示清晰"
},
{
"caozuo": "检查界面字符或提示语是否统一、汉化",
"yuqi": "统一汉化显示"
},
]
},
{
"desgin_name": "人机交互界面易操作性",
"test_item_name": "人机交互界面易操作性测试",
"ident": "YCZX",
"xq_desc": "测试软件人机交互界面的易操作性",
"chongfen": "测试用例覆盖人机交互界面易操作性子项要求的全部内容。\a"
"所有用例执行完毕,对于未执行的用例说明未执行原因。",
"test_method": [
{
"caozuo": "检查界面菜单、图标、按键等是否分布整齐、有序",
"yuqi": "分布整齐有序"
},
{
"caozuo": "检查菜单深度的合理性",
"yuqi": "菜单深度不超过3级"
},
]
},
{
"desgin_name": "人机交互界面操作测试",
"test_item_name": "人机交互界面操作测试测试",
"ident": "CZCC",
"xq_desc": "测试软件人机交互的界面操作测试",
"chongfen": "测试用例覆盖人机交互界面操作测试子项要求的全部内容。\a"
"所有用例执行完毕,对于未执行的用例说明未执行原因。",
"test_method": [
{
"caozuo": "检查按键、编辑框等使能情况(闪烁)",
"yuqi": "闪烁"
},
{
"caozuo": "执行任务期间,进行按键的多处连击",
"yuqi": "不会出现假死或崩溃"
},
{
"caozuo": "检查列表,列表中的内容允许编辑、数据变更后自动更新显示",
"yuqi": "内容允许编辑、数据变更后自动更新显示"
},
{
"caozuo": "能够进行选择和取消,选择及取消后处理正确",
"yuqi": "能够进行选择和取消,选择及取消后处理正确"
},
{
"caozuo": "选择或取消某个单选框后再进行复选操作",
"yuqi": "操作正确"
},
{
"caozuo": "执行一个任务,查看界面操作及显示的正确性",
"yuqi": "界面操作及显示正确"
},
{
"caozuo": "在一个菜单功能项执行过程中用使用按钮选择其他功能",
"yuqi": "不会出现假死或崩溃情况"
},
]
},
]

View File

@@ -8,45 +8,33 @@ from ninja import Query
from django.db import transaction
from django.contrib.auth import authenticate
from django.shortcuts import get_object_or_404
from ninja_jwt.tokens import RefreshToken
from ninja_jwt.authentication import JWTAuth
from ninja_jwt.controller import TokenObtainPairController
from ninja_jwt import schema
from typing import List
from utils.chen_response import ChenResponse
from apps.user.schema import UserInfoOutSchema, CreateUserSchema, CreateUserOutSchema, UserRetrieveInputSchema, \
from apps.user.schema import UserInfoOutSchema, CreateUserSchema, CreateUserOutSchema, \
UserRetrieveInputSchema, \
UserRetrieveOutSchema, UpdateDeleteUserSchema, UpdateDeleteUserOutSchema, DeleteUserSchema, LogOutSchema, \
LogInputSchema, LogDeleteInSchema, AdminModifyPasswordSchema
LogInputSchema, LogDeleteInSchema, AdminModifyPasswordSchema, MyTokenObtainPairOutSchema, \
MyTokenObtainPairInputSchema
from apps.user.models import TableOperationLog, Users as UserClass
from apps.project.models import Project
# 工具函数
from utils.chen_crud import update, multi_delete
from apps.user.tools.ldap_tools import load_ldap_users
# 导入登录日志函数
from utils.log_util.request_util import save_login_log
Users: UserClass = get_user_model() # type:ignore
Users = get_user_model()
# 定义用户登录接口包含token刷新和生成
@api_controller("/system", tags=['用户token控制和登录接口'])
class UserTokenController(TokenObtainPairController):
auto_import = True
@route.post("/login", url_name='login')
def obtain_token(self, user_token: schema.TokenObtainPairSerializer):
"""新版本有特性,后期修改"""
# 注意TokenObtainPairSerializer是老版本所以兼容本质是TokenObtainPairInputSchema
user: UserClass = user_token._user
if user:
# 判断是否为启用状态
if user.status == '2':
return ChenResponse(status=500, code=500, message='账号已被禁用,请联系管理员...')
save_login_log(request=self.context.request, user=user) # 保存登录日志
refresh = RefreshToken.for_user(user)
token = refresh.access_token # type:ignore
return ChenResponse(code=200,
data={'token': str(token), 'refresh': str(refresh),
'token_exp_data': datetime.fromtimestamp(token["exp"], tz=timezone.utc)})
@route.post("/login", response=MyTokenObtainPairOutSchema, url_name='login')
def obtain_token(self, user_token: MyTokenObtainPairInputSchema):
user_token.check_user_authentication_rule()
return user_token.to_response_schema()
@route.get("/getInfo", response=UserInfoOutSchema, url_name="get_info", auth=JWTAuth())
def get_user_info(self):
@@ -102,7 +90,8 @@ class UserManageController(ControllerBase):
create_datetime__range=date_list).order_by('-create_datetime')
return qs
@route.put("/update/{user_id}", response=UpdateDeleteUserOutSchema, permissions=[IsAuthenticated, IsAdminUser],
@route.put("/update/{user_id}", response=UpdateDeleteUserOutSchema,
permissions=[IsAuthenticated, IsAdminUser],
url_name="user-update")
def update_user(self, user_id: int, payload: UpdateDeleteUserSchema):
if payload.username == "superAdmin":
@@ -122,7 +111,8 @@ class UserManageController(ControllerBase):
return ChenResponse(code=200, status=200, message="删除成功")
# 管理员改变用户状态是否停用/启用
@route.get('/change_status', auth=JWTAuth(), permissions=[IsAuthenticated, IsAdminUser], url_name='user-change')
@route.get('/change_status', auth=JWTAuth(), permissions=[IsAuthenticated, IsAdminUser],
url_name='user-change')
def change_user_status(self, user_status: str, userId: int):
user = Users.objects.filter(id=userId).first()
if not user:
@@ -144,6 +134,7 @@ class UserManageController(ControllerBase):
user.set_password(payload.newPassword)
user.save()
return ChenResponse(status=200, code=200, message='管理员修改密码成功')
return None
# 用户登录后动态读取LDAP用户录入数据
@route.get("/ldap", url_name='user-ldap')
@@ -171,7 +162,8 @@ class LogController(ControllerBase):
logs = logs.filter(user__username__icontains=data.user, create_datetime__range=data.create_datetime)
return logs
@route.get('/operation_delete', url_name='log_delete', permissions=[IsAuthenticated, IsAdminUser], auth=JWTAuth())
@route.get('/operation_delete', url_name='log_delete', permissions=[IsAuthenticated, IsAdminUser],
auth=JWTAuth())
def log_delete(self, data: LogDeleteInSchema = Query(...)):
time = datetime.now() - timedelta(days=data.day)
log_qs = TableOperationLog.objects.filter(create_datetime__lt=time)

View File

@@ -3,9 +3,13 @@ from django.contrib.auth.models import Group
from ninja_schema import ModelSchema, model_validator, Schema
from ninja_extra.exceptions import APIException
from ninja_extra import status
from datetime import datetime
from typing import List
from datetime import datetime, timezone
from typing import List, Type, Dict
from ninja import Field
from ninja.errors import HttpError
from ninja_jwt.schema import TokenObtainInputSchemaBase
from ninja_jwt.tokens import RefreshToken
from utils.log_util.request_util import save_login_log
UserModel = Users
@@ -113,3 +117,34 @@ class AdminModifyPasswordSchema(Schema):
newPassword: str
newPassword_confirmation: str
oldPassword: str
# ~~~~~~~~~~~~~~~~~~~~JWT~~~~~~~~~~~~~~~~~~~~
# 定义输出的内容修改了输出access变为token新增token_exp_data字段
class MyTokenObtainPairOutSchema(Schema):
token: str
refresh: str
token_exp_data: datetime
class MyTokenObtainPairInputSchema(TokenObtainInputSchemaBase):
@classmethod
def get_response_schema(cls) -> Type[Schema]:
"""修改默认的返回Schema"""
return MyTokenObtainPairOutSchema
@classmethod
def get_token(cls, user) -> Dict:
"""因为输出Schema修改这里修改输出的token字典字段"""
values = {}
refresh = RefreshToken.for_user(user)
token = refresh.access_token
values["token"] = str(token) # 修改在这里 # type:ignore
values['refresh'] = str(refresh)
values["token_exp_data"] = datetime.fromtimestamp(token["exp"], tz=timezone.utc)
return values
def authenticate(self, request, credentials: Dict):
super().authenticate(request, credentials)
if self._user:
save_login_log(request, self._user)
if self._user.status == '2': # type:ignore
raise HttpError(401, "账号已被禁用,请联系管理员...")

View File

@@ -1,10 +1,22 @@
import ldap
from django.contrib.auth import get_user_model
import environ
def load_ldap_users(url='ldap://dns.paisat.cn:389',
dn="CN=Administrator,CN=Users,DC=sstc,DC=ctu",
pwd="WXWX2019!!!!!!",
search_dn="OU=ALL,DC=sstc,DC=ctu",
# 1. 环境变量读取
env = environ.Env()
# 2. LDAP服务器host和port
server_uri = env('AUTH_LDAP_SERVER_URI', default='ldap://dns.paisat.cn:389')
dn = env('AUTH_LDAP_BIND_DN',default='CN=Administrator,CN=Users,DC=sstc,DC=ctu')
password = env('AUTH_LDAP_BIND_PASSWORD',default='WXWX2019!!!!!!')
base_dn = env('BASE_DN',default='OU=all,DC=sstc,DC=ctu')
filter_str = env('FILTER_STR',default='(sAMAccountName=%(user)s)')
# 3. 连接LDAP服务器进行操作
def load_ldap_users(url=server_uri,
dn=dn,
pwd=password,
search_dn=base_dn,
search_filter='(&(sAMAccountName=*))'):
Users = get_user_model()
@@ -42,6 +54,7 @@ def load_ldap_users(url='ldap://dns.paisat.cn:389',
c_user.email = user_dict['email']
update_flag = True
if update_flag:
c_user.set_password('wxwx2018!!!')
c_user.save()
else:
user_dict['remark'] = '自动同步LDAP数据用户'

View File

@@ -155,6 +155,7 @@ API_OPERATION_EXCLUDE_START = [
'/api/system/abbreviation/index',
'/api/project/dut/soExist',
'/api/system/log/',
'/api/system/dict/'
]
# 配置单次请求最大字节数base64图片和上传需求文档适用

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -1 +1,2 @@
[WARNING][2025-04-29 14:20:41,997][logger.py:25][回归测试记录模块][单个问题单表格]片段:问题单4未关联用例请检查
[WARNING][2026-01-20 16:34:34,986][logger.py:25][回归测试说明模块][当前文档全部片段]片段:该项目没有创建轮次

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More