initial commit

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

View 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']

View 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))

View 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下面的stepcontent变量是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)

View 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='多轮回归测试用例记录生成完毕')

View 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下面的stepcontent变量是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='文档生成成功...')

View 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))

View 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}软件鉴定测评大纲'
# 这里大纲版本升级如何处理 - TODO1.大纲版本升级后版本处理
# 时间控制类
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='中间文档未找到,请检查你模版是否存在...')

View 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))