initial commit
This commit is contained in:
5
.env
Normal file
5
.env
Normal file
@@ -0,0 +1,5 @@
|
||||
AUTH_LDAP_SERVER_URI='ldap://dns.paisat.cn:389'
|
||||
AUTH_LDAP_BIND_DN='CN=Administrator,CN=Users,DC=sstc,DC=ctu'
|
||||
AUTH_LDAP_BIND_PASSWORD='WXWX2019!!!!!!'
|
||||
BASE_DN='OU=all,DC=sstc,DC=ctu'
|
||||
FILTER_STR='(sAMAccountName=%(user)s)'
|
||||
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/dist
|
||||
/venv
|
||||
/.idea
|
||||
/build
|
||||
/static
|
||||
*.zip
|
||||
/uploads
|
||||
.idea
|
||||
/打包后配置
|
||||
/packagesInitialize
|
||||
32
.idea/cdtestplant_v1.iml
generated
Normal file
32
.idea/cdtestplant_v1.iml
generated
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="django" name="Django">
|
||||
<configuration>
|
||||
<option name="rootFolder" value="$MODULE_DIR$" />
|
||||
<option name="settingsModule" value="cdtestplant_v1/settings.py" />
|
||||
<option name="manageScript" value="manage.py" />
|
||||
<option name="environment" value="<map/>" />
|
||||
<option name="doNotUseTestRunner" value="false" />
|
||||
<option name="trackFilePattern" value="" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||
<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="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="TemplatesService">
|
||||
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
||||
<option name="TEMPLATE_FOLDERS">
|
||||
<list>
|
||||
<option value="$MODULE_DIR$/dist/run/templates" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</module>
|
||||
12
.idea/dataSources.xml
generated
Normal file
12
.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="cdtestplant_v1" uuid="2b73fc88-303c-4aa1-a8da-50bba5915b7d">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://localhost:3306/chengdu_test_plant_v1</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="PROJECT_PROFILE" value="Default" />
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.8 (cdtestplant_v1)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (cdtestplant_v1)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/cdtestplant_v1.iml" filepath="$PROJECT_DIR$/.idea/cdtestplant_v1.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
11
README.md
Normal file
11
README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# change_log
|
||||
|
||||
## 内外V0.0.1版本
|
||||
|
||||
2024年7月3日 - V0.0.1版本首次导入内网并进行数据库迁移和部署
|
||||
|
||||
|
||||
|
||||
## 外V0.0.2版本
|
||||
|
||||
## 外V0.0.3版本
|
||||
0
apps/__init__.py
Normal file
0
apps/__init__.py
Normal file
BIN
apps/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
apps/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
apps/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
0
apps/createDocument/__init__.py
Normal file
0
apps/createDocument/__init__.py
Normal file
BIN
apps/createDocument/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
apps/createDocument/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
apps/createDocument/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/__pycache__/admin.cpython-313.pyc
Normal file
BIN
apps/createDocument/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/__pycache__/admin.cpython-38.pyc
Normal file
BIN
apps/createDocument/__pycache__/admin.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/__pycache__/apps.cpython-313.pyc
Normal file
BIN
apps/createDocument/__pycache__/apps.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/__pycache__/apps.cpython-38.pyc
Normal file
BIN
apps/createDocument/__pycache__/apps.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/__pycache__/models.cpython-313.pyc
Normal file
BIN
apps/createDocument/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/__pycache__/models.cpython-38.pyc
Normal file
BIN
apps/createDocument/__pycache__/models.cpython-38.pyc
Normal file
Binary file not shown.
3
apps/createDocument/admin.py
Normal file
3
apps/createDocument/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
9
apps/createDocument/apps.py
Normal file
9
apps/createDocument/apps.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class CreatedocumentConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.createDocument'
|
||||
|
||||
# 在app准备好时候执行的代码,以后放async、signal等
|
||||
def ready(self):
|
||||
pass
|
||||
12
apps/createDocument/controllers/__init__.py
Normal file
12
apps/createDocument/controllers/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# 导入所有本目录控制器,为了自动导入
|
||||
from apps.createDocument.controllers.dg import GenerateControllerDG
|
||||
from apps.createDocument.controllers.sm import GenerateControllerSM
|
||||
from apps.createDocument.controllers.jl import GenerateControllerJL
|
||||
from apps.createDocument.controllers.bg import GenerateControllerBG
|
||||
from apps.createDocument.controllers.wtd import GenerateControllerWtd
|
||||
from apps.createDocument.controllers.hsm import GenerateControllerHSM
|
||||
from apps.createDocument.controllers.hjl import GenerateControllerHJL
|
||||
|
||||
# 给外部导入
|
||||
__all__ = ['GenerateControllerDG', 'GenerateControllerSM', 'GenerateControllerJL', 'GenerateControllerBG',
|
||||
'GenerateControllerWtd', 'GenerateControllerHSM', 'GenerateControllerHJL']
|
||||
Binary file not shown.
Binary file not shown.
BIN
apps/createDocument/controllers/__pycache__/bg.cpython-313.pyc
Normal file
BIN
apps/createDocument/controllers/__pycache__/bg.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/controllers/__pycache__/bg.cpython-38.pyc
Normal file
BIN
apps/createDocument/controllers/__pycache__/bg.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/controllers/__pycache__/dg.cpython-313.pyc
Normal file
BIN
apps/createDocument/controllers/__pycache__/dg.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/controllers/__pycache__/dg.cpython-38.pyc
Normal file
BIN
apps/createDocument/controllers/__pycache__/dg.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/controllers/__pycache__/hjl.cpython-313.pyc
Normal file
BIN
apps/createDocument/controllers/__pycache__/hjl.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/controllers/__pycache__/hjl.cpython-38.pyc
Normal file
BIN
apps/createDocument/controllers/__pycache__/hjl.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/controllers/__pycache__/hsm.cpython-313.pyc
Normal file
BIN
apps/createDocument/controllers/__pycache__/hsm.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/controllers/__pycache__/hsm.cpython-38.pyc
Normal file
BIN
apps/createDocument/controllers/__pycache__/hsm.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/controllers/__pycache__/jl.cpython-313.pyc
Normal file
BIN
apps/createDocument/controllers/__pycache__/jl.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/controllers/__pycache__/jl.cpython-38.pyc
Normal file
BIN
apps/createDocument/controllers/__pycache__/jl.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/controllers/__pycache__/sm.cpython-313.pyc
Normal file
BIN
apps/createDocument/controllers/__pycache__/sm.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/controllers/__pycache__/sm.cpython-38.pyc
Normal file
BIN
apps/createDocument/controllers/__pycache__/sm.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/controllers/__pycache__/wtd.cpython-313.pyc
Normal file
BIN
apps/createDocument/controllers/__pycache__/wtd.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/controllers/__pycache__/wtd.cpython-38.pyc
Normal file
BIN
apps/createDocument/controllers/__pycache__/wtd.cpython-38.pyc
Normal file
Binary file not shown.
724
apps/createDocument/controllers/bg.py
Normal file
724
apps/createDocument/controllers/bg.py
Normal file
@@ -0,0 +1,724 @@
|
||||
from datetime import date, timedelta
|
||||
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 docxtpl import DocxTemplate
|
||||
from typing import Optional
|
||||
from docx import Document
|
||||
from ninja_extra.permissions import IsAuthenticated
|
||||
from ninja_jwt.authentication import JWTAuth
|
||||
# 导入模型
|
||||
from apps.project.models import Project, Dut, TestDemand, Problem
|
||||
# 工具类函数
|
||||
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
|
||||
# 根据轮次生成测评内容文档context
|
||||
from apps.createDocument.extensions.content_result_tool import create_round_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
|
||||
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.createSeiTaiDocument.extensions.logger import GenerateLogger
|
||||
|
||||
# @api_controller("/generateBG", tags=['生成报告文档系列'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||
@api_controller("/generateBG", tags=['生成报告文档系列'])
|
||||
class GenerateControllerBG(ControllerBase):
|
||||
logger = GenerateLogger('测评报告')
|
||||
|
||||
# important:删除之前的文件
|
||||
@route.get('/create/deleteBGDocument', url_name='delete-bg-document')
|
||||
def delete_bg_document(self, id: int):
|
||||
project_path_str = project_path(id)
|
||||
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/bg'
|
||||
delete_dir_files(save_path)
|
||||
|
||||
@route.get("/create/techyiju", url_name="create-techyiju")
|
||||
@transaction.atomic
|
||||
def create_techyiju(self, id: int):
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
duties_qs = project_obj.pdField.filter(Q(type='XQ') | Q(type='SJ') | Q(type='XY'))
|
||||
std_documents = []
|
||||
for duty in duties_qs:
|
||||
one_duty = {'doc_name': duty.name, 'ident_version': duty.ref + '-' + duty.version,
|
||||
'publish_date': duty.release_date, 'source': duty.release_union}
|
||||
std_documents.append(one_duty)
|
||||
# 添加大纲到这里
|
||||
## 判断是否为鉴定
|
||||
doc_name = f'{project_obj.name}软件测评大纲'
|
||||
if project_obj.report_type == '9':
|
||||
doc_name = f'{project_obj.name}软件鉴定测评大纲'
|
||||
# 时间控制类
|
||||
timer = DocTime(id)
|
||||
# 这里大纲版本升级如何处理
|
||||
dg_duty = {'doc_name': doc_name, 'ident_version': f'PT-{project_obj.ident}-TO-1.00',
|
||||
'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',
|
||||
'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',
|
||||
'publish_date': timer.jl_cover_time, 'source': project_obj.test_unit}
|
||||
# 循环所有轮次,除了第一轮
|
||||
std_documents.extend([sm_duty, jl_duty])
|
||||
rounds = project_obj.pField.exclude(key='0')
|
||||
name_list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
||||
index = 1
|
||||
for r in rounds:
|
||||
hsm_duty = {'doc_name': f'{project_obj.name}软件第{name_list[index]}轮测试说明',
|
||||
'ident_version': f'PT-{project_obj.ident}-TD{str(index + 1)}-1.00',
|
||||
'publish_date': r.beginTime, 'source': project_obj.test_unit}
|
||||
hjl_duty = {'doc_name': f'{project_obj.name}软件第{name_list[index]}轮测试记录',
|
||||
'ident_version': f'PT-{project_obj.ident}-TN{str(index + 1)}',
|
||||
'publish_date': r.endTime, 'source': project_obj.test_unit}
|
||||
std_documents.extend([hsm_duty, hjl_duty])
|
||||
index += 1
|
||||
# 生成二级文档
|
||||
context = {
|
||||
'std_documents': std_documents
|
||||
}
|
||||
return create_bg_docx("技术依据文件.docx", context, id)
|
||||
|
||||
# 测评地点和时间接口
|
||||
@route.get('/create/timeaddress')
|
||||
@transaction.atomic
|
||||
def create_timeaddress(self, id: int):
|
||||
timer = DocTime(id)
|
||||
context = timer.bg_address_time()
|
||||
return create_bg_docx('测评时间和地点.docx', context, id)
|
||||
|
||||
# 在报告生成多个版本被测软件基本信息
|
||||
@route.get('/create/baseInformation', url_name='create-baseInformation')
|
||||
def create_information(self, id: int):
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
languages = get_list_dict('language', project_obj.language)
|
||||
language_list = []
|
||||
for language in languages:
|
||||
language_list.append(language.get('ident_version'))
|
||||
|
||||
# 获取轮次
|
||||
rounds = project_obj.pField.all()
|
||||
round_list = []
|
||||
for r in rounds:
|
||||
round_dict = {}
|
||||
# 获取SO的dut
|
||||
so_dut: Dut = r.rdField.filter(type='SO').first()
|
||||
if so_dut:
|
||||
round_dict['version'] = so_dut.version
|
||||
round_dict['line_count'] = int(so_dut.total_lines)
|
||||
round_dict['effective_line'] = int(so_dut.effective_lines)
|
||||
round_list.append(round_dict)
|
||||
|
||||
context = {
|
||||
'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'),
|
||||
'language': "\a".join(language_list),
|
||||
'recv_date': project_obj.beginTime.strftime("%Y-%m-%d"),
|
||||
'dev_unit': project_obj.dev_unit,
|
||||
'version_info': round_list
|
||||
}
|
||||
return create_bg_docx('被测软件基本信息.docx', context, id)
|
||||
|
||||
# 生成测评完成情况
|
||||
@route.get('/create/completionstatus', url_name='create-completionstatus')
|
||||
def create_completionstatus(self, id: int):
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 找到第一轮轮次对象、第二轮轮次对象
|
||||
round1 = project_obj.pField.filter(key='0').first()
|
||||
# 第一轮测试项个数
|
||||
round1_demand_qs = round1.rtField.all()
|
||||
# 第一轮用例个数
|
||||
round1_case_qs = round1.rcField.all()
|
||||
# 这部分找出第一轮的所有测试类型,输出字符串,并排序
|
||||
test_type_set: set = set()
|
||||
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))))
|
||||
# 这里找出第一轮,源代码被测件,并获取版本
|
||||
so_dut = round1.rdField.filter(type='SO').first()
|
||||
so_dut_verson = "$请添加第一轮的源代码信息$"
|
||||
if so_dut:
|
||||
so_dut_verson = so_dut.version
|
||||
# 这里找出除第一轮的其他轮次
|
||||
rounds = project_obj.pField.exclude(key='0')
|
||||
rounds_str_chinese = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
||||
round_list = []
|
||||
for r in rounds:
|
||||
# 找所属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()
|
||||
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}个'
|
||||
else:
|
||||
current_round_description = '经测试软件更改正确,并且未引入新的问题'
|
||||
r_dict = {
|
||||
'version': so_dut.version if so_dut else '$请添加该轮次源代码信息$',
|
||||
'round_index': rounds_str_chinese[int(r.key)],
|
||||
'last_problem_count': last_problem_count,
|
||||
'current_round_description': current_round_description,
|
||||
'start_year': r.beginTime.year,
|
||||
'start_month': r.beginTime.month,
|
||||
'end_year': (r.beginTime + timedelta(days=4)).year, # 这里只是简单+4有待商榷
|
||||
'end_month': (r.beginTime + timedelta(days=4)).month,
|
||||
}
|
||||
round_list.append(r_dict)
|
||||
|
||||
# 这部分找到第一轮的问题
|
||||
problem_qs = get_round1_problem(project_obj)
|
||||
context = {
|
||||
'is_JD': True if project_obj.report_type == '9' else False,
|
||||
'project_name': project_obj.name,
|
||||
'start_time_year': project_obj.beginTime.year,
|
||||
'start_time_month': project_obj.beginTime.month,
|
||||
'round1_case_count': round1_case_qs.count(),
|
||||
'round1_demand_count': round1_demand_qs.count(),
|
||||
'round1_testType_str': '、'.join(round1_testType_list),
|
||||
'testType_count': len(round1_testType_list),
|
||||
'round1_version': so_dut_verson,
|
||||
'round1_problem_count': len(problem_qs),
|
||||
'end_time_year': date.today().year,
|
||||
'end_time_month': date.today().month,
|
||||
'round_list': round_list
|
||||
}
|
||||
# 注入时间
|
||||
timer = DocTime(id)
|
||||
context.update(**timer.bg_completion_situation())
|
||||
return create_bg_docx('测评完成情况.docx', context, id)
|
||||
|
||||
# 生成综述
|
||||
@route.get('/create/summary', url_name='create-summary')
|
||||
def create_summary(self, id: int):
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 找出所有问题单
|
||||
problem_qs = project_obj.projField.all()
|
||||
problem_grade_dict = {}
|
||||
problem_type_dict = {}
|
||||
# 建议问题统计
|
||||
problem_suggest_count = 0
|
||||
problem_suggest_solved_count = 0
|
||||
for problem in problem_qs:
|
||||
grade_key: str = get_str_dict(problem.grade, 'problemGrade')
|
||||
type_key: str = get_str_dict(problem.type, 'problemType')
|
||||
# 问题等级字典-计数
|
||||
if grade_key in problem_grade_dict.keys():
|
||||
problem_grade_dict[grade_key] += 1
|
||||
else:
|
||||
problem_grade_dict[grade_key] = 1
|
||||
# 问题类型字典-计数
|
||||
if type_key in problem_type_dict.keys():
|
||||
problem_type_dict[type_key] += 1
|
||||
else:
|
||||
problem_type_dict[type_key] = 1
|
||||
# 建议问题统计
|
||||
if problem.grade == '3':
|
||||
problem_suggest_count += 1
|
||||
if problem.status == '1':
|
||||
problem_suggest_solved_count += 1
|
||||
problem_grade_list = []
|
||||
problem_type_list = []
|
||||
for key, value in problem_grade_dict.items():
|
||||
problem_grade_list.append("".join([f"{key}问题", f"{value}个"]))
|
||||
for key, value in problem_type_dict.items():
|
||||
problem_type_list.append("".join([f"{key}", f"{value}个"]))
|
||||
# 用来生成建议问题信息
|
||||
if problem_suggest_count > 0 and problem_suggest_count - problem_suggest_solved_count > 0:
|
||||
all_str = (f"测评过程中提出了{problem_suggest_count}个建议改进,"
|
||||
f"其中{problem_suggest_solved_count}个建议改进已修改,"
|
||||
f"剩余{problem_suggest_count - problem_suggest_solved_count}个未修改并经总体单位认可同意")
|
||||
elif problem_suggest_count > 0 and problem_suggest_count - problem_suggest_solved_count == 0:
|
||||
all_str = (f"测评过程中提出了{problem_suggest_count}个建议改进,"
|
||||
f"全部建议问题已修改")
|
||||
else:
|
||||
all_str = f"测评过程中未提出建议项。"
|
||||
|
||||
context = {
|
||||
'problem_count': problem_qs.count(),
|
||||
'problem_grade_str': "、".join(problem_grade_list),
|
||||
'problem_type_str': '、'.join(problem_type_list),
|
||||
'all_str': all_str,
|
||||
}
|
||||
return create_bg_docx('综述.docx', context, id)
|
||||
|
||||
# 生成测试内容和结果[报告非常关键的一环-大模块]
|
||||
@route.get('/create/contentandresults_1', url_name='create-contentandresults_1')
|
||||
@transaction.atomic
|
||||
def create_content_results_1(self, id: int):
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
project_ident = project_obj.ident
|
||||
# ~~~~首轮信息~~~~
|
||||
round1 = project_obj.pField.filter(key='0').first() # !warning轮次1对象
|
||||
|
||||
# 1.处理首轮文档名称,新修改,这里取全部轮次的文档内容
|
||||
doc_list = []
|
||||
round1_duts = project_obj.pdField.filter(Q(type='SJ') | Q(type='XQ') | Q(type='XY'))
|
||||
index = 1
|
||||
for dut in round1_duts:
|
||||
dut_dict = {
|
||||
'name': dut.name,
|
||||
'ident': dut.ref,
|
||||
'version': dut.version,
|
||||
'index': index
|
||||
}
|
||||
doc_list.append(dut_dict)
|
||||
index += 1
|
||||
|
||||
# 2.处理首轮文档问题的统计 - 注意去重
|
||||
problems = project_obj.projField.all().distinct() # !important:大变量-项目所有问题
|
||||
problems_r1 = problems.filter(case__round__key='0') # !important:大变量-首轮的所有问题
|
||||
problems_doc_r1 = problems_r1.filter(case__test__testType='8') # 第一轮所有文档问题
|
||||
|
||||
# 3.第一轮代码审查问题统计/版本
|
||||
source_r1_dut = round1.rdField.filter(type='SO').first() # !warning:小变量-第一轮源代码对象
|
||||
program_r1_problems = problems_r1.filter(case__test__testType='2')
|
||||
|
||||
# 4.第一轮代码走查问题统计/版本
|
||||
zou_r1_problems = problems_r1.filter(case__test__testType='3')
|
||||
# 找下是否存在代码走查测试项
|
||||
r1_demand_qs = round1.rtField.filter(testType='3')
|
||||
has_zou = True if r1_demand_qs.count() > 0 else False
|
||||
|
||||
# 5.第一轮静态分析问题统计
|
||||
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'),
|
||||
~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)
|
||||
|
||||
# N.第一轮所有动态问题统计
|
||||
problems_dynamic_r1 = problems_r1.filter(~Q(case__test__testType='2'), ~Q(case__test__testType='3'),
|
||||
~Q(case__test__testType='8'),
|
||||
~Q(case__test__testType='15')) # !critical:大变量:第一轮动态问题单qs
|
||||
problem_dynamic_r1_type_str = create_problem_type_str(problems_dynamic_r1)
|
||||
problem_dynamic_r1_grade_str = create_problem_grade_str(problems_dynamic_r1)
|
||||
|
||||
context = {
|
||||
'project_name': project_obj.name,
|
||||
'doc_list': doc_list,
|
||||
'r1_doc_problem_count': problems_doc_r1.count(),
|
||||
'r1_doc_problem_str':
|
||||
f"{',其中' + create_problem_grade_str(problems_doc_r1) if problems_doc_r1.count() > 0 else '即未发现问题'}",
|
||||
'r1_version': source_r1_dut.version if source_r1_dut else "未录入首轮版本信息",
|
||||
'r1_program_problem_count': program_r1_problems.count(),
|
||||
'r1_program_problem_str':
|
||||
f'{",其中" + create_problem_grade_str(program_r1_problems) if program_r1_problems.count() > 0 else "即未发现问题"}',
|
||||
'r1_zou_problem_count': zou_r1_problems.count(),
|
||||
'r1_zou_problem_str': f'{",其中" + create_problem_grade_str(zou_r1_problems) if zou_r1_problems.count() > 0 else "即未发现问题"}',
|
||||
'has_zou': has_zou,
|
||||
'r1_static_problem_count': static_problems.count(),
|
||||
'r1_static_problem_str': f"{',其中' + create_problem_grade_str(static_problems) if static_problems.count() > 0 else '即未发现问题'}",
|
||||
'r1_case_count': case_r1_qs.count(),
|
||||
'r1_case_testType': "、".join(testType_list),
|
||||
'r1_case_testType_count': testType_count,
|
||||
'r1_problem_counts': len(problems_dynamic_r1),
|
||||
'r1_exe_info_all': summary_r1_demand_info,
|
||||
'r1_exe_info_type': summry_r1_demandType_info,
|
||||
'r1_dynamic_problem_str': problem_dynamic_r1_type_str,
|
||||
'r1_dynamic_problem_grade_str': problem_dynamic_r1_grade_str,
|
||||
}
|
||||
return create_bg_docx("测试内容和结果_第一轮次.docx", context, id)
|
||||
|
||||
# 查询除第一轮以外,生成其他轮次测试内容和结果
|
||||
@route.get('/create/contentandresults_2', url_name='create-contentandresults_2')
|
||||
@transaction.atomic
|
||||
def create_content_results_2(self, id: int):
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 查询除第一轮,其他有几轮
|
||||
round_qs = project_obj.pField.filter(~Q(key='0'))
|
||||
round_str_list = [item.key for item in round_qs]
|
||||
# 每个轮次都需要生成一个测试内容和标题
|
||||
project_path_str = project_path(id)
|
||||
for round_str in round_str_list:
|
||||
context = create_round_context(project_obj, round_str)
|
||||
template_path = Path.cwd() / 'media' / project_path_str / 'form_template' / 'bg' / '测试内容和结果_第二轮次.docx'
|
||||
doc = DocxTemplate(template_path)
|
||||
doc.render(context)
|
||||
try:
|
||||
doc.save(
|
||||
Path.cwd() / "media" / project_path_str / "output_dir/bg" / f"测试内容和结果_第{context['round_id']}轮次.docx")
|
||||
except PermissionError:
|
||||
ChenResponse(code=400, status=400, message='您已打开生成文件,请关闭后再试...')
|
||||
|
||||
# 软件问题统计
|
||||
@route.get('/create/problem_statistics')
|
||||
@transaction.atomic
|
||||
def create_problem_statistics(self, id: int):
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
problems = project_obj.projField.all().distinct() # 项目所有问题单
|
||||
context = {
|
||||
'closed_count': problems.filter(status='1').count(),
|
||||
'noclosed_count': problems.count() - problems.filter(status='1').count(),
|
||||
'problem_table': create_problem_table(problems),
|
||||
'problem_table_2': create_problem_type_table(problems)
|
||||
}
|
||||
return create_bg_docx("软件问题统计.docx", context, id)
|
||||
|
||||
# 测试有效性充分性说明
|
||||
@route.get('/create/effect_and_adquacy', url_name='create-effect_and_adquacy')
|
||||
@transaction.atomic
|
||||
def create_effect_and_adquacy(self, id: int):
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 判断是否为鉴定
|
||||
is_JD = False
|
||||
if project_obj.report_type == '9':
|
||||
is_JD = True
|
||||
# 统计测试项数量
|
||||
demand_qs = project_obj.ptField
|
||||
# 统计用例个数
|
||||
case_qs = project_obj.pcField
|
||||
# 测试用例的类型统计个数
|
||||
testType_list, testType_count = create_str_testType_list(case_qs.all())
|
||||
# 问题单总个数
|
||||
problem_qs = project_obj.projField
|
||||
|
||||
context = {
|
||||
'project_name': project_obj.name,
|
||||
'demand_count': demand_qs.count(),
|
||||
'case_count': case_qs.count(),
|
||||
'testType_list': "、".join(testType_list),
|
||||
'testType_count': testType_count,
|
||||
'problem_count': problem_qs.count(),
|
||||
'is_JD': is_JD,
|
||||
}
|
||||
return create_bg_docx('测试有效性充分性说明.docx', context, id)
|
||||
|
||||
# 需求指标符合性情况
|
||||
@route.get('/create/demand_effective', url_name='create-demand_effective')
|
||||
@transaction.atomic
|
||||
def create_demand_effective(self, id: int):
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 查询所有需求规格说明的 - 设计需求
|
||||
round1_design_qs = project_obj.psField.filter(round__key='0', dut__type='XQ') # qs:第一轮需求文档的设计需求
|
||||
# 将第一轮需求文档名称
|
||||
dut_name = f"《{project_obj.name}软件需求规格说明》"
|
||||
data_list = []
|
||||
design_index = 1
|
||||
for design in round1_design_qs:
|
||||
# 如果为“/”则写为隐含需求
|
||||
if design.chapter.strip() == '/':
|
||||
design_dict = {'source': "隐含需求"}
|
||||
else:
|
||||
design_dict = {'source': "".join([dut_name, design.name, ':', design.chapter])}
|
||||
# 将设计需求描述筛入
|
||||
rich_parser = RichParser(design.description)
|
||||
p_list = rich_parser.get_final_p_list()
|
||||
design_dict['description'] = '\a'.join(p_list)
|
||||
# 找出其中所有demand
|
||||
demand_qs = design.dtField.all()
|
||||
if not demand_qs.exists():
|
||||
design_dict['demands'] = '未关联测试项'
|
||||
else:
|
||||
demand_list = []
|
||||
index = 0
|
||||
for demand in demand_qs:
|
||||
index += 1
|
||||
demand_abbr = get_str_abbr(demand.testType, 'testType')
|
||||
demand_list.append(f'{index}、XQ_{demand_abbr}_{demand.ident}-{demand.name}')
|
||||
design_dict['demands'] = '\a'.join(demand_list)
|
||||
# 通过还是未通过
|
||||
design_dict['pass'] = '通过'
|
||||
design_dict['index'] = design_index
|
||||
data_list.append(design_dict)
|
||||
design_index += 1
|
||||
|
||||
# ~~~~指标符合性表~~~~
|
||||
data_yz_list = []
|
||||
# qs:第一轮需求文档的设计需求
|
||||
has_YZ = False
|
||||
round1_design_yz_qs = project_obj.psField.filter(round__key='0', dut__type='YZ')
|
||||
if round1_design_yz_qs.exists():
|
||||
has_YZ = True
|
||||
# 如果有研制总要求的dut,继续
|
||||
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)])}
|
||||
# 找出其中所有demand
|
||||
demand_qs = design.dtField.all()
|
||||
if not demand_qs.exists():
|
||||
design_dict['demands'] = '未关联测评大纲条款'
|
||||
else:
|
||||
# 大纲条款的列表
|
||||
demand_list = []
|
||||
demand_step_list = []
|
||||
index = 0
|
||||
for demand in demand_qs:
|
||||
index += 1
|
||||
demand_list.append(f'{index}、{demand.ident}-{demand.name}')
|
||||
# 测试需求步骤的列表
|
||||
step_list = []
|
||||
for step in demand.testQField.all():
|
||||
step_list.append(step.subName)
|
||||
demand_step_list.append('\a'.join(step_list))
|
||||
|
||||
design_dict['demands'] = '\a'.join(demand_list)
|
||||
design_dict['steps'] = '\a'.join(demand_step_list)
|
||||
|
||||
# 通过还是未通过
|
||||
design_dict['pass'] = '通过'
|
||||
data_yz_list.append(design_dict)
|
||||
# 处理没有steps字段
|
||||
if 'steps' not in design_dict:
|
||||
design_dict['steps'] = '该设计需求未关联测评大纲条款'
|
||||
|
||||
context = {
|
||||
'data_list': data_list,
|
||||
'data_yz_list': data_yz_list,
|
||||
'has_YZ': has_YZ,
|
||||
}
|
||||
return create_bg_docx('需求指标符合性情况.docx', context, id)
|
||||
|
||||
# 软件质量评价
|
||||
@route.get('/create/quality_evaluate', url_name='create-quality_evaluate')
|
||||
@transaction.atomic
|
||||
def create_quality_evaluate(self, id: int):
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 找出最后一轮
|
||||
rounds = project_obj.pField.order_by('-key') # qs:轮次
|
||||
last_dut_so: Optional[Dut] = None
|
||||
for round in rounds:
|
||||
# 查询其源代码dut
|
||||
dut_so = round.rdField.filter(type='SO').first()
|
||||
if dut_so:
|
||||
last_dut_so = dut_so
|
||||
break
|
||||
# 计算千行缺陷率
|
||||
problem_count = project_obj.projField.count()
|
||||
# 如果没有轮次信息则返回错误
|
||||
if not last_dut_so:
|
||||
return ChenResponse(code=400, status=400, message='您还未创建轮次,请进入工作区创建')
|
||||
# 计算注释率
|
||||
## 总行数
|
||||
total_lines = int(last_dut_so.total_lines)
|
||||
## 有效注释行
|
||||
effective_comment_lines = int(last_dut_so.comment_lines)
|
||||
comment_ratio = (effective_comment_lines / total_lines) * 100
|
||||
context = {
|
||||
'last_version': last_dut_so.version, # 最后轮次代码版本
|
||||
'comment_percent': format(comment_ratio, '.4f'), # 最后轮次代码注释率
|
||||
'qian_comment_rate': format(problem_count / int(last_dut_so.total_lines) * 1000, '.4f'),
|
||||
'avg_function_lines': "XXXX",
|
||||
'avg_cyclomatic': 'XXXX',
|
||||
'avg_fan_out': 'XXXX',
|
||||
}
|
||||
# 判断是否有metrics一对一模型关联
|
||||
if hasattr(last_dut_so, 'metrics'):
|
||||
context['avg_function_lines'] = str(last_dut_so.metrics.avg_function_lines)
|
||||
context['avg_cyclomatic'] = str(last_dut_so.metrics.avg_cyclomatic)
|
||||
context['avg_fan_out'] = str(last_dut_so.metrics.avg_fan_out)
|
||||
return create_bg_docx('软件质量评价.docx', context, id)
|
||||
|
||||
# 软件总体结论
|
||||
@route.get('/create/entire', url_name='create-entire')
|
||||
@transaction.atomic
|
||||
def create_entire(self, id: int):
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 是否鉴定
|
||||
is_JD = False
|
||||
if project_obj.report_type == '9':
|
||||
is_JD = True
|
||||
# 找出最后一轮并且有源代码的dut
|
||||
rounds = project_obj.pField.order_by('-key') # qs:轮次
|
||||
last_dut_so: Optional[Dut] = None
|
||||
for round in rounds:
|
||||
# 查询其源代码dut
|
||||
dut_so = round.rdField.filter(type='SO').first()
|
||||
if dut_so:
|
||||
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'))
|
||||
# ***Inspect-start***
|
||||
if not last_dut_so:
|
||||
self.logger.model = '测评报告'
|
||||
self.logger.write_warning_log('总体结论', f'项目没创建轮次,请检查')
|
||||
return None
|
||||
# ***Inspect-end***
|
||||
context = {
|
||||
'name': project_obj.name,
|
||||
'last_version': last_dut_so.version,
|
||||
'is_JD': is_JD,
|
||||
'dut_list': [
|
||||
{
|
||||
'index': index + 1,
|
||||
'name': dut_single.name,
|
||||
'ref': dut_single.ref,
|
||||
'version': dut_single.version,
|
||||
} for index, dut_single in enumerate(duties_qs)
|
||||
],
|
||||
'last_dut_so_ref': last_dut_so.ref,
|
||||
}
|
||||
return create_bg_docx('总体结论.docx', context, id)
|
||||
|
||||
# 研总需求追踪 - 注意生成每个轮次的追踪
|
||||
@route.get('/create/yzxq_track', url_name='create-yzxq_track')
|
||||
@transaction.atomic
|
||||
def create_yzxq_track(self, id: int):
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 是否是鉴定的变量,如果为鉴定,则需要研总的追踪
|
||||
is_JD = False
|
||||
if project_obj.report_type == '9':
|
||||
is_JD = True
|
||||
# 查询多少个轮次
|
||||
round_count = project_obj.pField.count()
|
||||
round_str_list = [str(i) for i in range(round_count)]
|
||||
# 生成研总的design_list
|
||||
design_list_all = []
|
||||
for round_str in round_str_list:
|
||||
# 找寻轮次里面源代码版本
|
||||
dut_version = 'XXX'
|
||||
dut_so = Dut.objects.filter(round__key=round_str, type='SO').first()
|
||||
if dut_so:
|
||||
dut_version = dut_so.version
|
||||
if is_JD:
|
||||
design_list_yz = create_bg_round1_zhui(project_obj, dut_str='YZ', round_str=round_str)
|
||||
one_table_dict = {
|
||||
'design_list': design_list_yz,
|
||||
'version': 'V' + dut_version,
|
||||
'title': '研制总要求'
|
||||
}
|
||||
design_list_all.append(one_table_dict)
|
||||
design_list_xq = create_bg_round1_zhui(project_obj, dut_str='XQ', round_str=round_str)
|
||||
one_table_dict_xq = {
|
||||
'design_list': design_list_xq,
|
||||
'version': 'V' + dut_version,
|
||||
'title': '需求规格说明'
|
||||
}
|
||||
design_list_all.append(one_table_dict_xq)
|
||||
else:
|
||||
design_list_xq = create_bg_round1_zhui(project_obj, dut_str='XQ', round_str=round_str)
|
||||
one_table_dict_xq = {
|
||||
'design_list': design_list_xq,
|
||||
'version': 'V' + dut_version,
|
||||
'title': '需求规格说明'
|
||||
}
|
||||
design_list_all.append(one_table_dict_xq)
|
||||
context = {
|
||||
'design_list_all': design_list_all,
|
||||
}
|
||||
|
||||
# 手动渲染tpl文档
|
||||
project_path_str = project_path(id)
|
||||
input_file = Path.cwd() / 'media' / project_path_str / 'form_template' / 'bg' / '研总需归追踪.docx'
|
||||
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.save(temporary_file)
|
||||
# 通过docx合并单元格
|
||||
if temporary_file.is_file():
|
||||
try:
|
||||
docu = Document(temporary_file)
|
||||
# 循环找到表格
|
||||
for table in docu.tables:
|
||||
util.merge_all_cell(table)
|
||||
# 储存到合适位置
|
||||
docu.save(out_put_file)
|
||||
return ChenResponse(code=200, status=200, message='文档生成成功...')
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='请检查文件是否打开,如果打开则关闭...')
|
||||
else:
|
||||
return ChenResponse(code=400, status=400, message='中间文档未找到,请检查你模版是否存在...')
|
||||
|
||||
# 生成问题汇总表
|
||||
@route.get('/create/problems_summary', url_name='create-problem_summary')
|
||||
@transaction.atomic
|
||||
def create_problem_summary(self, id: int):
|
||||
tpl_doc = Path.cwd() / "media" / project_path(id) / "form_template" / "bg" / "问题汇总表.docx"
|
||||
doc = DocxTemplate(tpl_doc)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
problem_prefix = "_".join(['PT', project_obj.ident])
|
||||
problems = project_obj.projField
|
||||
# 先查询有多少轮次
|
||||
round_count = project_obj.pField.count()
|
||||
round_str_list = [str(x) for x in range(round_count)]
|
||||
data_list = []
|
||||
for round_str in round_str_list:
|
||||
# 查询所属当前轮次的SO-dut
|
||||
so_dut = Dut.objects.filter(round__key=round_str, type='SO').first()
|
||||
round_dict = {
|
||||
'static': [],
|
||||
'dynamic': [],
|
||||
'version': so_dut.version if so_dut else "v1.0",
|
||||
}
|
||||
# 找出轮次中静态问题
|
||||
r1_static_problems = problems.filter(case__round__key=round_str,
|
||||
case__test__testType__in=['2', '3', '8', '15']).distinct()
|
||||
for problem in r1_static_problems:
|
||||
problem_dict = create_one_problem_dit(problem, problem_prefix, doc)
|
||||
round_dict['static'].append(problem_dict)
|
||||
|
||||
# 找出轮次中动态问题
|
||||
r1_dynamic_problems = problems.filter(case__round__key=round_str).exclude(
|
||||
case__test__testType__in=['2', '3', '8', '15']).distinct()
|
||||
for problem in r1_dynamic_problems:
|
||||
problem_dict = create_one_problem_dit(problem, problem_prefix, doc)
|
||||
round_dict['dynamic'].append(problem_dict)
|
||||
data_list.append(round_dict)
|
||||
|
||||
context = {
|
||||
'data_list': data_list
|
||||
}
|
||||
|
||||
doc.render(context)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/bg" / "问题汇总表.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/modi_list', url_name='create-modi-list')
|
||||
@transaction.atomic
|
||||
def create_modi_list(self, id: int):
|
||||
tpl_doc = Path.cwd() / "media" / project_path(id) / "form_template" / "bg" / "摸底清单.docx"
|
||||
doc = DocxTemplate(tpl_doc)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 查询所有轮次“摸底测试”的测试项
|
||||
demands_qs = project_obj.ptField.all()
|
||||
modi_list = []
|
||||
for demand in demands_qs:
|
||||
one_modi = {}
|
||||
testType_str = get_str_dict(demand.testType, 'testType')
|
||||
if "摸底" in testType_str:
|
||||
# 1.找到设计需求章节号以及描述
|
||||
design = demand.design
|
||||
one_modi['source'] = f"{design.chapter}-{design.name}" if design.chapter != '/' else "隐含需求"
|
||||
one_modi['desc'] = "\a".join(RichParser(design.description).get_final_p_list())
|
||||
# 2.找所有的case
|
||||
case_qs = demand.tcField.all()
|
||||
one_modi['result'] = []
|
||||
for case in case_qs:
|
||||
# 找case的步骤
|
||||
for step in case.step.all():
|
||||
if step.passed == '1': # 只获取通过的
|
||||
one_modi['result'].append("\a".join(RichParser(step.result).get_final_p_list()))
|
||||
modi_list.append(one_modi)
|
||||
# 渲染上下文
|
||||
context = {
|
||||
'modi_list': modi_list,
|
||||
}
|
||||
doc.render(context)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/bg" / "摸底清单.docx")
|
||||
return ChenResponse(status=200, code=200, message="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
784
apps/createDocument/controllers/dg.py
Normal file
784
apps/createDocument/controllers/dg.py
Normal file
@@ -0,0 +1,784 @@
|
||||
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 pathlib import Path
|
||||
from utils.chen_response import ChenResponse
|
||||
# 导入数据库ORM
|
||||
from apps.project.models import Project, Contact, Abbreviation
|
||||
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
|
||||
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||
from apps.createDocument.extensions.documentTime import DocTime
|
||||
from utils.path_utils import project_path
|
||||
# 记录生成日志
|
||||
from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
|
||||
# 导入mixins-处理文档片段
|
||||
from apps.createDocument.extensions.mixins import FragementToolsMixin
|
||||
|
||||
# @api_controller("/generate", tags=['生成大纲文档'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||
@api_controller("/generate", tags=['生成大纲文档'])
|
||||
class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
logger = GenerateLogger('测评大纲')
|
||||
|
||||
@route.get("/create/testdemand", url_name="create-testdemand")
|
||||
@transaction.atomic
|
||||
def create_testdemand(self, id: int): # type:ignore
|
||||
"""目前生成第一轮测试项"""
|
||||
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)
|
||||
# 先查询dict字典,查出总共有多少个testType
|
||||
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 _ in range(1, test_type_len + 1)]
|
||||
|
||||
# 查出第一轮所有testdemand
|
||||
project_round_one = project_qs.pField.filter(key=0).first()
|
||||
testDemand_qs = project_round_one.rtField.all()
|
||||
|
||||
# 遍历第一轮测试项:默认是ID排序
|
||||
for single_qs in testDemand_qs:
|
||||
type_index = type_number_list.index(int(single_qs.testType))
|
||||
# 先查询其testDemandContent信息
|
||||
content_list = []
|
||||
for (index, content) in enumerate(single_qs.testQField.all()):
|
||||
content_dict = {
|
||||
"index": index + 1,
|
||||
"rindex": str(index + 1).rjust(2, '0'),
|
||||
"subName": content.subName,
|
||||
# 修改遍历content下面的step,content变量是TestDemandContent表
|
||||
"subStep": [
|
||||
{'index': index + 1, 'operation': step_obj.operation, 'expect': step_obj.expect}
|
||||
for (index, step_obj) in enumerate(content.testStepField.all())
|
||||
],
|
||||
}
|
||||
content_list.append(content_dict)
|
||||
# 查询测试项中testMethod
|
||||
testmethod_str = ''
|
||||
for dict_item_qs in Dict.objects.get(code="testMethod").dictItem.all():
|
||||
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.append(ddict)
|
||||
|
||||
# 组装单个测试项
|
||||
testdemand_dict = {
|
||||
"name": single_qs.name,
|
||||
"key": single_qs.key,
|
||||
"ident": get_ident(single_qs),
|
||||
"priority": get_str_dict(single_qs.priority, "priority"),
|
||||
"doc_list": doc_list,
|
||||
"design_description": desc_list,
|
||||
"test_demand_content": content_list,
|
||||
"testMethod": testmethod_str,
|
||||
"adequacy": single_qs.adequacy.replace("\n", "\a"),
|
||||
"testDesciption": single_qs.testDesciption.replace("\n", "\a") # 测试项描述
|
||||
}
|
||||
list_list[type_index].append(testdemand_dict)
|
||||
|
||||
# 定义渲染context字典
|
||||
context = {
|
||||
"project_name": project_qs.name
|
||||
}
|
||||
output_list = []
|
||||
|
||||
for (index, li) in enumerate(list_list):
|
||||
qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1))
|
||||
context_str = qs.title
|
||||
sort = qs.sort
|
||||
table = {
|
||||
"type": context_str,
|
||||
"item": li,
|
||||
"sort": sort
|
||||
}
|
||||
output_list.append(table)
|
||||
|
||||
# 排序1:测试类型排序
|
||||
output_list = sorted(output_list, key=(lambda x: x["sort"]))
|
||||
|
||||
context["data"] = output_list
|
||||
doc.render(context)
|
||||
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/yiju", url_name='create-yiju')
|
||||
@transaction.atomic
|
||||
def create_yiju(self, id: int):
|
||||
# 先找出所属项目
|
||||
project_qs = get_object_or_404(Project, id=id)
|
||||
# 找出该项目的真实依据文件qs
|
||||
yiju_list = get_list_dict('standard', project_qs.standard)
|
||||
context = {
|
||||
'std_documents': yiju_list
|
||||
}
|
||||
return create_dg_docx('标准依据文件.docx', context, id)
|
||||
|
||||
@route.get("/create/techyiju", url_name='create-techyiju')
|
||||
@transaction.atomic
|
||||
def create_techyiju(self, id: int):
|
||||
# 找出所属项目
|
||||
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(
|
||||
round__key='0')
|
||||
# 先定义个字典
|
||||
std_documents = []
|
||||
for duty in duties_qs:
|
||||
one_duty = {'doc_name': duty.name, 'ident_version': duty.ref + '-' + duty.version,
|
||||
'publish_date': duty.release_date, 'source': duty.release_union}
|
||||
std_documents.append(one_duty)
|
||||
|
||||
# 生成二级文档
|
||||
context = {
|
||||
'std_documents': std_documents
|
||||
}
|
||||
return create_dg_docx('技术依据文件.docx', context, id)
|
||||
|
||||
@route.get("/create/contact", url_name='create-contact')
|
||||
@transaction.atomic
|
||||
def create_contact(self, id: int):
|
||||
# 先找出所属项目
|
||||
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',
|
||||
'dev_contact', 'dev_contact_phone', 'test_unit', 'test_contact',
|
||||
'test_contact_phone'])
|
||||
# 根据entrust_unit、dev_unit、test_unit查找Contact中地址信息
|
||||
entrust_addr = Contact.objects.get(name=contact_dict['entrust_unit']).addr
|
||||
dev_addr = Contact.objects.get(name=contact_dict['dev_unit']).addr
|
||||
test_addr = Contact.objects.get(name=contact_dict['test_unit']).addr
|
||||
contact_dict['entrust_addr'] = entrust_addr
|
||||
contact_dict['dev_addr'] = dev_addr
|
||||
contact_dict['test_addr'] = test_addr
|
||||
context = {
|
||||
'datas': contact_dict
|
||||
}
|
||||
return create_dg_docx('联系人和方式.docx', context, id)
|
||||
|
||||
# 生成测评时间和地点
|
||||
@route.get('/create/timeaddress', url_name='create-timeaddress')
|
||||
@transaction.atomic
|
||||
def create_timeaddress(self, id: int):
|
||||
doc_timer = DocTime(id)
|
||||
context = doc_timer.dg_address_time()
|
||||
return create_dg_docx('测评时间和地点.docx', context, id)
|
||||
|
||||
# 生成【主要功能和性能指标】文档片段
|
||||
@route.get('/create/indicators', url_name='create-indicators')
|
||||
@transaction.atomic
|
||||
def create_indicators(self, id: int):
|
||||
# 获取文档片段模版路径
|
||||
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '主要功能和性能指标.docx'
|
||||
doc = DocxTemplate(input_path)
|
||||
# 获取项目对象
|
||||
project_obj: Project = get_object_or_404(Project, id=id)
|
||||
# 定义JINJA上下文
|
||||
# 获取第一轮次所有功能、性能的设计需求[目前只支持XQ和YZ],因为要获取dut信息进行连表查询
|
||||
q_ex = Q(dut__type='XQ') | Q(dut__type='YZ')
|
||||
design_qs = project_obj.psField.filter(q_ex, round__key='0').select_related('dut')
|
||||
# 1.功能性能覆盖表
|
||||
# 定义功能/性能两个列表
|
||||
func_design_list = []
|
||||
performance_design_list = []
|
||||
# 遍历设计需求,然后放入两个列表
|
||||
for design_obj in design_qs:
|
||||
# 功能指标描述
|
||||
description = RichParser(design_obj.description).get_final_p_list()
|
||||
# 覆盖情况-【查询测试项-测试子项名称】
|
||||
demand_qs = design_obj.dtField.all()
|
||||
str_list = []
|
||||
for demand in demand_qs:
|
||||
# 再查询测试子项
|
||||
for subDemand in demand.testQField.all():
|
||||
str_list.append(subDemand.subName)
|
||||
coverage_str = "、".join(str_list)
|
||||
design_context_obj = {
|
||||
'chapter_info': f"《{design_obj.dut.name}》{design_obj.chapter}-{design_obj.name}",
|
||||
'indicator': "\a".join(description),
|
||||
'coverage': f"对{design_obj.name}进行全覆盖测试,包含{coverage_str},验证所描述内容是否满足需求等文档的要求"
|
||||
}
|
||||
demandType_str = get_str_dict(design_obj.demandType, 'demandType')
|
||||
# 判断是否包含“功能”/“性能”字样
|
||||
if '功能' in demandType_str:
|
||||
func_design_list.append(design_context_obj)
|
||||
elif '性能' in demandType_str:
|
||||
performance_design_list.append(design_context_obj)
|
||||
# 2.摸底指标清单
|
||||
# 先查第一轮次所有测试项
|
||||
is_has_modi = False
|
||||
md_demand_list = []
|
||||
round1_demand_qs = project_obj.ptField.filter(round__key='0')
|
||||
for one_demand in round1_demand_qs:
|
||||
testType_str = get_str_dict(one_demand.testType, 'testType')
|
||||
if '摸底' in testType_str:
|
||||
is_has_modi = True
|
||||
md_demand_list.append({
|
||||
'xq_source': "隐含需求",
|
||||
'desc': one_demand.testDesciption,
|
||||
'demand_name': one_demand.name,
|
||||
'demand_ident': "".join(["XQ_MD_", one_demand.ident])
|
||||
})
|
||||
# 上下文添加
|
||||
context = {
|
||||
'project_name': project_obj.name,
|
||||
'func_design_list': func_design_list,
|
||||
'performance_design_list': performance_design_list,
|
||||
'md_demand_list': md_demand_list,
|
||||
'is_has_modi': is_has_modi
|
||||
}
|
||||
doc.render(context)
|
||||
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/softComposition', url_name='create-softComposition')
|
||||
@transaction.atomic
|
||||
def create_softComposition(self, id: int):
|
||||
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 = {
|
||||
"replace": replace, # 指定是否由数据库文档片段进行生成
|
||||
"user_content": frag and rich_text_list
|
||||
}
|
||||
doc.render(context)
|
||||
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/interface', url_name='create-interface')
|
||||
def create_interface(self, id: int):
|
||||
project_qs = get_object_or_404(Project, id=id)
|
||||
project_name = project_qs.name
|
||||
interfaceNameList = []
|
||||
# 查询接口列表
|
||||
iters = project_qs.psField.filter(demandType=3)
|
||||
iters_length = len(iters)
|
||||
index = 0
|
||||
for inter in iters:
|
||||
interfaceNameList.append(inter.name)
|
||||
index += 1
|
||||
if index < iters_length:
|
||||
interfaceNameList.append('、')
|
||||
# 对每个接口进行字典处理
|
||||
interface_list = []
|
||||
for interface in iters:
|
||||
interface_dict = {
|
||||
'name': interface.name,
|
||||
'ident': interface.ident,
|
||||
'source': interface.source,
|
||||
'to': interface.to,
|
||||
'type': interface.type,
|
||||
'protocal': interface.protocal,
|
||||
}
|
||||
interface_list.append(interface_dict)
|
||||
context = {
|
||||
'project_name': project_name,
|
||||
'iters': interfaceNameList,
|
||||
'iter_list': interface_list,
|
||||
}
|
||||
return create_dg_docx('被测软件接口.docx', context, id)
|
||||
|
||||
# 生成顶层技术文件
|
||||
@route.get('/create/top_file', url_name='create-performance')
|
||||
def create_top_file(self, id: int):
|
||||
project_obj: Project = get_object_or_404(Project, id=id)
|
||||
is_JD = True if project_obj.report_type == '9' else False
|
||||
dut_qs = project_obj.pdField.filter(type='YZ')
|
||||
dut_list = [{
|
||||
'index': index + 2 if is_JD else index + 1,
|
||||
'name': dut_obj.name,
|
||||
'ident_and_version': '-'.join([dut_obj.ref, dut_obj.version]),
|
||||
'publish_date': dut_obj.release_date,
|
||||
'source': dut_obj.release_union,
|
||||
} for index, dut_obj in enumerate(dut_qs)]
|
||||
context = {
|
||||
'project_name': project_obj.name,
|
||||
'is_JD': is_JD,
|
||||
'dut_list': dut_list,
|
||||
}
|
||||
return create_dg_docx('顶层技术文件.docx', context, id)
|
||||
|
||||
# 静态测试环境说明
|
||||
@route.get('/create/static_env', url_name='create-static_env')
|
||||
def create_static_env(self, id: int):
|
||||
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 = {
|
||||
"replace": replace, # 指定是否由数据库文档片段进行生成
|
||||
"user_content": frag and rich_text_list
|
||||
}
|
||||
doc.render(context)
|
||||
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/static_soft', url_name='create-static_soft')
|
||||
def create_static_soft(self, id: int):
|
||||
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 = {
|
||||
"replace": replace, # 指定是否由数据库文档片段进行生成
|
||||
"user_content": frag and rich_text_list
|
||||
}
|
||||
return create_dg_docx("静态软件项.docx", context, id)
|
||||
|
||||
# 静态硬件和固件项
|
||||
@route.get('/create/static_hard', url_name='create-static_hard')
|
||||
def create_static_hard(self, id: int):
|
||||
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 = {
|
||||
"replace": replace, # 指定是否由数据库文档片段进行生成
|
||||
"user_content": frag and rich_text_list
|
||||
}
|
||||
return create_dg_docx("静态硬件和固件项.docx", context, id)
|
||||
|
||||
# 动态测评环境说明
|
||||
@route.get('/create/dynamic_env', url_name='create-dynamic_env')
|
||||
def create_dynamic_env(self, id: int):
|
||||
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 = {
|
||||
"replace": replace,
|
||||
"user_content": frag and rich_text_list
|
||||
}
|
||||
doc.render(context)
|
||||
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/dynamic_soft', url_name='create-dynamic_soft')
|
||||
def create_dynamic_soft(self, id: int):
|
||||
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 = {
|
||||
"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):
|
||||
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 = {
|
||||
"replace": replace,
|
||||
"user_content": frag and rich_text_list
|
||||
}
|
||||
return create_dg_docx("动态硬件和固件项.docx", context, id)
|
||||
|
||||
# 测试数据
|
||||
@route.get('/create/test_data', url_name='create-test_data')
|
||||
def create_test_data(self, id: int):
|
||||
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 = {
|
||||
"replace": replace,
|
||||
"user_content": frag and rich_text_list
|
||||
}
|
||||
return create_dg_docx("测评数据.docx", context, id)
|
||||
|
||||
# 环境差异性分析
|
||||
@route.get('/create/env_diff', url_name='create-env_diff')
|
||||
def create_env_diff(self, id: int):
|
||||
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 = {
|
||||
"replace": replace,
|
||||
"user_content": frag and rich_text_list
|
||||
}
|
||||
return create_dg_docx("环境差异性分析.docx", context, id)
|
||||
|
||||
# 生成被测软件-基本信息
|
||||
@route.get('/create/baseInformation', url_name='create-baseInformation')
|
||||
def create_information(self, id: int):
|
||||
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'))
|
||||
# 版本先找第一轮
|
||||
project_round = project_qs.pField.filter(key=0).first()
|
||||
first_round_SO = project_round.rdField.filter(type='SO').first()
|
||||
if not first_round_SO:
|
||||
return ChenResponse(code=400, status=400, message='您还未创建轮次,请进入工作区创建')
|
||||
version = first_round_SO.version
|
||||
line_count = int(first_round_SO.total_lines)
|
||||
dev_unit = project_qs.dev_unit
|
||||
# 渲染上下文
|
||||
context = {
|
||||
'project_name': project_qs.name,
|
||||
'security_level': security,
|
||||
'language': "\a".join(language_list),
|
||||
'version': version,
|
||||
'line_count': line_count,
|
||||
'effective_line': int(first_round_SO.effective_lines),
|
||||
'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
|
||||
}
|
||||
return create_dg_docx('被测软件基本信息.docx', context, id)
|
||||
|
||||
# 生成测试级别和测试类型
|
||||
@route.get('/create/levelAndType', url_name='create-levelAndType')
|
||||
def create_levelAndType(self, id: int):
|
||||
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, '测试级别和测试类型')
|
||||
if replace:
|
||||
context = {
|
||||
"replace": replace,
|
||||
"user_content": frag and rich_text_list
|
||||
}
|
||||
else:
|
||||
# 如果没有片段替换,则利用数据生成信息
|
||||
project_qs = get_object_or_404(Project, id=id)
|
||||
# 获取所有已录入测试类型
|
||||
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))
|
||||
# 定义测试类型一览的顺序,注意word里面也要一样
|
||||
word_types = ['文档审查', '静态分析', '代码审查', '逻辑测试', '功能测试', '性能测试', '边界测试',
|
||||
'恢复性测试', '安装性测试', '数据处理测试', '余量测试', '强度测试', '接口测试',
|
||||
'人机交互界面测试', '兼容性测试']
|
||||
type_index = []
|
||||
for index, test_type in enumerate(word_types):
|
||||
for exist_type in type_name_list:
|
||||
if exist_type == test_type:
|
||||
type_index.append(str(index))
|
||||
context = {
|
||||
"testTypes": "、".join(type_name_list),
|
||||
"project_name": project_qs.name,
|
||||
"type_index": type_index
|
||||
}
|
||||
return create_dg_docx("测试级别和测试类型.docx", context, id)
|
||||
|
||||
# 生成测试策略
|
||||
@route.get('/create/strategy', url_name='create-strategy')
|
||||
def create_strategy(self, id: int):
|
||||
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, '测试策略')
|
||||
if replace:
|
||||
context = {
|
||||
"replace": replace,
|
||||
"user_content": frag and rich_text_list
|
||||
}
|
||||
else:
|
||||
# 如果没有片段替换,则利用数据生成信息
|
||||
project_qs = get_object_or_404(Project, id=id)
|
||||
# 根据关键等级检查是否有代码审查
|
||||
security = project_qs.security_level
|
||||
isDmsc = True if int(security) <= 2 else False
|
||||
# 获取当前测试项的测试类型
|
||||
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))
|
||||
context = {
|
||||
"project_name": project_qs.name,
|
||||
# 查询关键等级-类似“关键”输出
|
||||
"security_level_str": get_str_abbr(security, 'security_level'),
|
||||
"isDmsc": isDmsc,
|
||||
"test_types": type_name_list
|
||||
}
|
||||
return create_dg_docx("测试策略.docx", context, id)
|
||||
|
||||
# 生成-测试内容充分性及测试方法有效性
|
||||
@route.get('/create/adequacy_effectiveness', url_name='create-adequacy_effectiveness')
|
||||
def create_adequacy_effectiveness(self, id: int):
|
||||
project_qs = get_object_or_404(Project, id=id)
|
||||
# 统计测试种类数量-只统计第一轮测试
|
||||
project_round_one = project_qs.pField.filter(key=0).first()
|
||||
if not project_round_one:
|
||||
return ChenResponse(status=400, code=400, message="未找到首轮测试信息!")
|
||||
# 通过字典获取-测试方法
|
||||
type_dict = {} # key为测试类型,value为数量
|
||||
testDemands = project_round_one.rtField.all()
|
||||
for testDemand in testDemands:
|
||||
# 获取每个测试项测试类型
|
||||
test_type = get_list_dict('testType', [testDemand.testType])[0].get('ident_version')
|
||||
# 如果字典没有该key,则创建并value=1
|
||||
if not test_type in type_dict:
|
||||
type_dict[test_type] = 1
|
||||
else:
|
||||
type_dict[test_type] += 1
|
||||
length = len(type_dict)
|
||||
type_str_list = []
|
||||
for key, value in type_dict.items():
|
||||
type_str_list.append(f"{key}{value}项")
|
||||
context = {
|
||||
'project_name': project_qs.name,
|
||||
'length': length,
|
||||
'type_str': "、".join(type_str_list),
|
||||
}
|
||||
return create_dg_docx('测试内容充分性及测试方法有效性分析.docx', context, id)
|
||||
|
||||
# 生成-测评项目组组成和分工
|
||||
@route.get('/create/group', url_name='create_group')
|
||||
def create_group(self, id: int):
|
||||
project_qs = get_object_or_404(Project, id=id)
|
||||
context = {
|
||||
'duty_person': project_qs.duty_person,
|
||||
'member_str': "、".join(project_qs.member),
|
||||
'quality_person': project_qs.quality_person,
|
||||
'vise_person': project_qs.vise_person,
|
||||
'config_person': project_qs.config_person,
|
||||
'dev_unit': project_qs.dev_unit,
|
||||
}
|
||||
return create_dg_docx('测评组织及任务分工.docx', context, id)
|
||||
|
||||
# 生成-测评条件保障
|
||||
@route.get('/create/guarantee', url_name='create-guarantee')
|
||||
def create_guarantee(self, id: int):
|
||||
project_qs = get_object_or_404(Project, id=id)
|
||||
context = {
|
||||
'project': project_qs
|
||||
}
|
||||
return create_dg_docx('测评条件保障.docx', context, id)
|
||||
|
||||
# 生成-缩略语
|
||||
@route.get('/create/abbreviation', url_name='create-abbreviation')
|
||||
def create_abbreviation(self, id: int):
|
||||
project_qs = get_object_or_404(Project, id=id)
|
||||
abbreviations = []
|
||||
for abbr in project_qs.abbreviation:
|
||||
abbr_dict = {'title': abbr, 'des': Abbreviation.objects.filter(title=abbr).first().des}
|
||||
abbreviations.append(abbr_dict)
|
||||
context = {
|
||||
'abbreviations': abbreviations
|
||||
}
|
||||
return create_dg_docx('缩略语.docx', context, id)
|
||||
|
||||
# 生成研制总要求-测试项追踪关系表
|
||||
@route.get('/create/yzComparison', url_name='create-yzComparison')
|
||||
def create_yzComparison(self, id: int):
|
||||
"""目前追踪需求项的章节号是硬编码,按6.2章节起步,6.2.1~x.x.x依次排序"""
|
||||
# 规定测试项的章节号开头
|
||||
test_item_prefix = '6.2'
|
||||
# 计算有多少种testType - '文档审查'/'功能测试' ->
|
||||
# 形成一个数组['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)
|
||||
context = {
|
||||
'design_list': design_list
|
||||
}
|
||||
return create_dg_docx('研制总要求追踪表.docx', context, id)
|
||||
|
||||
# 生成需求规格说明-测试项追踪关系表
|
||||
@route.get('/create/xqComparison', url_name='create-xqComparison')
|
||||
def create_xqComparison(self, id: int):
|
||||
project_qs = get_object_or_404(Project, id=id)
|
||||
test_item_prefix = '6.2'
|
||||
design_list = []
|
||||
project_round_one = project_qs.pField.filter(key=0).first()
|
||||
if project_round_one:
|
||||
testType_list, last_chapter_items = create_csx_chapter_dict(project_round_one)
|
||||
# 找出第一轮的被测件为'XQ'
|
||||
xq_dut = project_round_one.rdField.filter(type='XQ').first()
|
||||
# 找出第一轮被测件为'SO',其中的测试项
|
||||
so_dut = project_round_one.rdField.filter(type='SO').first()
|
||||
if so_dut:
|
||||
so_designs = so_dut.rsField.all()
|
||||
for design in so_designs:
|
||||
design_dict = {'name': "/", 'chapter': "/", 'test_demand': []}
|
||||
# 获取一个design的所有测试项
|
||||
test_items = []
|
||||
test_items.extend(design.dtField.all())
|
||||
test_items.extend(design.odField.all())
|
||||
|
||||
for test_item in test_items:
|
||||
# 只对文档审查、静态分析、代码走查、代码审查进行处理
|
||||
if test_item.testType in ['8', '15', '3', '2']:
|
||||
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)
|
||||
|
||||
if xq_dut:
|
||||
xq_designs = xq_dut.rsField.all()
|
||||
for design in xq_designs:
|
||||
design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []}
|
||||
# 获取一个design的所有测试项
|
||||
test_items = []
|
||||
test_items.extend(design.dtField.all())
|
||||
test_items.extend(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)
|
||||
context = {
|
||||
'design_list': design_list
|
||||
}
|
||||
return create_dg_docx('需求规格说明追踪表.docx', context, id)
|
||||
raise HttpError(400, "生成需求追踪表出错")
|
||||
|
||||
# 生成测试项-需求规格说明关系表【反向】
|
||||
@route.get('/create/fanXqComparison', url_name='create-fanXqComparison')
|
||||
def create_fanXqComparison(self, id: int):
|
||||
project_qs = get_object_or_404(Project, id=id)
|
||||
test_item_prefix = '6.2'
|
||||
# 取出第一轮所有测试项的章节处理列表和字典
|
||||
project_round_one = project_qs.pField.filter(key=0).first()
|
||||
testType_list, last_chapter_items = create_csx_chapter_dict(project_round_one)
|
||||
# 查询第一轮所有测试项
|
||||
test_items = []
|
||||
test_items.extend(project_round_one.rtField.all())
|
||||
# 最后渲染列表
|
||||
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']):
|
||||
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)])
|
||||
# 如果是SO里面的
|
||||
if test_item.testType in ['8', '15', '3', '2'] and test_item.dut.type == 'SO':
|
||||
test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident,
|
||||
'design': {
|
||||
'name': "/", 'chapter': "/"
|
||||
}}
|
||||
else:
|
||||
test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident,
|
||||
'design': {
|
||||
'name': test_item.design.name, 'chapter': test_item.design.chapter
|
||||
}}
|
||||
items_list.append(test_item_dict)
|
||||
context = {
|
||||
'items_list': items_list,
|
||||
}
|
||||
return create_dg_docx('反向需求规格追踪表.docx', context, id)
|
||||
|
||||
# 生成代码质量度量分析表
|
||||
@route.get('/create/codeQuality', url_name='create-codeQuality')
|
||||
def create_codeQuality(self, id: int):
|
||||
project_qs = get_object_or_404(Project, id=id)
|
||||
project_round_one = project_qs.pField.filter(key=0).first()
|
||||
context = {}
|
||||
context.update({'project_name': project_qs.name})
|
||||
if project_round_one:
|
||||
source_dut: Dut = project_round_one.rdField.filter(type='SO').first() # type:ignore
|
||||
if source_dut:
|
||||
context.update({'version': source_dut.version}) # type:ignore
|
||||
context.update({'size': int(source_dut.total_lines)})
|
||||
context.update({'total_code_line': int(source_dut.effective_lines)})
|
||||
context.update({'comment_line': int(source_dut.comment_lines)})
|
||||
comment_ratio = int(source_dut.comment_lines) / int(source_dut.total_lines)
|
||||
context.update({
|
||||
'comment_ratio': f"{comment_ratio * 100:.2f}%",
|
||||
'comment_ratio_right': '满足' if comment_ratio >= 0.2 else '不满足'
|
||||
})
|
||||
# 如果已经有metrics
|
||||
if hasattr(source_dut, 'metrics'):
|
||||
context.update({
|
||||
'black_line': source_dut.metrics.total_blanks,
|
||||
'function_count': source_dut.metrics.function_count,
|
||||
'avg_function_lines': source_dut.metrics.avg_function_lines,
|
||||
'avg_function_lines_right': '满足' if source_dut.metrics.avg_function_lines <= 200 else '不满足',
|
||||
'avg_fan_out': source_dut.metrics.avg_fan_out,
|
||||
'avg_fan_out_right': '满足' if source_dut.metrics.avg_fan_out <= 7 else '不满足',
|
||||
'avg_cyclomatic': source_dut.metrics.avg_cyclomatic,
|
||||
'avg_cyclomatic_right': '满足' if source_dut.metrics.avg_cyclomatic <= 10 else '不满足',
|
||||
'max_cyclomatic': source_dut.metrics.max_cyclomatic,
|
||||
'max_cyclomatic_right': '满足' if source_dut.metrics.max_cyclomatic <= 80 else '不满足',
|
||||
'high_cyclomatic_ratio': source_dut.metrics.high_cyclomatic_ratio,
|
||||
'high_cyclomatic_ratio_right': '满足' if source_dut.metrics.high_cyclomatic_ratio <= 0.2 else '不满足',
|
||||
})
|
||||
else:
|
||||
return ChenResponse(message='未找到源代码被测件', code=400)
|
||||
return create_dg_docx('代码质量度量分析表.docx', context, id)
|
||||
212
apps/createDocument/controllers/hjl.py
Normal file
212
apps/createDocument/controllers/hjl.py
Normal file
@@ -0,0 +1,212 @@
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from ninja_extra import api_controller, ControllerBase, route
|
||||
from ninja_extra.permissions import IsAuthenticated
|
||||
from ninja_jwt.authentication import JWTAuth
|
||||
from django.db import transaction
|
||||
from django.db.models import QuerySet
|
||||
from docxtpl import DocxTemplate
|
||||
from apps.dict.models import Dict
|
||||
from utils.chen_response import ChenResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from typing import Union
|
||||
from docxtpl import InlineImage
|
||||
from apps.project.models import Dut, Project, Round
|
||||
from utils.util import get_list_dict, get_str_dict, get_ident, get_case_ident
|
||||
from utils.chapter_tools.csx_chapter import create_csx_chapter_dict
|
||||
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.createSeiTaiDocument.extensions.logger import GenerateLogger
|
||||
|
||||
chinese_round_name: list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
||||
|
||||
# @api_controller("/generateHSM", tags=['生成回归记录系列文档'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||
@api_controller("/generateHJL", tags=['生成回归记录系列文档'])
|
||||
class GenerateControllerHJL(ControllerBase):
|
||||
logger = GenerateLogger('回归测试记录')
|
||||
|
||||
# important:删除之前的文件
|
||||
@route.get('/create/deleteHJLDocument', url_name='delete-hjl-document')
|
||||
def delete_hjl_document(self, id: int):
|
||||
project_path_str = project_path(id)
|
||||
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl'
|
||||
delete_dir_files(save_path)
|
||||
|
||||
@route.get("/create/basicInformation", url_name="create-basicInformation")
|
||||
@transaction.atomic
|
||||
def create_basicInformation(self, id: int):
|
||||
"""生成回归测试记录的被测软件基本信息"""
|
||||
project_path_str = project_path(id)
|
||||
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hjl' / '被测软件基本信息.docx'
|
||||
doc = DocxTemplate(tpl_path)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 第一轮次对象
|
||||
round1_obj: Union[Round, None] = project_obj.pField.filter(key='0').first()
|
||||
# 第一轮源代码被测件对象
|
||||
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]
|
||||
# 取非第一轮次
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
# ***Inspect-start***
|
||||
self.logger.model = '回归测试记录'
|
||||
self.logger.write_warning_log('当前文档全部片段', f'该项目没有创建轮次')
|
||||
# ***Inspect-end***
|
||||
return ChenResponse(code=400, status=400, message='您未创建轮次,请创建完毕后再试')
|
||||
|
||||
context = {
|
||||
'project_name': project_obj.name,
|
||||
'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'),
|
||||
'recv_date': project_obj.beginTime.strftime("%Y-%m-%d"),
|
||||
'dev_unit': project_obj.dev_unit,
|
||||
}
|
||||
|
||||
version_info = [{'version': round1_so_dut.version,
|
||||
'line_count': int(round1_so_dut.total_lines),
|
||||
'effective_line': int(round1_so_dut.effective_lines)}]
|
||||
# 循环回归的轮次
|
||||
for hround in hround_list:
|
||||
# 每个轮次独立渲染context
|
||||
context_round = deepcopy(context)
|
||||
# 取中文名称
|
||||
cname = chinese_round_name[int(hround.key)] # 输出二、三...
|
||||
# 取该轮次源代码版本放入版本列表
|
||||
so_dut: Dut = hround.rdField.filter(type='SO').first()
|
||||
if not so_dut:
|
||||
return ChenResponse(code=400, status=400, message=f'您第{cname}轮次中缺少源代码被测件,请添加')
|
||||
version_info.append(
|
||||
{'version': so_dut.version, 'line_count': int(so_dut.total_lines),
|
||||
'effective_line': int(so_dut.effective_lines)})
|
||||
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)
|
||||
try:
|
||||
doc.save(save_path)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
||||
return ChenResponse(code=200, status=200, message='多轮回归说明文档基本信息生成完毕')
|
||||
|
||||
@route.get("/create/caseinfo", url_name="create-caseinfo")
|
||||
@transaction.atomic
|
||||
def create_caseinfo(self, id: int):
|
||||
"""生成回归测试记录的-{测试用例记录}"""
|
||||
project_path_str = project_path(id)
|
||||
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hjl' / '测试用例记录.docx'
|
||||
doc = DocxTemplate(tpl_path)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
return None
|
||||
demand_prefix = '3.1'
|
||||
# 循环每轮轮次对象
|
||||
for hround in hround_list:
|
||||
cname = chinese_round_name[int(hround.key)] # var:输出二、三字样
|
||||
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)] # 每个测试类型组合为一个列表[[],[],[],[]]
|
||||
testType_list, last_chapter_items = create_csx_chapter_dict(hround)
|
||||
testDemands = hround.rtField.all() # 本轮所有测试项
|
||||
for demand in testDemands:
|
||||
type_index = type_number_list.index(int(demand.testType))
|
||||
demand_ident = get_ident(demand)
|
||||
# ~~~组装测试项~~~
|
||||
demand_last_chapter = last_chapter_items[demand.testType].index(demand.key) + 1
|
||||
demand_chapter = ".".join([demand_prefix, str(testType_list.index(demand.testType) + 1),
|
||||
str(demand_last_chapter)])
|
||||
demand_dict = {
|
||||
'name': demand.name,
|
||||
'ident': demand_ident,
|
||||
'chapter': demand_chapter,
|
||||
'item': []
|
||||
}
|
||||
# ~~~这里组装测试项里面的测试用例~~~
|
||||
for case in demand.tcField.all():
|
||||
step_list = []
|
||||
index = 1
|
||||
for one in case.step.all():
|
||||
# 这里需要对operation富文本处理
|
||||
rich_parser = RichParser(one.operation)
|
||||
desc_list = rich_parser.get_final_list(doc, img_size=68)
|
||||
rich_parser2 = RichParser(one.result)
|
||||
res_list = rich_parser2.get_final_list(doc, img_size=75)
|
||||
# 组装用例里面的步骤dict
|
||||
passed = '通过'
|
||||
if one.passed == '2':
|
||||
passed = '未通过'
|
||||
if one.passed == '3':
|
||||
passed = '未执行'
|
||||
step_dict = {
|
||||
'index': index,
|
||||
'operation': desc_list,
|
||||
'expect': one.expect,
|
||||
'result': res_list,
|
||||
'passed': passed,
|
||||
}
|
||||
step_list.append(step_dict)
|
||||
index += 1
|
||||
# 查询所有的problem
|
||||
problem_list = []
|
||||
problem_prefix = "PT"
|
||||
proj_ident = project_obj.ident
|
||||
for problem in case.caseField.all():
|
||||
problem_list.append("_".join([problem_prefix, proj_ident, problem.ident]))
|
||||
# fpga的时序图
|
||||
rich_parser3 = RichParser(case.timing_diagram)
|
||||
timing_diagram = rich_parser3.get_final_list(doc, img_size=115, height=50)
|
||||
has_timing_diagram = False
|
||||
if len(timing_diagram) > 0:
|
||||
if isinstance(timing_diagram[0], InlineImage):
|
||||
has_timing_diagram = True
|
||||
# 组装用例的dict
|
||||
case_dict = {
|
||||
'name': case.name,
|
||||
'ident': get_case_ident(demand_ident, case),
|
||||
'summary': case.summarize,
|
||||
'initialization': case.initialization,
|
||||
'premise': case.premise,
|
||||
'design_person': case.designPerson,
|
||||
'test_person': case.testPerson,
|
||||
'monitor_person': case.monitorPerson,
|
||||
'step': step_list,
|
||||
'time': str(case.exe_time) if case.exe_time is not None else str(case.update_datetime),
|
||||
'problems': "、".join(problem_list),
|
||||
'round_num_chn': cname,
|
||||
# 2025年4月24日新增
|
||||
'has_timing_diagram': has_timing_diagram,
|
||||
'timing_diagram': timing_diagram,
|
||||
}
|
||||
demand_dict['item'].append(case_dict)
|
||||
|
||||
list_list[type_index].append(demand_dict)
|
||||
# 定义渲染上下文
|
||||
context = {}
|
||||
output_list = []
|
||||
for (index, li) in enumerate(list_list):
|
||||
qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1))
|
||||
context_str = qs.title
|
||||
sort = qs.sort
|
||||
table = {
|
||||
"type": context_str,
|
||||
"item": li,
|
||||
"sort": sort
|
||||
}
|
||||
output_list.append(table)
|
||||
# 排序
|
||||
output_list = sorted(output_list, key=(lambda x: x["sort"]))
|
||||
context["data"] = output_list
|
||||
# 最后渲染
|
||||
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl' / f"第{cname}轮测试用例记录.docx"
|
||||
doc.render(context)
|
||||
try:
|
||||
doc.save(save_path)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
||||
return ChenResponse(code=200, status=200, message='多轮回归测试用例记录生成完毕')
|
||||
602
apps/createDocument/controllers/hsm.py
Normal file
602
apps/createDocument/controllers/hsm.py
Normal file
@@ -0,0 +1,602 @@
|
||||
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 docx import Document
|
||||
# 导入模型
|
||||
from apps.project.models import Project, Round, Dut
|
||||
from apps.dict.models import Dict, DictItem
|
||||
# 导入项目工具
|
||||
from utils.util import get_list_dict, get_str_dict, MyHTMLParser, 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
|
||||
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.createSeiTaiDocument.extensions.logger import GenerateLogger
|
||||
|
||||
chinese_round_name: list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
||||
|
||||
# @api_controller("/generateHSM", tags=['生成回归说明系列文档'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||
@api_controller("/generateHSM", tags=['生成回归说明系列文档'])
|
||||
class GenerateControllerHSM(ControllerBase):
|
||||
logger = GenerateLogger('回归测试说明')
|
||||
|
||||
# important:删除之前的文件
|
||||
@route.get('/create/deleteHSMDocument', url_name='delete-hsm-document')
|
||||
def delete_hsm_document(self, id: int):
|
||||
project_path_str = project_path(id)
|
||||
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm'
|
||||
try:
|
||||
delete_dir_files(save_path)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='另一个程序正在占用文件,请关闭后重试')
|
||||
|
||||
@route.get("/create/basicInformation", url_name="create-basicInformation")
|
||||
@transaction.atomic
|
||||
def create_basicInformation(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)
|
||||
project_obj: Project = get_object_or_404(Project, id=id)
|
||||
# 第一轮次对象
|
||||
round1_obj: Union[Round, None] = project_obj.pField.filter(key='0').first()
|
||||
# 第一轮源代码被测件对象
|
||||
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]
|
||||
# 取非第一轮次
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
# ***Inspect-start***
|
||||
self.logger.model = '回归测试说明'
|
||||
self.logger.write_warning_log('当前文档全部片段', f'该项目没有创建轮次')
|
||||
# ***Inspect-end***
|
||||
return ChenResponse(code=400, status=400, message='您未创建轮次,请创建完毕后再试')
|
||||
|
||||
context = {
|
||||
'project_name': project_obj.name,
|
||||
'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'),
|
||||
'recv_date': project_obj.beginTime.strftime("%Y-%m-%d"),
|
||||
'dev_unit': project_obj.dev_unit,
|
||||
}
|
||||
version_info = [{
|
||||
'version': round1_so_dut.version,
|
||||
'line_count': round1_so_dut.total_lines,
|
||||
'effective_count': round1_so_dut.effective_lines,
|
||||
}]
|
||||
# 循环回归的轮次
|
||||
for hround in hround_list:
|
||||
# 每个轮次独立渲染context
|
||||
context_round = deepcopy(context)
|
||||
# 取中文名称
|
||||
cname = chinese_round_name[int(hround.key)] # 输出二、三...
|
||||
# 取该轮次源代码版本放入版本列表
|
||||
so_dut: Dut = hround.rdField.filter(type='SO').first()
|
||||
if not so_dut:
|
||||
return ChenResponse(code=400, status=400, message=f'您第{cname}轮次中缺少源代码被测件,请添加')
|
||||
version_info.append(
|
||||
{
|
||||
'version': so_dut.version,
|
||||
'line_count': so_dut.total_lines,
|
||||
'effective_count': so_dut.effective_lines,
|
||||
}
|
||||
)
|
||||
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)
|
||||
try:
|
||||
doc.save(save_path)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
||||
return ChenResponse(code=200, status=200, message='多轮回归说明文档基本信息生成完毕')
|
||||
|
||||
@route.get("/create/docsummary", url_name="create-docsummary")
|
||||
@transaction.atomic
|
||||
def create_docsummary(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)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 非第一轮轮次对象
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
return None
|
||||
|
||||
context = {
|
||||
'project_obj': project_obj.name,
|
||||
}
|
||||
|
||||
for hround in hround_list:
|
||||
# 取出当前轮次key减1就是上一轮次
|
||||
cname = chinese_round_name[int(hround.key)] # 输出二、三...
|
||||
so_dut: Dut = hround.rdField.filter(type='SO').first()
|
||||
if not so_dut:
|
||||
return ChenResponse(code=400, status=400, message=f'您第{cname}轮次中缺少源代码被测件,请添加')
|
||||
# 取上一轮次
|
||||
so_dut_last: Dut = Dut.objects.filter(round__key=str(int(hround.key) - 1), project=project_obj,
|
||||
type='SO').first()
|
||||
round_context = deepcopy(context)
|
||||
round_context['current_version'] = so_dut.version
|
||||
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)
|
||||
try:
|
||||
doc.save(save_path)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
||||
return ChenResponse(code=200, status=200, message='多轮回归文档概述生成完毕')
|
||||
|
||||
@route.get("/create/jstech", url_name="create-jstech")
|
||||
@transaction.atomic
|
||||
def create_jstech(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)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
duties_qs = project_obj.pdField.filter(Q(type='XQ') | Q(type='SJ') | Q(type='XY'))
|
||||
std_documents = []
|
||||
for duty in duties_qs:
|
||||
one_duty = {'doc_name': duty.name, 'ident_version': duty.ref + '-' + duty.version,
|
||||
'publish_date': duty.release_date, 'source': duty.release_union}
|
||||
std_documents.append(one_duty)
|
||||
doc_name = f'{project_obj.name}软件测评大纲'
|
||||
if project_obj.report_type == '9':
|
||||
doc_name = f'{project_obj.name}软件鉴定测评大纲'
|
||||
# 时间控制类
|
||||
timer = DocTime(id)
|
||||
dg_duty = {'doc_name': doc_name, 'ident_version': f'PT-{project_obj.ident}-TO-1.00',
|
||||
'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',
|
||||
'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',
|
||||
'publish_date': timer.jl_cover_time, 'source': project_obj.test_unit}
|
||||
std_documents.extend([sm_duty, jl_duty])
|
||||
|
||||
# 非第一轮的轮次
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
return None
|
||||
for hround in hround_list:
|
||||
std_documents_round = deepcopy(std_documents)
|
||||
# 取出当前轮次key
|
||||
cname = chinese_round_name[int(hround.key)]
|
||||
hsm_duty = {'doc_name': f'{project_obj.name}软件第{cname}轮测试说明',
|
||||
'ident_version': f'PT-{project_obj.ident}-TD{int(hround.key) + 1}-1.00',
|
||||
'publish_date': hround.beginTime, 'source': project_obj.test_unit}
|
||||
hjl_duty = {'doc_name': f'{project_obj.name}软件第{cname}轮测试记录',
|
||||
'ident_version': f'PT-{project_obj.ident}-TN{int(hround.key) + 1}',
|
||||
'publish_date': hround.endTime, 'source': project_obj.test_unit}
|
||||
std_documents.extend([hsm_duty, hjl_duty])
|
||||
context = {
|
||||
'std_documents': std_documents_round
|
||||
}
|
||||
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"第{cname}轮技术依据文件.docx"
|
||||
doc.render(context=context)
|
||||
try:
|
||||
doc.save(save_path)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
||||
return ChenResponse(code=200, status=200, message='多轮回归技术依据文件生成完毕')
|
||||
|
||||
@route.get("/create/changePart", url_name="create-changePart")
|
||||
@transaction.atomic
|
||||
def create_changePart(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)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
context = {
|
||||
'project_name': project_obj.name,
|
||||
}
|
||||
# 非第一轮的轮次
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
return None
|
||||
for hround in hround_list:
|
||||
context_round = deepcopy(context)
|
||||
cname = chinese_round_name[int(hround.key)] # 输出二、三...
|
||||
so_dut: Dut = hround.rdField.filter(type='SO').first()
|
||||
if not so_dut:
|
||||
return ChenResponse(code=400, status=400, message=f'您第{cname}轮次中缺少源代码被测件,请添加')
|
||||
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_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
|
||||
# 如果存在这个轮次的需求文档,则查询上个版本
|
||||
last_xq_version = ""
|
||||
if xq_dut:
|
||||
last_xq_dut = last_round.rdField.filter(type='XQ').first()
|
||||
if not last_xq_dut:
|
||||
return ChenResponse(code=400, status=400,
|
||||
message=f'您第{chinese_round_name[int(hround.key)]}轮次中缺少需求文档信息')
|
||||
last_xq_version = last_xq_dut.version
|
||||
# 如果当前轮次有需求文档的修改
|
||||
now_xq_version = xq_dut.version
|
||||
context_round['xq_str'] = f",以及软件需求规格说明{now_xq_version}版本和{last_xq_version}版本"
|
||||
else:
|
||||
# 如果当前轮次没有需求文档则xq_str为空
|
||||
context_round['xq_str'] = ""
|
||||
|
||||
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)
|
||||
try:
|
||||
doc.save(save_path)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
||||
return ChenResponse(code=200, status=200, message='多轮回归文档概述生成完毕')
|
||||
|
||||
@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)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 非第一轮轮次对象
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
return None
|
||||
# 遍历非第一轮的轮次
|
||||
for hround in hround_list:
|
||||
cname = chinese_round_name[int(hround.key)] # var:输出二、三字样
|
||||
# 先查询dict字典,查出总共有多少个testType
|
||||
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:
|
||||
type_index = type_number_list.index(int(demand.testType))
|
||||
content_list = []
|
||||
for (index, content) in enumerate(demand.testQField.all()):
|
||||
content_dict = {
|
||||
"index": index + 1,
|
||||
"rindex": str(index + 1).rjust(2, '0'),
|
||||
"subName": content.subName,
|
||||
# 修改遍历content下面的step,content变量是TestDemandContent表
|
||||
"subStep": [
|
||||
{'index': index + 1, 'operation': step_obj.operation, 'expect': step_obj.expect}
|
||||
for (index, step_obj) in enumerate(content.testStepField.all())
|
||||
],
|
||||
}
|
||||
content_list.append(content_dict)
|
||||
testmethod_str = ''
|
||||
for dict_item_qs in Dict.objects.get(code="testMethod").dictItem.all():
|
||||
for tm_item in demand.testMethod:
|
||||
if tm_item == dict_item_qs.key:
|
||||
testmethod_str += dict_item_qs.title + " "
|
||||
# 设计需求的描述,富文本
|
||||
parser = RichParser(demand.design.description)
|
||||
# 查询关联design以及普通design
|
||||
doc_list = [{'dut_name': demand.dut.name, 'design_chapter': demand.design.chapter,
|
||||
'design_name': demand.design.name}]
|
||||
for relate_design in demand.otherDesign.all():
|
||||
ddict = {'dut_name': relate_design.dut.name, 'design_chapter': relate_design.chapter,
|
||||
'design_name': relate_design.name}
|
||||
doc_list.append(ddict)
|
||||
# 组装单个测试项
|
||||
testdemand_dict = {
|
||||
"name": demand.name,
|
||||
"key": demand.key,
|
||||
"ident": get_ident(demand),
|
||||
"priority": get_str_dict(demand.priority, "priority"),
|
||||
"doc_list": doc_list,
|
||||
"design_description": parser.get_final_list(doc),
|
||||
"test_demand_content": content_list,
|
||||
"testMethod": testmethod_str,
|
||||
"adequacy": demand.adequacy.replace("\n", "\a"),
|
||||
"testDesciption": demand.testDesciption.replace("\n", "\a") # 测试项描述
|
||||
}
|
||||
list_list[type_index].append(testdemand_dict)
|
||||
# 定义渲染context字典
|
||||
context = {
|
||||
"project_name": project_obj.name
|
||||
}
|
||||
output_list = []
|
||||
for (index, li) in enumerate(list_list):
|
||||
qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1))
|
||||
context_str = qs.title
|
||||
sort = qs.sort
|
||||
table = {
|
||||
"type": context_str,
|
||||
"item": li,
|
||||
"sort": sort
|
||||
}
|
||||
output_list.append(table)
|
||||
# 排序
|
||||
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)
|
||||
try:
|
||||
doc.save(save_path)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
||||
return ChenResponse(code=200, status=200, message='多轮回归测试需求生成完毕')
|
||||
|
||||
@route.get("/create/caseListDesc", url_name="create-caseListDesc")
|
||||
@transaction.atomic
|
||||
def create_caseListDesc(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)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 非第一轮轮次对象
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
return None
|
||||
for hround in hround_list:
|
||||
# 先查询dict字典,查出总共有多少个testType
|
||||
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)]
|
||||
cname = chinese_round_name[int(hround.key)] # 输出二、三...
|
||||
testDemands = hround.rtField.all()
|
||||
for demand in testDemands:
|
||||
type_index = type_number_list.index(int(demand.testType))
|
||||
demand_ident = get_ident(demand)
|
||||
demand_dict = {
|
||||
'name': demand.name,
|
||||
'item': []
|
||||
}
|
||||
for case in demand.tcField.all():
|
||||
case_dict = {
|
||||
'name': case.name,
|
||||
'ident': get_case_ident(demand_ident, case),
|
||||
'summary': case.summarize,
|
||||
}
|
||||
demand_dict['item'].append(case_dict)
|
||||
list_list[type_index].append(demand_dict)
|
||||
# 定义渲染上下文
|
||||
context = {}
|
||||
output_list = []
|
||||
for (index, li) in enumerate(list_list):
|
||||
qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1))
|
||||
sort = qs.sort
|
||||
table = {
|
||||
"item": li,
|
||||
"sort": sort
|
||||
}
|
||||
output_list.append(table)
|
||||
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)
|
||||
try:
|
||||
doc.save(save_path)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
||||
return ChenResponse(code=200, status=200, message='多轮回归测试用例概述生成完毕')
|
||||
|
||||
@route.get("/create/caseList", url_name="create-caseList")
|
||||
@transaction.atomic
|
||||
def create_caseList(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)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 非第一轮轮次对象
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
return None
|
||||
for hround in hround_list:
|
||||
cname = chinese_round_name[int(hround.key)] # 输出二、三...
|
||||
# 先查询dict字典,查出总共有多少个testType
|
||||
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)]
|
||||
demand_prefix = '3.1'
|
||||
testType_list, last_chapter_items = create_csx_chapter_dict(hround)
|
||||
testDemands = hround.rtField.all()
|
||||
# 首先轮询所有测试需求
|
||||
for demand in testDemands:
|
||||
type_index = type_number_list.index(int(demand.testType))
|
||||
demand_ident = get_ident(demand)
|
||||
# ~~~~~这里组装测试项~~~~~
|
||||
## 确定测试需求章节号(后面可提取后进行复用)
|
||||
demand_last_chapter = last_chapter_items[demand.testType].index(demand.key) + 1
|
||||
demand_chapter = ".".join([demand_prefix, str(testType_list.index(demand.testType) + 1),
|
||||
str(demand_last_chapter)])
|
||||
demand_dict = {
|
||||
'name': demand.name,
|
||||
'ident': demand_ident,
|
||||
'chapter': demand_chapter,
|
||||
'item': []
|
||||
}
|
||||
# ~~~这里组装测试项里面的测试用例~~~
|
||||
for case in demand.tcField.all():
|
||||
step_list = []
|
||||
index = 1
|
||||
for one in case.step.all():
|
||||
# 这里需要对operation富文本处理
|
||||
rich_parser = RichParser(one.operation)
|
||||
desc_list = rich_parser.get_final_list(doc, img_size=70)
|
||||
step_dict = {
|
||||
'index': index,
|
||||
'operation': desc_list,
|
||||
'expect': one.expect,
|
||||
}
|
||||
step_list.append(step_dict)
|
||||
index += 1
|
||||
|
||||
case_dict = {
|
||||
'name': case.name,
|
||||
'ident': get_case_ident(demand_ident, case),
|
||||
'summary': case.summarize,
|
||||
'initialization': case.initialization,
|
||||
'premise': case.premise,
|
||||
'design_person': case.designPerson,
|
||||
'step': step_list
|
||||
}
|
||||
demand_dict['item'].append(case_dict)
|
||||
|
||||
list_list[type_index].append(demand_dict)
|
||||
# 定义渲染上下文
|
||||
context = {}
|
||||
output_list = []
|
||||
for (index, li) in enumerate(list_list):
|
||||
qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1))
|
||||
context_str = qs.title
|
||||
sort = qs.sort
|
||||
table = {
|
||||
"type": context_str,
|
||||
"item": li,
|
||||
"sort": sort
|
||||
}
|
||||
output_list.append(table)
|
||||
# 排序
|
||||
output_list = sorted(output_list, key=(lambda x: x["sort"]))
|
||||
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)
|
||||
try:
|
||||
doc.save(save_path)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
||||
return ChenResponse(code=200, status=200, message='多轮测试用例生成完毕')
|
||||
|
||||
@route.get("/create/track", url_name="create-track")
|
||||
@transaction.atomic
|
||||
def create_track(self, id: int):
|
||||
"""
|
||||
生成非第一轮的用例追踪
|
||||
"""
|
||||
project_path_str = project_path(id)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 非第一轮轮次对象
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
demand_prefix = '4.1'
|
||||
if len(hround_list) < 1:
|
||||
return
|
||||
for hround in hround_list:
|
||||
# 取出当前轮次key减1就是上一轮次
|
||||
cname = chinese_round_name[int(hround.key)] # 输出二、三...
|
||||
design_list = []
|
||||
testType_list, last_chapter_items = create_csx_chapter_dict(hround)
|
||||
# 找出当前轮次被测件为'SO'的
|
||||
so_dut = hround.rdField.filter(type='SO').first()
|
||||
if not so_dut:
|
||||
return ChenResponse(code=400, status=400, message=f'第{cname}轮次无源代码被测件')
|
||||
so_designs = so_dut.rsField.filter()
|
||||
for design in so_designs:
|
||||
design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []}
|
||||
test_items = []
|
||||
test_items.extend(design.dtField.all())
|
||||
test_items.extend(design.odField.all())
|
||||
for test_item in test_items:
|
||||
if test_item.testType in ['2', '3', '15', '8']:
|
||||
design_dict.update({'name': "/", 'chapter': "/"})
|
||||
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([demand_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,
|
||||
'case_list': []}
|
||||
for case in test_item.tcField.all():
|
||||
case_dict = {
|
||||
'name': case.name,
|
||||
'ident': get_case_ident(reveal_ident, case)
|
||||
}
|
||||
test_item_dict['case_list'].append(case_dict)
|
||||
design_dict['test_demand'].append(test_item_dict)
|
||||
design_list.append(design_dict)
|
||||
# 找出当前轮次的被测件为'XQ'的第一个
|
||||
xq_dut = hround.rdField.filter(type='XQ').first()
|
||||
if not xq_dut:
|
||||
return ChenResponse(code=400, status=400,
|
||||
message=f'第{cname}轮次没有找到需求被测件,只有放在被测件为<需求>的设计需求、测试项、用例才会被追踪')
|
||||
xq_designs = xq_dut.rsField.all()
|
||||
for design in xq_designs:
|
||||
design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []}
|
||||
test_items = []
|
||||
test_items.extend(design.dtField.all())
|
||||
test_items.extend(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([demand_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,
|
||||
'case_list': []}
|
||||
for case in test_item.tcField.all():
|
||||
case_dict = {
|
||||
'name': case.name,
|
||||
'ident': get_case_ident(reveal_ident, case)
|
||||
}
|
||||
test_item_dict['case_list'].append(case_dict)
|
||||
design_dict['test_demand'].append(test_item_dict)
|
||||
design_list.append(design_dict)
|
||||
context = {
|
||||
'design_list': design_list,
|
||||
}
|
||||
|
||||
# 手动渲染tpl生成文档
|
||||
input_file = Path.cwd() / 'media' / project_path_str / 'form_template' / 'hsm' / '用例追踪.docx'
|
||||
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.save(temporary_file)
|
||||
# 通过docx合并单元格
|
||||
if temporary_file.is_file():
|
||||
try:
|
||||
docu = Document(temporary_file)
|
||||
# 找到其中的表格
|
||||
util.merge_all_cell(docu.tables[0])
|
||||
# 储存到合适位置
|
||||
docu.save(out_put_file)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='请检查文件是否打开,如果打开则关闭...')
|
||||
else:
|
||||
return ChenResponse(code=400, status=400, message='中间文档未找到,请检查你模版是否存在...')
|
||||
return ChenResponse(code=200, status=200, message='文档生成成功...')
|
||||
154
apps/createDocument/controllers/jl.py
Normal file
154
apps/createDocument/controllers/jl.py
Normal file
@@ -0,0 +1,154 @@
|
||||
# 内置模块导入
|
||||
from pathlib import Path
|
||||
# 导入框架相关
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404
|
||||
from ninja_extra import api_controller, ControllerBase, route
|
||||
# 导入权限相关
|
||||
from ninja_extra.permissions import IsAuthenticated
|
||||
from ninja_jwt.authentication import JWTAuth
|
||||
# 导入数据库模型
|
||||
from apps.project.models import Project
|
||||
from apps.dict.models import Dict
|
||||
from docxtpl import InlineImage
|
||||
# 导入文档处理相关
|
||||
from docxtpl import DocxTemplate
|
||||
# 导入自己工具
|
||||
from utils.chapter_tools.csx_chapter import create_csx_chapter_dict
|
||||
from utils.util import get_ident, get_case_ident, get_testType
|
||||
from utils.chen_response import ChenResponse
|
||||
from utils.path_utils import project_path
|
||||
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||
|
||||
# @api_controller("/generateJL", tags=['生成测试记录系列'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||
@api_controller("/generateJL", tags=['生成测试记录系列'])
|
||||
class GenerateControllerJL(ControllerBase):
|
||||
@route.get("/create/caserecord", url_name="create-caserecord")
|
||||
@transaction.atomic
|
||||
def create_caserecord(self, id: int):
|
||||
"""生成测试记录表格"""
|
||||
project_path_str = project_path(id)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 测试用例记录模版位置
|
||||
record_template_path = Path.cwd() / 'media' / project_path_str / 'form_template/jl' / '测试用例记录.docx'
|
||||
# 打开模版文档
|
||||
doc = DocxTemplate(record_template_path)
|
||||
# 准备返回的测试类型数组的嵌套
|
||||
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)] # 每个测试类型组合为一个列表[[],[],[],[]]
|
||||
# 查询出第一轮次
|
||||
round_one = project_obj.pField.filter(key=0).first()
|
||||
# 测试项的章节号预置处理 - 根据轮次生成list和dict的二维数据
|
||||
demand_prefix = '6.2'
|
||||
testType_list, last_chapter_items = create_csx_chapter_dict(round_one)
|
||||
# 找出所有测试项
|
||||
testDemands = round_one.rtField.all()
|
||||
# 首先轮询所有测试需求
|
||||
for demand in testDemands:
|
||||
type_index = type_number_list.index(int(demand.testType))
|
||||
demand_ident = get_ident(demand)
|
||||
# ~~~组装测试项~~~
|
||||
demand_last_chapter = last_chapter_items[demand.testType].index(demand.key) + 1
|
||||
demand_chapter = ".".join([demand_prefix, str(testType_list.index(demand.testType) + 1),
|
||||
str(demand_last_chapter)])
|
||||
demand_dict = {
|
||||
'name': demand.name,
|
||||
'ident': demand_ident,
|
||||
'chapter': demand_chapter,
|
||||
'item': []
|
||||
}
|
||||
# ~~~这里组装测试项里面的测试用例~~~
|
||||
for case in demand.tcField.all():
|
||||
step_list = []
|
||||
index = 1
|
||||
for one in case.step.all():
|
||||
# 这里需要对operation富文本处理
|
||||
rich_parser = RichParser(one.operation)
|
||||
desc_list = rich_parser.get_final_list(doc, img_size=68)
|
||||
# 这里需要对result富文本处理
|
||||
rich_parser2 = RichParser(one.result)
|
||||
res_list = rich_parser2.get_final_list(doc, img_size=75)
|
||||
# 组装用例里面的步骤dict
|
||||
passed = '通过'
|
||||
if one.passed == '2':
|
||||
passed = '未通过'
|
||||
elif one.passed == '3':
|
||||
passed = '未执行'
|
||||
step_dict = {
|
||||
'index': index,
|
||||
'operation': desc_list,
|
||||
'expect': one.expect,
|
||||
'result': res_list,
|
||||
'passed': passed,
|
||||
}
|
||||
index += 1
|
||||
step_list.append(step_dict)
|
||||
# 这里判断里面的单个步骤的执行情况,来输出一个整个用例的执行情况
|
||||
exe_noncount = 0
|
||||
execution_str = '已执行'
|
||||
for ste in step_list:
|
||||
if ste.get('execution') == '3':
|
||||
exe_noncount += 1
|
||||
if exe_noncount > 0 and exe_noncount != len(step_list):
|
||||
execution_str = '部分执行'
|
||||
elif exe_noncount == len(step_list):
|
||||
execution_str = '未执行'
|
||||
else:
|
||||
execution_str = '已执行'
|
||||
# 查询所有的problem
|
||||
problem_list = []
|
||||
problem_prefix = "PT"
|
||||
proj_ident = project_obj.ident
|
||||
for problem in case.caseField.all():
|
||||
problem_list.append("_".join([problem_prefix, proj_ident, problem.ident]))
|
||||
# 组装用例的dict
|
||||
rich_parser3 = RichParser(case.timing_diagram)
|
||||
timing_diagram = rich_parser3.get_final_list(doc, img_size=115, height=50)
|
||||
has_timing_diagram = False
|
||||
if len(timing_diagram) > 0:
|
||||
if isinstance(timing_diagram[0], InlineImage):
|
||||
has_timing_diagram = True
|
||||
case_dict = {
|
||||
'name': case.name,
|
||||
'ident': get_case_ident(demand_ident, case),
|
||||
'summary': case.summarize,
|
||||
'initialization': case.initialization,
|
||||
'premise': case.premise,
|
||||
'design_person': case.designPerson,
|
||||
'test_person': case.testPerson,
|
||||
'monitor_person': case.monitorPerson,
|
||||
'step': step_list,
|
||||
'execution': execution_str,
|
||||
'time': str(case.exe_time) if case.exe_time is not None else str(case.update_datetime),
|
||||
'problems': "、".join(problem_list),
|
||||
# 2025年4月24日新增
|
||||
'has_timing_diagram': has_timing_diagram,
|
||||
'timing_diagram': timing_diagram,
|
||||
}
|
||||
demand_dict['item'].append(case_dict)
|
||||
|
||||
list_list[type_index].append(demand_dict)
|
||||
# 定义渲染上下文
|
||||
context = {}
|
||||
output_list = []
|
||||
for (index, li) in enumerate(list_list):
|
||||
qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1)) # type:ignore
|
||||
context_str = qs.title
|
||||
sort = qs.sort
|
||||
table = {
|
||||
"type": context_str,
|
||||
"item": li,
|
||||
"sort": sort
|
||||
}
|
||||
output_list.append(table)
|
||||
# 排序
|
||||
output_list = sorted(output_list, key=(lambda x: x["sort"]))
|
||||
context["data"] = output_list
|
||||
|
||||
doc.render(context)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path_str / "output_dir/jl" / "测试用例记录.docx")
|
||||
return ChenResponse(status=200, code=200, message="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
282
apps/createDocument/controllers/sm.py
Normal file
282
apps/createDocument/controllers/sm.py
Normal file
@@ -0,0 +1,282 @@
|
||||
from pathlib import Path
|
||||
# 导入框架东西
|
||||
from ninja_extra import ControllerBase, api_controller, route
|
||||
# 框架组件
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db.models import Q
|
||||
# 认证和权限
|
||||
from ninja_extra.permissions import IsAuthenticated
|
||||
from ninja_jwt.authentication import JWTAuth
|
||||
# 导入模型
|
||||
from apps.project.models import Project
|
||||
from apps.dict.models import Dict
|
||||
# 导入文档处理类
|
||||
from docxtpl import DocxTemplate
|
||||
from docx import Document
|
||||
# 导入自己工具
|
||||
from utils.chen_response import ChenResponse
|
||||
from utils.util import get_ident, get_case_ident, get_testType
|
||||
from utils.chapter_tools.csx_chapter import create_csx_chapter_dict
|
||||
from apps.createDocument.extensions import util
|
||||
from apps.createDocument.extensions.util import create_sm_docx
|
||||
from utils.path_utils import project_path
|
||||
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||
from apps.createDocument.extensions.documentTime import DocTime
|
||||
|
||||
# @api_controller("/generateSM", tags=['生成说明文档系列'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||
@api_controller("/generateSM", tags=['生成说明文档系列'])
|
||||
class GenerateControllerSM(ControllerBase):
|
||||
@route.get("/create/techyiju", url_name="create-techyiju")
|
||||
@transaction.atomic
|
||||
def create_techyiju(self, id: int):
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
duties_qs = project_obj.pdField.filter(Q(type='XQ') | Q(type='SJ') | Q(type='XY'))
|
||||
std_documents = []
|
||||
for duty in duties_qs:
|
||||
one_duty = {'doc_name': duty.name, 'ident_version': duty.ref + '-' + duty.version,
|
||||
'publish_date': duty.release_date, 'source': duty.release_union}
|
||||
std_documents.append(one_duty)
|
||||
# 添加大纲到这里
|
||||
## 判断是否为鉴定
|
||||
doc_name = f'{project_obj.name}软件测评大纲'
|
||||
if project_obj.report_type == '9':
|
||||
doc_name = f'{project_obj.name}软件鉴定测评大纲'
|
||||
# 这里大纲版本升级如何处理 - TODO:1.大纲版本升级后版本处理
|
||||
# 时间控制类
|
||||
timer = DocTime(id)
|
||||
dg_duty = {'doc_name': doc_name, 'ident_version': f'PT-{project_obj.ident}-TO-1.00',
|
||||
'publish_date': timer.dg_cover_time, 'source': project_obj.test_unit}
|
||||
std_documents.append(dg_duty)
|
||||
# 生成二级文档
|
||||
context = {
|
||||
'std_documents': std_documents
|
||||
}
|
||||
return create_sm_docx("技术依据文件.docx", context, id)
|
||||
|
||||
@route.get('/create/caseList', url_name='create-caseList')
|
||||
@transaction.atomic
|
||||
def create_caseList(self, id: int):
|
||||
"""创建第一轮文档"""
|
||||
project_path_str = project_path(id)
|
||||
# 生成测试用例需要doc对象
|
||||
case_template_doc_path = Path.cwd() / 'media' / project_path_str / 'form_template/sm' / '测试用例.docx'
|
||||
doc = DocxTemplate(case_template_doc_path)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 先查询dict字典,查出总共有多少个testType
|
||||
test_type_len = Dict.objects.get(code='testType').dictItem.count()
|
||||
type_number_list = [i for i in
|
||||
range(1, test_type_len + 1)] # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
|
||||
list_list = [[] for j in
|
||||
range(1, test_type_len + 1)] # [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
|
||||
# 先找到第一轮次
|
||||
project_round_one = project_obj.pField.filter(key=0).first()
|
||||
# 测试项的章节号预置处理
|
||||
demand_prefix = '6.2'
|
||||
testType_list, last_chapter_items = create_csx_chapter_dict(project_round_one)
|
||||
|
||||
testDemands = project_round_one.rtField.all()
|
||||
# 首先轮询所有测试需求
|
||||
for demand in testDemands:
|
||||
type_index = type_number_list.index(int(demand.testType))
|
||||
demand_ident = get_ident(demand)
|
||||
# ~~~~~这里组装测试项~~~~~
|
||||
## 确定测试需求章节号(后面可提取后进行复用)
|
||||
demand_last_chapter = last_chapter_items[demand.testType].index(demand.key) + 1
|
||||
demand_chapter = ".".join([demand_prefix, str(testType_list.index(demand.testType) + 1),
|
||||
str(demand_last_chapter)])
|
||||
demand_dict = {
|
||||
'name': demand.name,
|
||||
'ident': demand_ident,
|
||||
'chapter': demand_chapter,
|
||||
'item': []
|
||||
}
|
||||
# ~~~这里组装测试项里面的测试用例~~~
|
||||
for case in demand.tcField.all():
|
||||
step_list = []
|
||||
index = 1
|
||||
for one in case.step.all():
|
||||
# 这里需要对operation富文本处理
|
||||
rich_parser = RichParser(one.operation)
|
||||
desc_list = rich_parser.get_final_list(doc, img_size=70)
|
||||
step_dict = {
|
||||
'index': index,
|
||||
'operation': desc_list,
|
||||
'expect': one.expect,
|
||||
}
|
||||
step_list.append(step_dict)
|
||||
index += 1
|
||||
|
||||
case_dict = {
|
||||
'name': case.name,
|
||||
'ident': get_case_ident(demand_ident, case),
|
||||
'summary': case.summarize,
|
||||
'initialization': case.initialization,
|
||||
'premise': case.premise,
|
||||
'design_person': case.designPerson,
|
||||
'step': step_list
|
||||
}
|
||||
demand_dict['item'].append(case_dict)
|
||||
|
||||
list_list[type_index].append(demand_dict)
|
||||
# 定义渲染上下文
|
||||
context = {}
|
||||
output_list = []
|
||||
for (index, li) in enumerate(list_list):
|
||||
qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1))
|
||||
context_str = qs.title
|
||||
sort = qs.sort
|
||||
table = {
|
||||
"type": context_str,
|
||||
"item": li,
|
||||
"sort": sort
|
||||
}
|
||||
output_list.append(table)
|
||||
# 排序
|
||||
output_list = sorted(output_list, key=(lambda x: x["sort"]))
|
||||
context["data"] = output_list
|
||||
doc.render(context)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path_str / "output_dir/sm" / "测试用例.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/caseBreifList', url_name='create-caseBreifList')
|
||||
@transaction.atomic
|
||||
def create_caseBreifList(self, id: int):
|
||||
# 生成第一轮的测试说明
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 先查询dict字典,查出总共有多少个testType
|
||||
test_type_len = Dict.objects.get(code='testType').dictItem.count()
|
||||
type_number_list = [i for i in
|
||||
range(1, test_type_len + 1)] # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
|
||||
list_list = [[] for j in
|
||||
range(1, test_type_len + 1)] # [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
|
||||
# 先找到第一轮次
|
||||
project_round_one = project_obj.pField.filter(key=0).first()
|
||||
# 找到第一轮的全部测试项
|
||||
testDemands = project_round_one.rtField.all()
|
||||
for demand in testDemands:
|
||||
type_index = type_number_list.index(int(demand.testType))
|
||||
demand_ident = get_ident(demand)
|
||||
demand_dict = {
|
||||
'name': demand.name,
|
||||
'item': []
|
||||
}
|
||||
for case in demand.tcField.all():
|
||||
case_dict = {
|
||||
'name': case.name,
|
||||
'ident': get_case_ident(demand_ident, case),
|
||||
'summary': case.summarize,
|
||||
}
|
||||
demand_dict['item'].append(case_dict)
|
||||
list_list[type_index].append(demand_dict)
|
||||
# 定义渲染上下文
|
||||
context = {}
|
||||
output_list = []
|
||||
for (index, li) in enumerate(list_list):
|
||||
qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1))
|
||||
sort = qs.sort
|
||||
table = {
|
||||
"item": li,
|
||||
"sort": sort
|
||||
}
|
||||
output_list.append(table)
|
||||
output_list = sorted(output_list, key=(lambda x: x["sort"]))
|
||||
context["data"] = output_list
|
||||
return create_sm_docx("用例说明.docx", context, id)
|
||||
|
||||
@route.get('/create/smtrack', url_name='create-smtrack')
|
||||
@transaction.atomic
|
||||
def create_smtrack(self, id: int):
|
||||
"""生成说明的需求追踪表"""
|
||||
project_path_str = project_path(id)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
demand_prefix = '6.2'
|
||||
design_list = []
|
||||
project_round_one = project_obj.pField.filter(key='0').first()
|
||||
if project_round_one:
|
||||
testType_list, last_chapter_items = create_csx_chapter_dict(project_round_one)
|
||||
# 找出第一轮被测件为'SO'的
|
||||
so_dut = project_round_one.rdField.filter(type='SO').first()
|
||||
if so_dut:
|
||||
so_designs = so_dut.rsField.all()
|
||||
for design in so_designs:
|
||||
design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []}
|
||||
test_items = []
|
||||
test_items.extend(design.dtField.all())
|
||||
test_items.extend(design.odField.all())
|
||||
for test_item in test_items:
|
||||
# 对4个测试类型单独处理:因为这4类肯定没有章节号
|
||||
if test_item.testType in ['2', '3', '15', '8']:
|
||||
design_dict.update({'name': "/", 'chapter': "/"})
|
||||
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([demand_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,
|
||||
'case_list': []}
|
||||
for case in test_item.tcField.all():
|
||||
case_dict = {
|
||||
'name': case.name,
|
||||
'ident': get_case_ident(reveal_ident, case)
|
||||
}
|
||||
test_item_dict['case_list'].append(case_dict)
|
||||
design_dict['test_demand'].append(test_item_dict)
|
||||
design_list.append(design_dict)
|
||||
|
||||
# 上面找出了源代码被测件,这里找XQ被测件
|
||||
xq_dut = project_round_one.rdField.filter(type='XQ').first()
|
||||
if xq_dut:
|
||||
xq_designs = xq_dut.rsField.all()
|
||||
for design in xq_designs:
|
||||
design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []}
|
||||
# 获取一个design的所有测试项
|
||||
# 注意:这里有关联测试项!!!需要多对多关系拼接
|
||||
test_items = []
|
||||
test_items.extend(design.dtField.all())
|
||||
test_items.extend(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([demand_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,
|
||||
'case_list': []}
|
||||
for case in test_item.tcField.all():
|
||||
case_dict = {
|
||||
'name': case.name,
|
||||
'ident': get_case_ident(reveal_ident, case)
|
||||
}
|
||||
test_item_dict['case_list'].append(case_dict)
|
||||
design_dict['test_demand'].append(test_item_dict)
|
||||
design_list.append(design_dict)
|
||||
context = {
|
||||
'design_list': design_list,
|
||||
}
|
||||
|
||||
# 手动渲染tpl生成文档
|
||||
input_file = Path.cwd() / 'media' / project_path_str / 'form_template' / 'sm' / '说明追踪.docx'
|
||||
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.save(temporary_file)
|
||||
# 通过docx合并单元格
|
||||
if temporary_file.is_file():
|
||||
try:
|
||||
docu = Document(temporary_file)
|
||||
# 找到其中的表格
|
||||
util.merge_all_cell(docu.tables[0])
|
||||
# 储存到合适位置
|
||||
docu.save(out_put_file)
|
||||
return ChenResponse(code=200, status=200, message='文档生成成功...')
|
||||
except PermissionError as e:
|
||||
return ChenResponse(code=400, status=400, message='请检查文件是否打开,如果打开则关闭...')
|
||||
else:
|
||||
return ChenResponse(code=400, status=400, message='中间文档未找到,请检查你模版是否存在...')
|
||||
155
apps/createDocument/controllers/wtd.py
Normal file
155
apps/createDocument/controllers/wtd.py
Normal file
@@ -0,0 +1,155 @@
|
||||
# 导入内置模块
|
||||
from pathlib import Path
|
||||
# 导入django、ninja等模块
|
||||
from ninja_extra import api_controller, ControllerBase, route
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404
|
||||
# 导入文档处理模块
|
||||
from docxtpl import DocxTemplate, InlineImage
|
||||
# 导入ORM模型
|
||||
from apps.project.models import Project
|
||||
# 导入工具
|
||||
from utils.util import get_str_abbr, get_str_dict
|
||||
from utils.chen_response import ChenResponse
|
||||
from utils.path_utils import project_path
|
||||
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||
# 导入生成日志记录模块
|
||||
from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
|
||||
|
||||
gloger = GenerateLogger("问题单二段文档")
|
||||
|
||||
# @api_controller("/generateWtd", tags=['生成问题单文档系列'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||
@api_controller('/generateWtd', tags=['生成问题单文档系列'])
|
||||
class GenerateControllerWtd(ControllerBase):
|
||||
@route.get("/create/problem", url_name="create-problem")
|
||||
@transaction.atomic
|
||||
def create_problem(self, id: int):
|
||||
"""生成问题单"""
|
||||
project_path_str = project_path(id)
|
||||
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/wtd' / '问题详情表.docx'
|
||||
doc = DocxTemplate(tpl_path)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
problem_list = list(project_obj.projField.distinct()) # 去掉重复,因为和case是多对多
|
||||
problem_list.sort(key=lambda x: int(x.ident))
|
||||
data_list = []
|
||||
for problem in problem_list:
|
||||
problem_dict = {'ident': problem.ident, 'name': problem.name}
|
||||
# 1.生成被测对象名称、被测对象标识、被测对象版本
|
||||
cases = problem.case.all()
|
||||
# generate_log:无关联问题单进入生成日志
|
||||
if cases.count() < 1:
|
||||
gloger.write_warning_log('单个问题单表格', f'问题单{problem.ident}未关联用例,请检查')
|
||||
str_dut_name_list = []
|
||||
str_dut_ident_list = []
|
||||
str_dut_version_list = []
|
||||
# 2.所属用例标识
|
||||
case_ident_list = []
|
||||
# 3.获取依据要求
|
||||
case_design_list = []
|
||||
for case in cases:
|
||||
if case.test.testType == '8':
|
||||
# 1.1.如果为文档审查,提取所属文档名称、文档被测件标识、文档被测件版本
|
||||
str_dut_name_list.append(case.dut.name)
|
||||
str_dut_ident_list.append(case.dut.ref)
|
||||
str_dut_version_list.append(case.dut.version)
|
||||
# 对应dut名称,design章节号,design描述
|
||||
case_design_list.append("".join([case.dut.name, case.design.chapter]))
|
||||
else:
|
||||
# 1.2.如果不为文档审查,则提取该轮次源代码dut的信息
|
||||
so_dut = case.round.rdField.filter(type='SO').first()
|
||||
if so_dut:
|
||||
str_dut_name_list.append(project_obj.name + '软件')
|
||||
str_dut_ident_list.append(so_dut.ref)
|
||||
str_dut_version_list.append(so_dut.version)
|
||||
# TODO:如何处理设计需求的内容,暂时设置为取出图片,只保留文字
|
||||
p_list = []
|
||||
rich_parse_remove_img = RichParser(case.design.description)
|
||||
rich_list = rich_parse_remove_img.get_final_list(doc)
|
||||
for rich in rich_list:
|
||||
if isinstance(rich, dict) or isinstance(rich, InlineImage):
|
||||
continue
|
||||
else:
|
||||
p_list.append(rich)
|
||||
|
||||
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 # 中间变量
|
||||
testType_abbr = get_str_abbr(demand_testType, 'testType') # 输出FT
|
||||
case_ident_list.append("_".join(
|
||||
['YL', testType_abbr, demand.ident, str(int(case.key[-1]) + 1).rjust(3, '0')]))
|
||||
problem_dict['duts_name'] = "/".join(set(str_dut_name_list))
|
||||
problem_dict['duts_ref'] = "/".join(set(str_dut_ident_list))
|
||||
problem_dict['duts_version'] = "/".join(set(str_dut_version_list))
|
||||
temp_name_version = []
|
||||
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['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')
|
||||
|
||||
# 依据要求-获取其设计需求
|
||||
problem_dict['yaoqiu'] = "\a".join(case_design_list)
|
||||
# 问题操作 - HTML解析
|
||||
desc_list = ['【问题操作】']
|
||||
rich_parser = RichParser(problem.operation)
|
||||
desc_list.extend(rich_parser.get_final_list(doc))
|
||||
|
||||
# 问题影响
|
||||
desc_list_result = [f'\a【问题影响】\a{problem.result}']
|
||||
desc_list.extend(desc_list_result)
|
||||
# 问题描述赋值
|
||||
problem_dict['desc'] = desc_list
|
||||
|
||||
# 4.原因分析
|
||||
desc_list_3 = [f'【原因分析】\a{problem.analysis}']
|
||||
problem_dict['cause'] = desc_list_3
|
||||
|
||||
# 5.影响域分析~~~~
|
||||
desc_list_4 = [f'【影响域分析】\a{problem.effect_scope}']
|
||||
problem_dict['effect_scope'] = desc_list_4
|
||||
|
||||
# 6.改正措施
|
||||
problem_dict['solve'] = problem.solve
|
||||
|
||||
# 7.回归验证结果
|
||||
desc_list_5 = []
|
||||
rich_parser5 = RichParser(problem.verify_result)
|
||||
desc_list_5.extend(rich_parser5.get_final_list(doc))
|
||||
problem_dict['verify_result'] = desc_list_5
|
||||
|
||||
# 8.其他日期和人员
|
||||
problem_dict['postPerson'] = problem.postPerson
|
||||
problem_dict['postDate'] = problem.postDate
|
||||
close_str = '□修改文档 □修改程序 □不修改'
|
||||
if len(problem.closeMethod) < 1:
|
||||
close_str = '□修改文档 □修改程序 ■不修改'
|
||||
elif len(problem.closeMethod) == 2:
|
||||
close_str = '■修改文档 ■修改程序 □不修改'
|
||||
else:
|
||||
if problem.closeMethod[0] == '1':
|
||||
close_str = '■修改文档 □修改程序 □不修改'
|
||||
elif problem.closeMethod[0] == '2':
|
||||
close_str = '□修改文档 ■修改程序 □不修改'
|
||||
else:
|
||||
close_str = '□修改文档 □修改程序 □不修改'
|
||||
problem_dict['closeMethod'] = close_str
|
||||
problem_dict['designer'] = problem.designerPerson
|
||||
problem_dict['designDate'] = problem.designDate
|
||||
problem_dict['verifyPerson'] = problem.verifyPerson
|
||||
problem_dict['verifyDate'] = problem.verifyDate
|
||||
data_list.append(problem_dict)
|
||||
context = {
|
||||
'project_name': project_obj.name,
|
||||
'project_ident': project_obj.ident,
|
||||
'problem_list': data_list,
|
||||
}
|
||||
doc.render(context)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path_str / "output_dir/wtd" / '问题详情表.docx')
|
||||
return ChenResponse(status=200, code=200, message="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
apps/createDocument/extensions/__pycache__/mixins.cpython-38.pyc
Normal file
BIN
apps/createDocument/extensions/__pycache__/mixins.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
apps/createDocument/extensions/__pycache__/util.cpython-313.pyc
Normal file
BIN
apps/createDocument/extensions/__pycache__/util.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/extensions/__pycache__/util.cpython-38.pyc
Normal file
BIN
apps/createDocument/extensions/__pycache__/util.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/extensions/__pycache__/zhui.cpython-313.pyc
Normal file
BIN
apps/createDocument/extensions/__pycache__/zhui.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createDocument/extensions/__pycache__/zhui.cpython-38.pyc
Normal file
BIN
apps/createDocument/extensions/__pycache__/zhui.cpython-38.pyc
Normal file
Binary file not shown.
80
apps/createDocument/extensions/content_result_tool.py
Normal file
80
apps/createDocument/extensions/content_result_tool.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from apps.project.models import Project
|
||||
from utils.util import *
|
||||
from utils.chen_response import ChenResponse
|
||||
from django.db.models import Q
|
||||
|
||||
def create_round_context(project_obj: Project, round_id: str):
|
||||
"""根据轮次,生成测评报告中的测评结果"""
|
||||
# 0. 首先定义个轮次对应中文
|
||||
round_chinese = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
||||
round_id = int(round_id)
|
||||
round_str_id = str(round_id)
|
||||
# 1. 首先先查询给定轮次的round对向
|
||||
round_obj = project_obj.pField.filter(key=round_str_id).first()
|
||||
# 如果没有轮次对象则返回错误
|
||||
if not round_obj:
|
||||
return ChenResponse(code=400, status=400, message='注意您没有设置第二轮测试,请添加!')
|
||||
dut_qs_r2 = round_obj.rdField # 当前轮被测件dut对象qs
|
||||
dut_qs_r1 = project_obj.pdField.filter(round__key=str(round_id - 1)) # 上一个轮次被测件dut对象qs
|
||||
|
||||
# 查询当前轮次duts,是否有源代码,如果没有返回错误
|
||||
so_dut = dut_qs_r2.filter(type='SO').first() # 当前轮次源代码被测件对象
|
||||
if not so_dut:
|
||||
return ChenResponse(code=400, status=400, message='注意您某轮次没有编写源代码被测件信息,务必添加...')
|
||||
|
||||
# 查询上一个轮次的dut中的源代码、需求文档
|
||||
r1_so_dut = dut_qs_r1.filter(type='SO').first()
|
||||
|
||||
# 3. 文档审查清单
|
||||
doc_list = []
|
||||
round_duts = round_obj.rdField.filter(Q(type='SJ') | Q(type='XQ') | Q(type='XY'))
|
||||
for dut in round_duts:
|
||||
dut_dict = {
|
||||
'name': dut.name,
|
||||
'ident': dut.ref,
|
||||
'version': dut.version
|
||||
}
|
||||
doc_list.append(dut_dict)
|
||||
|
||||
# 4. 发现多少个问题,什么类型多少个问题
|
||||
problems = project_obj.projField.all().distinct() # !important:大变量-项目所有问题
|
||||
problems_r2 = problems.filter(case__round__key=round_str_id) # 当前轮次所有问题
|
||||
|
||||
# 7. 第二轮动态测试用例个数(动态测试-非静态分析、文档审查、代码审查、代码走查4个)
|
||||
case_r2_qs = round_obj.rcField.filter(~Q(test__testType='2'), ~Q(test__testType='3'), ~Q(test__testType='8'),
|
||||
~Q(test__testType='15')) # !warning:中变量-第一轮动态测试用例qs
|
||||
testType_list, testType_count = create_str_testType_list(case_r2_qs)
|
||||
## 动态测试(第一轮)各个类型测试用例执行表/各个测试需求表
|
||||
demand_r2_dynamic_qs = round_obj.rtField.filter(~Q(testType='2'), ~Q(testType='3'), ~Q(testType='8'),
|
||||
~Q(testType='15')) # !warning:中变量:第一轮动态测试的测试项
|
||||
summary_r2_demand_info, summry_r2_demandType_info = create_demand_summary(demand_r2_dynamic_qs,
|
||||
project_obj.ident)
|
||||
|
||||
# 8.第二轮所有动态问题统计
|
||||
problems_dynamic_r2 = problems_r2.filter(~Q(case__test__testType='2'), ~Q(case__test__testType='3'),
|
||||
~Q(case__test__testType='8'),
|
||||
~Q(case__test__testType='15')) # !critical:大变量:第一轮动态问题单qs
|
||||
problem_dynamic_r2_type_str = create_problem_type_str(problems_dynamic_r2)
|
||||
problem_dynamic_r2_grade_str = create_problem_grade_str(problems_dynamic_r2)
|
||||
r2_dynamic_str = "本轮测试未发现新问题。"
|
||||
has_r2_dynamic = False
|
||||
if len(problems_dynamic_r2) > 0:
|
||||
has_r2_dynamic = True
|
||||
r2_dynamic_str = (f"第{round_chinese[int(round_id)]}轮动态测试共发现问题{problems_dynamic_r2.count()}个,"
|
||||
f"其中{problem_dynamic_r2_type_str};"
|
||||
f"{problem_dynamic_r2_grade_str}。")
|
||||
|
||||
context = {
|
||||
'project_name': project_obj.name,
|
||||
'r1_so_version': r1_so_dut.version,
|
||||
'so_version': so_dut.version,
|
||||
'case_dynamic_r2_count': case_r2_qs.count(),
|
||||
'dynamic_testType_list': '、'.join(testType_list),
|
||||
'dynamic_testType_count': testType_count,
|
||||
'r2_exe_info_all': summary_r2_demand_info,
|
||||
'r2_exe_info_type': summry_r2_demandType_info,
|
||||
'has_r2_dynamic': has_r2_dynamic,
|
||||
'r2_dynamic_str': r2_dynamic_str,
|
||||
'round_id': round_chinese[round_id],
|
||||
}
|
||||
return context
|
||||
207
apps/createDocument/extensions/documentTime.py
Normal file
207
apps/createDocument/extensions/documentTime.py
Normal file
@@ -0,0 +1,207 @@
|
||||
# 本模块主要以项目开始时间、结束时间、轮次开始时间、结束时间计算文档中的各个时间
|
||||
from datetime import timedelta, date
|
||||
from apps.project.models import Project
|
||||
from django.shortcuts import get_object_or_404
|
||||
from ninja.errors import HttpError # 从代码抛出该异常,被ninja截取变为response
|
||||
|
||||
def format_remove_heng(dateT: date) -> str:
|
||||
"""该函数将date对象的横杠-去掉,输出str"""
|
||||
return str(dateT).replace('-', '')
|
||||
|
||||
def times_by_cover_time(cover_time: date) -> dict:
|
||||
"""该函数为每个产品文档根据封面时间,渲染签署页时间、文档变更记录时间"""
|
||||
return {
|
||||
'preparation_time_no_format': cover_time - timedelta(days=2),
|
||||
'preparation_time': format_remove_heng(cover_time - timedelta(days=2)), # 拟制时间:为编制结束时间-2天
|
||||
'inspect_time': format_remove_heng(cover_time - timedelta(days=1)), # 校对时间:为编制时间+1天
|
||||
'auditing_time': format_remove_heng(cover_time),
|
||||
'ratify_time': format_remove_heng(cover_time),
|
||||
'create_doc_time': format_remove_heng(cover_time - timedelta(days=2)),
|
||||
'doc_v1_time': format_remove_heng(cover_time)
|
||||
}
|
||||
|
||||
class DocTime:
|
||||
def __init__(self, project_id: int):
|
||||
self.project = get_object_or_404(Project, id=project_id)
|
||||
# 用户录入时间-项目
|
||||
self.p_start = self.project.beginTime # 被测件接收时间/
|
||||
self.p_end = self.project.endTime # 大纲测评时间周期结束时间/
|
||||
# 遍历轮次时间-多个
|
||||
self.round_count = self.project.pField.count()
|
||||
self.round_time = [] # 轮次按顺序排序
|
||||
for round in self.project.pField.all():
|
||||
self.round_time.append({
|
||||
'start': round.beginTime,
|
||||
'end': round.endTime,
|
||||
'location': round.location
|
||||
})
|
||||
# ~~~~由上面时间二次计算得出时间~~~~ -> TODO:可由用户设置间隔时间!!!!
|
||||
self.dg_bz_start = self.p_start + timedelta(days=1) # 大纲编制开始时间,项目开始时间+1天
|
||||
self.dg_bz_end = self.dg_bz_start + timedelta(days=6) # 大纲编制结束时间,大纲编制开始+6天
|
||||
self.test_sj_start = self.dg_bz_end + timedelta(days=1) # 测评设计与实现时间,在大纲编制结束+1天
|
||||
self.test_sj_end = self.test_sj_start + timedelta(days=5) # 测评设计与实现结束,在开始+5天
|
||||
# ~~~~储存每个文档的cover_time~~~~
|
||||
self.dg_cover_time = self.dg_bz_end
|
||||
self.sm_cover_time = self.test_sj_end
|
||||
self.jl_cover_time = self.round_time[0]['end']
|
||||
self.wtd_cover_time = self.round_time[-1]['end']
|
||||
|
||||
# 该函数生成大纲文档片段-测评时间和地点的时间和地点信息
|
||||
def dg_address_time(self):
|
||||
"""直接返回context去渲染"""
|
||||
# 需要判断round_time是否有值
|
||||
if len(self.round_time) <= 0:
|
||||
raise HttpError(status_code=400, message='您还未创建轮次时间,请填写后生成')
|
||||
return {
|
||||
'start_year': self.p_start.year,
|
||||
'start_month': self.p_start.month,
|
||||
'end_year': self.p_end.year,
|
||||
'end_month': self.p_end.month,
|
||||
'beginTime_strf': format_remove_heng(self.p_start),
|
||||
'dgCompileStart': format_remove_heng(self.dg_bz_start),
|
||||
'dgCompileEnd': format_remove_heng(self.dg_bz_end),
|
||||
'designStart': format_remove_heng(self.test_sj_start),
|
||||
'designEnd': format_remove_heng(self.test_sj_end),
|
||||
'location': self.round_time[0]['location']
|
||||
}
|
||||
|
||||
# 该函数生成报告文档片段-测评时间和地点【注意使用了dg_address_time -> 所以后续有修改注意前导】
|
||||
def bg_address_time(self):
|
||||
if len(self.round_time) <= 0:
|
||||
raise HttpError(status_code=400, message='您还未创建轮次时间,请填写后生成')
|
||||
# 先使用大纲的时间行数作为前三行
|
||||
cname = ['首轮测试', '第二轮测试', '第三轮测试', '第四轮测试', '第五轮测试', '第六轮测试', '第七轮测试',
|
||||
'第八轮测试', '第九轮测试', '第十轮测试']
|
||||
dg_address_time = self.dg_address_time()
|
||||
round_time_list = []
|
||||
index = 0
|
||||
for round_dict in self.round_time:
|
||||
one_dict = {
|
||||
'name': cname[index],
|
||||
'start': format_remove_heng(round_dict['start']),
|
||||
'end': format_remove_heng(round_dict['end']),
|
||||
'location': round_dict['location']
|
||||
}
|
||||
index += 1
|
||||
round_time_list.append(one_dict)
|
||||
return {
|
||||
'begin_year': dg_address_time['start_year'],
|
||||
'begin_month': dg_address_time['start_month'],
|
||||
'end_year': dg_address_time['end_year'],
|
||||
'end_month': dg_address_time['end_month'],
|
||||
'begin_time': dg_address_time['beginTime_strf'],
|
||||
'dg_weave_start_date': dg_address_time['dgCompileStart'],
|
||||
'dg_weave_end_date': dg_address_time['dgCompileEnd'],
|
||||
'sj_weave_start_date': dg_address_time['designStart'],
|
||||
'sj_weave_end_date': dg_address_time['designEnd'],
|
||||
'round_time_list': round_time_list,
|
||||
# 测评总结 -> 依据项目结束时间-7 ~ 项目结束时间
|
||||
'summary_start_date': format_remove_heng(self.p_end - timedelta(days=7)),
|
||||
'summary_end_date': format_remove_heng(self.p_end),
|
||||
}
|
||||
|
||||
# 生成报告中测评完成情况 -> 必须依据其他内容生成时间【注意使用了bg_address_time -> 所以后续有修改注意前导】
|
||||
def bg_completion_situation(self):
|
||||
bg_timer_dict = self.bg_address_time()
|
||||
xq_fx_time_end = self.dg_bz_start + timedelta(days=2)
|
||||
ch_time_start = xq_fx_time_end + timedelta(days=1)
|
||||
ch_time_end = self.dg_bz_end
|
||||
if len(self.round_time) < 1:
|
||||
raise HttpError(status_code=400, message='您还未创建第一轮测试的时间,请填写后再生成')
|
||||
return {
|
||||
'start_time_year': bg_timer_dict['begin_year'],
|
||||
'start_time_month': bg_timer_dict['begin_month'],
|
||||
'xq_fx_time_start_year': self.dg_bz_start.year,
|
||||
'xq_fx_time_start_month': self.dg_bz_start.month,
|
||||
'xq_fx_time_start_day': self.dg_bz_start.day,
|
||||
'xq_fx_time_end_year': xq_fx_time_end.year, # 需求分析结束时间是大纲编制开始+2
|
||||
'xq_fx_time_end_month': xq_fx_time_end.month,
|
||||
'xq_fx_time_end_day': xq_fx_time_end.day,
|
||||
'ch_start_year': ch_time_start.year,
|
||||
'ch_start_month': ch_time_start.month,
|
||||
'ch_start_day': ch_time_start.day,
|
||||
'ch_end_year': ch_time_end.year,
|
||||
'ch_end_month': ch_time_end.month,
|
||||
'ch_end_day': ch_time_end.day,
|
||||
'sj_start_year': self.test_sj_start.year,
|
||||
'sj_start_month': self.test_sj_start.month,
|
||||
'sj_start_day': self.test_sj_start.day,
|
||||
'sj_end_year': self.test_sj_end.year,
|
||||
'sj_end_month': self.test_sj_end.month,
|
||||
'sj_end_day': self.test_sj_end.day,
|
||||
'end_time_year': self.p_end.year,
|
||||
'end_time_month': self.p_end.month,
|
||||
'exec_start_time_year': self.round_time[0]['start'].year,
|
||||
'exec_start_time_month': self.round_time[0]['start'].month,
|
||||
'exec_start_time_day': self.round_time[0]['start'].day,
|
||||
'exec_end_time_year': self.round_time[0]['end'].year,
|
||||
'exec_end_time_month': self.round_time[0]['end'].month,
|
||||
'exec_end_time_day': self.round_time[0]['end'].day,
|
||||
}
|
||||
|
||||
# 该函数生成最终大纲的时间
|
||||
def dg_final_time(self):
|
||||
cover_time = self.dg_bz_end
|
||||
context = times_by_cover_time(cover_time)
|
||||
context.update(cover_time=cover_time.strftime("%Y年%m月%d日"))
|
||||
# 新增给大纲模版10.2章节context
|
||||
context.update(basic_line1=cover_time.strftime("%Y年%m月"), basic_line2=self.p_end.strftime("%Y年%m月"))
|
||||
# 新增给大纲模版10.3.2章节的context
|
||||
sm_context = self.sm_final_time()
|
||||
context.update(sm_end_time=sm_context['preparation_time_no_format'].strftime("%Y年%m月"))
|
||||
return context
|
||||
|
||||
# 该函数生成说明文档的时间 -> 依据项目时间而非用户第一轮填写时间!
|
||||
def sm_final_time(self):
|
||||
cover_time = self.test_sj_end # 封面时间:为大纲时间中“测评设计与实现”结束时间
|
||||
context = times_by_cover_time(cover_time)
|
||||
context.update(cover_time=cover_time.strftime("%Y年%m月%d日"))
|
||||
return context
|
||||
|
||||
# 该函数生成记录文档的时间 -> 依据第一轮测试用户填写的事件
|
||||
def jl_final_time(self):
|
||||
if len(self.round_time) < 1:
|
||||
raise HttpError(status_code=400, message='您还未创建第一轮测试的时间,请填写后再生成')
|
||||
cover_time = self.round_time[0]['end'] # 封面时间为用户填写第一轮结束时间
|
||||
context = times_by_cover_time(cover_time)
|
||||
context.update(cover_time=cover_time.strftime("%Y年%m月%d日"))
|
||||
return context
|
||||
|
||||
# 问题单的时间 -> 依据最后一轮次的结束时间+1天
|
||||
def wtd_final_time(self):
|
||||
if len(self.round_time) < 1:
|
||||
raise HttpError(status_code=400, message='您还未创建第一轮测试的时间,请填写后再生成')
|
||||
cover_time = self.round_time[-1]['end']
|
||||
context = times_by_cover_time(cover_time)
|
||||
context.update(cover_time=cover_time.strftime("%Y年%m月%d日"))
|
||||
return context
|
||||
|
||||
# 回归测试说明时间 -> 根据第二轮、第三轮...的开始时间
|
||||
def hsm_final_time(self, round_key: str):
|
||||
if len(self.round_time) < int(round_key) + 1:
|
||||
raise HttpError(status_code=400, message='您填写的回归轮次时间不正确,请填写后再生成')
|
||||
cover_time = self.round_time[int(round_key)]['start']
|
||||
context = times_by_cover_time(cover_time)
|
||||
context.update(cover_time=cover_time.strftime("%Y年%m月%d日"))
|
||||
return context
|
||||
|
||||
# 回归测试记录时间 -> 根据第二轮、第三轮...的结束时间
|
||||
def hjl_final_time(self, round_key: str) -> dict:
|
||||
if len(self.round_time) < int(round_key) + 1:
|
||||
raise HttpError(status_code=400, message='您填写的回归轮次时间不正确,请填写后再生成')
|
||||
cover_time = self.round_time[int(round_key)]['end']
|
||||
context = times_by_cover_time(cover_time)
|
||||
context.update(cover_time=cover_time.strftime("%Y年%m月%d日"))
|
||||
return context
|
||||
|
||||
# 生成报告非过程时间 -> 根据项目结束时间来定
|
||||
def bg_final_time(self) -> dict:
|
||||
if len(self.round_time) <= 0:
|
||||
raise HttpError(status_code=400, message='您还未创建轮次时间,请填写后生成')
|
||||
cover_time = self.p_end
|
||||
# 这里做判断,如果项目结束时间/最后一轮结束时间
|
||||
if cover_time < self.round_time[-1]['end']:
|
||||
raise HttpError(500, message='项目结束时间早于最后一轮次结束时间或等于开始时间,请修改项目结束时间')
|
||||
context = times_by_cover_time(cover_time)
|
||||
context.update(cover_time=cover_time.strftime("%Y年%m月%d日"))
|
||||
return context
|
||||
21
apps/createDocument/extensions/mixins.py
Normal file
21
apps/createDocument/extensions/mixins.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from abc import ABC
|
||||
from apps.project.models import Project
|
||||
from django.shortcuts import get_object_or_404
|
||||
from apps.dict.models import Fragment
|
||||
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||
|
||||
class FragementToolsMixin(ABC):
|
||||
"""该混合主要给文档片段进行功能封装"""
|
||||
def _generate_frag(self, id: int, doc, doc_name: str):
|
||||
"""传入项目id/"""
|
||||
project_qs = get_object_or_404(Project, id=id)
|
||||
replace = False # 是否替换标志
|
||||
rich_text_list = []
|
||||
# [deprecated]判断是否有当前项目的文档片段
|
||||
fragments = project_qs.frag.all()
|
||||
# 传入'片段名称'和判断is_main
|
||||
frag: Fragment = fragments.filter(name=doc_name, is_main=True).first()
|
||||
if frag:
|
||||
replace = True
|
||||
rich_text_list = RichParser(frag.content).get_final_format_list(doc)
|
||||
return replace, frag, rich_text_list
|
||||
127
apps/createDocument/extensions/parse_rich_text.py
Normal file
127
apps/createDocument/extensions/parse_rich_text.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
专门解析富文本插件tinymce的html内容
|
||||
"""
|
||||
import pandas as pd
|
||||
from bs4 import BeautifulSoup
|
||||
from bs4.element import Tag, NavigableString
|
||||
import base64
|
||||
import io
|
||||
from docxtpl import InlineImage
|
||||
from docx.shared import Mm, Cm
|
||||
import re
|
||||
|
||||
# text.replace('\xa0', ' '))
|
||||
class RichParser:
|
||||
def __init__(self, rich_text):
|
||||
# 将rich_text的None变为空字符串:鲁棒
|
||||
if rich_text is None:
|
||||
rich_text = ""
|
||||
# 对原始html解析后的bs对象
|
||||
self.bs = BeautifulSoup(rich_text, 'html.parser')
|
||||
self.content = self.remove_n_in_contents()
|
||||
# 最终的解析后的列表
|
||||
self.data_list = []
|
||||
self.line_parse()
|
||||
|
||||
# 1.函数:将self.bs.contents去掉\n,获取每行数据
|
||||
def remove_n_in_contents(self):
|
||||
content_list = []
|
||||
for line in self.bs.contents:
|
||||
if line != '\n':
|
||||
content_list.append(line)
|
||||
return content_list
|
||||
|
||||
# 2.逐个遍历self.content,去掉table元素Tag对象单独解析
|
||||
def line_parse(self):
|
||||
for tag in self.content:
|
||||
if isinstance(tag, NavigableString):
|
||||
self.data_list.append(tag.text)
|
||||
elif isinstance(tag, Tag):
|
||||
if tag.name == 'p':
|
||||
img_list = tag.find_all('img')
|
||||
if len(img_list) > 0:
|
||||
for img_item in img_list:
|
||||
self.data_list.append(img_item.get('src'))
|
||||
else:
|
||||
self.data_list.append(tag.text)
|
||||
elif tag.name == 'table':
|
||||
df_dict_list = self.parse_tag2list(tag)
|
||||
self.data_list.append(df_dict_list)
|
||||
elif tag.name == 'div':
|
||||
table_list = tag.find_all('table')
|
||||
if len(table_list) > 0:
|
||||
for table in table_list:
|
||||
df_dict_list = self.parse_tag2list(table)
|
||||
self.data_list.append(df_dict_list)
|
||||
|
||||
# 3.1.辅助方法,将<table>的Tag对象转为[[]]二维列表格式
|
||||
def parse_tag2list(self, table_tag):
|
||||
# str(tag)可直接变成<table>xxx</table>
|
||||
pd_list = pd.read_html(io.StringIO(str(table_tag)))
|
||||
# 将dataframe变为数组
|
||||
df = pd_list[0]
|
||||
# 处理第一行为数字的情况,如果为数字则删除第一行,让第二行为列名
|
||||
if all(isinstance(col, int) for col in df.columns):
|
||||
df.columns = df.iloc[0]
|
||||
df = df.drop(0) # 删除原来的第一行
|
||||
# 转为列表的列表(二维列表)
|
||||
# return df.values.tolist()
|
||||
return df.fillna('').T.reset_index().T.values.tolist()
|
||||
|
||||
# 3.2.辅助方法,打印解析后列表
|
||||
def print_content(self):
|
||||
for line in self.data_list:
|
||||
print(line)
|
||||
|
||||
# 4.1.最终方法,生成给docxtpl可用的列表 -> 注意需要传递DocxTemplate对象,在接口函数里面初始化的
|
||||
def get_final_list(self, doc, /, *, img_size=100, height=80):
|
||||
"""注意关键字传参可修改图片大小img_size:int=100"""
|
||||
final_list = []
|
||||
for oneline in self.data_list:
|
||||
# 这里要单独处理下二维列表
|
||||
if isinstance(oneline, list):
|
||||
final_list.append({'isTable': True, 'data': oneline})
|
||||
continue
|
||||
if oneline.startswith("data:image/png;base64"):
|
||||
base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", ""))
|
||||
# ~~~设置了固定宽度、高度~~~
|
||||
final_list.append(InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=Mm(height)))
|
||||
else:
|
||||
final_list.append(oneline)
|
||||
if len(final_list) <= 0:
|
||||
final_list.append("")
|
||||
return final_list
|
||||
|
||||
# 4.2.最终方法,在上面方法基础上,增加格式,例如<p>增加缩进,图片居中,<p>包含“图x”则居中
|
||||
def get_final_format_list(self, doc, /, *, img_size=115, height=80):
|
||||
final_list = []
|
||||
for oneline in self.data_list:
|
||||
# 这里要单独处理下二维列表
|
||||
if isinstance(oneline, list):
|
||||
final_list.append({'isTable': True, 'data': oneline})
|
||||
continue
|
||||
if oneline.startswith("data:image/png;base64"):
|
||||
base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", ""))
|
||||
# 1.和上面函数变化:图片更改为dict然后isCenter属性居中
|
||||
final_list.append(
|
||||
{'isCenter': True,
|
||||
'data': InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=height)})
|
||||
else:
|
||||
# 2.和上面区别:如果<p>带有“图”则居中
|
||||
if re.match(r"[表图]\d.*", oneline):
|
||||
final_list.append({"isCenter": True, "data": oneline})
|
||||
else:
|
||||
final_list.append({"isCenter": False, "data": oneline})
|
||||
if len(final_list) <= 0:
|
||||
final_list.append("")
|
||||
return final_list
|
||||
|
||||
# 5.最终方法,去掉图片和table元素 -> 纯文本列表
|
||||
def get_final_p_list(self):
|
||||
final_list = []
|
||||
for oneline in self.data_list:
|
||||
if isinstance(oneline, list) or oneline.startswith("data:image/png;base64"):
|
||||
continue
|
||||
else:
|
||||
final_list.append(oneline)
|
||||
return final_list
|
||||
35
apps/createDocument/extensions/solve_problem.py
Normal file
35
apps/createDocument/extensions/solve_problem.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from apps.project.models import Problem
|
||||
from utils.util import get_str_dict
|
||||
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||
|
||||
def parse_html(html_txt, a_list, doc):
|
||||
"""解析HTML字段的文字和图片到列表,输入是HTML字段的txt以及列表,输出列表"""
|
||||
parser = RichParser(html_txt)
|
||||
a_list.extend(parser.get_final_list(doc, img_size=80))
|
||||
return a_list
|
||||
|
||||
def create_one_problem_dit(problem: Problem, problem_prefix: str, doc) -> dict:
|
||||
"""问题单汇总表每个问题作为一行的数据"""
|
||||
problem_dict = {
|
||||
'ident': '_'.join([problem_prefix, problem.ident]),
|
||||
'grade': get_str_dict(problem.grade, 'problemGrade'),
|
||||
'type': get_str_dict(problem.type, 'problemType'),
|
||||
'status': get_str_dict(problem.status, 'problemStatu')
|
||||
}
|
||||
# 问题操作 - HTML解析
|
||||
desc_list = ['【问题描述】']
|
||||
desc_list = parse_html(problem.operation, desc_list, doc)
|
||||
desc_list_yq = [f'\a【问题影响】\a{problem.result}']
|
||||
desc_list.extend(desc_list_yq)
|
||||
problem_dict['desciption'] = desc_list
|
||||
# 问题处理方式表格单元格
|
||||
solve_list = [f'【原因分析】\a{problem.analysis}']
|
||||
solve_list_effect = [f'\a【影响域】\a{problem.effect_scope}']
|
||||
solve_list_basic = ['\a【处理方式】', problem.solve]
|
||||
solve_list_verify = ['\a【回归验证】']
|
||||
solve_list_verify = parse_html(problem.verify_result, solve_list_verify, doc)
|
||||
solve_list.extend(solve_list_effect)
|
||||
solve_list.extend(solve_list_basic)
|
||||
solve_list.extend(solve_list_verify)
|
||||
problem_dict['solve'] = solve_list
|
||||
return problem_dict
|
||||
97
apps/createDocument/extensions/util.py
Normal file
97
apps/createDocument/extensions/util.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from pathlib import Path
|
||||
from docxtpl import DocxTemplate
|
||||
from docx.table import Table
|
||||
from utils.chen_response import ChenResponse
|
||||
from typing import Any
|
||||
from apps.project.models import Project
|
||||
from utils.path_utils import project_path
|
||||
|
||||
def merge_all_cell(table: Table) -> None:
|
||||
"""生成需求研总对照表工具:逐个找第二列和第三列单元格的text,如果一致则合并"""
|
||||
col_list = [table.columns[1], table.columns[2]]
|
||||
# 合并第二列相同的单元格
|
||||
for col_right in col_list:
|
||||
index = 0
|
||||
temp_text = ""
|
||||
for cell in col_right.cells:
|
||||
if index == 0:
|
||||
temp_text = cell.text
|
||||
else:
|
||||
if cell.text == temp_text:
|
||||
if cell.text == '': # 不知道什么原因必须这样判断下
|
||||
cell.text = '/'
|
||||
text_temp = cell.text
|
||||
ce = cell.merge(col_right.cells[index - 1])
|
||||
ce.text = text_temp
|
||||
else:
|
||||
temp_text = cell.text
|
||||
index += 1
|
||||
|
||||
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)
|
||||
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_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)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / 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_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)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/bg" / 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_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)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/wtd" / template_name)
|
||||
return ChenResponse(status=200, code=200, message="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
|
||||
def get_round1_problem(project: Project) -> Any:
|
||||
"""
|
||||
从项目返回第一轮问题单
|
||||
:param project: Project项目Model对象
|
||||
:return: 问题单的列表
|
||||
"""
|
||||
all_problem_qs = project.projField.all()
|
||||
# 遍历每个问题,找出第一轮的问题
|
||||
problem_set = set()
|
||||
for problem in all_problem_qs:
|
||||
flag = False
|
||||
for case in problem.case.all():
|
||||
if case.round.key == '0':
|
||||
flag = True
|
||||
if flag:
|
||||
problem_set.add(problem)
|
||||
return list(problem_set)
|
||||
|
||||
def delete_dir_files(path: Path) -> Any:
|
||||
"""传入一个Path对象,如果是文件夹则删除里面所有的文件(不删除文件夹)"""
|
||||
if path.is_dir():
|
||||
for file in path.iterdir():
|
||||
if file.is_file():
|
||||
file.unlink()
|
||||
99
apps/createDocument/extensions/zhui.py
Normal file
99
apps/createDocument/extensions/zhui.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from apps.project.models import Project
|
||||
from utils.chapter_tools.csx_chapter import create_csx_chapter_dict
|
||||
from utils.util import get_testType, get_case_ident
|
||||
|
||||
# 传入项目对象、dut的类型例如'XQ'、round_str字符串表示例如第一轮为'XQ',测试项其实章节前缀例如
|
||||
def create_bg_round1_zhui(project_obj: Project, dut_str='XQ', round_str='0'):
|
||||
"""传入项目对象,返回{仅第一轮}的design_list渲染到模版的列表"""
|
||||
# 首先定义后面用问题单前缀
|
||||
problem_prefix = "".join(['PT_', project_obj.ident, '_'])
|
||||
# 如果是第一轮,测试项章节号前缀则为6.2,其他轮次为4.1
|
||||
demand_prefix = '6.2' if round_str == '0' else "3.1"
|
||||
design_list = []
|
||||
round_obj = project_obj.pField.filter(key=round_str).first() # 轮次对象
|
||||
case_index = 1
|
||||
if round_obj:
|
||||
testType_list, last_chapter_items = create_csx_chapter_dict(round_obj)
|
||||
specific_dut = round_obj.rdField.filter(type=dut_str).first() # design的列表
|
||||
if dut_str == 'XQ':
|
||||
so_dut = round_obj.rdField.filter(type='SO').first()
|
||||
if so_dut:
|
||||
designs = so_dut.rsField.all()
|
||||
for design in designs:
|
||||
design_dict = {'name': "/", 'chapter': "/", 'test_demand': []}
|
||||
# 获取一个design的所以测试项,包括关联测试项
|
||||
test_items = []
|
||||
test_items.extend(design.dtField.all())
|
||||
test_items.extend(design.odField.all())
|
||||
for test_item in test_items:
|
||||
key_index = int(test_item.key.split("-")[-1]) + 1
|
||||
test_index = str(key_index).rjust(3, '0')
|
||||
reveal_ident = "_".join(
|
||||
["XQ", get_testType(test_item.testType, "testType"),
|
||||
test_item.ident, test_index])
|
||||
test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1
|
||||
test_chapter = ".".join([demand_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,
|
||||
'case_list': []}
|
||||
for case in test_item.tcField.all():
|
||||
# 用例如果关联了问题单,那么直接判断未通过,如果没有关联问题单,则找步骤里面是否有未执行
|
||||
# 如果未执行,不显示未执行,显示“/”斜杠
|
||||
is_passed = '通过'
|
||||
problem_ident_list = []
|
||||
for problem in case.caseField.all():
|
||||
problem_ident_list.append("".join([problem_prefix, problem.ident]))
|
||||
if len(problem_ident_list) > 0:
|
||||
is_passed = '未通过'
|
||||
case_dict = {
|
||||
'index': case_index,
|
||||
'name': case.name,
|
||||
'ident': get_case_ident(reveal_ident, case),
|
||||
'passed': is_passed,
|
||||
'problem_ident_list': "\a".join(problem_ident_list)
|
||||
}
|
||||
test_item_dict['case_list'].append(case_dict)
|
||||
case_index += 1
|
||||
design_dict['test_demand'].append(test_item_dict)
|
||||
design_list.append(design_dict)
|
||||
|
||||
if specific_dut:
|
||||
designs = specific_dut.rsField.all()
|
||||
for design in designs:
|
||||
design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []}
|
||||
# 获取一个design的所以测试项,包括关联测试项
|
||||
test_items = []
|
||||
test_items.extend(design.dtField.all())
|
||||
test_items.extend(design.odField.all())
|
||||
for test_item in test_items:
|
||||
key_index = int(test_item.key.split("-")[-1]) + 1
|
||||
test_index = str(key_index).rjust(3, '0')
|
||||
reveal_ident = "_".join(
|
||||
["XQ", get_testType(test_item.testType, "testType"),
|
||||
test_item.ident, test_index])
|
||||
test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1
|
||||
test_chapter = ".".join([demand_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,
|
||||
'case_list': []}
|
||||
for case in test_item.tcField.all():
|
||||
# 用例如果关联了问题单,那么直接判断未通过,如果没有关联问题单,则找步骤里面是否有未执行
|
||||
# 如果未执行,不显示未执行,显示“/”斜杠
|
||||
is_passed = '通过'
|
||||
problem_ident_list = []
|
||||
for problem in case.caseField.all():
|
||||
problem_ident_list.append("".join([problem_prefix, problem.ident]))
|
||||
if len(problem_ident_list) > 0:
|
||||
is_passed = '未通过'
|
||||
case_dict = {
|
||||
"index": case_index,
|
||||
'name': case.name,
|
||||
'ident': get_case_ident(reveal_ident, case),
|
||||
'passed': is_passed,
|
||||
'problem_ident_list': "\a".join(problem_ident_list)
|
||||
}
|
||||
test_item_dict['case_list'].append(case_dict)
|
||||
case_index += 1
|
||||
design_dict['test_demand'].append(test_item_dict)
|
||||
design_list.append(design_dict)
|
||||
return design_list
|
||||
0
apps/createDocument/migrations/__init__.py
Normal file
0
apps/createDocument/migrations/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
1
apps/createDocument/models.py
Normal file
1
apps/createDocument/models.py
Normal file
@@ -0,0 +1 @@
|
||||
from django.db import models
|
||||
0
apps/createDocument/schema/__init__.py
Normal file
0
apps/createDocument/schema/__init__.py
Normal file
BIN
apps/createDocument/schema/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
apps/createDocument/schema/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
3
apps/createDocument/tests.py
Normal file
3
apps/createDocument/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
1
apps/createDocument/views.py
Normal file
1
apps/createDocument/views.py
Normal file
@@ -0,0 +1 @@
|
||||
from django.shortcuts import render
|
||||
0
apps/createSeiTaiDocument/__init__.py
Normal file
0
apps/createSeiTaiDocument/__init__.py
Normal file
BIN
apps/createSeiTaiDocument/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
apps/createSeiTaiDocument/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createSeiTaiDocument/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
apps/createSeiTaiDocument/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/createSeiTaiDocument/__pycache__/admin.cpython-313.pyc
Normal file
BIN
apps/createSeiTaiDocument/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createSeiTaiDocument/__pycache__/admin.cpython-38.pyc
Normal file
BIN
apps/createSeiTaiDocument/__pycache__/admin.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/createSeiTaiDocument/__pycache__/apps.cpython-313.pyc
Normal file
BIN
apps/createSeiTaiDocument/__pycache__/apps.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createSeiTaiDocument/__pycache__/apps.cpython-38.pyc
Normal file
BIN
apps/createSeiTaiDocument/__pycache__/apps.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
apps/createSeiTaiDocument/__pycache__/controllers.cpython-38.pyc
Normal file
BIN
apps/createSeiTaiDocument/__pycache__/controllers.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
apps/createSeiTaiDocument/__pycache__/docXmlUtils.cpython-38.pyc
Normal file
BIN
apps/createSeiTaiDocument/__pycache__/docXmlUtils.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/createSeiTaiDocument/__pycache__/models.cpython-313.pyc
Normal file
BIN
apps/createSeiTaiDocument/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/createSeiTaiDocument/__pycache__/models.cpython-38.pyc
Normal file
BIN
apps/createSeiTaiDocument/__pycache__/models.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/createSeiTaiDocument/__pycache__/schema.cpython-313.pyc
Normal file
BIN
apps/createSeiTaiDocument/__pycache__/schema.cpython-313.pyc
Normal file
Binary file not shown.
3
apps/createSeiTaiDocument/admin.py
Normal file
3
apps/createSeiTaiDocument/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
5
apps/createSeiTaiDocument/apps.py
Normal file
5
apps/createSeiTaiDocument/apps.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class CreateseitaidocumentConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.createSeiTaiDocument'
|
||||
493
apps/createSeiTaiDocument/controllers.py
Normal file
493
apps/createSeiTaiDocument/controllers.py
Normal file
@@ -0,0 +1,493 @@
|
||||
from pathlib import Path
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
from utils.path_utils import project_path
|
||||
from ninja import File, UploadedFile
|
||||
from ninja.errors import HttpError
|
||||
from ninja_extra.controllers import api_controller, ControllerBase, route
|
||||
from ninja_jwt.authentication import JWTAuth
|
||||
from ninja_extra.permissions import IsAuthenticated
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db.models import QuerySet
|
||||
from docx import Document
|
||||
from docxtpl import DocxTemplate
|
||||
# 工具
|
||||
from apps.createSeiTaiDocument.docXmlUtils import generate_temp_doc, get_frag_from_document
|
||||
from apps.createSeiTaiDocument.schema import SeitaiInputSchema
|
||||
from utils.chen_response import ChenResponse
|
||||
from apps.project.models import Project, Dut
|
||||
from apps.createDocument.extensions.documentTime import DocTime
|
||||
from utils.util import get_str_dict
|
||||
from apps.createSeiTaiDocument.extensions.download_response import get_file_respone
|
||||
# 图片工具docx
|
||||
from apps.createSeiTaiDocument.extensions.shape_size_tool import set_shape_size
|
||||
# 修改temp文本片段工具
|
||||
from apps.createSeiTaiDocument.docXmlUtils import get_jinja_stdContent_element, stdContent_modify
|
||||
|
||||
main_download_path = Path(settings.BASE_DIR) / 'media'
|
||||
|
||||
# @api_controller("/create", tags=['生成产品文档接口'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||
@api_controller("/create", tags=['生成产品文档接口'])
|
||||
class GenerateSeitaiController(ControllerBase):
|
||||
chinese_round_name: list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.project_obj: Project | None = None
|
||||
self.temp_context = {}
|
||||
|
||||
@route.post("/dgDocument", url_name="create-dgDocument")
|
||||
@transaction.atomic
|
||||
def create_dgDocument(self, payload: SeitaiInputSchema):
|
||||
# 获取项目Model
|
||||
self.project_obj = get_object_or_404(Project, id=payload.id)
|
||||
# 生成大纲需要的文本片段信息储存在字典里面
|
||||
sec_title = get_str_dict(self.project_obj.secret, 'secret')
|
||||
duty_person = self.project_obj.duty_person
|
||||
is_jd = True if self.project_obj.report_type == '9' else False
|
||||
self.temp_context = {
|
||||
'is_jd': is_jd,
|
||||
'jd_or_third': "鉴定" if is_jd else "第三方",
|
||||
'project_ident': self.project_obj.ident,
|
||||
'project_name': self.project_obj.name,
|
||||
'test_purpose': "装备鉴定和列装定型" if is_jd else "软件交付和使用",
|
||||
'sec_title': sec_title,
|
||||
'sec': sec_title,
|
||||
'duty_person': duty_person,
|
||||
'member': self.project_obj.member[0] if len(
|
||||
self.project_obj.member) > 0 else duty_person,
|
||||
'entrust_unit': self.project_obj.entrust_unit
|
||||
} | DocTime(payload.id).dg_final_time() # python3.9以上推荐使用|运算符合并
|
||||
# 调用self添加temp_context信息
|
||||
self.get_first_round_code_ident()
|
||||
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未报出错误原因,反正在生成文档出错'))
|
||||
dg_replace_path, dg_seitai_final_path = result
|
||||
# ~~~~start:2025/04/19-新增渲染单个字段(可能封装为函数-对temp文件下的jinja字段处理)~~~~
|
||||
# 现在已经把alias和stdContent对应起来了
|
||||
text_frag_name_list, doc_docx = get_jinja_stdContent_element(dg_replace_path)
|
||||
# 遍历找出来的文本片段进行修改
|
||||
self.text_frag_replace_handle(text_frag_name_list, doc_docx)
|
||||
# ~~~~end~~~~
|
||||
try:
|
||||
doc_docx.save(dg_seitai_final_path)
|
||||
# 文件下载
|
||||
return get_file_respone(payload.id, '测评大纲')
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="文档未生成或生成错误!,{0}".format(e))
|
||||
|
||||
@route.post('/smDocument', url_name='create-smDocument')
|
||||
@transaction.atomic
|
||||
def create_smDocument(self, payload: SeitaiInputSchema):
|
||||
"""生成最后说明文档"""
|
||||
# 获取项目对象
|
||||
self.project_obj = get_object_or_404(Project, id=payload.id)
|
||||
# 首先第二层模版所需变量
|
||||
is_jd = True if self.project_obj.report_type == '9' else False
|
||||
self.temp_context = {
|
||||
'project_name': self.project_obj.name,
|
||||
'project_ident': self.project_obj.ident,
|
||||
'is_jd': is_jd,
|
||||
'jd_or_third': "鉴定" if is_jd else "第三方",
|
||||
'ident': self.project_obj.ident,
|
||||
'sec_title': get_str_dict(self.project_obj.secret, 'secret'),
|
||||
'sec': get_str_dict(self.project_obj.secret, 'secret'),
|
||||
'duty_person': self.project_obj.duty_person,
|
||||
'member': self.project_obj.member[0] if len(
|
||||
self.project_obj.member) > 0 else self.project_obj.duty_person,
|
||||
} | DocTime(payload.id).sm_final_time()
|
||||
self.get_first_round_code_ident()
|
||||
# 文档片段操作
|
||||
result = generate_temp_doc('sm', payload.id, frag_list=payload.frag)
|
||||
if isinstance(result, dict):
|
||||
return ChenResponse(code=400, status=400, message=result.get('msg', '无错误原因'))
|
||||
sm_to_tpl_file, sm_seitai_final_file = result
|
||||
|
||||
# 文本片段操作
|
||||
text_frag_name_list, doc_docx = get_jinja_stdContent_element(sm_to_tpl_file)
|
||||
self.text_frag_replace_handle(text_frag_name_list, doc_docx)
|
||||
# 注册时间变量
|
||||
try:
|
||||
doc_docx.save(sm_seitai_final_file)
|
||||
return get_file_respone(payload.id, '测试说明')
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
|
||||
@route.post('/jlDocument', url_name='create-jlDocument')
|
||||
@transaction.atomic
|
||||
def create_jlDocument(self, payload: SeitaiInputSchema):
|
||||
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
|
||||
self.temp_context = {
|
||||
'project_name': self.project_obj.name,
|
||||
'project_ident': self.project_obj.ident,
|
||||
'is_jd': is_jd,
|
||||
'name': self.project_obj.name,
|
||||
'ident': self.project_obj.ident,
|
||||
'sec_title': get_str_dict(self.project_obj.secret, 'secret'),
|
||||
'duty_person': self.project_obj.duty_person, 'member': member
|
||||
} | DocTime(payload.id).jl_final_time()
|
||||
self.get_xq_doc_informations() # 添加文本片段“xq_version”
|
||||
result = generate_temp_doc('jl', payload.id, frag_list=payload.frag)
|
||||
if isinstance(result, dict):
|
||||
return ChenResponse(code=400, status=400, message=result.get('msg', '无错误原因'))
|
||||
jl_to_tpl_file, jl_seitai_final_file = result
|
||||
text_frag_name_list, doc_docx = get_jinja_stdContent_element(jl_to_tpl_file)
|
||||
# 文本片段操作
|
||||
self.text_frag_replace_handle(text_frag_name_list, doc_docx)
|
||||
# 重新设置时序图大小
|
||||
for shape in doc_docx.inline_shapes:
|
||||
set_shape_size(shape)
|
||||
try:
|
||||
doc_docx.save(jl_seitai_final_file)
|
||||
return get_file_respone(payload.id, '测试记录')
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
|
||||
@route.post('/hsmDocument', url_name='create-hsmDocument')
|
||||
@transaction.atomic
|
||||
def create_hsmDocument(self, payload: SeitaiInputSchema):
|
||||
"""生成最后的回归测试说明-(多个文档)"""
|
||||
self.project_obj = get_object_or_404(Project, id=payload.id)
|
||||
hround_list: QuerySet = self.project_obj.pField.exclude(key='0') # 非第一轮次
|
||||
cname_list = []
|
||||
if len(hround_list) < 1:
|
||||
return ChenResponse(code=400, status=400, message='无回归轮次,请添加后再生成')
|
||||
for hround in hround_list:
|
||||
# 获取当前轮次中文数字
|
||||
cname = self.chinese_round_name[int(hround.key)]
|
||||
# 将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
|
||||
# 回归轮次的标识和版本
|
||||
so_dut: Dut = hround.rdField.filter(type='SO').first()
|
||||
if not so_dut:
|
||||
return ChenResponse(status=400, code=400, message=f'您缺少第{cname}轮的源代码被测件')
|
||||
# 每次循环会更新temp_context
|
||||
self.temp_context = {
|
||||
'project_name': self.project_obj.name,
|
||||
'project_ident': self.project_obj.ident,
|
||||
'is_jd': is_jd,
|
||||
'sec_title': get_str_dict(self.project_obj.secret, 'secret'),
|
||||
'duty_person': self.project_obj.duty_person,
|
||||
'member': member,
|
||||
'round_num': str(int(hround.key) + 1),
|
||||
'round_num_chn': cname,
|
||||
'soft_ident': so_dut.ref,
|
||||
'soft_version': so_dut.version,
|
||||
'location': hround.location,
|
||||
} | DocTime(payload.id).hsm_final_time(hround.key)
|
||||
# 注意回归测试说明、回归测试记录都生成多个文档
|
||||
result = generate_temp_doc('hsm', payload.id, round_num=cname, frag_list=payload.frag)
|
||||
if isinstance(result, dict):
|
||||
return ChenResponse(status=400, code=400,
|
||||
message=result.get('msg', '回归测试说明生成报错...'))
|
||||
hsm_replace_path, hsm_seitai_final_path = result
|
||||
text_frag_name_list, doc_docx = get_jinja_stdContent_element(hsm_replace_path)
|
||||
# 文本片段操作
|
||||
self.text_frag_replace_handle(text_frag_name_list, doc_docx)
|
||||
try:
|
||||
doc_docx.save(hsm_seitai_final_path)
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
# 因为回归说明、回归记录可能有多份,多份下载zip否则docx
|
||||
if len(cname_list) == 1:
|
||||
return get_file_respone(payload.id, '第二轮回归测试说明')
|
||||
else:
|
||||
return get_file_respone(payload.id, list(map(lambda x: f"第{x}轮回归测试说明", cname_list)))
|
||||
|
||||
@route.post('/hjlDocument', url_name='create-hjlDocument')
|
||||
@transaction.atomic
|
||||
def create_hjlDocument(self, payload: SeitaiInputSchema):
|
||||
"""生成最后的回归测试记录-(多个文档)"""
|
||||
self.project_obj: Project = get_object_or_404(Project, id=payload.id)
|
||||
# 取非第一轮次
|
||||
hround_list: QuerySet = self.project_obj.pField.exclude(key='0')
|
||||
cname_list = []
|
||||
if len(hround_list) < 1:
|
||||
return ChenResponse(code=400, status=400, message='无回归测试轮次,请创建后再试')
|
||||
for hround in hround_list:
|
||||
# 取出当前轮次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
|
||||
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:
|
||||
return ChenResponse(status=400, code=400, message=f'您缺少第{cname}轮的源代码被测件')
|
||||
self.temp_context = {
|
||||
'project_name': self.project_obj.name,
|
||||
'project_ident': self.project_obj.ident,
|
||||
'is_jd': is_jd,
|
||||
'sec_title': get_str_dict(self.project_obj.secret, 'secret'),
|
||||
'duty_person': self.project_obj.duty_person,
|
||||
'member': member,
|
||||
'round_num': str(int(hround.key) + 1),
|
||||
'round_num_chn': cname,
|
||||
'soft_ident': so_dut.ref,
|
||||
'soft_version': so_dut.version,
|
||||
} | DocTime(payload.id).hjl_final_time(hround.key)
|
||||
|
||||
result = generate_temp_doc('hjl', payload.id, round_num=cname, frag_list=payload.frag)
|
||||
if isinstance(result, dict):
|
||||
return ChenResponse(status=400, code=400,
|
||||
message=result.get('msg', '回归测试记录生成错误!'))
|
||||
hjl_replace_path, hjl_seitai_final_path = result
|
||||
text_frag_name_list, doc_docx = get_jinja_stdContent_element(hjl_replace_path)
|
||||
# 文本片段操作
|
||||
self.text_frag_replace_handle(text_frag_name_list, doc_docx)
|
||||
# 重新设置时序图大小(注意不变)
|
||||
for shape in doc_docx.inline_shapes:
|
||||
set_shape_size(shape)
|
||||
try:
|
||||
doc_docx.save(hjl_seitai_final_path)
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
if len(cname_list) == 1:
|
||||
return get_file_respone(payload.id, '第二轮回归测试记录')
|
||||
else:
|
||||
return get_file_respone(payload.id, list(map(lambda x: f"第{x}轮回归测试说明", cname_list)))
|
||||
|
||||
@route.post('/wtdDocument', url_name='create-wtdDocument')
|
||||
@transaction.atomic
|
||||
def create_wtdDocument(self, payload: SeitaiInputSchema):
|
||||
"""生成最后的问题单"""
|
||||
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
|
||||
is_jd = True if self.project_obj.report_type == '9' else False
|
||||
self.temp_context = {
|
||||
"project_name": self.project_obj.name,
|
||||
'project_ident': self.project_obj.ident,
|
||||
'is_jd': is_jd,
|
||||
'member': member,
|
||||
'duty_person': self.project_obj.duty_person,
|
||||
'sec_title': get_str_dict(self.project_obj.secret, 'secret'),
|
||||
} | 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未报出错误原因,反正在生成文档出错'))
|
||||
wtd_replace_path, wtd_seitai_final_path = result
|
||||
text_frag_name_list, doc_docx = get_jinja_stdContent_element(wtd_replace_path)
|
||||
# 文本片段操作
|
||||
self.text_frag_replace_handle(text_frag_name_list, doc_docx)
|
||||
try:
|
||||
doc_docx.save(wtd_seitai_final_path)
|
||||
return get_file_respone(payload.id, '问题单')
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
|
||||
@route.post('/bgDocument', url_name='create-bgDocument')
|
||||
@transaction.atomic
|
||||
def create_bgDocument(self, payload: SeitaiInputSchema):
|
||||
"""生成最后的报告文档"""
|
||||
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
|
||||
is_jd = True if self.project_obj.report_type == '9' else False
|
||||
self.temp_context = {
|
||||
'project_name': self.project_obj.name,
|
||||
'project_ident': self.project_obj.ident,
|
||||
'test_purpose': "装备鉴定和列装定型" if is_jd else "软件交付和使用",
|
||||
'is_jd': is_jd,
|
||||
'sec_title': get_str_dict(self.project_obj.secret, 'secret'),
|
||||
'duty_person': self.project_obj.duty_person,
|
||||
'jd_or_third': "鉴定" if is_jd else "第三方",
|
||||
'entrust_unit': self.project_obj.entrust_unit,
|
||||
'member': member,
|
||||
'joined_part': f'驻{self.project_obj.dev_unit}军事代表室、{self.project_obj.dev_unit}',
|
||||
} | 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未报出错误原因,反正在生成文档出错'))
|
||||
bg_replace_path, bg_seitai_final_path = result
|
||||
text_frag_name_list, doc_docx = get_jinja_stdContent_element(bg_replace_path)
|
||||
# 文本片段操作
|
||||
self.text_frag_replace_handle(text_frag_name_list, doc_docx)
|
||||
try:
|
||||
doc_docx.save(bg_seitai_final_path)
|
||||
return get_file_respone(payload.id, '测评报告')
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
|
||||
# ~~~~模版设计模式~~~~
|
||||
# 1.传入sdtContent列表,和替换后的文档对象,进行替换操作
|
||||
def text_frag_replace_handle(self, text_frag_name_list, doc_docx: Document):
|
||||
for text_frag in text_frag_name_list:
|
||||
alias = text_frag['alias']
|
||||
if alias in self.temp_context:
|
||||
sdtContent = text_frag['sdtContent']
|
||||
stdContent_modify(self.temp_context[alias], doc_docx, sdtContent)
|
||||
else:
|
||||
print('未查找的文本片段变量:', alias)
|
||||
|
||||
# ~~~~下面是生成文档辅助文本片段取变量,统一设置报错信息等,后续看重复代码封装~~~~
|
||||
# self拥有变量:self.project_obj / self.temp_context(用于替换文本片段的字典)
|
||||
# 1.获取项目第一轮round的源代码dut的用户标识/版本/第一轮测试地点
|
||||
def get_first_round_code_ident(self):
|
||||
round_obj = self.project_obj.pField.filter(key='0').first()
|
||||
if round_obj:
|
||||
self.temp_context.update({
|
||||
'location': round_obj.location,
|
||||
})
|
||||
code_dut_obj = round_obj.rdField.filter(type='SO').first()
|
||||
if code_dut_obj:
|
||||
self.temp_context.update({
|
||||
'soft_ident': code_dut_obj.ref,
|
||||
'soft_version': code_dut_obj.version,
|
||||
})
|
||||
return
|
||||
raise HttpError(500, "第一轮次未创建,或第一轮动态测试地点为填写,或源代码被测件未创建,请先创建")
|
||||
|
||||
# 2.获取第一轮次需求规格说明dut
|
||||
def get_xq_doc_informations(self):
|
||||
round1_xq_dut = self.project_obj.pdField.filter(round__key='0', type='XQ').first()
|
||||
if round1_xq_dut:
|
||||
self.temp_context.update({'xq_version': round1_xq_dut.version})
|
||||
return
|
||||
raise HttpError(500, "第一轮次被测件:需求规格说明可能未创建,生成文档失败")
|
||||
|
||||
# documentType - 对应的目录名称
|
||||
documentType_to_dir = {
|
||||
'测评大纲': '',
|
||||
'测试说明': 'sm',
|
||||
'测试记录': 'jl',
|
||||
'回归测试说明': 'hsm',
|
||||
'回归测试记录': 'hjl',
|
||||
'问题单': 'wtd',
|
||||
'测评报告': 'bg'
|
||||
}
|
||||
|
||||
# 处理文档片段相关请求
|
||||
@api_controller('/createfragment', tags=['生成文档-文档片段接口集合'])
|
||||
class CreateFragmentController(ControllerBase):
|
||||
@route.get("/get_fragments", url_name='get-fragments')
|
||||
def get_fragements(self, id: int, documentType: str):
|
||||
"""根据项目id和文档类型获取有哪些文档片段"""
|
||||
# 获取文档片段的字符串列表
|
||||
frags = self.get_fragment_name_by_document_name(id, documentType)
|
||||
# 如果没有文档片段-说明没有生成二段文档
|
||||
if not frags:
|
||||
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"
|
||||
# 2.识别其中的文档片段
|
||||
frag_list = get_frag_from_document(document_path)
|
||||
# 3.这里处理报告里第十轮次前端展示问题
|
||||
## 3.1先判断是否为报告
|
||||
if document_name == '测评报告':
|
||||
## 3.2然后判断有几个轮次
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
round_qs = project_obj.pField.all()
|
||||
white_list_frag = []
|
||||
## 3.3将希望有的片段名称加入白名单
|
||||
for round_obj in round_qs:
|
||||
chn_num = digit_to_chinese(int(round_obj.key) + 1)
|
||||
exclude_str = f"测试内容和结果_第{chn_num}轮次" # 组成识别字符串
|
||||
white_list_frag.append(exclude_str)
|
||||
## 3.4过滤包含“测试内容和结果的轮次在白名单的通过”
|
||||
# 去掉所有“测试内容和结果_”的片段
|
||||
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_frags.extend(content_and_result_frags)
|
||||
return filter_frags
|
||||
return frag_list
|
||||
|
||||
@route.get("/get_round_exit", url_name='get-round-exit')
|
||||
def get_round_exit(self, id: int):
|
||||
"""该函数主要识别有几轮回归测试说明、几轮回归测试记录"""
|
||||
project_obj: Project = get_object_or_404(Project, id=id)
|
||||
# 取非第一轮次的轮次的个数
|
||||
round_count = project_obj.pField.exclude(key='0').count()
|
||||
return {'count': round_count}
|
||||
|
||||
# 自定义修改Django的文件系统-启动覆盖模式
|
||||
class OverwriteStorage(FileSystemStorage):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['allow_overwrite'] = True # 启用覆盖模式
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def digit_to_chinese(num):
|
||||
num_dict = {'0': '零', '1': '一', '2': '二', '3': '三', '4': '四',
|
||||
'5': '五', '6': '六', '7': '七', '8': '八', '9': '九', '10': '十'}
|
||||
return ''.join(num_dict[d] for d in str(num))
|
||||
|
||||
# 处理用户上传有文档片段的产品文档文件:注意回归测试说明、回归测试记录需要单独处理
|
||||
@api_controller('/documentUpload', tags=['生成文档-上传模版文档接口'])
|
||||
class UploadDocumentController(ControllerBase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# 储存上传文件
|
||||
self.upload_file: UploadedFile | None = None
|
||||
|
||||
@route.post("/file", url_name='upload-file')
|
||||
def upload_file(self, id: int, documentType: str, file: File[UploadedFile], round_num: int = None):
|
||||
self.upload_file = file
|
||||
# 1.获取储存路径
|
||||
target_dir = main_download_path / project_path(id) / 'form_template' / 'products'
|
||||
# 2.初始化文件系统
|
||||
fs = OverwriteStorage(location=target_dir)
|
||||
|
||||
# 新:如果是大纲片段,则大纲所有片段以文档片段方式储存在/reuse文件夹下面
|
||||
if documentType == '测评大纲':
|
||||
self.get_dg_to_reuse_dir(target_dir.parent.parent / 'reuse')
|
||||
|
||||
if round_num is None:
|
||||
# 处理非“回归测试说明”/“回归测试记录”文档的上传
|
||||
# warning:不校验文档内是否有文档片段,由用户保证上传内容
|
||||
# 3.覆盖储存,注意会返回文件的name属性
|
||||
fs.save(f"{documentType}.docx", self.upload_file)
|
||||
else:
|
||||
# 处理“回归测试说明”/“回归测试记录”文档的上传
|
||||
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)
|
||||
for frag_item in frag_list:
|
||||
# 目的是格式明确按照“测评大纲.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)
|
||||
filename = f"{frag_item['alias']}.docx"
|
||||
new_doc.save((reuse_dir_path / filename).as_posix())
|
||||
|
||||
# 辅助函数:将上传文件的文档片段以列表形式返回
|
||||
def get_document_frag_list(self, doc: Document):
|
||||
body = doc.element.body
|
||||
sdt_element_list = body.xpath('./w:sdt') # 只查询文档片段,非文本片段
|
||||
frag_list = []
|
||||
for sdt_element in sdt_element_list:
|
||||
alias_name = None
|
||||
sdtContent = None
|
||||
for sdt_child in sdt_element.iterchildren():
|
||||
if sdt_child.tag.endswith('sdtPr'):
|
||||
for sdtPr_child in sdt_child.getchildren():
|
||||
if sdtPr_child.tag.endswith('alias'):
|
||||
if len(sdtPr_child.attrib.values()) > 0:
|
||||
alias_name = sdtPr_child.attrib.values()[0]
|
||||
if sdt_child.tag.endswith("sdtContent"):
|
||||
sdtContent = sdt_child
|
||||
frag_list.append({'alias': alias_name, 'content': sdtContent})
|
||||
return list(filter(lambda x: x['alias'] is not None, frag_list))
|
||||
348
apps/createSeiTaiDocument/docXmlUtils.py
Normal file
348
apps/createSeiTaiDocument/docXmlUtils.py
Normal file
@@ -0,0 +1,348 @@
|
||||
"""该文件是:替换文档片段然后生成辅助生成最终文档"""
|
||||
from io import BytesIO
|
||||
from typing import List, Dict
|
||||
from pathlib import Path
|
||||
from docx import Document
|
||||
from docx.text.paragraph import Paragraph
|
||||
from docx.table import Table
|
||||
from docx.oxml.table import CT_Tbl
|
||||
from docx.oxml.text.paragraph import CT_P
|
||||
from docx.oxml.text.run import CT_R
|
||||
from docx.oxml.shape import CT_Picture
|
||||
from docx.parts.image import ImagePart
|
||||
from docx.text.run import Run
|
||||
from docx.shared import Mm
|
||||
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
||||
from lxml.etree import _Element
|
||||
|
||||
# 路径工具
|
||||
from utils.path_utils import project_path
|
||||
|
||||
### 模块变量:定义常用图片所在区域的宽高
|
||||
Demand_table_xqms = Mm(134) # 1.测评大纲-测试项里面-需求描述单元格
|
||||
Timing_diagram_width = Mm(242) # 2.测试记录-时序图
|
||||
Test_result_width = Mm(78) # 3.测试记录-测试结果
|
||||
Horizatal_width = Mm(130) # 4.所有文档-页面图片的横向距离(图片宽度预设置)
|
||||
|
||||
def getParentRunNode(node):
|
||||
"""传入oxml节点对象,获取其祖先节点的CT_R"""
|
||||
if isinstance(node, CT_R):
|
||||
return node
|
||||
return getParentRunNode(node.getparent())
|
||||
|
||||
def generate_temp_doc(doc_type: str, project_id: int, round_num=None, frag_list=None):
|
||||
""" 该函数参数:
|
||||
:param frag_list: 储存用户不覆盖的片段列表
|
||||
:param round_num: 只有回归说明和回归记录有
|
||||
:param project_id: 项目id
|
||||
:param doc_type:大纲 sm:说明 jl:记录 bg:报告 hsm:回归测试说明 hjl:回归测试记录,默认路径为dg -> 所以如果传错就生成生成大纲了
|
||||
:return (to_tpl_file路径, seitai_final_file路径)
|
||||
"""
|
||||
if frag_list is None:
|
||||
frag_list = []
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
project_path_str = project_path(project_id)
|
||||
# 根据传入需要处理的文档类型,自动获路径
|
||||
prefix = Path.cwd() / 'media' / project_path_str
|
||||
template_file: Path = prefix / 'form_template' / 'products' / '测评大纲.docx'
|
||||
to_tpl_file: Path = prefix / 'temp' / '测评大纲.docx'
|
||||
seitai_final_file: Path = prefix / 'final_seitai' / '测评大纲.docx'
|
||||
if doc_type == 'sm':
|
||||
template_file = prefix / 'form_template' / 'products' / '测试说明.docx'
|
||||
to_tpl_file = prefix / 'temp' / '测试说明.docx'
|
||||
seitai_final_file: Path = prefix / 'final_seitai' / '测试说明.docx'
|
||||
elif doc_type == 'jl':
|
||||
template_file = prefix / 'form_template' / 'products' / '测试记录.docx'
|
||||
to_tpl_file = prefix / 'temp' / '测试记录.docx'
|
||||
seitai_final_file: Path = prefix / 'final_seitai' / '测试记录.docx'
|
||||
elif doc_type == 'bg':
|
||||
template_file = prefix / 'form_template' / 'products' / '测评报告.docx'
|
||||
to_tpl_file = prefix / 'temp' / '测评报告.docx'
|
||||
seitai_final_file: Path = prefix / 'final_seitai' / '测评报告.docx'
|
||||
elif doc_type == 'hsm':
|
||||
# 如果products里面存在“用户上传的第n轮回归测试说明.docx,则使用它作为模版”
|
||||
template_file = prefix / 'form_template' / 'products' / f'第{round_num}轮回归测试说明.docx'
|
||||
if not template_file.exists():
|
||||
template_file = prefix / 'form_template' / 'products' / '回归测试说明.docx'
|
||||
to_tpl_file = prefix / 'temp' / f'第{round_num}轮回归测试说明.docx'
|
||||
seitai_final_file: Path = prefix / 'final_seitai' / f'第{round_num}轮回归测试说明.docx'
|
||||
elif doc_type == 'hjl':
|
||||
# 如果products里面存在“用户上传的第n轮回归测试记录.docx,则使用它作为模版”
|
||||
template_file = prefix / 'form_template' / 'products' / f'第{round_num}轮回归测试记录.docx'
|
||||
if not template_file.exists():
|
||||
template_file = prefix / 'form_template' / 'products' / '回归测试记录.docx'
|
||||
to_tpl_file = prefix / 'temp' / f'第{round_num}轮回归测试记录.docx'
|
||||
seitai_final_file: Path = prefix / 'final_seitai' / f'第{round_num}轮回归测试记录.docx'
|
||||
elif doc_type == 'wtd':
|
||||
template_file = prefix / 'form_template' / 'products' / '问题单.docx'
|
||||
to_tpl_file = prefix / 'temp' / '问题单.docx'
|
||||
seitai_final_file: Path = prefix / 'final_seitai' / '问题单.docx'
|
||||
# 定义找寻被复制文件根路径 - 后续会根据type找子路径
|
||||
output_files_path = prefix / 'output_dir'
|
||||
# 这里可能修改,储存大纲里面的文档片段
|
||||
dg_copied_files = []
|
||||
# 储存sm/jl/hsm/hjl/bg/wtd的文档片段
|
||||
exclusive_copied_files = []
|
||||
# 新:储存reuse的文档片段
|
||||
reuse_files = []
|
||||
# 将被拷贝文件分别放入不同两个数组
|
||||
for file in output_files_path.iterdir():
|
||||
if file.is_file():
|
||||
if file.suffix == '.docx':
|
||||
dg_copied_files.append(file)
|
||||
elif file.is_dir():
|
||||
# 如果文件夹名称为sm/jl/hsm/hjl/bg/wtd则进入该判断
|
||||
# 所以要求文件系统文件夹名称必须是sm/jl/hsm/hjl/bg/wtd不然无法生成
|
||||
if file.stem == doc_type:
|
||||
for f in file.iterdir():
|
||||
if f.suffix == '.docx':
|
||||
exclusive_copied_files.append(f)
|
||||
for file in (prefix / 'reuse').iterdir():
|
||||
if file.is_file():
|
||||
if file.suffix == '.docx':
|
||||
reuse_files.append(file)
|
||||
# 找到基础模版的所有std域
|
||||
doc = Document(template_file.as_posix())
|
||||
body = doc.element.body
|
||||
sdt_element_list = body.xpath('./w:sdt')
|
||||
# 找到sdt域的名称 -> 为了对应output_dir文件 / 储存所有output_dir图片
|
||||
area_name_list = []
|
||||
image_part_list = [] # 修改为字典两个字段{ 'name':'测评对象', 'img':ImagePart }
|
||||
# 筛选片段【二】:用户前端要求不要覆盖的文档片段
|
||||
frag_is_cover_dict = {item.name: item.isCover for item in frag_list}
|
||||
# 遍历所有控件 -> 放入area_name_list【这里准备提取公共代码】
|
||||
for sdt_ele in sdt_element_list:
|
||||
isLock = False
|
||||
for elem in sdt_ele.iterchildren():
|
||||
# 【一】用户设置lock - 下面2个if将需要被替换的(控件名称)存入area_name_list
|
||||
if elem.tag.endswith('sdtPr'):
|
||||
for el in elem.getchildren():
|
||||
if el.tag.endswith('lock'):
|
||||
isLock = True
|
||||
if elem.tag.endswith('sdtPr'):
|
||||
for el in elem.getchildren():
|
||||
if el.tag.endswith('alias'):
|
||||
# 筛序【一】:取出用户设置lock的文档片段
|
||||
if len(el.attrib.values()) > 0 and (isLock == False):
|
||||
area_name = el.attrib.values()[0]
|
||||
# 筛选【二】:前端用户选择要覆盖的片段
|
||||
if frag_is_cover_dict.get(area_name):
|
||||
area_name_list.append(area_name)
|
||||
# 下面开始替换area_name_list的“域”(这时已经被筛选-因为sdtPr和sdtContent是成对出现)
|
||||
if elem.tag.endswith('sdtContent'):
|
||||
if len(area_name_list) > 0:
|
||||
# 从第一个片段名称开始取,取到模版的“域”名称
|
||||
area_pop_name = area_name_list.pop(0)
|
||||
# 这里先去找media/output_dir/xx下文件,然后找media/output下文件
|
||||
copied_file_path = ""
|
||||
# 下面if...else是找output_dir下面文件与“域”名称匹配,匹配到存入copied_file_path
|
||||
if doc_type == 'dg':
|
||||
for file in dg_copied_files:
|
||||
if file.stem == area_pop_name:
|
||||
copied_file_path = file
|
||||
else:
|
||||
# 如果不是大纲
|
||||
if round_num is None:
|
||||
# 如果非回归说明、记录
|
||||
for file in exclusive_copied_files:
|
||||
if file.stem == area_pop_name:
|
||||
copied_file_path = file
|
||||
# 这里判断是否copied_file_path没取到文件,然后遍历reuse下文件
|
||||
if not copied_file_path:
|
||||
for file in reuse_files:
|
||||
if file.stem == area_pop_name:
|
||||
copied_file_path = file
|
||||
# 如果上面被复制文件还没找到,然后遍历output_dir下文件
|
||||
if not copied_file_path:
|
||||
for file in dg_copied_files:
|
||||
if file.stem == area_pop_name:
|
||||
copied_file_path = file
|
||||
else:
|
||||
# 因为回归的轮次,前面会加 -> 第{round_num}轮
|
||||
for file in exclusive_copied_files: # 这里多了第{round_num}轮
|
||||
if file.stem == f"第{round_num}轮{area_pop_name}":
|
||||
copied_file_path = file
|
||||
if not copied_file_path:
|
||||
for file in reuse_files:
|
||||
if file.stem == area_pop_name:
|
||||
copied_file_path = file
|
||||
if not copied_file_path:
|
||||
for file in dg_copied_files:
|
||||
if file.stem == area_pop_name:
|
||||
copied_file_path = file
|
||||
# 找到文档片段.docx,将其数据复制到对应area_name的“域”
|
||||
if copied_file_path:
|
||||
doc_copied = Document(copied_file_path)
|
||||
copied_element_list = []
|
||||
element_list = doc_copied.element.body.inner_content_elements
|
||||
for elet in element_list:
|
||||
if isinstance(elet, CT_P):
|
||||
copied_element_list.append(Paragraph(elet, doc_copied))
|
||||
if isinstance(elet, CT_Tbl):
|
||||
copied_element_list.append(Table(elet, doc_copied))
|
||||
elem.clear()
|
||||
for para_copied in copied_element_list:
|
||||
elem.append(para_copied._element)
|
||||
|
||||
# 下面代码就是将图片全部提取到image_part_list,以便后续插入,注意这时候已经是筛选后的
|
||||
doc_copied = Document(copied_file_path) # 需要重新获取否则namespace错误
|
||||
copied_body = doc_copied.element.body
|
||||
img_node_list = copied_body.xpath('.//pic:pic')
|
||||
if not img_node_list:
|
||||
pass
|
||||
else:
|
||||
for img_node in img_node_list:
|
||||
img: CT_Picture = img_node
|
||||
# 根据节点找到图片的关联id
|
||||
embed = img.xpath('.//a:blip/@r:embed')[0]
|
||||
# 这里得到ImagePart -> 马上要给新文档添加
|
||||
related_part: ImagePart = doc_copied.part.related_parts[embed]
|
||||
# doc_copied.part.related_parts是一个字典
|
||||
image_part_list.append({'name': area_pop_name, 'img': related_part})
|
||||
|
||||
# 现在是替换后,找到替换后文档所有pic:pic,并对“域”名称进行识别
|
||||
graph_node_list = body.xpath('.//pic:pic')
|
||||
graph_node_list_transform = []
|
||||
for picNode in graph_node_list:
|
||||
# 遍历替换后模版的所有pic,去找祖先
|
||||
sdt_node = picNode.xpath('ancestor::w:sdt[1]')[0]
|
||||
for sdt_node_child in sdt_node.iterchildren():
|
||||
# 找到sdt下一级的stdPr
|
||||
if sdt_node_child.tag.endswith('sdtPr'):
|
||||
for sdtPr_node_child in sdt_node_child.getchildren():
|
||||
if sdtPr_node_child.tag.endswith('alias'):
|
||||
yu_name = sdtPr_node_child.attrib.values()[0]
|
||||
graph_node_list_transform.append({'yu_name': yu_name, 'yu_node': picNode})
|
||||
for graph_node in graph_node_list_transform:
|
||||
image_run_node = getParentRunNode(graph_node['yu_node'])
|
||||
image_run_node.clear()
|
||||
# 循环去image_part_list找name和yu_name相等的图片
|
||||
for img_part in image_part_list:
|
||||
# 1.如果找到相等
|
||||
if img_part['name'] == graph_node['yu_name']:
|
||||
# 2.找到即可添加图片到“域”
|
||||
image_run_node.clear()
|
||||
# 辅助:去找其父节点是否为段落,是段落则存起来,后面好居中
|
||||
image_run_parent_paragraph = image_run_node.getparent()
|
||||
father_paragraph = None
|
||||
if isinstance(image_run_parent_paragraph, CT_P):
|
||||
father_paragraph = Paragraph(image_run_parent_paragraph, doc)
|
||||
copied_bytes_io = BytesIO(img_part['img'].image.blob)
|
||||
r_element = Run(image_run_node, doc)
|
||||
inline_shape = r_element.add_picture(copied_bytes_io)
|
||||
## 2.1.统一:这里设置文档片段里面的图片大小和位置
|
||||
source_width = inline_shape.width
|
||||
source_height = inline_shape.height
|
||||
if source_width >= source_height:
|
||||
inline_shape.width = Mm(120)
|
||||
inline_shape.height = int(inline_shape.height * (inline_shape.width / source_width))
|
||||
else:
|
||||
inline_shape.height = Mm(60)
|
||||
inline_shape.width = int(inline_shape.width * (inline_shape.height / source_height))
|
||||
## 2.2.设置图片所在段落居中对齐
|
||||
if father_paragraph:
|
||||
father_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
||||
r_element.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
||||
# 3.因为按顺序的,所以移除image_part_list中已经替换的图片
|
||||
image_part_list.remove(img_part)
|
||||
break
|
||||
try:
|
||||
# 这里直接生成产品文档
|
||||
doc.save(str(to_tpl_file))
|
||||
return to_tpl_file, seitai_final_file
|
||||
except PermissionError as e:
|
||||
return {'code': 'error', 'msg': '生成的temp文件已打开,请关闭后重试...'}
|
||||
|
||||
def get_frag_from_document(doc_path: Path) -> List[Dict]:
|
||||
"""传入products的文件路径,识别出所有文档片段名称,数组返回:要求docx里面文档名称不能更变"""
|
||||
doc = Document(doc_path.as_posix())
|
||||
sdt_element_list = doc.element.body.xpath('./w:sdt')
|
||||
# 整个for循环识别文档片段名称
|
||||
area_name_list = []
|
||||
for sdt_ele in sdt_element_list:
|
||||
isLock = False
|
||||
alias_value = None
|
||||
for elem in sdt_ele.iterchildren():
|
||||
if elem.tag.endswith('sdtPr'):
|
||||
for el in elem.getchildren():
|
||||
if el.tag.endswith('alias'):
|
||||
alias_value = el.attrib.values()
|
||||
# 查找是否被用户在模版上标记了Lock
|
||||
if el.tag.endswith('lock'):
|
||||
isLock = True
|
||||
if alias_value and len(alias_value):
|
||||
area_name_list.append({'frag_name': alias_value[0], 'isLock': isLock})
|
||||
return area_name_list
|
||||
|
||||
# 辅助函数-传入temp文件路径(已替换文档片段的temp文档),输出stdContent
|
||||
def get_jinja_stdContent_element(temp_docx_path: Path):
|
||||
doc_docx = Document(temp_docx_path.as_posix())
|
||||
body = doc_docx.element.body
|
||||
# 储存文本片段
|
||||
text_frag_name_list = []
|
||||
sdt_element_list = body.xpath('//w:sdt')
|
||||
|
||||
# 注意python-docx的页头的文本片段不在body里面,而在section.header里面
|
||||
# 所以定义辅助函数,统一处理
|
||||
def deel_sdt_content(*args):
|
||||
"""传入sdt_element列表,将其sdtContent加入外部的文本片段列表"""
|
||||
for sdt_ele in args:
|
||||
# 找出每个sdt下面的3个标签
|
||||
tag_value = None
|
||||
alias_value = None
|
||||
sdtContent_ele = None
|
||||
for sdt_ele_child in sdt_ele.iterchildren():
|
||||
if sdt_ele_child.tag.endswith('sdtPr'):
|
||||
for sdtPr_ele_child in sdt_ele_child.getchildren():
|
||||
if sdtPr_ele_child.tag.endswith('tag'):
|
||||
if len(sdtPr_ele_child.attrib.values()) > 0:
|
||||
tag_value = sdtPr_ele_child.attrib.values()[0]
|
||||
if sdtPr_ele_child.tag.endswith('alias'):
|
||||
if len(sdtPr_ele_child.attrib.values()) > 0:
|
||||
alias_value = sdtPr_ele_child.attrib.values()[0]
|
||||
if sdt_ele_child.tag.endswith('sdtContent'):
|
||||
sdtContent_ele = sdt_ele_child
|
||||
# 找出所有tag_value为jinja的文本片段
|
||||
if tag_value == 'jinja' and alias_value is not None and sdtContent_ele is not None:
|
||||
text_frag_name_list.append({'alias': alias_value, 'sdtContent': sdtContent_ele})
|
||||
|
||||
deel_sdt_content(*sdt_element_list)
|
||||
for section in doc_docx.sections:
|
||||
header = section.header
|
||||
header_sdt_list = header.part.element.xpath('//w:sdt')
|
||||
deel_sdt_content(*header_sdt_list)
|
||||
|
||||
return text_frag_name_list, doc_docx
|
||||
|
||||
# 封装一个根据alias名称修改stdContent的函数 -> 在接口处理函数中取数据放入函数修改文档
|
||||
def stdContent_modify(modify_str: str | bool, doc_docx: Document, sdtContent: _Element):
|
||||
# 正常处理
|
||||
for ele in sdtContent:
|
||||
if isinstance(ele, CT_R):
|
||||
run_ele = Run(ele, doc_docx)
|
||||
if isinstance(modify_str, bool):
|
||||
# 如果是True,则不修改原来
|
||||
if modify_str:
|
||||
break
|
||||
else:
|
||||
modify_str = ""
|
||||
# 有时候会int类型,转换一下防止报错
|
||||
if isinstance(modify_str, int):
|
||||
modify_str = str(modify_str)
|
||||
run_ele.text = modify_str
|
||||
sdtContent.clear()
|
||||
sdtContent.append(run_ele._element)
|
||||
break
|
||||
|
||||
if isinstance(ele, CT_P):
|
||||
para_ele = Paragraph(ele, doc_docx)
|
||||
if isinstance(modify_str, bool):
|
||||
if modify_str:
|
||||
break
|
||||
else:
|
||||
modify_str = ""
|
||||
para_ele.clear()
|
||||
para_ele.text = modify_str
|
||||
sdtContent.clear()
|
||||
sdtContent.append(para_ele._element)
|
||||
break
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
38
apps/createSeiTaiDocument/extensions/download_response.py
Normal file
38
apps/createSeiTaiDocument/extensions/download_response.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import os, io
|
||||
from typing import List
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from django.conf import settings
|
||||
from utils.path_utils import project_path
|
||||
from utils.chen_response import ChenResponse
|
||||
from django.http import FileResponse, HttpResponse
|
||||
|
||||
main_download_path = Path(settings.BASE_DIR) / 'media'
|
||||
|
||||
def get_file_respone(id: int, file_name: str | List[str]):
|
||||
"""将生成文档下载响应"""
|
||||
# 1.如果传入的是str,直接是文件名
|
||||
if isinstance(file_name, str):
|
||||
file_name = "".join([file_name, '.docx'])
|
||||
file_abs_path = main_download_path / project_path(id) / 'final_seitai' / file_name
|
||||
if not file_abs_path.is_file():
|
||||
return ChenResponse(status=404, code=404, message="文档未生成或生成错误!")
|
||||
response = FileResponse(open(file_abs_path, 'rb'))
|
||||
response['Content-Type'] = 'application/octet-stream'
|
||||
response['Content-Disposition'] = f"attachment; filename={file_name}.docx"
|
||||
return response
|
||||
# 2.如果传入的是列表,多个文件名
|
||||
elif isinstance(file_name, list):
|
||||
file_name_list = file_name
|
||||
zip_buffer = io.BytesIO()
|
||||
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
||||
for file_name in file_name_list:
|
||||
file_name = "".join([file_name, '.docx'])
|
||||
file_abs_path = main_download_path / project_path(id) / 'final_seitai' / file_name
|
||||
zip_file.write(file_abs_path, os.path.basename(file_abs_path))
|
||||
zip_buffer.seek(0)
|
||||
response = HttpResponse(zip_buffer, content_type='application/zip')
|
||||
response['Content-Disposition'] = 'attachment; filename="回归测试说明文档.zip"'
|
||||
return response
|
||||
else:
|
||||
return ChenResponse(code=500, status=500, message='下载文档出现错误,确认是否有多个轮次内容')
|
||||
31
apps/createSeiTaiDocument/extensions/logger.py
Normal file
31
apps/createSeiTaiDocument/extensions/logger.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import logging
|
||||
from conf.logConfig import LOG_GENERATE_FILE
|
||||
|
||||
generate_logger = logging.getLogger("generate_document_logger")
|
||||
|
||||
class GenerateLogger(object):
|
||||
instance = None
|
||||
|
||||
# 单例模式
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls.instance is None:
|
||||
cls.instance = object.__new__(cls)
|
||||
return cls.instance
|
||||
else:
|
||||
return cls.instance
|
||||
|
||||
def __init__(self, model: str = '通用文档'):
|
||||
self.logger = generate_logger
|
||||
# 模块属性
|
||||
self.model = model
|
||||
|
||||
def write_warning_log(self, fragment: str, message: str):
|
||||
"""警告日志记录,暂时简单点:model和message"""
|
||||
whole_message = f"[{self.model}模块][{fragment}]片段:{message}"
|
||||
self.logger.warning(whole_message)
|
||||
|
||||
@staticmethod
|
||||
def delete_one_logs():
|
||||
"""删除生成文档logger的日志记录"""
|
||||
with open(LOG_GENERATE_FILE, 'w') as f:
|
||||
f.truncate()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user