Compare commits
10 Commits
eb0c7b1952
...
v0.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
| beb8c2d25b | |||
| f755422cb3 | |||
| 8ba5d4fd23 | |||
| 3e048ea876 | |||
| f3806687b0 | |||
| 9db8b28f5b | |||
| a396a8fcfa | |||
| e6c593c920 | |||
| 1b2c3ec3d6 | |||
| 322096c069 |
4
.env
4
.env
@@ -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'
|
||||
2
.idea/cdtestplant_v1.iml
generated
2
.idea/cdtestplant_v1.iml
generated
@@ -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
2
.idea/misc.xml
generated
@@ -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
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.13.11
|
||||
@@ -3,8 +3,7 @@
|
||||
## 内外V0.0.1版本
|
||||
|
||||
2024年7月3日 - V0.0.1版本首次导入内网并进行数据库迁移和部署
|
||||
|
||||
|
||||
2025年12月22日 - V0.1.1版本导入内网并数据库迁移部署
|
||||
|
||||
## 外V0.0.2版本
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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="文档生成成功!")
|
||||
|
||||
@@ -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下面的step,content变量是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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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="文档生成成功!")
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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="文档生成成功!")
|
||||
|
||||
Binary file not shown.
BIN
apps/createDocument/extensions/__pycache__/tools.cpython-313.pyc
Normal file
BIN
apps/createDocument/extensions/__pycache__/tools.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
@@ -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”则居中
|
||||
|
||||
9
apps/createDocument/extensions/tools.py
Normal file
9
apps/createDocument/extensions/tools.py
Normal 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']
|
||||
@@ -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="文档生成成功!")
|
||||
|
||||
BIN
apps/createDocument/schema/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
apps/createDocument/schema/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
# ~~~~start:2025/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
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)}个用例执行时间")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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='测试子项一句话描述'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
@@ -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="回归结果")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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]
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
apps/project/tool/__pycache__/batchTools.cpython-313.pyc
Normal file
BIN
apps/project/tool/__pycache__/batchTools.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/project/tool/__pycache__/dragAndDrop.cpython-313.pyc
Normal file
BIN
apps/project/tool/__pycache__/dragAndDrop.cpython-313.pyc
Normal file
Binary file not shown.
111
apps/project/tool/batchTools.py
Normal file
111
apps/project/tool/batchTools.py
Normal 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
|
||||
43
apps/project/tool/dragAndDrop.py
Normal file
43
apps/project/tool/dragAndDrop.py
Normal 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()
|
||||
Binary file not shown.
BIN
apps/project/tools/__pycache__/rj_data_cont.cpython-313.pyc
Normal file
BIN
apps/project/tools/__pycache__/rj_data_cont.cpython-313.pyc
Normal file
Binary file not shown.
@@ -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='界面操作结果符合预期', )
|
||||
|
||||
141
apps/project/tools/rj_data_cont.py
Normal file
141
apps/project/tools/rj_data_cont.py
Normal 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": "不会出现假死或崩溃情况"
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
|
||||
@@ -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, "账号已被禁用,请联系管理员...")
|
||||
|
||||
Binary file not shown.
@@ -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数据用户'
|
||||
|
||||
Binary file not shown.
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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][回归测试说明模块][当前文档全部片段]片段:该项目没有创建轮次
|
||||
|
||||
2483
logs/root_log
2483
logs/root_log
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user