Compare commits
23 Commits
eb0c7b1952
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 66e48d3165 | |||
| dffc1d5872 | |||
| 518f2f43a7 | |||
| 46768e53c3 | |||
| 0638950286 | |||
| 74d3d22ffe | |||
| 2f58bdc668 | |||
| a76cd8674c | |||
| 0bee950a52 | |||
| a2781c902a | |||
| 007712c63c | |||
| 48a0fad7e4 | |||
| 4a1881bf32 | |||
| beb8c2d25b | |||
| f755422cb3 | |||
| 8ba5d4fd23 | |||
| 3e048ea876 | |||
| f3806687b0 | |||
| 9db8b28f5b | |||
| a396a8fcfa | |||
| e6c593c920 | |||
| 1b2c3ec3d6 | |||
| 322096c069 |
2
.env
2
.env
@@ -3,3 +3,5 @@ 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)'
|
||||
|
||||
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">
|
||||
|
||||
4
.idea/misc.xml
generated
4
.idea/misc.xml
generated
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.8 (cdtestplant_v1)" />
|
||||
<option name="sdkName" value="uv (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,9 +16,10 @@ 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.content_result_tool import create_round_context, create_influence_context
|
||||
from apps.createDocument.extensions.zhui import create_bg_round1_zhui
|
||||
from apps.createDocument.extensions.solve_problem import create_one_problem_dit
|
||||
from utils.path_utils import project_path
|
||||
@@ -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])
|
||||
@@ -102,6 +105,10 @@ class GenerateControllerBG(ControllerBase):
|
||||
language_list = []
|
||||
for language in languages:
|
||||
language_list.append(language.get('ident_version'))
|
||||
runtimes = get_list_dict('runtime', project_obj.runtime)
|
||||
runtime_list = [item['ident_version'] for item in runtimes]
|
||||
devplants = get_list_dict('devplant', project_obj.devplant)
|
||||
devplant_list = [item['ident_version'] for item in devplants]
|
||||
|
||||
# 获取轮次
|
||||
rounds = project_obj.pField.all()
|
||||
@@ -109,7 +116,7 @@ class GenerateControllerBG(ControllerBase):
|
||||
for r in rounds:
|
||||
round_dict = {}
|
||||
# 获取SO的dut
|
||||
so_dut: Dut = r.rdField.filter(type='SO').first()
|
||||
so_dut: Dut | None = r.rdField.filter(type='SO').first()
|
||||
if so_dut:
|
||||
round_dict['version'] = so_dut.version
|
||||
round_dict['line_count'] = int(so_dut.total_lines)
|
||||
@@ -120,8 +127,8 @@ class GenerateControllerBG(ControllerBase):
|
||||
'project_name': project_obj.name,
|
||||
'soft_type': project_obj.get_soft_type_display(),
|
||||
'security_level': get_str_dict(project_obj.security_level, 'security_level'),
|
||||
'runtime': get_str_dict(project_obj.runtime, 'runtime'),
|
||||
'devplant': get_str_dict(project_obj.devplant, 'devplant'),
|
||||
'runtime': "\a".join(runtime_list),
|
||||
'devplant': "\a".join(devplant_list),
|
||||
'language': "\a".join(language_list),
|
||||
'recv_date': project_obj.beginTime.strftime("%Y-%m-%d"),
|
||||
'dev_unit': project_obj.dev_unit,
|
||||
@@ -144,7 +151,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 +166,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 +303,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'),
|
||||
@@ -347,10 +358,12 @@ class GenerateControllerBG(ControllerBase):
|
||||
# 每个轮次都需要生成一个测试内容和标题
|
||||
project_path_str = project_path(id)
|
||||
for round_str in round_str_list:
|
||||
context = create_round_context(project_obj, round_str)
|
||||
context, round_obj = 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)
|
||||
# ~~~额外添加:除第一轮次的影响域分析~~~
|
||||
context['influence'] = create_influence_context(doc, round_obj, project_obj)
|
||||
doc.render(context, autoescape=True)
|
||||
try:
|
||||
doc.save(
|
||||
Path.cwd() / "media" / project_path_str / "output_dir/bg" / f"测试内容和结果_第{context['round_id']}轮次.docx")
|
||||
@@ -435,7 +448,7 @@ class GenerateControllerBG(ControllerBase):
|
||||
design_dict['demands'] = '\a'.join(demand_list)
|
||||
# 通过还是未通过
|
||||
design_dict['pass'] = '通过'
|
||||
design_dict['index'] = design_index
|
||||
design_dict['index'] = design_index # noqa
|
||||
data_list.append(design_dict)
|
||||
design_index += 1
|
||||
|
||||
@@ -450,7 +463,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 +559,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 +635,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 +692,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 +730,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,19 +1,25 @@
|
||||
import base64
|
||||
import io
|
||||
from typing import Any
|
||||
from datetime import datetime
|
||||
from docx.shared import Mm
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
from docx.enum.table import WD_ALIGN_VERTICAL
|
||||
from docx.oxml.ns import qn
|
||||
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
|
||||
from docxtpl import DocxTemplate, InlineImage
|
||||
from pathlib import Path
|
||||
from utils.chen_response import ChenResponse
|
||||
# 导入数据库ORM
|
||||
from apps.project.models import Project, Contact, Abbreviation
|
||||
from apps.project.models import Project, Contact, Abbreviation, ProjectSoftSummary, StuctSortData, StaticSoftItem, StaticSoftHardware, \
|
||||
DynamicSoftTable, DynamicHardwareTable, ProjectDynamicDescription, EvaluateData, EnvAnalysis, Design
|
||||
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 +30,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, set_table_border_by_cell_position, set_cell_margins
|
||||
|
||||
# @api_controller("/generate", tags=['生成大纲文档'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||
@api_controller("/generate", tags=['生成大纲文档'])
|
||||
@@ -34,7 +42,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 +54,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 +68,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}
|
||||
@@ -70,24 +82,35 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
for tm_item in single_qs.testMethod:
|
||||
if tm_item == dict_item_qs.key:
|
||||
testmethod_str += dict_item_qs.title + " "
|
||||
# 富文本解析
|
||||
# ***Inspect-start:检查设计需求的描述是否为空***
|
||||
if single_qs.design.description == '':
|
||||
design_info = single_qs.design.ident + '-' + single_qs.design.name
|
||||
self.logger.write_warning_log('测试项', f'设计需求中的描述为空,请检查 -> {design_info}')
|
||||
# ***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}]
|
||||
for relate_design in single_qs.otherDesign.all():
|
||||
ddict = {'dut_name': relate_design.dut.name, 'design_chapter': relate_design.chapter,
|
||||
'design_name': relate_design.name}
|
||||
doc_list = [{
|
||||
'dut_name': single_qs.dut.name,
|
||||
'design_chapter': single_qs.design.chapter,
|
||||
'design_name': single_qs.design.name,
|
||||
'dut_type': single_qs.dut.type # 添加 type 字段用于排序
|
||||
}]
|
||||
for relate_design in single_qs.otherDesign.all(): # type: Design
|
||||
ddict = {
|
||||
'dut_name': relate_design.dut.name,
|
||||
'design_chapter': relate_design.chapter,
|
||||
'design_name': relate_design.name,
|
||||
'dut_type': relate_design.dut.type # 添加 type 字段用于排序
|
||||
}
|
||||
doc_list.append(ddict)
|
||||
# 定义排序顺序映射
|
||||
TYPE_ORDER = {'YZ': 0, 'XQ': 1, 'XY': 2, 'SJ': 3, None: 99, '': 999}
|
||||
doc_list.sort(key=lambda x: TYPE_ORDER.get(x.get('dut_type'), 999))
|
||||
|
||||
# 组装单个测试项
|
||||
## 打印本项目是FPGA还是CPU
|
||||
testdemand_dict = {
|
||||
"name": single_qs.name,
|
||||
"key": single_qs.key,
|
||||
@@ -96,16 +119,14 @@ 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") # 测试项描述
|
||||
# 测试项描述FPGA或'静态分析'、'文档审查'、'代码审查'
|
||||
"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 +143,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 +176,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 +198,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 +220,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,17 +295,88 @@ 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="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
|
||||
# 生成dataSchemas的context - 服务于 1、测评对象 2、动态环境描述
|
||||
@classmethod
|
||||
def create_data_schema_list_context(cls, data_qs, doc: DocxTemplate):
|
||||
if data_qs.exists():
|
||||
data_list = []
|
||||
for data_obj in data_qs.all():
|
||||
item_context: dict[str, Any] = {"fontnote": data_obj.fontnote, 'type': data_obj.type}
|
||||
# 根据数据类型处理content字段
|
||||
if data_obj.type == 'text':
|
||||
item_context['content'] = data_obj.content
|
||||
elif data_obj.type == 'table':
|
||||
# 使用subdoc
|
||||
subdoc = doc.new_subdoc()
|
||||
rows = len(data_obj.content)
|
||||
cols = len(data_obj.content[0])
|
||||
table = subdoc.add_table(rows=rows, cols=cols)
|
||||
# 单元格处理
|
||||
for row in range(rows):
|
||||
for col in range(cols):
|
||||
cell = table.cell(row, col)
|
||||
cell.text = data_obj.content[row][col]
|
||||
# 第一行设置居中
|
||||
if row == 0:
|
||||
# 黑体设置
|
||||
cell.text = ""
|
||||
pa = cell.paragraphs[0]
|
||||
run = pa.add_run(str(data_obj.content[row][col]))
|
||||
run.font.name = '黑体'
|
||||
run._element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')
|
||||
run.font.bold = False
|
||||
pa.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
# 垂直居中
|
||||
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
||||
# 表格居中
|
||||
table.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
# 设置边框
|
||||
set_table_border_by_cell_position(table)
|
||||
item_context['content'] = subdoc
|
||||
elif data_obj.type == 'image':
|
||||
base64_bytes = base64.b64decode(data_obj.content.replace("data:image/png;base64,", ""))
|
||||
item_context['content'] = InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(120))
|
||||
data_list.append(item_context)
|
||||
context = {
|
||||
"datas": data_list,
|
||||
}
|
||||
return context
|
||||
return None
|
||||
|
||||
# 统将需要多个DataSchemas的一对一项目字段生成响应
|
||||
@classmethod
|
||||
def uniform_res_from_mul_data_schemas(cls, id: int, filename: str, r_filename: str, model) -> ChenResponse | None:
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / filename
|
||||
doc = DocxTemplate(input_path)
|
||||
qs = model.objects.filter(project=project_obj)
|
||||
if qs.exists():
|
||||
data_qs = qs.first().data_schemas
|
||||
context = cls.create_data_schema_list_context(data_qs, doc)
|
||||
doc.render(context)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / r_filename)
|
||||
return ChenResponse(status=200, code=200, message="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
return None
|
||||
|
||||
# 生成测评对象 - 包括大纲、说明、回归说明和报告
|
||||
@route.get('/create/softComposition', url_name='create-softComposition')
|
||||
@transaction.atomic
|
||||
def create_softComposition(self, id: int):
|
||||
# 首先判断是否包含 - 项目信息-软件概述
|
||||
res = self.uniform_res_from_mul_data_schemas(id, '测评对象_2.docx', '测评对象.docx', ProjectSoftSummary)
|
||||
if res is not None:
|
||||
return res
|
||||
# 原来文档片段或者初始内容
|
||||
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, '测评对象')
|
||||
@@ -273,7 +384,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="文档生成成功!")
|
||||
@@ -283,6 +394,8 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
# 生成被测软件接口章节
|
||||
@route.get('/create/interface', url_name='create-interface')
|
||||
def create_interface(self, id: int):
|
||||
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '被测软件接口.docx'
|
||||
doc = DocxTemplate(input_path)
|
||||
project_qs = get_object_or_404(Project, id=id)
|
||||
project_name = project_qs.name
|
||||
interfaceNameList = []
|
||||
@@ -301,18 +414,37 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
interface_dict = {
|
||||
'name': interface.name,
|
||||
'ident': interface.ident,
|
||||
'source': interface.source,
|
||||
'to': interface.to,
|
||||
'type': interface.type,
|
||||
'protocal': interface.protocal,
|
||||
'is_bidirectional': interface.is_bidirectional, # 是否有反向
|
||||
'jk_info_list': [{
|
||||
'source': item.source,
|
||||
'destination': item.destination,
|
||||
'description': item.description,
|
||||
} for item in interface.jkField.all()]
|
||||
}
|
||||
interface_list.append(interface_dict)
|
||||
# 项目接口图处理 - 2026/2/4
|
||||
image_obj = StuctSortData.objects.filter(project=project_qs)
|
||||
## 判断是否存在
|
||||
image_render = None
|
||||
fontnote = None
|
||||
if image_obj.exists():
|
||||
base64_bytes = base64.b64decode(image_obj.first().content.replace("data:image/png;base64,", ""))
|
||||
image_render = InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(120))
|
||||
fontnote = image_obj.first().fontnote
|
||||
context = {
|
||||
'project_name': project_name,
|
||||
'iters': interfaceNameList,
|
||||
'iter_list': interface_list,
|
||||
'image_render': image_render if image_render else "",
|
||||
'fontnote': fontnote if fontnote else "".join([project_name, '接口示意图']),
|
||||
}
|
||||
return create_dg_docx('被测软件接口.docx', context, id)
|
||||
doc.render(context, autoescape=True)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '被测软件接口.docx')
|
||||
return ChenResponse(status=200, code=200, message="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
|
||||
# 生成顶层技术文件
|
||||
@route.get('/create/top_file', url_name='create-performance')
|
||||
@@ -344,16 +476,111 @@ 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="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
|
||||
# 通用生成静态软件项、静态硬件项、动态软件项、动态硬件信息、测评数据的context,包含fontnote和table
|
||||
@classmethod
|
||||
def create_table_context(cls, table_data: list[list[str]], doc: DocxTemplate):
|
||||
"""注意:该函数会增加一列序号列,并且支持单元格内回车换行(段落换行)"""
|
||||
subdoc = doc.new_subdoc()
|
||||
rows = len(table_data)
|
||||
cols = len(table_data[0]) + 1
|
||||
table = subdoc.add_table(rows=rows, cols=cols)
|
||||
|
||||
# 单元格处理
|
||||
for row in range(rows):
|
||||
for col in range(cols):
|
||||
cell = table.cell(row, col)
|
||||
set_cell_margins(cell, left=100, right=100, top=100, bottom=100)
|
||||
|
||||
# 获取要显示的文本内容(字符串或按行拆分后的列表)
|
||||
if col == 0:
|
||||
# 序号列
|
||||
lines = ["序号"] if row == 0 else [str(row)]
|
||||
else:
|
||||
raw_text = table_data[row][col - 1]
|
||||
# 按换行符 \n 拆分为多个段落
|
||||
lines = raw_text.split('\n') if raw_text else ['']
|
||||
|
||||
# 清空单元格原有段落(add_table 默认有一个段落)
|
||||
cell.text = ""
|
||||
# 删除默认段落,稍后统一添加
|
||||
for para in cell.paragraphs:
|
||||
p = para._element
|
||||
p.getparent().remove(p)
|
||||
|
||||
# 逐个添加段落
|
||||
for i, line in enumerate(lines):
|
||||
if i == 0:
|
||||
para = cell.add_paragraph(line)
|
||||
else:
|
||||
para = cell.add_paragraph(line)
|
||||
|
||||
# 设置段落对齐(第一列居中,其他左对齐,可根据需要调整)
|
||||
if col == 0:
|
||||
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
else:
|
||||
para.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
||||
|
||||
# 对第一行(表头)设置黑体字体
|
||||
if row == 0:
|
||||
for run in para.runs:
|
||||
run.font.name = '黑体'
|
||||
run._element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')
|
||||
run.font.bold = False
|
||||
# 表头段落居中(覆盖前面的 left)
|
||||
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
|
||||
# 垂直居中
|
||||
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
||||
|
||||
# 设置序号列宽度
|
||||
for cell in table.columns[0].cells:
|
||||
cell.width = Mm(15)
|
||||
for para in cell.paragraphs:
|
||||
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
|
||||
# 表格居中
|
||||
table.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
# 设置表格外边框
|
||||
set_table_border_by_cell_position(table)
|
||||
return subdoc
|
||||
|
||||
# 统一静态软件项、静态硬件项、动态软件项、动态硬件信息的word生成 - 模版模式
|
||||
@classmethod
|
||||
def uniform_static_dynamic_response(cls, id: int, filename: str, r_filename: str, model) -> ChenResponse | None:
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / filename
|
||||
doc = DocxTemplate(input_path)
|
||||
qs = model.objects.filter(project=project_obj)
|
||||
if qs.exists():
|
||||
obj = qs.first()
|
||||
table_data = obj.table
|
||||
subdoc = cls.create_table_context(table_data, doc)
|
||||
context = {
|
||||
'fontnote': obj.fontnote,
|
||||
'table': subdoc,
|
||||
}
|
||||
doc.render(context, autoescape=True)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / r_filename)
|
||||
return ChenResponse(status=200, code=200, message="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
return None
|
||||
|
||||
# 静态软件项
|
||||
@route.get('/create/static_soft', url_name='create-static_soft')
|
||||
def create_static_soft(self, id: int):
|
||||
res = self.uniform_static_dynamic_response(id, '静态软件项_2.docx', '静态软件项.docx', StaticSoftItem)
|
||||
if res is not None:
|
||||
return res
|
||||
|
||||
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, '静态软件项')
|
||||
@@ -366,6 +593,10 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
# 静态硬件和固件项
|
||||
@route.get('/create/static_hard', url_name='create-static_hard')
|
||||
def create_static_hard(self, id: int):
|
||||
res = self.uniform_static_dynamic_response(id, '静态硬件和固件项_2.docx', '静态硬件和固件项.docx', StaticSoftHardware)
|
||||
if res is not None:
|
||||
return res
|
||||
|
||||
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, '静态硬件和固件项')
|
||||
@@ -375,17 +606,24 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
}
|
||||
return create_dg_docx("静态硬件和固件项.docx", context, id)
|
||||
|
||||
# 动态测评环境说明
|
||||
# 动态测评环境说明 - 多dataSchemas格式
|
||||
@route.get('/create/dynamic_env', url_name='create-dynamic_env')
|
||||
def create_dynamic_env(self, id: int):
|
||||
res = self.uniform_res_from_mul_data_schemas(id, '动态测试环境说明_2.docx',
|
||||
'动态测试环境说明.docx', ProjectDynamicDescription)
|
||||
if res is not None:
|
||||
return res
|
||||
# 老内容
|
||||
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,18 +633,29 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
# 动态软件项
|
||||
@route.get('/create/dynamic_soft', url_name='create-dynamic_soft')
|
||||
def create_dynamic_soft(self, id: int):
|
||||
res = self.uniform_static_dynamic_response(id, '动态软件项_2.docx', '动态软件项.docx', DynamicSoftTable)
|
||||
if res is not None:
|
||||
return res
|
||||
|
||||
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
|
||||
}
|
||||
return create_dg_docx("动态软件项.docx", context, id)
|
||||
|
||||
# 动态软件项
|
||||
# 动态硬件项
|
||||
@route.get('/create/dynamic_hard', url_name='create-dynamic_hard')
|
||||
def create_dynamic_hard(self, id: int):
|
||||
res = self.uniform_static_dynamic_response(id, '动态硬件和固件项_2.docx',
|
||||
'动态硬件和固件项.docx', DynamicHardwareTable)
|
||||
if res is not None:
|
||||
return res
|
||||
|
||||
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, '动态硬件和固件项')
|
||||
@@ -419,6 +668,11 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
# 测试数据
|
||||
@route.get('/create/test_data', url_name='create-test_data')
|
||||
def create_test_data(self, id: int):
|
||||
res = self.uniform_static_dynamic_response(id, '测评数据_2.docx',
|
||||
'测评数据.docx', EvaluateData)
|
||||
if res is not None:
|
||||
return res
|
||||
# 老内容
|
||||
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, '测评数据')
|
||||
@@ -431,6 +685,27 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
# 环境差异性分析
|
||||
@route.get('/create/env_diff', url_name='create-env_diff')
|
||||
def create_env_diff(self, id: int):
|
||||
project_obj: Project = get_object_or_404(Project, id=id)
|
||||
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '环境差异性分析_2.docx'
|
||||
doc = DocxTemplate(input_path)
|
||||
qs = EnvAnalysis.objects.filter(project=project_obj)
|
||||
if qs.exists():
|
||||
obj = qs.first()
|
||||
table_data = obj.table
|
||||
subdoc = self.create_table_context(table_data, doc)
|
||||
context = {
|
||||
"description": obj.description,
|
||||
"table": subdoc,
|
||||
"fontnote": obj.fontnote,
|
||||
}
|
||||
doc.render(context, autoescape=True)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '环境差异性分析.docx')
|
||||
return ChenResponse(status=200, code=200, message="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
|
||||
# 老内容
|
||||
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, '环境差异性分析')
|
||||
@@ -446,11 +721,13 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
project_qs = get_object_or_404(Project, id=id)
|
||||
security = get_str_dict(project_qs.security_level, 'security_level')
|
||||
languages = get_list_dict('language', project_qs.language)
|
||||
runtime = get_str_dict(project_qs.runtime, 'runtime')
|
||||
devplant = get_str_dict(project_qs.devplant, 'devplant')
|
||||
language_list = []
|
||||
for language in languages:
|
||||
language_list.append(language.get('ident_version'))
|
||||
runtimes = get_list_dict('runtime', project_qs.runtime)
|
||||
runtime_list = [item['ident_version'] for item in runtimes]
|
||||
devplants = get_list_dict('devplant', project_qs.devplant)
|
||||
devplant_list = [item['ident_version'] for item in devplants]
|
||||
# 版本先找第一轮
|
||||
project_round = project_qs.pField.filter(key=0).first()
|
||||
first_round_SO = project_round.rdField.filter(type='SO').first()
|
||||
@@ -462,6 +739,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,
|
||||
@@ -470,8 +748,8 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
'recv_date': project_qs.beginTime.strftime("%Y-%m-%d"),
|
||||
'dev_unit': dev_unit,
|
||||
'soft_type': project_qs.get_soft_type_display(),
|
||||
'runtime': runtime,
|
||||
'devplant': devplant
|
||||
'runtime': "\a".join(runtime_list),
|
||||
'devplant': "\a".join(devplant_list),
|
||||
}
|
||||
return create_dg_docx('被测软件基本信息.docx', context, id)
|
||||
|
||||
@@ -492,7 +770,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 +782,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 +806,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 +852,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 +904,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 +969,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 +992,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 +1028,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 +1092,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
|
||||
|
||||
@@ -48,6 +48,10 @@ class GenerateControllerHJL(ControllerBase):
|
||||
round1_so_dut: Union[Dut, None] = round1_obj.rdField.filter(type='SO').first()
|
||||
languages = get_list_dict('language', project_obj.language)
|
||||
language_list = [item['ident_version'] for item in languages]
|
||||
runtimes = get_list_dict('runtime', project_obj.runtime)
|
||||
runtime_list = [item['ident_version'] for item in runtimes]
|
||||
devplants = get_list_dict('devplant', project_obj.devplant)
|
||||
devplant_list = [item['ident_version'] for item in devplants]
|
||||
# 取非第一轮次
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
@@ -62,8 +66,8 @@ class GenerateControllerHJL(ControllerBase):
|
||||
'language': "、".join(language_list),
|
||||
'soft_type': project_obj.get_soft_type_display(),
|
||||
'security_level': get_str_dict(project_obj.security_level, 'security_level'),
|
||||
'runtime': get_str_dict(project_obj.runtime, 'runtime'),
|
||||
'devplant': get_str_dict(project_obj.devplant, 'devplant'),
|
||||
'runtime': "、".join(runtime_list),
|
||||
'devplant': "、".join(devplant_list),
|
||||
'recv_date': project_obj.beginTime.strftime("%Y-%m-%d"),
|
||||
'dev_unit': project_obj.dev_unit,
|
||||
}
|
||||
@@ -87,7 +91,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 +208,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
|
||||
@@ -25,8 +19,11 @@ from utils.path_utils import project_path
|
||||
from apps.createDocument.extensions.util import delete_dir_files
|
||||
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||
from apps.createDocument.extensions.documentTime import DocTime
|
||||
from apps.createDocument.extensions.content_result_tool import create_influence_context
|
||||
# 导入生成日志记录模块
|
||||
from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
|
||||
# 导入排序
|
||||
from apps.createDocument.extensions.tools import demand_sort_by_designKey
|
||||
|
||||
chinese_round_name: list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
||||
|
||||
@@ -59,6 +56,10 @@ class GenerateControllerHSM(ControllerBase):
|
||||
round1_so_dut: Union[Dut, None] = round1_obj.rdField.filter(type='SO').first()
|
||||
languages = get_list_dict('language', project_obj.language)
|
||||
language_list = [item['ident_version'] for item in languages]
|
||||
runtimes = get_list_dict('runtime', project_obj.runtime)
|
||||
runtime_list = [item['ident_version'] for item in runtimes]
|
||||
devplants = get_list_dict('devplant', project_obj.devplant)
|
||||
devplant_list = [item['ident_version'] for item in devplants]
|
||||
# 取非第一轮次
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
@@ -73,8 +74,8 @@ class GenerateControllerHSM(ControllerBase):
|
||||
'language': "、".join(language_list),
|
||||
'soft_type': project_obj.get_soft_type_display(),
|
||||
'security_level': get_str_dict(project_obj.security_level, 'security_level'),
|
||||
'runtime': get_str_dict(project_obj.runtime, 'runtime'),
|
||||
'devplant': get_str_dict(project_obj.devplant, 'devplant'),
|
||||
'runtime': "、".join(runtime_list),
|
||||
'devplant': "、".join(devplant_list),
|
||||
'recv_date': project_obj.beginTime.strftime("%Y-%m-%d"),
|
||||
'dev_unit': project_obj.dev_unit,
|
||||
}
|
||||
@@ -103,7 +104,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 +142,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 +172,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 +199,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:
|
||||
@@ -230,13 +233,16 @@ class GenerateControllerHSM(ControllerBase):
|
||||
xq_dut: Dut = hround.rdField.filter(type='XQ').first()
|
||||
# 处理代码版本
|
||||
last_round_key = str(int(hround.key) - 1)
|
||||
last_round: Round = project_obj.pField.filter(key=last_round_key).first()
|
||||
last_round: Round | None = project_obj.pField.filter(key=last_round_key).first()
|
||||
last_round_so_dut = last_round.rdField.filter(type='SO').first()
|
||||
if not last_round_so_dut:
|
||||
return ChenResponse(code=400, status=400,
|
||||
message=f'您第{chinese_round_name[int(hround.key)]}轮次中缺少源代码版本信息,请添加')
|
||||
last_dm_version = last_round_so_dut.version
|
||||
now_dm_version = so_dut.version
|
||||
# 这里插入影响域分析部分,并加入context
|
||||
context_round['influence'] = create_influence_context(doc, hround, project_obj) # noqa
|
||||
context_round['influence'] = None
|
||||
# 如果存在这个轮次的需求文档,则查询上个版本
|
||||
last_xq_version = ""
|
||||
if xq_dut:
|
||||
@@ -254,7 +260,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 +270,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 +286,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 +330,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 +354,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 +411,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 +502,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 +593,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():
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# 导入内置模块
|
||||
from pathlib import Path
|
||||
from django.db.models import QuerySet
|
||||
# 导入django、ninja等模块
|
||||
from ninja_extra import api_controller, ControllerBase, route
|
||||
from django.db import transaction
|
||||
@@ -7,7 +8,7 @@ from django.shortcuts import get_object_or_404
|
||||
# 导入文档处理模块
|
||||
from docxtpl import DocxTemplate, InlineImage
|
||||
# 导入ORM模型
|
||||
from apps.project.models import Project
|
||||
from apps.project.models import Project, Case
|
||||
# 导入工具
|
||||
from utils.util import get_str_abbr, get_str_dict
|
||||
from utils.chen_response import ChenResponse
|
||||
@@ -35,7 +36,7 @@ class GenerateControllerWtd(ControllerBase):
|
||||
for problem in problem_list:
|
||||
problem_dict = {'ident': problem.ident, 'name': problem.name}
|
||||
# 1.生成被测对象名称、被测对象标识、被测对象版本
|
||||
cases = problem.case.all()
|
||||
cases: QuerySet[Case] = problem.case.all()
|
||||
# generate_log:无关联问题单进入生成日志
|
||||
if cases.count() < 1:
|
||||
gloger.write_warning_log('单个问题单表格', f'问题单{problem.ident}未关联用例,请检查')
|
||||
@@ -70,9 +71,7 @@ class GenerateControllerWtd(ControllerBase):
|
||||
continue
|
||||
else:
|
||||
p_list.append(rich)
|
||||
|
||||
case_design_list.append(
|
||||
"-".join([case.dut.name, case.design.chapter + '章节' + ":" + ''.join(p_list)]))
|
||||
case_design_list.append("-".join([case.dut.name, case.design.chapter + '章节' + ":" + ''.join(p_list)]))
|
||||
# 2.用例标识修改-YL_测试项类型_测试项标识_用例key+1
|
||||
demand = case.test # 中间变量
|
||||
demand_testType = demand.testType # 中间变量
|
||||
@@ -86,12 +85,13 @@ class GenerateControllerWtd(ControllerBase):
|
||||
for i in range(len(str_dut_name_list)):
|
||||
temp_name_version.append(
|
||||
"".join([str_dut_name_list[i] + str_dut_ident_list[i], '/V', str_dut_version_list[i]]))
|
||||
problem_dict['dut_name_version'] = "\a".join(temp_name_version)
|
||||
problem_dict['dut_name_version'] = "\a".join(set(temp_name_version))
|
||||
problem_dict['case_ident'] = ",".join(set(case_ident_list))
|
||||
problem_dict['type'] = get_str_dict(problem.type, 'problemType')
|
||||
problem_dict['grade'] = get_str_dict(problem.grade, 'problemGrade')
|
||||
|
||||
# 依据要求-获取其设计需求
|
||||
print(case_design_list)
|
||||
problem_dict['yaoqiu'] = "\a".join(case_design_list)
|
||||
# 问题操作 - HTML解析
|
||||
desc_list = ['【问题操作】']
|
||||
@@ -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.
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.
@@ -1,7 +1,9 @@
|
||||
from apps.project.models import Project
|
||||
from apps.project.models import Project, Round, InfluenceArea
|
||||
from docxtpl import DocxTemplate
|
||||
from utils.util import *
|
||||
from utils.chen_response import ChenResponse
|
||||
from django.db.models import Q
|
||||
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||
|
||||
def create_round_context(project_obj: Project, round_id: str):
|
||||
"""根据轮次,生成测评报告中的测评结果"""
|
||||
@@ -77,4 +79,37 @@ def create_round_context(project_obj: Project, round_id: str):
|
||||
'r2_dynamic_str': r2_dynamic_str,
|
||||
'round_id': round_chinese[round_id],
|
||||
}
|
||||
return context
|
||||
return context, round_obj
|
||||
|
||||
# ~~~影响域分析:内容返回influence的render_list~~~
|
||||
def create_influence_context(doc: DocxTemplate, round_obj: Round, project_obj: Project) -> None | list:
|
||||
area_qs = InfluenceArea.objects.filter(round=round_obj)
|
||||
item_render_list = []
|
||||
## 如果存在则查询items
|
||||
if area_qs.exists():
|
||||
area_obj = area_qs.first()
|
||||
items_qs = area_obj.influence_items.all()
|
||||
if items_qs.exists():
|
||||
index = 1
|
||||
for item in items_qs:
|
||||
# 1.处理关联case - 找第一轮cases
|
||||
case_str_list = []
|
||||
for case in project_obj.pcField.filter(key__in=item.effect_cases):
|
||||
case_ident_index = str(int(case.key.split("-")[-1]) + 1).zfill(3)
|
||||
case_str_list.append("_".join(["YL", get_str_abbr(case.test.testType, "testType"), case.ident, case_ident_index]))
|
||||
# 2.处理富文本框
|
||||
parser = RichParser(item.change_des)
|
||||
item_dict = {
|
||||
"change_type": item.change_type,
|
||||
"change_influ": item.change_influ,
|
||||
"case_str_list": case_str_list,
|
||||
"change_des": parser.get_final_list(doc, img_size=40, height=30), # 富文本未处理
|
||||
"index": str(index),
|
||||
}
|
||||
index = index + 1
|
||||
item_render_list.append(item_dict)
|
||||
|
||||
if len(item_render_list) > 0:
|
||||
return item_render_list
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -7,7 +7,7 @@ from bs4.element import Tag, NavigableString
|
||||
import base64
|
||||
import io
|
||||
from docxtpl import InlineImage
|
||||
from docx.shared import Mm, Cm
|
||||
from docx.shared import Mm
|
||||
import re
|
||||
|
||||
# text.replace('\xa0', ' '))
|
||||
@@ -22,6 +22,8 @@ class RichParser:
|
||||
# 最终的解析后的列表
|
||||
self.data_list = []
|
||||
self.line_parse()
|
||||
# 匹配“表1-3”或“表1”等字符的正则
|
||||
self.biao_pattern = re.compile(r"表\d+(?:-\d+)?")
|
||||
|
||||
# 1.函数:将self.bs.contents去掉\n,获取每行数据
|
||||
def remove_n_in_contents(self):
|
||||
@@ -82,14 +84,21 @@ class RichParser:
|
||||
if isinstance(oneline, list):
|
||||
final_list.append({'isTable': True, 'data': oneline})
|
||||
continue
|
||||
if oneline.startswith("data:image/png;base64"):
|
||||
if oneline.startswith("data:image/png;base64") or oneline.startswith("data:image/jpeg;base64,") or oneline.startswith(
|
||||
"data:image/jpg;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)))
|
||||
inline_image = InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=Mm(height))
|
||||
final_list.append(inline_image)
|
||||
else:
|
||||
# ~~~新增:将\xa0修改为普通空格~~~
|
||||
oneline = oneline.replace('\xa0', ' ')
|
||||
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”则居中
|
||||
@@ -109,9 +118,9 @@ class RichParser:
|
||||
else:
|
||||
# 2.和上面区别:如果<p>带有“图”则居中
|
||||
if re.match(r"[表图]\d.*", oneline):
|
||||
final_list.append({"isCenter": True, "data": oneline})
|
||||
final_list.append({"isCenter": True, "data": oneline.replace('\xa0', ' ')})
|
||||
else:
|
||||
final_list.append({"isCenter": False, "data": oneline})
|
||||
final_list.append({"isCenter": False, "data": oneline.replace('\xa0', ' ')})
|
||||
if len(final_list) <= 0:
|
||||
final_list.append("")
|
||||
return final_list
|
||||
@@ -122,6 +131,13 @@ class RichParser:
|
||||
for oneline in self.data_list:
|
||||
if isinstance(oneline, list) or oneline.startswith("data:image/png;base64"):
|
||||
continue
|
||||
else:
|
||||
final_list.append(oneline)
|
||||
cleaned_line = oneline
|
||||
cleaned_line = re.sub(r'\s+', '', cleaned_line)
|
||||
cleaned_line = cleaned_line.replace(')', ')')
|
||||
cleaned_line = cleaned_line.strip()
|
||||
# 去掉以“表3”的行
|
||||
if self.biao_pattern.search(cleaned_line):
|
||||
continue
|
||||
if cleaned_line:
|
||||
final_list.append(cleaned_line)
|
||||
return final_list
|
||||
|
||||
142
apps/createDocument/extensions/tools.py
Normal file
142
apps/createDocument/extensions/tools.py
Normal file
@@ -0,0 +1,142 @@
|
||||
from apps.project.models import TestDemand
|
||||
from docx.oxml import OxmlElement
|
||||
from docx.oxml.ns import qn
|
||||
from docx.table import _Cell, Table
|
||||
|
||||
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
|
||||
|
||||
# 传入cell设置边框
|
||||
def set_cell_border(cell: _Cell, **kwargs):
|
||||
tc = cell._tc
|
||||
tcPr = tc.get_or_add_tcPr()
|
||||
|
||||
# 检查标签是否存在,如果没有找到,则创建一个
|
||||
tcBorders = tcPr.first_child_found_in("w:tcBorders")
|
||||
if tcBorders is None:
|
||||
tcBorders = OxmlElement('w:tcBorders')
|
||||
tcPr.append(tcBorders)
|
||||
|
||||
for border_type in ['left', 'top', 'right', 'bottom']:
|
||||
# 设置为固定的“黑色加粗”
|
||||
border_data = kwargs.get(border_type, {"sz": "6", "val": "single", "color": "#000000", "space": "0"})
|
||||
tag = 'w:{}'.format(border_type)
|
||||
element = tcBorders.find(qn(tag))
|
||||
if element is None:
|
||||
element = OxmlElement(tag)
|
||||
tcBorders.append(element)
|
||||
for key in ["sz", "val", "color", "space", "shadow"]:
|
||||
if key in border_data:
|
||||
element.set(qn('w:{}'.format(key)), str(border_data[key]))
|
||||
|
||||
# 弃用,请使用下面函数
|
||||
def set_table_border(table, **kwargs):
|
||||
"""docx-设置表格上下左右边框"""
|
||||
# 获取或创建表格属性
|
||||
tbl_pr = table._tbl.tblPr
|
||||
|
||||
# 查找并移除现有的边框设置
|
||||
existing_borders = tbl_pr.find(qn('w:tblBorders'))
|
||||
if existing_borders is not None:
|
||||
tbl_pr.remove(existing_borders)
|
||||
|
||||
# 创建新的边框元素
|
||||
borders = OxmlElement('w:tblBorders')
|
||||
|
||||
# 只设置外边框:top, left, bottom, right - 设置为固定“黑色加粗”
|
||||
# 不设置 insideV 和 insideH(内部边框)
|
||||
for border_type in ['top', 'left', 'bottom', 'right']:
|
||||
border_data = kwargs.get(border_type, {"sz": "12", "val": "single", "color": "#000000"})
|
||||
border_elem = OxmlElement(f'w:{border_type}')
|
||||
|
||||
# 设置边框属性
|
||||
border_elem.set(qn('w:val'), border_data.get('val', 'single')) # 线条类型
|
||||
border_elem.set(qn('w:sz'), border_data.get('sz', '12')) # 线条粗细(8代表1磅)
|
||||
border_elem.set(qn('w:color'), border_data.get('color', '#000000')) # 颜色
|
||||
borders.append(border_elem) # type:ignore
|
||||
|
||||
# 将边框设置添加到表格属性中
|
||||
tbl_pr.append(borders)
|
||||
|
||||
# ~~~新解决方案:传入table对象,遍历cell,判断cell是否在外层~~~
|
||||
def set_table_border_by_cell_position(table: Table):
|
||||
"""
|
||||
智能设置表格边框:外边框粗,内边框细。
|
||||
"""
|
||||
# 获取表格的总行数和总列数
|
||||
total_rows = len(table.rows)
|
||||
total_cols = len(table.columns)
|
||||
|
||||
for row_idx, row in enumerate(table.rows):
|
||||
for col_idx, cell in enumerate(row.cells):
|
||||
# 初始化边框参数字典
|
||||
border_kwargs = {}
|
||||
|
||||
# 1. 判断上边框:如果是第一行,则设置粗上边框,否则不设置(由上一行的下边框决定,或单独设置细线)
|
||||
if row_idx == 0:
|
||||
border_kwargs['top'] = {"sz": "12", "val": "single", "color": "#000000"}
|
||||
# 2. 判断下边框:如果是最后一行,则设置粗下边框
|
||||
if row_idx == total_rows - 1:
|
||||
border_kwargs['bottom'] = {"sz": "12", "val": "single", "color": "#000000"}
|
||||
# 3. 判断左边框:如果是第一列,则设置粗左边框
|
||||
if col_idx == 0:
|
||||
border_kwargs['left'] = {"sz": "12", "val": "single", "color": "#000000"}
|
||||
# 4. 判断右边框:如果是最后一列,则设置粗右边框
|
||||
if col_idx == total_cols - 1:
|
||||
border_kwargs['right'] = {"sz": "12", "val": "single", "color": "#000000"}
|
||||
|
||||
# 5. 设置内部网格线(细线)
|
||||
# 内部横线 (insideH): 所有单元格都需要,但最后一行不需要(已经是外边框)
|
||||
if row_idx < total_rows - 1:
|
||||
border_kwargs['insideH'] = {"sz": "6", "val": "single", "color": "#000000"}
|
||||
# 内部竖线 (insideV): 所有单元格都需要,但最后一列不需要(已经是外边框)
|
||||
if col_idx < total_cols - 1:
|
||||
border_kwargs['insideV'] = {"sz": "6", "val": "single", "color": "#000000"}
|
||||
|
||||
# 调用您已有的 set_cell_border 函数
|
||||
set_cell_border(cell, **border_kwargs)
|
||||
|
||||
# 设置cell的左右边距
|
||||
def set_cell_margins(cell: _Cell, **kwargs):
|
||||
"""
|
||||
设置单元格边距,确保在Office和WPS中均能生效。
|
||||
参数示例: set_cell_margins(cell, left=50, right=50, top=100, bottom=100)
|
||||
参数单位: 为二十分之一磅 (dxa, 1/1440英寸)。
|
||||
"""
|
||||
tc = cell._tc
|
||||
tcPr = tc.get_or_add_tcPr()
|
||||
|
||||
# 关键步骤1:检查或创建 w:tcMar 元素
|
||||
tcMar = tcPr.find(qn('w:tcMar'))
|
||||
if tcMar is None:
|
||||
tcMar = OxmlElement('w:tcMar')
|
||||
tcPr.append(tcMar)
|
||||
|
||||
# 关键步骤2:为每个指定的边距方向创建元素,并同时设置新旧两套属性以保证兼容性[2](@ref)
|
||||
# 定义映射:我们的参数名 -> (XML元素名, 备用的XML元素名)
|
||||
margin_map = {
|
||||
'left': ('left', 'start'),
|
||||
'right': ('right', 'end'),
|
||||
'top': ('top', None),
|
||||
'bottom': ('bottom', None)
|
||||
}
|
||||
|
||||
for margin_key, value in kwargs.items():
|
||||
if margin_key in margin_map:
|
||||
primary_tag, alternate_tag = margin_map[margin_key]
|
||||
tags_to_set = [primary_tag]
|
||||
if alternate_tag: # 如果存在备选标签(如left/start),则同时设置
|
||||
tags_to_set.append(alternate_tag)
|
||||
|
||||
for tag in tags_to_set:
|
||||
# 检查该边距元素是否已存在
|
||||
margin_element = tcMar.find(qn(f'w:{tag}'))
|
||||
if margin_element is None:
|
||||
margin_element = OxmlElement(f'w:{tag}')
|
||||
tcMar.append(margin_element) # type:ignore
|
||||
# 设置边距值和单位类型
|
||||
margin_element.set(qn('w:w'), str(value))
|
||||
margin_element.set(qn('w:type'), 'dxa')
|
||||
@@ -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,18 +31,29 @@ 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="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
|
||||
def create_hsm_docx(template_name: str, context: dict, id: int) -> ChenResponse:
|
||||
"""生成最终回归测试说明文档工具函数"""
|
||||
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'hsm' / template_name
|
||||
doc = DocxTemplate(input_path)
|
||||
doc.render(context, autoescape=True)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/hsm" / template_name)
|
||||
return ChenResponse(status=200, code=200, message="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
|
||||
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 +64,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 +75,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="文档生成成功!")
|
||||
@@ -95,3 +106,4 @@ def delete_dir_files(path: Path) -> Any:
|
||||
for file in path.iterdir():
|
||||
if file.is_file():
|
||||
file.unlink()
|
||||
|
||||
|
||||
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.
Binary file not shown.
@@ -1,3 +1,6 @@
|
||||
from io import BytesIO
|
||||
import copy
|
||||
from docx.parts.image import ImagePart
|
||||
from pathlib import Path
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
@@ -52,7 +55,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 +67,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 +126,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 +169,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 +222,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 +267,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 +280,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 +299,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 +316,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 +386,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 +415,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
|
||||
@@ -457,22 +471,53 @@ class UploadDocumentController(ControllerBase):
|
||||
fs.save(f"第{digit_to_chinese(round_num)}轮{documentType}.docx", self.upload_file)
|
||||
return ChenResponse(status=200, code=200, message=f'上传{documentType}成功!')
|
||||
|
||||
# 主功能函数:将所有大纲的片段储存在reuse下面,以便其他文件使用
|
||||
def get_dg_to_reuse_dir(self, reuse_dir_path: Path):
|
||||
"""将大纲的文档片段储存在/reuse文件夹下面"""
|
||||
doc = Document(self.upload_file)
|
||||
frag_list = self.get_document_frag_list(doc)
|
||||
"""将大纲的文档片段储存在/reuse文件夹下面(保留图片)"""
|
||||
src_doc = Document(self.upload_file)
|
||||
frag_list = self.get_document_frag_list(src_doc)
|
||||
|
||||
for frag_item in frag_list:
|
||||
# 目的是格式明确按照“测评大纲.docx”进行,后续文档一样必须按照这样
|
||||
if frag_item['content'] is None:
|
||||
continue
|
||||
|
||||
# 1. 创建目标文档(基于 basic_doc.docx 模板)
|
||||
new_doc = Document((reuse_dir_path / 'basic_doc.docx').as_posix())
|
||||
if frag_item['content'] is not None:
|
||||
# XML元素可以直接append
|
||||
new_doc.element.body.clear_content()
|
||||
for frag_child in frag_item['content'].iterchildren():
|
||||
new_doc.element.body.append(frag_child)
|
||||
new_doc.element.body.clear_content()
|
||||
|
||||
# 2. 逐元素复制 XML,并修复图片引用
|
||||
for child in frag_item['content'].iterchildren():
|
||||
self._copy_element_with_images(child, src_doc, new_doc)
|
||||
|
||||
# 3. 保存片段文件
|
||||
filename = f"{frag_item['alias']}.docx"
|
||||
new_doc.save((reuse_dir_path / filename).as_posix())
|
||||
|
||||
def _copy_element_with_images(self, element, src_doc, dst_doc):
|
||||
"""
|
||||
复制 lxml 元素及其子树,将源文档中的图片关系迁移至目标文档。
|
||||
"""
|
||||
# 1. 深拷贝元素
|
||||
new_element = copy.deepcopy(element)
|
||||
# 2. 使用本地名称匹配查找图片节点(完全避免命名空间前缀参数)
|
||||
pic_nodes = new_element.xpath('.//*[local-name()="pic"]')
|
||||
for pic in pic_nodes:
|
||||
blip_nodes = pic.xpath('.//*[local-name()="blip"]')
|
||||
for blip in blip_nodes:
|
||||
embed_attr = '{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed'
|
||||
old_rId = blip.get(embed_attr)
|
||||
if not old_rId:
|
||||
continue
|
||||
src_image_part = src_doc.part.related_parts.get(old_rId)
|
||||
if src_image_part is None or not isinstance(src_image_part, ImagePart):
|
||||
continue
|
||||
# 添加图片到目标文档,获取新 rId
|
||||
image_stream = BytesIO(src_image_part.blob)
|
||||
new_rId, _ = dst_doc.part.get_or_add_image(image_stream)
|
||||
# 更新属性
|
||||
blip.set(embed_attr, new_rId)
|
||||
# 3. 追加到目标文档 body
|
||||
dst_doc.element.body.append(new_element)
|
||||
|
||||
# 辅助函数:将上传文件的文档片段以列表形式返回
|
||||
def get_document_frag_list(self, doc: Document):
|
||||
body = doc.element.body
|
||||
|
||||
@@ -196,7 +196,11 @@ def generate_temp_doc(doc_type: str, project_id: int, round_num=None, frag_list=
|
||||
# 根据节点找到图片的关联id
|
||||
embed = img.xpath('.//a:blip/@r:embed')[0]
|
||||
# 这里得到ImagePart -> 马上要给新文档添加
|
||||
related_part: ImagePart = doc_copied.part.related_parts[embed]
|
||||
related_part: ImagePart = doc_copied.part.related_parts.get(embed)
|
||||
if related_part is None:
|
||||
# 可选:记录警告日志,便于排查哪些文档片段有问题
|
||||
print(f"警告: 文档片段 '{area_pop_name}' 中的图片引用 {embed} 未找到,已跳过!!!!")
|
||||
continue
|
||||
# doc_copied.part.related_parts是一个字典
|
||||
image_part_list.append({'name': area_pop_name, 'img': related_part})
|
||||
|
||||
|
||||
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.
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
|
||||
@@ -207,7 +257,7 @@ class CaseController(ControllerBase):
|
||||
single_qs.key = case_key
|
||||
index = index + 1
|
||||
single_qs.save()
|
||||
return ChenResponse(message="测试用例删除成功!")
|
||||
return ChenResponse(message="测试用例删除成功!影响域分析中如果有该关联用例则被删除。")
|
||||
|
||||
# 右键测试项,根据测试子项生成用例
|
||||
@route.post("/case/create_by_demand", url_name='case-create-by-demand')
|
||||
@@ -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,42 @@ 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)}个用例执行时间")
|
||||
|
||||
# 给级联选择器数据 -> 上一轮次所有用例
|
||||
@route.get("/case/getRelatedCase", url_name='case-related-case')
|
||||
def get_cases_related_case(self, id: int, round_key: str):
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
previous_round_obj = project_obj.pField.filter(key=int(round_key) - 1).first()
|
||||
# dut -> design
|
||||
data_list = []
|
||||
for dut in previous_round_obj.rdField.all():
|
||||
dut_dict = {'label': dut.name, 'value': dut.id, 'key': dut.key, 'children': []}
|
||||
for design in dut.rsField.all():
|
||||
design_dict = {'label': design.name, 'value': design.id, 'key': design.key, 'children': []}
|
||||
for case in design.dcField.all():
|
||||
case_dict = {'label': case.name, 'value': case.id, 'key': case.key}
|
||||
design_dict['children'].append(case_dict)
|
||||
dut_dict['children'].append(design_dict)
|
||||
data_list.append(dut_dict)
|
||||
return ChenResponse(message='获取成功', data=data_list)
|
||||
|
||||
@@ -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
|
||||
@@ -13,13 +15,31 @@ from typing import List
|
||||
from utils.chen_response import ChenResponse
|
||||
from utils.chen_crud import multi_delete_design
|
||||
from utils.codes import HTTP_INDEX_ERROR
|
||||
from apps.project.models import Design, Dut, Round, Project
|
||||
from apps.project.models import Design, Dut, Round, Project, JKDesignInfo
|
||||
from apps.project.schemas.design import DeleteSchema, DesignFilterSchema, DesignModelOutSchema, \
|
||||
DesignTreeReturnSchema, \
|
||||
DesignTreeInputSchema, DesignCreateOutSchema, DesignCreateInputSchema, MultiDesignCreateInputSchema, \
|
||||
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
|
||||
|
||||
def _save_jk_direction_info(design: Design, direction: str, source: str, destination: str, description: str):
|
||||
"""保存或更新 JKDesignInfo 记录"""
|
||||
if not source and not destination and not description:
|
||||
# 如果三个字段全为空,则删除可能存在的记录(避免冗余数据)
|
||||
JKDesignInfo.objects.filter(jk=design, direction=direction).delete()
|
||||
return
|
||||
JKDesignInfo.objects.update_or_create(
|
||||
jk=design,
|
||||
direction=direction,
|
||||
defaults={
|
||||
'source': source or '',
|
||||
'destination': destination or '',
|
||||
'description': description or '',
|
||||
}
|
||||
)
|
||||
|
||||
@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['设计需求数据'])
|
||||
class DesignController(ControllerBase):
|
||||
@@ -63,7 +83,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
|
||||
|
||||
# 添加设计需求
|
||||
@@ -90,7 +113,30 @@ class DesignController(ControllerBase):
|
||||
{'key': key_string, 'round': round_instance, 'dut': dut_instance, 'title': payload.name})
|
||||
asert_dict.pop("round_key")
|
||||
asert_dict.pop("dut_key")
|
||||
# 去掉Design不使用的字段
|
||||
asert_dict.pop("forward_source", None)
|
||||
asert_dict.pop("forward_destination", None)
|
||||
asert_dict.pop("forward_description", None)
|
||||
asert_dict.pop("reverse_source", None)
|
||||
asert_dict.pop("reverse_destination", None)
|
||||
asert_dict.pop("reverse_description", None)
|
||||
qs = Design.objects.create(**asert_dict)
|
||||
# 处理接口方向信息(仅当 demandType == '3' 且存在正向/反向数据时)
|
||||
if payload.demandType == '3':
|
||||
_save_jk_direction_info(
|
||||
design=qs,
|
||||
direction=JKDesignInfo.Direction.FORWARD,
|
||||
source=payload.forward_source,
|
||||
destination=payload.forward_destination,
|
||||
description=payload.forward_description,
|
||||
)
|
||||
_save_jk_direction_info(
|
||||
design=qs,
|
||||
direction=JKDesignInfo.Direction.REVERSE,
|
||||
source=payload.reverse_source,
|
||||
destination=payload.reverse_destination,
|
||||
description=payload.reverse_description,
|
||||
)
|
||||
return qs
|
||||
|
||||
# 批量增加设计需求,对应前端批量增加页面modal
|
||||
@@ -129,15 +175,36 @@ class DesignController(ControllerBase):
|
||||
# 判断是否和同项目同轮次的标识重复
|
||||
if len(design_search) > 1 and payload.ident != '':
|
||||
return ChenResponse(code=400, status=400, message='研制需求的标识重复,请检查')
|
||||
|
||||
# 查到当前
|
||||
design_qs = Design.objects.get(id=id)
|
||||
for attr, value in payload.dict().items():
|
||||
if attr == 'project_id' or attr == 'round_key' or attr == 'dut_key':
|
||||
if attr in ('project_id', 'round_key', 'dut_key'):
|
||||
continue
|
||||
if attr == 'name':
|
||||
setattr(design_qs, "title", value)
|
||||
setattr(design_qs, attr, value)
|
||||
design_qs.save()
|
||||
|
||||
# 处理接口方向信息更新
|
||||
if payload.demandType == '3':
|
||||
_save_jk_direction_info(
|
||||
design=design_qs,
|
||||
direction=JKDesignInfo.Direction.FORWARD,
|
||||
source=payload.forward_source,
|
||||
destination=payload.forward_destination,
|
||||
description=payload.forward_description,
|
||||
)
|
||||
_save_jk_direction_info(
|
||||
design=design_qs,
|
||||
direction=JKDesignInfo.Direction.REVERSE,
|
||||
source=payload.reverse_source,
|
||||
destination=payload.reverse_destination,
|
||||
description=payload.reverse_description,
|
||||
)
|
||||
else:
|
||||
# 如果需求类型不再是接口,则删除已有的方向信息
|
||||
JKDesignInfo.objects.filter(jk=design_qs).delete()
|
||||
return design_qs
|
||||
|
||||
# 删除设计需求
|
||||
@@ -162,7 +229,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 +263,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,
|
||||
|
||||
@@ -10,11 +10,14 @@ from ninja_jwt.authentication import JWTAuth
|
||||
from apps.user.models import Users
|
||||
from utils.chen_pagination import MyPagination
|
||||
from ninja.pagination import paginate
|
||||
from ninja.errors import HttpError
|
||||
from ninja import Query
|
||||
from utils.chen_response import ChenResponse
|
||||
from utils.chen_crud import create, multi_delete_project
|
||||
from apps.project.models import Project, Round
|
||||
from apps.project.schemas.project import ProjectRetrieveSchema, ProjectFilterSchema, ProjectCreateInput, DeleteSchema
|
||||
from apps.project.models import Project, Round, ProjectSoftSummary, StuctSortData, StaticSoftItem, StaticSoftHardware, DynamicSoftTable, \
|
||||
DynamicHardwareTable, ProjectDynamicDescription, EvaluateData, EnvAnalysis
|
||||
from apps.project.schemas.project import ProjectRetrieveSchema, ProjectFilterSchema, ProjectCreateInput, \
|
||||
DeleteSchema, SoftSummarySchema, DataSchema, StaticDynamicData, EnvAnalysisSchema
|
||||
from utils.util import get_str_dict
|
||||
# 时间处理模块
|
||||
from apps.project.tool.timeList import time_return_to
|
||||
@@ -89,12 +92,14 @@ class ProjectController(ControllerBase):
|
||||
try:
|
||||
copytree(src_dir, dist_dir) # shutil模块直接是复制并命名,如果命名文件存在则抛出FileExists异常
|
||||
except PermissionError:
|
||||
return ChenResponse(code=500, status=500, message="错误,检查是否打开了服务器的conf中的文档,关闭后重试")
|
||||
return ChenResponse(code=500, status=500,
|
||||
message="错误,检查是否打开了服务器的conf中的文档,关闭后重试")
|
||||
except FileExistsError:
|
||||
return ChenResponse(code=500, status=500, message='文件标识已存在或输入为空格,请修改')
|
||||
except FileNotFoundError:
|
||||
return ChenResponse(code=500, status=500, message='文件不存在,请检查')
|
||||
return ChenResponse(code=200, status=200, message="添加项目成功,并添加第一轮测试")
|
||||
return ChenResponse(code=400, status=400, message="未添加任何项目")
|
||||
|
||||
@route.put("/update/{project_id}")
|
||||
@transaction.atomic
|
||||
@@ -135,7 +140,7 @@ class ProjectController(ControllerBase):
|
||||
project_media_path = media_path / ident
|
||||
try:
|
||||
rmtree(project_media_path)
|
||||
except FileNotFoundError as e:
|
||||
except FileNotFoundError:
|
||||
return ChenResponse(status=400, code=400, message='项目模版目录可能不存在,可能之前已删除')
|
||||
return ChenResponse(message="删除成功!")
|
||||
|
||||
@@ -183,8 +188,8 @@ class ProjectController(ControllerBase):
|
||||
# 7.将时间提取 todo:后续将计算的事件放入该页面
|
||||
timers = {'round_time': []}
|
||||
rounds = project_obj.pField.all()
|
||||
timers['start_time'] = project_obj.beginTime
|
||||
timers['end_time'] = project_obj.endTime
|
||||
timers['start_time'] = project_obj.beginTime # type:ignore
|
||||
timers['end_time'] = project_obj.endTime # type:ignore
|
||||
for round in rounds:
|
||||
round_number = int(round.key) + 1
|
||||
timers['round_time'].append({
|
||||
@@ -197,7 +202,8 @@ class ProjectController(ControllerBase):
|
||||
# 9.提取测试类型下面测试项数量、用例数量
|
||||
data_list = []
|
||||
for round in rounds:
|
||||
round_dict = {'name': f'第{int(round.key) + 1}轮次', 'desings': [], 'method_demand': {}, 'method_case': {}}
|
||||
round_dict = {'name': f'第{int(round.key) + 1}轮次', 'desings': [], 'method_demand': {},
|
||||
'method_case': {}}
|
||||
designs = round.dsField.all()
|
||||
for design in designs:
|
||||
design_dict = {
|
||||
@@ -264,3 +270,228 @@ class ProjectController(ControllerBase):
|
||||
def document_time_show(self, id: int):
|
||||
time = time_return_to(id)
|
||||
return time
|
||||
|
||||
# [变] 项目级信息前端告警数据获取
|
||||
@route.get("/project_info_status/")
|
||||
@transaction.atomic
|
||||
def project_info_status(self, id: int):
|
||||
project_obj = self.get_project_by_id(id)
|
||||
|
||||
# 统一配置每个状态的检查逻辑
|
||||
status_configs = {
|
||||
"soft_summary": {
|
||||
"model": ProjectSoftSummary,
|
||||
"check": lambda qs: qs.exists() and qs.first().data_schemas.exists()
|
||||
},
|
||||
"interface_image": {
|
||||
"model": StuctSortData,
|
||||
"check": lambda qs: qs.exists()
|
||||
},
|
||||
"static_soft_item": {
|
||||
"model": StaticSoftItem,
|
||||
"check": lambda qs: qs.exists()
|
||||
},
|
||||
"static_soft_hardware": {
|
||||
"model": StaticSoftHardware,
|
||||
"check": lambda qs: qs.exists()
|
||||
},
|
||||
"dynamic_soft_item": {
|
||||
"model": DynamicSoftTable,
|
||||
"check": lambda qs: qs.exists()
|
||||
},
|
||||
"dynamic_soft_hardware": {
|
||||
"model": DynamicHardwareTable,
|
||||
"check": lambda qs: qs.exists()
|
||||
},
|
||||
"dynamic_des": {
|
||||
"model": ProjectDynamicDescription,
|
||||
"check": lambda qs: qs.exists() and qs.first().data_schemas.exists()
|
||||
},
|
||||
"evaluate_data": {
|
||||
"model": EvaluateData,
|
||||
"check": lambda qs: qs.exists()
|
||||
},
|
||||
"env_analysis": {
|
||||
"model": EnvAnalysis,
|
||||
"check": lambda qs: qs.exists()
|
||||
}
|
||||
}
|
||||
|
||||
all_status = {}
|
||||
for status_key, config in status_configs.items():
|
||||
qs = config["model"].objects.filter(project=project_obj)
|
||||
all_status[status_key] = config["check"](qs)
|
||||
return ChenResponse(status=200, code=20000, data=all_status, message='查询成功')
|
||||
|
||||
# [变] 封装结构化数据新增-修改(针对project - OneToOne - DataSchemas形式)
|
||||
@classmethod
|
||||
def bulk_create_data_schemas(cls, parent_obj, datas: list[DataSchema]):
|
||||
"""
|
||||
批量创建结构化排序数据 (自动类型推断)
|
||||
Args:
|
||||
parent_obj: 父级对象,可以是 ProjectSoftSummary 或 Project 的实例
|
||||
datas (list[DataSchema]): 数据模式对象列表
|
||||
"""
|
||||
# 动态确定所属父model
|
||||
field_name = None # type:ignore
|
||||
if isinstance(parent_obj, ProjectSoftSummary):
|
||||
field_name = 'soft_summary'
|
||||
elif isinstance(parent_obj, Project):
|
||||
field_name = 'project'
|
||||
elif isinstance(parent_obj, ProjectDynamicDescription):
|
||||
field_name = 'dynamic_description'
|
||||
else:
|
||||
raise HttpError(400, "添加的数据未在系统内,请联系管理员")
|
||||
|
||||
data_list = []
|
||||
for data in datas:
|
||||
new_data = StuctSortData(
|
||||
type=data.type,
|
||||
fontnote=data.fontnote,
|
||||
content=data.content,
|
||||
)
|
||||
setattr(new_data, field_name, parent_obj)
|
||||
data_list.append(new_data)
|
||||
StuctSortData.objects.bulk_create(data_list)
|
||||
|
||||
# 封装只有model不同 -修改和新增dataSchemas(针对project - OneToOne - DataSchemas形式)
|
||||
@classmethod
|
||||
def create_or_modify_data_schemas(cls, id: int, model, data):
|
||||
project_obj = get_object_or_404(Project, pk=id)
|
||||
qs = model.objects.filter(project=project_obj)
|
||||
if qs.exists():
|
||||
obj = qs.first()
|
||||
# 如果存在则修改:先删除再创建
|
||||
obj.data_schemas.all().delete()
|
||||
cls.bulk_create_data_schemas(obj, data)
|
||||
else:
|
||||
parent_obj = model.objects.create(project=project_obj)
|
||||
cls.bulk_create_data_schemas(parent_obj, data)
|
||||
|
||||
# ~~~软件概述-新增和修改~~~
|
||||
@route.post('/soft_summary/')
|
||||
@transaction.atomic
|
||||
def soft_summary(self, payload: SoftSummarySchema):
|
||||
self.create_or_modify_data_schemas(payload.id, ProjectSoftSummary, payload.data)
|
||||
|
||||
# ~~~动态环境描述-新增和修改~~~
|
||||
@route.post('/dynamic_description/')
|
||||
@transaction.atomic
|
||||
def dynamic_description(self, payload: SoftSummarySchema):
|
||||
self.create_or_modify_data_schemas(payload.id, ProjectDynamicDescription, payload.data)
|
||||
|
||||
@classmethod
|
||||
def get_res_from_info(cls, project_obj: Project, model) -> list[dict] | None:
|
||||
"""model: 当前一对一模型,直接获取结构化数据信息数组返回"""
|
||||
qs = model.objects.filter(project=project_obj)
|
||||
if qs.exists():
|
||||
obj = qs.first()
|
||||
ds_qs = obj.data_schemas.all()
|
||||
data_list = [{
|
||||
"type": item.type,
|
||||
"content": item.content,
|
||||
"fontnote": item.fontnote,
|
||||
} for item in ds_qs]
|
||||
return data_list
|
||||
return None
|
||||
|
||||
# ~~~软件概述-获取到前端展示~~~
|
||||
@route.get("/get_soft_summary/", response=list[DataSchema])
|
||||
@transaction.atomic
|
||||
def get_soft_summary(self, id: int):
|
||||
project_obj = self.get_project_by_id(id)
|
||||
data_list = self.get_res_from_info(project_obj, ProjectSoftSummary)
|
||||
if data_list:
|
||||
return ChenResponse(status=200, code=20000, data=data_list)
|
||||
return ChenResponse(status=200, code=20000, data=[])
|
||||
|
||||
# ~~~动态环境描述 - 获取展示~~~
|
||||
@route.get("/dynamic_des/", response=list[DataSchema])
|
||||
@transaction.atomic
|
||||
def get_dynamic_des(self, id: int):
|
||||
project_obj = self.get_project_by_id(id)
|
||||
data_list = self.get_res_from_info(project_obj, ProjectDynamicDescription)
|
||||
if data_list:
|
||||
return ChenResponse(status=200, code=20000, data=data_list)
|
||||
return ChenResponse(status=200, code=20000, data=[])
|
||||
|
||||
# ~~~接口图新增或修改~~~
|
||||
@route.post("/interface_image/")
|
||||
@transaction.atomic
|
||||
def post_interface_image(self, id: int, dataSchema: DataSchema):
|
||||
project_obj = self.get_project_by_id(id)
|
||||
image_qs = StuctSortData.objects.filter(project=project_obj)
|
||||
if image_qs.exists():
|
||||
image_qs.delete()
|
||||
self.bulk_create_data_schemas(project_obj, [dataSchema])
|
||||
|
||||
# ~~~接口图-获取数据~~~
|
||||
@route.get("/get_interface_image/", response=DataSchema)
|
||||
@transaction.atomic
|
||||
def get_interface_image(self, id: int):
|
||||
project_obj = self.get_project_by_id(id)
|
||||
image_qs = StuctSortData.objects.filter(project=project_obj)
|
||||
if image_qs.exists():
|
||||
# 如果存在则返回数据
|
||||
image_obj = image_qs.first()
|
||||
return ChenResponse(status=200, code=25001, data={
|
||||
"type": image_obj.type,
|
||||
"content": image_obj.content,
|
||||
"fontnote": image_obj.fontnote,
|
||||
})
|
||||
return ChenResponse(status=200, code=25002, data=None)
|
||||
|
||||
# 动态返回是哪个模型
|
||||
@classmethod
|
||||
def get_model_from_category(cls, category: str):
|
||||
mapDict = {
|
||||
'静态软件项': StaticSoftItem,
|
||||
'静态硬件项': StaticSoftHardware,
|
||||
'动态软件项': DynamicSoftTable,
|
||||
'动态硬件项': DynamicHardwareTable,
|
||||
'测评数据': EvaluateData
|
||||
}
|
||||
return mapDict[category]
|
||||
|
||||
# ~~~静态软件项、静态硬件项、动态软件项、动态硬件项 - 获取~~~
|
||||
@route.get("/get_static_dynamic_items/")
|
||||
def get_static_dynamic_items(self, id: int, category: str):
|
||||
project_obj = self.get_project_by_id(id)
|
||||
item_qs = self.get_model_from_category(category).objects.filter(project=project_obj)
|
||||
if item_qs.exists():
|
||||
item_obj = item_qs.first()
|
||||
return ChenResponse(status=200, code=25001, data={"table": item_obj.table, "fontnote": item_obj.fontnote})
|
||||
return ChenResponse(status=200, code=25002, data=None)
|
||||
|
||||
# ~~~静态软件项、静态硬件项、动态软件项、动态硬件项、测评数据 - 新增或修改~~~
|
||||
@route.post("/post_static_dynamic_item/")
|
||||
@transaction.atomic
|
||||
def post_static_dynamic_item(self, data: StaticDynamicData):
|
||||
project_obj = self.get_project_by_id(data.id)
|
||||
model = self.get_model_from_category(data.category)
|
||||
item_qs = model.objects.filter(project=project_obj)
|
||||
if item_qs.exists():
|
||||
# 如果存在则修改
|
||||
item_qs.delete()
|
||||
model.objects.create(project=project_obj, table=data.table, fontnote=data.fontnote)
|
||||
|
||||
# ~~~环境差异性分析 - 获取~~~
|
||||
@route.get("/get_env_analysis/")
|
||||
@transaction.atomic
|
||||
def get_env_analysis(self, id: int):
|
||||
project_obj = self.get_project_by_id(id)
|
||||
qs = EnvAnalysis.objects.filter(project=project_obj)
|
||||
if qs.exists():
|
||||
obj = qs.first()
|
||||
return ChenResponse(status=200, code=25001, data={"table": obj.table, "fontnote": obj.fontnote, "description": obj.description})
|
||||
return ChenResponse(status=200, code=25002, data=None)
|
||||
|
||||
# ~~~环境差异性分析 - 新增和修改~~~
|
||||
@route.post("/post_env_analysis/")
|
||||
@transaction.atomic
|
||||
def post_env_analysis(self, data: EnvAnalysisSchema):
|
||||
project_obj = self.get_project_by_id(data.id)
|
||||
qs = EnvAnalysis.objects.filter(project=project_obj)
|
||||
if qs.exists():
|
||||
qs.delete()
|
||||
EnvAnalysis.objects.create(project=project_obj, table=data.table, fontnote=data.fontnote, description=data.description)
|
||||
|
||||
@@ -2,9 +2,9 @@ from ninja_extra import api_controller, ControllerBase, route
|
||||
from ninja_jwt.authentication import JWTAuth
|
||||
from ninja_extra.permissions import IsAuthenticated
|
||||
from django.db import transaction
|
||||
from apps.project.models import Round
|
||||
from apps.project.models import Round, InfluenceArea, InfluenceItem
|
||||
from apps.project.schemas.round import TreeReturnRound, RoundInfoOutSchema, EditSchemaIn, DeleteSchema, \
|
||||
CreateRoundOutSchema, CreateRoundInputSchema
|
||||
CreateRoundOutSchema, CreateRoundInputSchema, InfluenceItemOutSchema, InfluenceInputSchema
|
||||
from typing import List
|
||||
from utils.chen_response import ChenResponse
|
||||
from apps.project.tools.delete_change_key import round_delete_sub_node_key
|
||||
@@ -81,3 +81,62 @@ class RoundController(ControllerBase):
|
||||
return ChenResponse(code=400, status=400, message='标识和其他重复')
|
||||
Round.objects.create(**asert_dict)
|
||||
return ChenResponse(message="新增轮次成功")
|
||||
|
||||
# ~~~影响域分析 - 获取数据和状态~~~
|
||||
@route.get("/round/get_influence", response=List[InfluenceItemOutSchema], url_name="round-get-influence-items")
|
||||
@transaction.atomic
|
||||
def get_influence(self, id: int, round_key: str):
|
||||
round_qs = Round.objects.filter(project__id=id, key=round_key)
|
||||
round_obj = round_qs.first()
|
||||
influence_qs = InfluenceArea.objects.filter(round=round_obj)
|
||||
if influence_qs.exists():
|
||||
influence = influence_qs.first()
|
||||
items_qs = influence.influence_items.all()
|
||||
if items_qs.exists():
|
||||
return items_qs
|
||||
return ChenResponse(status=200, code=25002, data=[])
|
||||
|
||||
# ~~~影响域分析是否有值~~~
|
||||
@route.get("/round/get_status_influence", url_name="round-get-status-influence")
|
||||
@transaction.atomic
|
||||
def get_status_influence(self, id: int, round_key: str):
|
||||
round_qs = Round.objects.filter(project__id=id, key=round_key)
|
||||
round_obj = round_qs.first()
|
||||
influence_qs = InfluenceArea.objects.filter(round=round_obj)
|
||||
if influence_qs.exists():
|
||||
influence = influence_qs.first()
|
||||
items_qs = influence.influence_items.all()
|
||||
if items_qs.exists():
|
||||
return ChenResponse(status=200, code=25005, data=True)
|
||||
return ChenResponse(status=200, code=25006, data=False)
|
||||
|
||||
# ~~~影响域分析 - 修改或新增~~~
|
||||
@route.post("/round/create_influence", url_name="round-influence-create")
|
||||
@transaction.atomic
|
||||
def post_influence(self, data: InfluenceInputSchema):
|
||||
round_obj = Round.objects.filter(project_id=data.id, key=data.round_key).first()
|
||||
influence_area_qs = InfluenceArea.objects.filter(round=round_obj)
|
||||
if influence_area_qs.exists():
|
||||
influence_area_obj = influence_area_qs.first()
|
||||
influence_area_obj.influence_items.all().delete()
|
||||
# 先删除再创建
|
||||
data_list = []
|
||||
for item in data.item_list:
|
||||
new_item = InfluenceItem(influence=influence_area_obj,
|
||||
change_type=item.change_type,
|
||||
change_influ=item.change_influ,
|
||||
change_des=item.change_des,
|
||||
effect_cases=item.effect_cases)
|
||||
data_list.append(new_item)
|
||||
InfluenceItem.objects.bulk_create(data_list)
|
||||
else:
|
||||
parent_obj = InfluenceArea.objects.create(round=round_obj)
|
||||
data_list = []
|
||||
for item in data.item_list:
|
||||
new_item = InfluenceItem(influence=parent_obj,
|
||||
change_type=item.change_type,
|
||||
change_influ=item.change_influ,
|
||||
change_des=item.change_des,
|
||||
effect_cases=item.effect_cases)
|
||||
data_list.append(new_item)
|
||||
InfluenceItem.objects.bulk_create(data_list)
|
||||
|
||||
@@ -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='测试子项一句话描述'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,45 @@
|
||||
# Generated by Django 6.0.1 on 2026-02-02 15:00
|
||||
|
||||
import apps.project.models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0018_testdemandcontent_subdescription'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ProjectSoftSummary',
|
||||
fields=[
|
||||
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='projSoftSummary', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '软件概述表',
|
||||
'verbose_name_plural': '软件概述表',
|
||||
'db_table': 'project_soft_summary',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='StuctSortData',
|
||||
fields=[
|
||||
('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')),
|
||||
('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')),
|
||||
('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
|
||||
('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
|
||||
('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')),
|
||||
('type', models.CharField(choices=[('text', '文本'), ('table', '表格'), ('image', '图片')], default='text', max_length=20, verbose_name='数据类型')),
|
||||
('fontnote', models.CharField(blank=True, default='', help_text='数据的题注说明', max_length=256, verbose_name='题注')),
|
||||
('content', models.JSONField(default=apps.project.models.default_json_value, help_text='存储文本内容或二维表格数据或图片数据', verbose_name='内容')),
|
||||
('soft_summary', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='data_schemas', to='project.projectsoftsummary', verbose_name='所属软件概述')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '结构排序化数据',
|
||||
'verbose_name_plural': '结构排序化数据',
|
||||
'db_table': 'data_schemas',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 6.0.2 on 2026-02-04 10:28
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0019_projectsoftsummary_stuctsortdata'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='stuctsortdata',
|
||||
name='project',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='data_schemas', to='project.project', verbose_name='该接口图所属的项目'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='stuctsortdata',
|
||||
name='soft_summary',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='data_schemas', to='project.projectsoftsummary', verbose_name='所属软件概述'),
|
||||
),
|
||||
]
|
||||
19
apps/project/migrations/0021_alter_stuctsortdata_project.py
Normal file
19
apps/project/migrations/0021_alter_stuctsortdata_project.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 6.0.2 on 2026-02-04 10:31
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0020_stuctsortdata_project_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='stuctsortdata',
|
||||
name='project',
|
||||
field=models.OneToOneField(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='data_schemas', to='project.project', verbose_name='该接口图所属的项目'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,63 @@
|
||||
# Generated by Django 6.0.2 on 2026-02-05 09:45
|
||||
|
||||
import apps.project.models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0021_alter_stuctsortdata_project'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DynamicHardwareTable',
|
||||
fields=[
|
||||
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='dynamic_hardware', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||
('table', models.JSONField(default=apps.project.models.default_json_value, help_text='储存表格二维数组', verbose_name='储存表格二维数组')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '动态硬件项表',
|
||||
'verbose_name_plural': '动态硬件项表',
|
||||
'db_table': 'project_dynamic_hardware',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DynamicSoftTable',
|
||||
fields=[
|
||||
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='dynamic_soft_item', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||
('table', models.JSONField(default=apps.project.models.default_json_value, help_text='储存表格二维数组', verbose_name='储存表格二维数组')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '动态软件项表',
|
||||
'verbose_name_plural': '动态软件项表',
|
||||
'db_table': 'project_dynamic_soft_item',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='StaticSoftHardware',
|
||||
fields=[
|
||||
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='static_hardware', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||
('table', models.JSONField(default=apps.project.models.default_json_value, help_text='储存表格二维数组', verbose_name='储存表格二维数组')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '静态硬件项表',
|
||||
'verbose_name_plural': '静态硬件项表',
|
||||
'db_table': 'project_static_hardware',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='StaticSoftItem',
|
||||
fields=[
|
||||
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='static_soft_item', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||
('table', models.JSONField(default=apps.project.models.default_json_value, help_text='储存表格二维数组', verbose_name='储存表格二维数组')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '静态软件项表',
|
||||
'verbose_name_plural': '静态软件项表',
|
||||
'db_table': 'project_static_soft_item',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 6.0.2 on 2026-02-05 11:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0022_dynamichardwaretable_dynamicsofttable_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='dynamichardwaretable',
|
||||
name='fontnote',
|
||||
field=models.CharField(default='', help_text='数据的题注说明', max_length=256, null=True, verbose_name='题注'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dynamicsofttable',
|
||||
name='fontnote',
|
||||
field=models.CharField(default='', help_text='数据的题注说明', max_length=256, null=True, verbose_name='题注'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='staticsofthardware',
|
||||
name='fontnote',
|
||||
field=models.CharField(default='', help_text='数据的题注说明', max_length=256, null=True, verbose_name='题注'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='staticsoftitem',
|
||||
name='fontnote',
|
||||
field=models.CharField(default='', help_text='数据的题注说明', max_length=256, null=True, verbose_name='题注'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,30 @@
|
||||
# Generated by Django 6.0.2 on 2026-02-05 17:48
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0023_dynamichardwaretable_fontnote_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ProjectDynamicDescription',
|
||||
fields=[
|
||||
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='dynamic_des', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '动态环境描述',
|
||||
'verbose_name_plural': '动态环境描述',
|
||||
'db_table': 'project_dynamic_description',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='stuctsortdata',
|
||||
name='dynamic_description',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='data_schemas', to='project.projectdynamicdescription', verbose_name='所属动态环境描述'),
|
||||
),
|
||||
]
|
||||
28
apps/project/migrations/0025_evaluatedata.py
Normal file
28
apps/project/migrations/0025_evaluatedata.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 6.0.2 on 2026-02-06 10:56
|
||||
|
||||
import apps.project.models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0024_projectdynamicdescription_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EvaluateData',
|
||||
fields=[
|
||||
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='evaluate_data', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||
('table', models.JSONField(default=apps.project.models.default_json_value, help_text='储存表格二维数组', verbose_name='储存表格二维数组')),
|
||||
('fontnote', models.CharField(default='', help_text='数据的题注说明', max_length=256, null=True, verbose_name='题注')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '测评数据',
|
||||
'verbose_name_plural': '测评数据',
|
||||
'db_table': 'project_evaluate_data',
|
||||
},
|
||||
),
|
||||
]
|
||||
29
apps/project/migrations/0026_envanalysis.py
Normal file
29
apps/project/migrations/0026_envanalysis.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 6.0.2 on 2026-02-06 13:56
|
||||
|
||||
import apps.project.models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0025_evaluatedata'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EnvAnalysis',
|
||||
fields=[
|
||||
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='env_analysis', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||
('table', models.JSONField(default=apps.project.models.default_json_value, help_text='储存表格二维数组', verbose_name='储存表格二维数组')),
|
||||
('fontnote', models.CharField(default='', help_text='数据的题注说明', max_length=256, null=True, verbose_name='题注')),
|
||||
('description', models.CharField(default='', max_length=1024, null=True, verbose_name='差异性分析文字')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '环境差异性分析表',
|
||||
'verbose_name_plural': '环境差异性分析表',
|
||||
'db_table': 'project_env_analysis',
|
||||
},
|
||||
),
|
||||
]
|
||||
46
apps/project/migrations/0027_influencearea_influenceitem.py
Normal file
46
apps/project/migrations/0027_influencearea_influenceitem.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# Generated by Django 6.0.2 on 2026-02-06 16:05
|
||||
|
||||
import apps.project.models
|
||||
import django.db.models.deletion
|
||||
import tinymce.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0026_envanalysis'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InfluenceArea',
|
||||
fields=[
|
||||
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='influence', serialize=False, to='project.round', verbose_name='关联项目')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '影响域分析',
|
||||
'verbose_name_plural': '影响域分析',
|
||||
'db_table': 'round_influence_area',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InfluenceItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')),
|
||||
('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')),
|
||||
('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
|
||||
('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
|
||||
('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')),
|
||||
('change_type', models.CharField(default='', help_text='更改类型', max_length=256, null=True, verbose_name='更改类型')),
|
||||
('change_des', tinymce.models.HTMLField(blank=True, null=True, verbose_name='更改内容描述')),
|
||||
('effect_cases', models.JSONField(default=apps.project.models.create_list, verbose_name='影响的用例key数组')),
|
||||
('influence', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='influence_items', to='project.influencearea', verbose_name='所属影响域分析')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '影响域分析 - 行数据',
|
||||
'verbose_name_plural': '影响域分析 - 行数据',
|
||||
'db_table': 'influence_item',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.2 on 2026-02-07 13:45
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0027_influencearea_influenceitem'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='influencearea',
|
||||
old_name='project',
|
||||
new_name='round',
|
||||
),
|
||||
]
|
||||
18
apps/project/migrations/0029_influenceitem_change_influ.py
Normal file
18
apps/project/migrations/0029_influenceitem_change_influ.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.2 on 2026-02-07 16:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0028_rename_project_influencearea_round'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='influenceitem',
|
||||
name='change_influ',
|
||||
field=models.TextField(default='', help_text='影响域分析', max_length=2048, null=True, verbose_name='影响域分析'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 6.0.4 on 2026-04-17 13:57
|
||||
|
||||
import apps.project.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0029_influenceitem_change_influ'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='devplant',
|
||||
field=models.JSONField(blank=True, default=apps.project.models.create_list, help_text='开发环境', null=True, verbose_name='开发环境'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='runtime',
|
||||
field=models.JSONField(blank=True, default=apps.project.models.create_list, help_text='运行环境', null=True, verbose_name='运行环境'),
|
||||
),
|
||||
]
|
||||
18
apps/project/migrations/0031_alter_testdemand_adequacy.py
Normal file
18
apps/project/migrations/0031_alter_testdemand_adequacy.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.4 on 2026-04-17 16:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0030_alter_project_devplant_alter_project_runtime'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='testdemand',
|
||||
name='adequacy',
|
||||
field=models.CharField(blank=True, help_text='充分条件', max_length=2048, null=True, verbose_name='充分条件'),
|
||||
),
|
||||
]
|
||||
18
apps/project/migrations/0032_alter_design_protocal.py
Normal file
18
apps/project/migrations/0032_alter_design_protocal.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.4 on 2026-04-20 10:36
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0031_alter_testdemand_adequacy'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='design',
|
||||
name='protocal',
|
||||
field=models.CharField(blank=True, default='', help_text='接口数据', max_length=1024, null=True, verbose_name='接口数据'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,51 @@
|
||||
# Generated by Django 6.0.4 on 2026-04-21 18:21
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0032_alter_design_protocal'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='design',
|
||||
name='protocal',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='design',
|
||||
name='to',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='design',
|
||||
name='type',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='design',
|
||||
name='source',
|
||||
field=models.CharField(blank=True, default='', help_text='接口来源', max_length=256, null=True, verbose_name='接口来源'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='JKDesignInfo',
|
||||
fields=[
|
||||
('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')),
|
||||
('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')),
|
||||
('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
|
||||
('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
|
||||
('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')),
|
||||
('direction', models.CharField(choices=[('forward', '正向'), ('reverse', '反向')], max_length=10, verbose_name='方向')),
|
||||
('description', models.TextField(blank=True, default='', max_length=1024, null=True, verbose_name='接口描述')),
|
||||
('source', models.CharField(blank=True, default='', max_length=200, null=True, verbose_name='来源')),
|
||||
('destination', models.CharField(blank=True, default='', max_length=200, null=True, verbose_name='目的地')),
|
||||
('jk', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='jkField', to='project.design', verbose_name='所属接口Design')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '接口一个方向的信息',
|
||||
'verbose_name_plural': '接口一个方向的信息',
|
||||
'unique_together': {('jk', 'direction')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,37 @@
|
||||
# Generated by Django 6.0.4 on 2026-04-21 18:27
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0033_remove_design_protocal_remove_design_to_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='design',
|
||||
name='source',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='design',
|
||||
name='type',
|
||||
field=models.CharField(blank=True, default='', help_text='接口类型', max_length=1024, null=True, verbose_name='接口类型'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='jkdesigninfo',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, max_length=1024, null=True, verbose_name='接口描述'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='jkdesigninfo',
|
||||
name='destination',
|
||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='目的地'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='jkdesigninfo',
|
||||
name='source',
|
||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='来源'),
|
||||
),
|
||||
]
|
||||
18
apps/project/migrations/0035_design_is_bidirectional.py
Normal file
18
apps/project/migrations/0035_design_is_bidirectional.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.4 on 2026-04-21 18:38
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0034_remove_design_source_design_type_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='design',
|
||||
name='is_bidirectional',
|
||||
field=models.BooleanField(default=False, verbose_name='是否双向'),
|
||||
),
|
||||
]
|
||||
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.
@@ -13,58 +13,43 @@ 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="项目名称")
|
||||
beginTime = models.DateField(auto_now_add=True, null=True, blank=True, help_text="开始时间",
|
||||
name = models.CharField(max_length=100, blank=True, null=True, verbose_name="项目名称",
|
||||
help_text="项目名称")
|
||||
beginTime = models.DateField(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(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="质量监督员")
|
||||
config_person = models.CharField(max_length=64, verbose_name="配置管理员", help_text="配置管理员")
|
||||
# ~~~~~~~~~~~
|
||||
security_level = models.CharField(max_length=8, blank=True, null=True, verbose_name="安全等级",
|
||||
help_text="安全等级")
|
||||
test_level = models.JSONField(null=True, blank=True, help_text="测试级别", verbose_name="测试级别",
|
||||
default=create_list)
|
||||
plant_type = models.JSONField(null=True, blank=True, help_text="平台类型", verbose_name="平台类型",
|
||||
default=create_list)
|
||||
security_level = models.CharField(max_length=8, blank=True, null=True, verbose_name="安全等级", help_text="安全等级")
|
||||
test_level = models.JSONField(null=True, blank=True, help_text="测试级别", verbose_name="测试级别", 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="报告类型")
|
||||
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="依据标准",
|
||||
default=create_list)
|
||||
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="依据标准", default=create_list)
|
||||
entrust_unit = models.CharField(max_length=64, verbose_name="委托方单位", help_text="委托方单位")
|
||||
entrust_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方联系人",
|
||||
help_text="委托方联系人")
|
||||
entrust_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方电话",
|
||||
help_text="委托方电话")
|
||||
entrust_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方邮箱",
|
||||
help_text="委托方邮箱")
|
||||
entrust_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方联系人", help_text="委托方联系人")
|
||||
entrust_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方电话", help_text="委托方电话")
|
||||
entrust_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方邮箱", help_text="委托方邮箱")
|
||||
dev_unit = models.CharField(max_length=64, verbose_name="开发方单位", help_text="开发方单位")
|
||||
dev_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方联系人",
|
||||
help_text="研制方联系人")
|
||||
dev_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方电话",
|
||||
help_text="研制方电话")
|
||||
dev_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方邮箱",
|
||||
help_text="研制方邮箱")
|
||||
dev_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方联系人", help_text="研制方联系人")
|
||||
dev_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方电话", help_text="研制方电话")
|
||||
dev_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方邮箱", help_text="研制方邮箱")
|
||||
test_unit = models.CharField(max_length=64, verbose_name="测试方单位", help_text="测试方单位")
|
||||
test_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心联系人",
|
||||
help_text="测评中心联系人")
|
||||
test_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心电话",
|
||||
help_text="测评中心电话")
|
||||
test_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心邮箱",
|
||||
help_text="测评中心邮箱")
|
||||
test_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心联系人", help_text="测评中心联系人")
|
||||
test_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心电话", 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="项目阶段")
|
||||
abbreviation = models.JSONField(null=True, blank=True, help_text="缩略语", verbose_name="缩略语",
|
||||
default=create_list)
|
||||
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="运行环境")
|
||||
devplant = 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, '沿用')), default=1)
|
||||
runtime = models.JSONField(null=True, blank=True, help_text="运行环境", verbose_name="运行环境", default=create_list)
|
||||
devplant = models.JSONField(null=True, blank=True, help_text="开发环境", verbose_name="开发环境", default=create_list)
|
||||
# 9月2日新增字段:密级
|
||||
secret = models.CharField(max_length=30, default='1', verbose_name='密级', help_text='密级')
|
||||
|
||||
@@ -85,23 +70,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 +113,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 +160,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,32 +175,35 @@ 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')
|
||||
# 如果是demandTye='3'则加上如下字段
|
||||
source = models.CharField(max_length=64, blank=True, null=True, default='', verbose_name='接口来源',
|
||||
help_text='接口来源')
|
||||
to = models.CharField(max_length=64, blank=True, null=True, default='', verbose_name='接口目的地',
|
||||
help_text='接口目的地')
|
||||
type = models.CharField(max_length=64, blank=True, null=True, default='', verbose_name='接口类型',
|
||||
type = models.CharField(max_length=1024, blank=True, null=True, default='', verbose_name='接口类型',
|
||||
help_text='接口类型')
|
||||
# 注意:该字段改为接口数据
|
||||
protocal = models.CharField(max_length=64, blank=True, null=True, default='', verbose_name='接口数据',
|
||||
help_text='接口数据')
|
||||
is_bidirectional = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="是否双向"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f'设计需求:{self.name}'
|
||||
@@ -209,34 +214,62 @@ class Design(CoreModel):
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ('key',)
|
||||
|
||||
class JKDesignInfo(CoreModel):
|
||||
class Direction(models.TextChoices):
|
||||
FORWARD = 'forward', '正向'
|
||||
REVERSE = 'reverse', '反向'
|
||||
|
||||
jk = models.ForeignKey(Design, on_delete=models.CASCADE, related_name="jkField", verbose_name="所属接口Design")
|
||||
direction = models.CharField(
|
||||
max_length=10,
|
||||
choices=Direction.choices, # type: ignore
|
||||
verbose_name="方向"
|
||||
)
|
||||
description = models.TextField(max_length=1024, blank=True, null=True, verbose_name="接口描述")
|
||||
source = models.CharField(max_length=200, blank=True, null=True, verbose_name="来源")
|
||||
destination = models.CharField(max_length=200, blank=True, null=True, verbose_name="目的地")
|
||||
|
||||
class Meta:
|
||||
unique_together = [['jk', 'direction']] # 同一个方向仅一条记录
|
||||
verbose_name = "接口一个方向的信息"
|
||||
verbose_name_plural = "接口一个方向的信息"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.jk.name} - {self.get_direction_display()}"
|
||||
|
||||
class TestDemand(CoreModel):
|
||||
objects = models.Manager()
|
||||
"""测试项"""
|
||||
ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="测试需求标识",
|
||||
help_text="测试需求标识")
|
||||
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="测试需求名称")
|
||||
adequacy = models.CharField(max_length=256, blank=True, null=True, verbose_name="充分条件", help_text="充分条件")
|
||||
adequacy = models.CharField(max_length=2048, 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")
|
||||
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",
|
||||
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 +279,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 +295,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 +364,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 +383,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="回归结果")
|
||||
@@ -389,6 +457,9 @@ class Contact(CoreModel):
|
||||
ordering = ('create_datetime',)
|
||||
|
||||
# ~~~~~2024年2月27日新增~~~~~
|
||||
def default_json_value():
|
||||
return ""
|
||||
|
||||
class Abbreviation(models.Model):
|
||||
objects = models.Manager()
|
||||
title = models.CharField(max_length=64, verbose_name="缩略语", help_text="缩略语")
|
||||
@@ -401,3 +472,163 @@ class Abbreviation(models.Model):
|
||||
db_table = 'project_abbreviation'
|
||||
verbose_name = '缩略语和行业词汇'
|
||||
verbose_name_plural = '缩略语和行业词汇'
|
||||
|
||||
# 一对一项目model:软件概述
|
||||
class ProjectSoftSummary(models.Model):
|
||||
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False, related_name="projSoftSummary", on_delete=models.CASCADE,
|
||||
verbose_name="关联项目", help_text="关联项目")
|
||||
|
||||
class Meta:
|
||||
db_table = 'project_soft_summary'
|
||||
verbose_name = "软件概述表"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
# 一对一项目model:动态测试环境描述
|
||||
class ProjectDynamicDescription(models.Model):
|
||||
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False, related_name="dynamic_des", on_delete=models.CASCADE,
|
||||
verbose_name="关联项目", help_text="关联项目")
|
||||
|
||||
class Meta:
|
||||
db_table = 'project_dynamic_description'
|
||||
verbose_name = "动态环境描述"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
# 一对一项目model:静态软件项表
|
||||
class StaticSoftItem(models.Model):
|
||||
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False, related_name="static_soft_item", on_delete=models.CASCADE,
|
||||
verbose_name="关联项目", help_text="关联项目")
|
||||
table = models.JSONField(verbose_name="储存表格二维数组", help_text="储存表格二维数组", default=default_json_value)
|
||||
fontnote = models.CharField(max_length=256, null=True, default="", verbose_name="题注", help_text="数据的题注说明")
|
||||
|
||||
class Meta:
|
||||
db_table = 'project_static_soft_item'
|
||||
verbose_name = "静态软件项表"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
# 一对一项目model:静态硬件项表
|
||||
class StaticSoftHardware(models.Model):
|
||||
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False, related_name="static_hardware", on_delete=models.CASCADE,
|
||||
verbose_name="关联项目", help_text="关联项目")
|
||||
table = models.JSONField(verbose_name="储存表格二维数组", help_text="储存表格二维数组", default=default_json_value)
|
||||
fontnote = models.CharField(max_length=256, null=True, default="", verbose_name="题注", help_text="数据的题注说明")
|
||||
|
||||
class Meta:
|
||||
db_table = 'project_static_hardware'
|
||||
verbose_name = "静态硬件项表"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
# 一对一项目model:动态软件项表
|
||||
class DynamicSoftTable(models.Model):
|
||||
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False, related_name="dynamic_soft_item", on_delete=models.CASCADE,
|
||||
verbose_name="关联项目", help_text="关联项目")
|
||||
table = models.JSONField(verbose_name="储存表格二维数组", help_text="储存表格二维数组", default=default_json_value)
|
||||
fontnote = models.CharField(max_length=256, null=True, default="", verbose_name="题注", help_text="数据的题注说明")
|
||||
|
||||
class Meta:
|
||||
db_table = 'project_dynamic_soft_item'
|
||||
verbose_name = "动态软件项表"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
# 一对一项目model:动态硬件项
|
||||
class DynamicHardwareTable(models.Model):
|
||||
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False, related_name="dynamic_hardware", on_delete=models.CASCADE,
|
||||
verbose_name="关联项目", help_text="关联项目")
|
||||
table = models.JSONField(verbose_name="储存表格二维数组", help_text="储存表格二维数组", default=default_json_value)
|
||||
fontnote = models.CharField(max_length=256, null=True, default="", verbose_name="题注", help_text="数据的题注说明")
|
||||
|
||||
class Meta:
|
||||
db_table = 'project_dynamic_hardware'
|
||||
verbose_name = "动态硬件项表"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
# 一对一项目model:动态环境 - 测评数据
|
||||
class EvaluateData(models.Model):
|
||||
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False, related_name="evaluate_data", on_delete=models.CASCADE,
|
||||
verbose_name="关联项目", help_text="关联项目")
|
||||
table = models.JSONField(verbose_name="储存表格二维数组", help_text="储存表格二维数组", default=default_json_value)
|
||||
fontnote = models.CharField(max_length=256, null=True, default="", verbose_name="题注", help_text="数据的题注说明")
|
||||
|
||||
class Meta:
|
||||
db_table = 'project_evaluate_data'
|
||||
verbose_name = "测评数据"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
# 一对一项目model:环境差异性分析
|
||||
class EnvAnalysis(models.Model):
|
||||
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False,
|
||||
related_name="env_analysis", on_delete=models.CASCADE,
|
||||
verbose_name="关联项目", help_text="关联项目")
|
||||
table = models.JSONField(verbose_name="储存表格二维数组", help_text="储存表格二维数组", default=default_json_value)
|
||||
fontnote = models.CharField(max_length=256, null=True, default="", verbose_name="题注", help_text="数据的题注说明")
|
||||
description = models.CharField(max_length=1024, null=True, default="", verbose_name="差异性分析文字")
|
||||
|
||||
class Meta:
|
||||
db_table = 'project_env_analysis'
|
||||
verbose_name = "环境差异性分析表"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
# 结构化排序数据
|
||||
class StuctSortData(CoreModel):
|
||||
"""
|
||||
与其他项目信息的多对一关系
|
||||
"""
|
||||
# 软件概述内容
|
||||
soft_summary = models.ForeignKey(ProjectSoftSummary, db_constraint=False, related_name="data_schemas", verbose_name="所属软件概述",
|
||||
on_delete=models.CASCADE, null=True, blank=True)
|
||||
# 接口图
|
||||
project = models.OneToOneField(Project, db_constraint=False, related_name="data_schemas", on_delete=models.CASCADE, null=True, blank=True,
|
||||
verbose_name="该接口图所属的项目")
|
||||
# 动态环境描述
|
||||
dynamic_description = models.ForeignKey(ProjectDynamicDescription, db_constraint=False, related_name="data_schemas",
|
||||
verbose_name="所属动态环境描述",
|
||||
on_delete=models.CASCADE, null=True, blank=True)
|
||||
|
||||
type = models.CharField(
|
||||
max_length=20,
|
||||
choices=(('text', '文本'), ('table', '表格'), ('image', '图片')),
|
||||
default='text',
|
||||
verbose_name="数据类型",
|
||||
)
|
||||
# 题注字段
|
||||
fontnote = models.CharField(
|
||||
max_length=256,
|
||||
blank=True,
|
||||
default="",
|
||||
verbose_name="题注",
|
||||
help_text="数据的题注说明"
|
||||
)
|
||||
# 内容字段 - 存储字符串、列表、字典
|
||||
content = models.JSONField(verbose_name="内容", help_text="存储文本内容或二维表格数据或图片数据", default=default_json_value)
|
||||
|
||||
class Meta:
|
||||
db_table = 'data_schemas'
|
||||
verbose_name = "结构排序化数据"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return f"结构排序化数据:({self.pk})"
|
||||
|
||||
# 影响域分析 - 隶属:轮次(不能是第一轮次)
|
||||
class InfluenceArea(models.Model):
|
||||
round = models.OneToOneField(to="Round", primary_key=True, db_constraint=False,
|
||||
related_name="influence", on_delete=models.CASCADE,
|
||||
verbose_name="关联项目", help_text="关联项目")
|
||||
|
||||
class Meta:
|
||||
db_table = 'round_influence_area'
|
||||
verbose_name = "影响域分析"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
class InfluenceItem(CoreModel):
|
||||
# 外键:影响域分析
|
||||
influence = models.ForeignKey(InfluenceArea, db_constraint=False, related_name="influence_items", verbose_name="所属影响域分析",
|
||||
on_delete=models.CASCADE, null=True, blank=True)
|
||||
change_type = models.CharField(max_length=256, null=True, default="", verbose_name="更改类型", help_text="更改类型")
|
||||
change_influ = models.TextField(max_length=2048, null=True, default="", verbose_name="影响域分析", help_text="影响域分析")
|
||||
change_des = HTMLField(blank=True, null=True, verbose_name="更改内容描述")
|
||||
effect_cases = models.JSONField(default=create_list, verbose_name="影响的用例key数组")
|
||||
|
||||
class Meta:
|
||||
db_table = 'influence_item'
|
||||
verbose_name = "影响域分析 - 行数据"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
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.
@@ -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]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from typing import Optional
|
||||
from apps.project.models import Design
|
||||
from apps.project.models import Design, JKDesignInfo
|
||||
from ninja import Field, Schema, ModelSchema
|
||||
from typing import List, Union
|
||||
from pydantic import AliasChoices
|
||||
@@ -23,17 +23,60 @@ 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
|
||||
is_bidirectional: bool = Field(False, alias='is_bidirectional')
|
||||
forward_source: str = Field("")
|
||||
forward_destination: str = Field("")
|
||||
forward_description: str = Field("")
|
||||
reverse_source: str = Field("")
|
||||
reverse_destination: str = Field("")
|
||||
reverse_description: str = Field("")
|
||||
|
||||
class Config:
|
||||
class Meta:
|
||||
model = Design
|
||||
model_exclude = ['project', 'round', 'dut', 'remark', 'sort']
|
||||
exclude = ['project', 'round', 'dut', 'remark', 'sort']
|
||||
|
||||
# ---------- 解析器方法 ----------
|
||||
@staticmethod
|
||||
def resolve_is_bidirectional(obj: Design) -> bool:
|
||||
return obj.is_bidirectional
|
||||
|
||||
@staticmethod
|
||||
def resolve_forward_source(obj: Design) -> str:
|
||||
"""从 JKDesignInfo 正向记录中获取 source"""
|
||||
info = obj.jkField.filter(direction=JKDesignInfo.Direction.FORWARD).first()
|
||||
return info.source if info else ""
|
||||
|
||||
@staticmethod
|
||||
def resolve_forward_destination(obj: Design) -> str:
|
||||
info = obj.jkField.filter(direction=JKDesignInfo.Direction.FORWARD).first()
|
||||
return info.destination if info else ""
|
||||
|
||||
@staticmethod
|
||||
def resolve_forward_description(obj: Design) -> str:
|
||||
info = obj.jkField.filter(direction=JKDesignInfo.Direction.FORWARD).first()
|
||||
return info.description if info else ""
|
||||
|
||||
@staticmethod
|
||||
def resolve_reverse_source(obj: Design) -> str:
|
||||
info = obj.jkField.filter(direction=JKDesignInfo.Direction.REVERSE).first()
|
||||
return info.source if info else ""
|
||||
|
||||
@staticmethod
|
||||
def resolve_reverse_destination(obj: Design) -> str:
|
||||
info = obj.jkField.filter(direction=JKDesignInfo.Direction.REVERSE).first()
|
||||
return info.destination if info else ""
|
||||
|
||||
@staticmethod
|
||||
def resolve_reverse_description(obj: Design) -> str:
|
||||
info = obj.jkField.filter(direction=JKDesignInfo.Direction.REVERSE).first()
|
||||
return info.description if info else ""
|
||||
|
||||
# 处理树状结构的schema
|
||||
class DesignTreeReturnSchema(Schema):
|
||||
@@ -66,11 +109,15 @@ class DesignCreateInputSchema(Schema):
|
||||
demandType: str = Field(None, alias="demandType")
|
||||
description: str = Field("", alias="description")
|
||||
chapter: str = Field(None, alias='chapter')
|
||||
# 接口独有的4个字段
|
||||
source: str = Field('', alias='source')
|
||||
to: str = Field('', alias='to')
|
||||
# 接口类型包含
|
||||
type: str = Field('', alias='type')
|
||||
protocal: str = Field('', alias='protocal')
|
||||
is_bidirectional: bool = Field(False, alias='is_bidirectional')
|
||||
forward_source: str = Field("")
|
||||
forward_destination: str = Field("")
|
||||
forward_description: str = Field("")
|
||||
reverse_source: str = Field("")
|
||||
reverse_destination: str = Field("")
|
||||
reverse_description: str = Field("")
|
||||
|
||||
class SingleDesignSchema(Schema):
|
||||
ident: str = Field(None, alias="ident")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from ninja.errors import HttpError
|
||||
from apps.project.models import Project
|
||||
from pyasn1_modules.rfc2315 import Data
|
||||
|
||||
from apps.project.models import Project, StuctSortData
|
||||
from ninja import Schema, ModelSchema
|
||||
from pydantic import field_validator
|
||||
from typing import List, Optional
|
||||
@@ -7,9 +9,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 +28,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
|
||||
@@ -39,3 +41,31 @@ class ProjectCreateInput(ModelSchema):
|
||||
|
||||
class DeleteSchema(Schema):
|
||||
ids: List[int]
|
||||
|
||||
# ~~~软件概述~~~
|
||||
class DataSchema(Schema):
|
||||
type: Optional[str] = "text"
|
||||
fontnote: Optional[str] = ""
|
||||
content: str | list[list[str]]
|
||||
|
||||
## 输入
|
||||
class SoftSummarySchema(Schema):
|
||||
id: int
|
||||
data: list[DataSchema]
|
||||
|
||||
# ~~~软件接口图~~~
|
||||
## 复用DataSchema
|
||||
|
||||
# ~~~静态软件项、静态硬件项、动态软件项、动态硬件项~~~
|
||||
class StaticDynamicData(Schema):
|
||||
id: int
|
||||
category: str
|
||||
table: list[list[str]]
|
||||
fontnote: Optional[str] = ""
|
||||
|
||||
# ~~~环境差异性分析~~~
|
||||
class EnvAnalysisSchema(Schema):
|
||||
id: int
|
||||
table: list[list[str]]
|
||||
fontnote: Optional[str] = ""
|
||||
description: Optional[str] = ""
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import Optional
|
||||
from ninja import Schema, ModelSchema
|
||||
from pydantic import Field
|
||||
from apps.project.models import Round
|
||||
from apps.project.models import Round, InfluenceItem
|
||||
|
||||
# 输出树状信息的schema
|
||||
class TreeReturnRound(Schema):
|
||||
@@ -55,3 +55,21 @@ class CreateRoundInputSchema(ModelSchema):
|
||||
fields_optional = ['best_condition_tem', 'best_condition_voltage',
|
||||
'low_condition_tem', 'low_condition_voltage', 'typical_condition_tem',
|
||||
'typical_condition_voltage' 'grade']
|
||||
|
||||
# influence_item return
|
||||
class InfluenceItemOutSchema(ModelSchema):
|
||||
class Meta:
|
||||
model = InfluenceItem
|
||||
fields = ['id', 'change_type', 'change_des', 'effect_cases', 'change_influ']
|
||||
|
||||
# influence input
|
||||
class OneItemInputSchema(Schema):
|
||||
change_type: str
|
||||
change_des: Optional[str] = ""
|
||||
effect_cases: Optional[list[str]] = []
|
||||
change_influ: Optional[str] = ""
|
||||
|
||||
class InfluenceInputSchema(Schema):
|
||||
id: int
|
||||
round_key: str
|
||||
item_list: list[OneItemInputSchema]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user