diff --git a/apps/createDocument/controllers/__pycache__/bg.cpython-313.pyc b/apps/createDocument/controllers/__pycache__/bg.cpython-313.pyc index 3f70447..dae5931 100644 Binary files a/apps/createDocument/controllers/__pycache__/bg.cpython-313.pyc and b/apps/createDocument/controllers/__pycache__/bg.cpython-313.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/dg.cpython-313.pyc b/apps/createDocument/controllers/__pycache__/dg.cpython-313.pyc index 4bac01e..a404fbf 100644 Binary files a/apps/createDocument/controllers/__pycache__/dg.cpython-313.pyc and b/apps/createDocument/controllers/__pycache__/dg.cpython-313.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/hjl.cpython-313.pyc b/apps/createDocument/controllers/__pycache__/hjl.cpython-313.pyc index eedbf18..845b3ec 100644 Binary files a/apps/createDocument/controllers/__pycache__/hjl.cpython-313.pyc and b/apps/createDocument/controllers/__pycache__/hjl.cpython-313.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/hsm.cpython-313.pyc b/apps/createDocument/controllers/__pycache__/hsm.cpython-313.pyc index 6be7995..b49b3c8 100644 Binary files a/apps/createDocument/controllers/__pycache__/hsm.cpython-313.pyc and b/apps/createDocument/controllers/__pycache__/hsm.cpython-313.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/wtd.cpython-313.pyc b/apps/createDocument/controllers/__pycache__/wtd.cpython-313.pyc index 96b6c84..6d5ae71 100644 Binary files a/apps/createDocument/controllers/__pycache__/wtd.cpython-313.pyc and b/apps/createDocument/controllers/__pycache__/wtd.cpython-313.pyc differ diff --git a/apps/createDocument/controllers/bg.py b/apps/createDocument/controllers/bg.py index dd7423b..ef98fcb 100644 --- a/apps/createDocument/controllers/bg.py +++ b/apps/createDocument/controllers/bg.py @@ -1,734 +1,738 @@ -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, QuerySet -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, create_influence_context -from apps.createDocument.extensions.zhui import create_bg_round1_zhui -from apps.createDocument.extensions.solve_problem import create_one_problem_dit -from utils.path_utils import project_path -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, round_obj = create_round_context(project_obj, round_str) - template_path = Path.cwd() / 'media' / project_path_str / 'form_template' / 'bg' / '测试内容和结果_第二轮次.docx' - doc = DocxTemplate(template_path) - # ~~~额外添加:除第一轮次的影响域分析~~~ - context['influence'] = create_influence_context(doc, round_obj, project_obj) - doc.render(context, autoescape=True) - try: - doc.save( - Path.cwd() / "media" / project_path_str / "output_dir/bg" / f"测试内容和结果_第{context['round_id']}轮次.docx") - 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 # noqa - 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 = 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, autoescape=True) - 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, autoescape=True) - 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, autoescape=True) - 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)) +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, QuerySet +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, create_influence_context +from apps.createDocument.extensions.zhui import create_bg_round1_zhui +from apps.createDocument.extensions.solve_problem import create_one_problem_dit +from utils.path_utils import project_path +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')) + runtimes = get_list_dict('runtime', project_obj.runtime) + runtime_list = [item['ident_version'] for item in runtimes] + devplants = get_list_dict('devplant', project_obj.devplant) + devplant_list = [item['ident_version'] for item in devplants] + + # 获取轮次 + rounds = project_obj.pField.all() + round_list = [] + for r in rounds: + round_dict = {} + # 获取SO的dut + so_dut: Dut | None = r.rdField.filter(type='SO').first() + if so_dut: + round_dict['version'] = so_dut.version + round_dict['line_count'] = int(so_dut.total_lines) + 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': "\a".join(runtime_list), + 'devplant': "\a".join(devplant_list), + 'language': "\a".join(language_list), + 'recv_date': project_obj.beginTime.strftime("%Y-%m-%d"), + 'dev_unit': project_obj.dev_unit, + '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, round_obj = create_round_context(project_obj, round_str) + template_path = Path.cwd() / 'media' / project_path_str / 'form_template' / 'bg' / '测试内容和结果_第二轮次.docx' + doc = DocxTemplate(template_path) + # ~~~额外添加:除第一轮次的影响域分析~~~ + context['influence'] = create_influence_context(doc, round_obj, project_obj) + doc.render(context, autoescape=True) + try: + doc.save( + Path.cwd() / "media" / project_path_str / "output_dir/bg" / f"测试内容和结果_第{context['round_id']}轮次.docx") + 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 # noqa + 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 = 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, autoescape=True) + 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, autoescape=True) + 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, autoescape=True) + 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)) diff --git a/apps/createDocument/controllers/dg.py b/apps/createDocument/controllers/dg.py index a312daf..ab7dc11 100644 --- a/apps/createDocument/controllers/dg.py +++ b/apps/createDocument/controllers/dg.py @@ -1,1071 +1,1090 @@ -import base64 -import io -from typing import Any -from datetime import datetime -from docx.shared import Mm -from docx.enum.text import WD_ALIGN_PARAGRAPH -from docx.enum.table import WD_ALIGN_VERTICAL -from docx.oxml.ns import qn -from ninja.errors import HttpError -from ninja_extra import ControllerBase, api_controller, route -from django.db import transaction -from django.db.models import Q -from docxtpl import DocxTemplate, InlineImage -from pathlib import Path -from utils.chen_response import ChenResponse -# 导入数据库ORM -from apps.project.models import Project, Contact, Abbreviation, ProjectSoftSummary, StuctSortData, StaticSoftItem, StaticSoftHardware, \ - DynamicSoftTable, DynamicHardwareTable, ProjectDynamicDescription, EvaluateData, EnvAnalysis -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 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 -# 导入工具 -from apps.createDocument.extensions.tools import demand_sort_by_designKey, set_table_border_by_cell_position, set_cell_margins - -# @api_controller("/generate", tags=['生成大纲文档'], auth=JWTAuth(), permissions=[IsAuthenticated]) -@api_controller("/generate", tags=['生成大纲文档']) -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().select_related('design') - # 按照自己key排序,这样可以按照design的key排序 - sorted_demand_qs = sorted(testDemand_qs, key=demand_sort_by_designKey) - - # 遍历第一轮测试项:默认是ID排序 - for single_qs in sorted_demand_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, - "subDescription": content.subDescription, - # 修改遍历content下面的step,content变量是TestDemandContent表 - "subStep": [ - {'index': index + 1, 'operation': step_obj.operation, 'expect': step_obj.expect} - for (index, step_obj) in enumerate(content.testStepField.all()) - ], - } - content_list.append(content_dict) - # 查询测试项中testMethod - testmethod_str = '' - for dict_item_qs in Dict.objects.get(code="testMethod").dictItem.all(): - for tm_item in single_qs.testMethod: - if tm_item == dict_item_qs.key: - testmethod_str += dict_item_qs.title + " " - # ***Inspect-start:检查设计需求的描述是否为空*** - if single_qs.design.description == '': - design_info = single_qs.design.ident + '-' + single_qs.design.name - self.logger.write_warning_log('测试项', f'设计需求中的描述为空,请检查 -> {design_info}') - # ***Inspect-end*** - # 富文本解析 - html_parser = RichParser(single_qs.design.description) - desc_list = html_parser.get_final_list(doc) - # 查询关联design以及普通design - doc_list = [{'dut_name': single_qs.dut.name, 'design_chapter': single_qs.design.chapter, - 'design_name': single_qs.design.name}] - for relate_design in single_qs.otherDesign.all(): - ddict = {'dut_name': relate_design.dut.name, 'design_chapter': relate_design.chapter, - 'design_name': relate_design.name} - doc_list.append(ddict) - - # 组装单个测试项 - ## 打印本项目是FPGA还是CPU - 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.strip(), - "adequacy": single_qs.adequacy.replace("\n", "\a"), - # 测试项描述FPGA或'静态分析'、'文档审查'、'代码审查' - "testDesciption": single_qs.testDesciption.replace("\n", "\a"), - "testType": get_testType(single_qs.testType, 'testType'), - } - list_list[type_index].append(testdemand_dict) - - 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字典 - context = { - "project_name": project_qs.name, - "is_JD": True if project_qs.report_type == '9' else False, - "data": output_list, - "isFPGA": '1' in project_qs.plant_type - } - - doc.render(context, autoescape=True) - try: - doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / "测试项及方法.docx") - return ChenResponse(status=200, code=200, message="文档生成成功!") - 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() - context = self.change_time_to_another(context, ['beginTime_strf', 'dgCompileStart', 'dgCompileEnd', - 'designStart', 'designEnd']) - return create_dg_docx('测评时间和地点.docx', context, id) - - # 2025/12/11:将20250417格式改为2025年04月17日 - 封装函数,传入字典和键值,修改对应键值信息 - def change_time_to_another(self, context: dict, key_list: list[str]): - for key in key_list: - time_val = context.get(key, None) - if time_val: - context[key] = datetime.strptime(time_val, "%Y%m%d").strftime("%Y年%m月%d日") - return context - - # 生成【主要功能和性能指标】文档片段 - @route.get('/create/indicators', url_name='create-indicators') - @transaction.atomic - 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, autoescape=True) - try: - doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '主要功能和性能指标.docx') - return ChenResponse(status=200, code=200, message="文档生成成功!") - except PermissionError as e: - return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) - - # 生成dataSchemas的context - 服务于 1、测评对象 2、动态环境描述 - @classmethod - def create_data_schema_list_context(cls, data_qs, doc: DocxTemplate): - if data_qs.exists(): - data_list = [] - for data_obj in data_qs.all(): - item_context: dict[str, Any] = {"fontnote": data_obj.fontnote, 'type': data_obj.type} - # 根据数据类型处理content字段 - if data_obj.type == 'text': - item_context['content'] = data_obj.content - elif data_obj.type == 'table': - # 使用subdoc - subdoc = doc.new_subdoc() - rows = len(data_obj.content) - cols = len(data_obj.content[0]) - table = subdoc.add_table(rows=rows, cols=cols) - # 单元格处理 - for row in range(rows): - for col in range(cols): - cell = table.cell(row, col) - cell.text = data_obj.content[row][col] - # 第一行设置居中 - if row == 0: - # 黑体设置 - cell.text = "" - pa = cell.paragraphs[0] - run = pa.add_run(str(data_obj.content[row][col])) - run.font.name = '黑体' - run._element.rPr.rFonts.set(qn('w:eastAsia'), '黑体') - run.font.bold = False - pa.alignment = WD_ALIGN_PARAGRAPH.CENTER - # 垂直居中 - cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER - # 表格居中 - table.alignment = WD_ALIGN_PARAGRAPH.CENTER - # 设置边框 - set_table_border_by_cell_position(table) - item_context['content'] = subdoc - elif data_obj.type == 'image': - base64_bytes = base64.b64decode(data_obj.content.replace("data:image/png;base64,", "")) - item_context['content'] = InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(120)) - data_list.append(item_context) - context = { - "datas": data_list, - } - return context - return None - - # 统将需要多个DataSchemas的一对一项目字段生成响应 - @classmethod - def uniform_res_from_mul_data_schemas(cls, id: int, filename: str, r_filename: str, model) -> ChenResponse | None: - project_obj = get_object_or_404(Project, id=id) - input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / filename - doc = DocxTemplate(input_path) - qs = model.objects.filter(project=project_obj) - if qs.exists(): - data_qs = qs.first().data_schemas - context = cls.create_data_schema_list_context(data_qs, doc) - doc.render(context) - try: - doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / r_filename) - return ChenResponse(status=200, code=200, message="文档生成成功!") - except PermissionError as e: - return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) - return None - - # 生成测评对象 - 包括大纲、说明、回归说明和报告 - @route.get('/create/softComposition', url_name='create-softComposition') - @transaction.atomic - def create_softComposition(self, id: int): - # 首先判断是否包含 - 项目信息-软件概述 - res = self.uniform_res_from_mul_data_schemas(id, '测评对象_2.docx', '测评对象.docx', ProjectSoftSummary) - if res is not None: - return res - # 原来文档片段或者初始内容 - input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '测评对象.docx' - doc = DocxTemplate(input_path) - replace, frag, rich_text_list = self._generate_frag(id, doc, '测评对象') - context = { - "replace": replace, # 指定是否由数据库文档片段进行生成 - "user_content": frag and rich_text_list - } - doc.render(context, autoescape=True) - try: - doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '测评对象.docx') - return ChenResponse(status=200, code=200, message="文档生成成功!") - except PermissionError as e: - return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) - - # 生成被测软件接口章节 - @route.get('/create/interface', url_name='create-interface') - def create_interface(self, id: int): - input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '被测软件接口.docx' - doc = DocxTemplate(input_path) - project_qs = get_object_or_404(Project, id=id) - project_name = project_qs.name - interfaceNameList = [] - # 查询接口列表 - 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) - # 项目接口图处理 - 2026/2/4 - image_obj = StuctSortData.objects.filter(project=project_qs) - ## 判断是否存在 - image_render = None - fontnote = None - if image_obj.exists(): - base64_bytes = base64.b64decode(image_obj.first().content.replace("data:image/png;base64,", "")) - image_render = InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(120)) - fontnote = image_obj.first().fontnote - context = { - 'project_name': project_name, - 'iters': interfaceNameList, - 'iter_list': interface_list, - 'image_render': image_render if image_render else "", - 'fontnote': fontnote if fontnote else "".join([project_name, '接口示意图']) - } - doc.render(context, autoescape=True) - try: - doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '被测软件接口.docx') - return ChenResponse(status=200, code=200, message="文档生成成功!") - except PermissionError as e: - return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) - - # 生成顶层技术文件 - @route.get('/create/top_file', url_name='create-performance') - 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, autoescape=True) - try: - doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '静态测试环境说明.docx') - return ChenResponse(status=200, code=200, message="文档生成成功!") - except PermissionError as e: - return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) - - # 通用生成静态软件项、静态硬件项、动态软件项、动态硬件信息的context,包含fontnote和table - @classmethod - def create_table_context(cls, table_data: list[list[str]], doc: DocxTemplate): - """注意:该函数会增加一列序号列""" - subdoc = doc.new_subdoc() - rows = len(table_data) - cols = len(table_data[0]) + 1 # 多渲染一个序号列 - table = subdoc.add_table(rows=rows, cols=cols) - # 单元格处理 - for row in range(rows): - for col in range(cols): - cell = table.cell(row, col) # 单元格数据 - # 设置边距 - 所有单元格 - set_cell_margins(cell, left=100, right=100, top=100, bottom=100) - pa = cell.paragraphs[0] - # 处理第一列 - 要居中 - if col == 0: - if row == 0: - cell.text = "序号" - else: - cell.text = str(row) - pa.alignment = WD_ALIGN_PARAGRAPH.CENTER - # 处理非第一列 - else: - cell.text = table_data[row][col - 1] - # 垂直居中 - cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER - # 单独处理第一行 - for col in range(cols): - cell = table.cell(0, col) - cell.text = "" - pa = cell.paragraphs[0] - if col == 0: - run = pa.add_run("序号") - else: - run = pa.add_run(str(table_data[0][col - 1])) - run.font.name = '黑体' - run._element.rPr.rFonts.set(qn('w:eastAsia'), '黑体') - run.font.bold = False - pa.alignment = WD_ALIGN_PARAGRAPH.CENTER - # 设置序号列宽度 - 先自动调整为False然后设置True - for cell in table.columns[0].cells: - cell.width = Mm(15) - pa = cell.paragraphs[0] - pa.alignment = WD_ALIGN_PARAGRAPH.CENTER - # 表格居中 - table.alignment = WD_ALIGN_PARAGRAPH.CENTER - # 最后设置表格外边框 - set_table_border_by_cell_position(table) - return subdoc - - # 统一静态软件项、静态硬件项、动态软件项、动态硬件信息的word生成 - 模版模式 - @classmethod - def uniform_static_dynamic_response(cls, id: int, filename: str, r_filename: str, model) -> ChenResponse | None: - project_obj = get_object_or_404(Project, id=id) - input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / filename - doc = DocxTemplate(input_path) - qs = model.objects.filter(project=project_obj) - if qs.exists(): - obj = qs.first() - table_data = obj.table - subdoc = cls.create_table_context(table_data, doc) - context = { - 'fontnote': obj.fontnote, - 'table': subdoc, - } - doc.render(context, autoescape=True) - try: - doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / r_filename) - return ChenResponse(status=200, code=200, message="文档生成成功!") - except PermissionError as e: - return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) - return None - - # 静态软件项 - @route.get('/create/static_soft', url_name='create-static_soft') - def create_static_soft(self, id: int): - res = self.uniform_static_dynamic_response(id, '静态软件项_2.docx', '静态软件项.docx', StaticSoftItem) - if res is not None: - return res - - input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '静态软件项.docx' - doc = DocxTemplate(input_path) - replace, frag, rich_text_list = self._generate_frag(id, doc, '静态软件项') - 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): - res = self.uniform_static_dynamic_response(id, '静态硬件和固件项_2.docx', '静态硬件和固件项.docx', StaticSoftHardware) - if res is not None: - return res - - input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '静态硬件和固件项.docx' - doc = DocxTemplate(input_path) - replace, frag, rich_text_list = self._generate_frag(id, doc, '静态硬件和固件项') - context = { - "replace": replace, # 指定是否由数据库文档片段进行生成 - "user_content": frag and rich_text_list - } - return create_dg_docx("静态硬件和固件项.docx", context, id) - - # 动态测评环境说明 - 多dataSchemas格式 - @route.get('/create/dynamic_env', url_name='create-dynamic_env') - def create_dynamic_env(self, id: int): - res = self.uniform_res_from_mul_data_schemas(id, '动态测试环境说明_2.docx', - '动态测试环境说明.docx', ProjectDynamicDescription) - if res is not None: - return res - # 老内容 - project_obj: Project = get_object_or_404(Project, id=id) - input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '动态测试环境说明.docx' - doc = DocxTemplate(input_path) - replace, frag, rich_text_list = self._generate_frag(id, doc, '动态测试环境说明') - context = { - 'project_name': project_obj.name, - "replace": replace, - "user_content": frag and rich_text_list - } - doc.render(context, autoescape=True) - try: - doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '动态测试环境说明.docx') - return ChenResponse(status=200, code=200, message="文档生成成功!") - except PermissionError as e: - return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) - - # 动态软件项 - @route.get('/create/dynamic_soft', url_name='create-dynamic_soft') - def create_dynamic_soft(self, id: int): - res = self.uniform_static_dynamic_response(id, '动态软件项_2.docx', '动态软件项.docx', DynamicSoftTable) - if res is not None: - return res - - project_obj: Project = get_object_or_404(Project, id=id) - input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '动态软件项.docx' - doc = DocxTemplate(input_path) - replace, frag, rich_text_list = self._generate_frag(id, doc, '动态软件项') - context = { - 'project_name': project_obj.name, - "replace": replace, - "user_content": frag and rich_text_list - } - return create_dg_docx("动态软件项.docx", context, id) - - # 动态硬件项 - @route.get('/create/dynamic_hard', url_name='create-dynamic_hard') - def create_dynamic_hard(self, id: int): - res = self.uniform_static_dynamic_response(id, '动态硬件和固件项_2.docx', - '动态硬件和固件项.docx', DynamicHardwareTable) - if res is not None: - return res - - input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '动态硬件和固件项.docx' - doc = DocxTemplate(input_path) - replace, frag, rich_text_list = self._generate_frag(id, doc, '动态硬件和固件项') - 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): - res = self.uniform_static_dynamic_response(id, '测评数据_2.docx', - '测评数据.docx', EvaluateData) - if res is not None: - return res - # 老内容 - input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '测评数据.docx' - doc = DocxTemplate(input_path) - replace, frag, rich_text_list = self._generate_frag(id, doc, '测评数据') - 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): - project_obj: Project = get_object_or_404(Project, id=id) - input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '环境差异性分析_2.docx' - doc = DocxTemplate(input_path) - qs = EnvAnalysis.objects.filter(project=project_obj) - if qs.exists(): - obj = qs.first() - table_data = obj.table - subdoc = self.create_table_context(table_data, doc) - context = { - "description": obj.description, - "table": subdoc, - "fontnote": obj.fontnote, - } - doc.render(context, autoescape=True) - try: - doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '环境差异性分析.docx') - return ChenResponse(status=200, code=200, message="文档生成成功!") - except PermissionError as e: - return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) - - # 老内容 - input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '环境差异性分析.docx' - doc = DocxTemplate(input_path) - replace, frag, rich_text_list = self._generate_frag(id, doc, '环境差异性分析') - 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, - 'is_JD': True if project_qs.report_type == '9' else False, - '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 = { - "security_level": get_str_dict(project_qs.security_level, 'security_level'), - "testTypes": "、".join(type_name_list), - "project_name": project_qs.name, - "type_index": type_index - } - 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 - # 获取第一轮所有测试项QuerySet - project_round_one = project_qs.pField.filter(key=0).first() - testDemand_qs = project_round_one.rtField.all() - # grouped_data的键是测试类型名称,值为测试项名称数组 - grouped_data = {} - for item in testDemand_qs: - grouped_data.setdefault(get_str_dict(item.testType, 'testType'), []).append(item.name) - # 获取当前测试项的测试类型 - test_types = testDemand_qs.values("testType").distinct() - type_name_list = list( - map(lambda qs_item: get_str_dict(qs_item['testType'], 'testType'), test_types)) - context = { - "project_name": project_qs.name, - # 查询关键等级-类似“关键”输出 - "security_level_str": get_str_abbr(security, 'security_level'), - "isDmsc": isDmsc, - "test_types": type_name_list, - "grouped_data": grouped_data, - } - 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, - 'test_item_count': testDemands.count(), - '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的思路进行追踪 - # 查询第一轮次 - project_round_one = project_qs.pField.filter(key=0).first() - testType_list, last_chapter_items = create_csx_chapter_dict(project_round_one) - # 找出第一轮的研总 - yz_dut = project_round_one.rdField.filter(type='YZ').first() - if yz_dut: - # 查询出验证所有design - yz_designs = yz_dut.rsField.all() - # 遍历所有研总的design - for design in yz_designs: - design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []} - # 获取一个design的所有测试项 - test_items = design.dtField.all() - # 连接两个QuerySet,默认去重 - test_items = test_items.union(design.odField.all()) - for test_item in test_items: - reveal_ident = "_".join( - ["XQ", get_testType(test_item.testType, "testType"), test_item.ident]) - # 查字典方式确认章节号最后一位 - test_item_last_chapter = last_chapter_items[test_item.testType].index( - test_item.key) + 1 - test_chapter = ".".join( - [test_item_prefix, str(testType_list.index(test_item.testType) + 1), - str(test_item_last_chapter)]) - test_item_dict = {'name': test_item.name, 'chapter': test_chapter, - 'ident': reveal_ident} - design_dict['test_demand'].append(test_item_dict) - design_list.append(design_dict) - try: - design_list = sorted(design_list, key=chapter_key) - except Exception as e: - print("研总的追踪排序报错,错误原因:", e) - context = { - 'design_list': design_list - } - 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) - # 根据design的chapter排序-为防止报错崩溃使用try-但难排查 - try: - design_list = sorted(design_list, key=chapter_key) - except Exception as e: - print("追踪排序报错,错误原因:", e) - 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) - -# 工具方法-给sorted排序使用-知识点:python里面可以元组排序 -def chapter_key(item): - big_num = [5000, 5000, 5000, 5000] - if "." in item['chapter']: - # 如果是有章节号的则排序 - return [int(part) for part in item['chapter'].split(".")] - if item['test_demand'][0]['name'] in ['文档审查', '静态分析', '代码审查', '代码走查']: - return [0, 0, 0, 0] - return big_num +import base64 +import io +from typing import Any +from datetime import datetime +from docx.shared import Mm +from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.enum.table import WD_ALIGN_VERTICAL +from docx.oxml.ns import qn +from ninja.errors import HttpError +from ninja_extra import ControllerBase, api_controller, route +from django.db import transaction +from django.db.models import Q +from docxtpl import DocxTemplate, InlineImage +from pathlib import Path +from utils.chen_response import ChenResponse +# 导入数据库ORM +from apps.project.models import Project, Contact, Abbreviation, ProjectSoftSummary, StuctSortData, StaticSoftItem, StaticSoftHardware, \ + DynamicSoftTable, DynamicHardwareTable, ProjectDynamicDescription, EvaluateData, EnvAnalysis +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 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 +# 导入工具 +from apps.createDocument.extensions.tools import demand_sort_by_designKey, set_table_border_by_cell_position, set_cell_margins + +# @api_controller("/generate", tags=['生成大纲文档'], auth=JWTAuth(), permissions=[IsAuthenticated]) +@api_controller("/generate", tags=['生成大纲文档']) +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().select_related('design') + # 按照自己key排序,这样可以按照design的key排序 + sorted_demand_qs = sorted(testDemand_qs, key=demand_sort_by_designKey) + + # 遍历第一轮测试项:默认是ID排序 + for single_qs in sorted_demand_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, + "subDescription": content.subDescription, + # 修改遍历content下面的step,content变量是TestDemandContent表 + "subStep": [ + {'index': index + 1, 'operation': step_obj.operation, 'expect': step_obj.expect} + for (index, step_obj) in enumerate(content.testStepField.all()) + ], + } + content_list.append(content_dict) + # 查询测试项中testMethod + testmethod_str = '' + for dict_item_qs in Dict.objects.get(code="testMethod").dictItem.all(): + for tm_item in single_qs.testMethod: + if tm_item == dict_item_qs.key: + testmethod_str += dict_item_qs.title + " " + # ***Inspect-start:检查设计需求的描述是否为空*** + if single_qs.design.description == '': + design_info = single_qs.design.ident + '-' + single_qs.design.name + self.logger.write_warning_log('测试项', f'设计需求中的描述为空,请检查 -> {design_info}') + # ***Inspect-end*** + # 富文本解析 + html_parser = RichParser(single_qs.design.description) + desc_list = html_parser.get_final_list(doc) + # 查询关联design以及普通design + doc_list = [{'dut_name': single_qs.dut.name, 'design_chapter': single_qs.design.chapter, + 'design_name': single_qs.design.name}] + for relate_design in single_qs.otherDesign.all(): + ddict = {'dut_name': relate_design.dut.name, 'design_chapter': relate_design.chapter, + 'design_name': relate_design.name} + doc_list.append(ddict) + + # 组装单个测试项 + ## 打印本项目是FPGA还是CPU + 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.strip(), + "adequacy": single_qs.adequacy.replace("\n", "\a"), + # 测试项描述FPGA或'静态分析'、'文档审查'、'代码审查' + "testDesciption": single_qs.testDesciption.replace("\n", "\a"), + "testType": get_testType(single_qs.testType, 'testType'), + } + list_list[type_index].append(testdemand_dict) + + 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字典 + context = { + "project_name": project_qs.name, + "is_JD": True if project_qs.report_type == '9' else False, + "data": output_list, + "isFPGA": '1' in project_qs.plant_type + } + + doc.render(context, autoescape=True) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / "测试项及方法.docx") + return ChenResponse(status=200, code=200, message="文档生成成功!") + 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() + context = self.change_time_to_another(context, ['beginTime_strf', 'dgCompileStart', 'dgCompileEnd', + 'designStart', 'designEnd']) + return create_dg_docx('测评时间和地点.docx', context, id) + + # 2025/12/11:将20250417格式改为2025年04月17日 - 封装函数,传入字典和键值,修改对应键值信息 + def change_time_to_another(self, context: dict, key_list: list[str]): + for key in key_list: + time_val = context.get(key, None) + if time_val: + context[key] = datetime.strptime(time_val, "%Y%m%d").strftime("%Y年%m月%d日") + return context + + # 生成【主要功能和性能指标】文档片段 + @route.get('/create/indicators', url_name='create-indicators') + @transaction.atomic + 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, autoescape=True) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '主要功能和性能指标.docx') + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + # 生成dataSchemas的context - 服务于 1、测评对象 2、动态环境描述 + @classmethod + def create_data_schema_list_context(cls, data_qs, doc: DocxTemplate): + if data_qs.exists(): + data_list = [] + for data_obj in data_qs.all(): + item_context: dict[str, Any] = {"fontnote": data_obj.fontnote, 'type': data_obj.type} + # 根据数据类型处理content字段 + if data_obj.type == 'text': + item_context['content'] = data_obj.content + elif data_obj.type == 'table': + # 使用subdoc + subdoc = doc.new_subdoc() + rows = len(data_obj.content) + cols = len(data_obj.content[0]) + table = subdoc.add_table(rows=rows, cols=cols) + # 单元格处理 + for row in range(rows): + for col in range(cols): + cell = table.cell(row, col) + cell.text = data_obj.content[row][col] + # 第一行设置居中 + if row == 0: + # 黑体设置 + cell.text = "" + pa = cell.paragraphs[0] + run = pa.add_run(str(data_obj.content[row][col])) + run.font.name = '黑体' + run._element.rPr.rFonts.set(qn('w:eastAsia'), '黑体') + run.font.bold = False + pa.alignment = WD_ALIGN_PARAGRAPH.CENTER + # 垂直居中 + cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER + # 表格居中 + table.alignment = WD_ALIGN_PARAGRAPH.CENTER + # 设置边框 + set_table_border_by_cell_position(table) + item_context['content'] = subdoc + elif data_obj.type == 'image': + base64_bytes = base64.b64decode(data_obj.content.replace("data:image/png;base64,", "")) + item_context['content'] = InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(120)) + data_list.append(item_context) + context = { + "datas": data_list, + } + return context + return None + + # 统将需要多个DataSchemas的一对一项目字段生成响应 + @classmethod + def uniform_res_from_mul_data_schemas(cls, id: int, filename: str, r_filename: str, model) -> ChenResponse | None: + project_obj = get_object_or_404(Project, id=id) + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / filename + doc = DocxTemplate(input_path) + qs = model.objects.filter(project=project_obj) + if qs.exists(): + data_qs = qs.first().data_schemas + context = cls.create_data_schema_list_context(data_qs, doc) + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / r_filename) + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + return None + + # 生成测评对象 - 包括大纲、说明、回归说明和报告 + @route.get('/create/softComposition', url_name='create-softComposition') + @transaction.atomic + def create_softComposition(self, id: int): + # 首先判断是否包含 - 项目信息-软件概述 + res = self.uniform_res_from_mul_data_schemas(id, '测评对象_2.docx', '测评对象.docx', ProjectSoftSummary) + if res is not None: + return res + # 原来文档片段或者初始内容 + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '测评对象.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '测评对象') + context = { + "replace": replace, # 指定是否由数据库文档片段进行生成 + "user_content": frag and rich_text_list + } + doc.render(context, autoescape=True) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '测评对象.docx') + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + # 生成被测软件接口章节 + @route.get('/create/interface', url_name='create-interface') + def create_interface(self, id: int): + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '被测软件接口.docx' + doc = DocxTemplate(input_path) + project_qs = get_object_or_404(Project, id=id) + project_name = project_qs.name + interfaceNameList = [] + # 查询接口列表 + 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) + # 项目接口图处理 - 2026/2/4 + image_obj = StuctSortData.objects.filter(project=project_qs) + ## 判断是否存在 + image_render = None + fontnote = None + if image_obj.exists(): + base64_bytes = base64.b64decode(image_obj.first().content.replace("data:image/png;base64,", "")) + image_render = InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(120)) + fontnote = image_obj.first().fontnote + context = { + 'project_name': project_name, + 'iters': interfaceNameList, + 'iter_list': interface_list, + 'image_render': image_render if image_render else "", + 'fontnote': fontnote if fontnote else "".join([project_name, '接口示意图']) + } + doc.render(context, autoescape=True) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '被测软件接口.docx') + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + # 生成顶层技术文件 + @route.get('/create/top_file', url_name='create-performance') + 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, autoescape=True) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '静态测试环境说明.docx') + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + # 通用生成静态软件项、静态硬件项、动态软件项、动态硬件信息的context,包含fontnote和table + @classmethod + def create_table_context(cls, table_data: list[list[str]], doc: DocxTemplate): + """注意:该函数会增加一列序号列,并且支持单元格内回车换行(段落换行)""" + subdoc = doc.new_subdoc() + rows = len(table_data) + cols = len(table_data[0]) + 1 + table = subdoc.add_table(rows=rows, cols=cols) + + # 单元格处理 + for row in range(rows): + for col in range(cols): + cell = table.cell(row, col) + set_cell_margins(cell, left=100, right=100, top=100, bottom=100) + + # 获取要显示的文本内容(字符串或按行拆分后的列表) + if col == 0: + # 序号列 + lines = ["序号"] if row == 0 else [str(row)] + else: + raw_text = table_data[row][col - 1] + # 按换行符 \n 拆分为多个段落 + lines = raw_text.split('\n') if raw_text else [''] + + # 清空单元格原有段落(add_table 默认有一个段落) + cell.text = "" + # 删除默认段落,稍后统一添加 + for para in cell.paragraphs: + p = para._element + p.getparent().remove(p) + + # 逐个添加段落 + for i, line in enumerate(lines): + if i == 0: + para = cell.add_paragraph(line) + else: + para = cell.add_paragraph(line) + + # 设置段落对齐(第一列居中,其他左对齐,可根据需要调整) + if col == 0: + para.alignment = WD_ALIGN_PARAGRAPH.CENTER + else: + para.alignment = WD_ALIGN_PARAGRAPH.LEFT + + # 对第一行(表头)设置黑体字体 + if row == 0: + for run in para.runs: + run.font.name = '黑体' + run._element.rPr.rFonts.set(qn('w:eastAsia'), '黑体') + run.font.bold = False + # 表头段落居中(覆盖前面的 left) + para.alignment = WD_ALIGN_PARAGRAPH.CENTER + + # 垂直居中 + cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER + + # 设置序号列宽度 + for cell in table.columns[0].cells: + cell.width = Mm(15) + for para in cell.paragraphs: + para.alignment = WD_ALIGN_PARAGRAPH.CENTER + + # 表格居中 + table.alignment = WD_ALIGN_PARAGRAPH.CENTER + # 设置表格外边框 + set_table_border_by_cell_position(table) + return subdoc + + # 统一静态软件项、静态硬件项、动态软件项、动态硬件信息的word生成 - 模版模式 + @classmethod + def uniform_static_dynamic_response(cls, id: int, filename: str, r_filename: str, model) -> ChenResponse | None: + project_obj = get_object_or_404(Project, id=id) + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / filename + doc = DocxTemplate(input_path) + qs = model.objects.filter(project=project_obj) + if qs.exists(): + obj = qs.first() + table_data = obj.table + subdoc = cls.create_table_context(table_data, doc) + context = { + 'fontnote': obj.fontnote, + 'table': subdoc, + } + doc.render(context, autoescape=True) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / r_filename) + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + return None + + # 静态软件项 + @route.get('/create/static_soft', url_name='create-static_soft') + def create_static_soft(self, id: int): + res = self.uniform_static_dynamic_response(id, '静态软件项_2.docx', '静态软件项.docx', StaticSoftItem) + if res is not None: + return res + + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '静态软件项.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '静态软件项') + 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): + res = self.uniform_static_dynamic_response(id, '静态硬件和固件项_2.docx', '静态硬件和固件项.docx', StaticSoftHardware) + if res is not None: + return res + + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '静态硬件和固件项.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '静态硬件和固件项') + context = { + "replace": replace, # 指定是否由数据库文档片段进行生成 + "user_content": frag and rich_text_list + } + return create_dg_docx("静态硬件和固件项.docx", context, id) + + # 动态测评环境说明 - 多dataSchemas格式 + @route.get('/create/dynamic_env', url_name='create-dynamic_env') + def create_dynamic_env(self, id: int): + res = self.uniform_res_from_mul_data_schemas(id, '动态测试环境说明_2.docx', + '动态测试环境说明.docx', ProjectDynamicDescription) + if res is not None: + return res + # 老内容 + project_obj: Project = get_object_or_404(Project, id=id) + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '动态测试环境说明.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '动态测试环境说明') + context = { + 'project_name': project_obj.name, + "replace": replace, + "user_content": frag and rich_text_list + } + doc.render(context, autoescape=True) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '动态测试环境说明.docx') + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + # 动态软件项 + @route.get('/create/dynamic_soft', url_name='create-dynamic_soft') + def create_dynamic_soft(self, id: int): + res = self.uniform_static_dynamic_response(id, '动态软件项_2.docx', '动态软件项.docx', DynamicSoftTable) + if res is not None: + return res + + project_obj: Project = get_object_or_404(Project, id=id) + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '动态软件项.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '动态软件项') + context = { + 'project_name': project_obj.name, + "replace": replace, + "user_content": frag and rich_text_list + } + return create_dg_docx("动态软件项.docx", context, id) + + # 动态硬件项 + @route.get('/create/dynamic_hard', url_name='create-dynamic_hard') + def create_dynamic_hard(self, id: int): + res = self.uniform_static_dynamic_response(id, '动态硬件和固件项_2.docx', + '动态硬件和固件项.docx', DynamicHardwareTable) + if res is not None: + return res + + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '动态硬件和固件项.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '动态硬件和固件项') + 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): + res = self.uniform_static_dynamic_response(id, '测评数据_2.docx', + '测评数据.docx', EvaluateData) + if res is not None: + return res + # 老内容 + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '测评数据.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '测评数据') + 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): + project_obj: Project = get_object_or_404(Project, id=id) + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '环境差异性分析_2.docx' + doc = DocxTemplate(input_path) + qs = EnvAnalysis.objects.filter(project=project_obj) + if qs.exists(): + obj = qs.first() + table_data = obj.table + subdoc = self.create_table_context(table_data, doc) + context = { + "description": obj.description, + "table": subdoc, + "fontnote": obj.fontnote, + } + doc.render(context, autoescape=True) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '环境差异性分析.docx') + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + # 老内容 + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '环境差异性分析.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '环境差异性分析') + 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) + language_list = [] + for language in languages: + language_list.append(language.get('ident_version')) + runtimes = get_list_dict('runtime', project_qs.runtime) + runtime_list = [item['ident_version'] for item in runtimes] + devplants = get_list_dict('devplant', project_qs.devplant) + devplant_list = [item['ident_version'] for item in devplants] + # 版本先找第一轮 + project_round = project_qs.pField.filter(key=0).first() + first_round_SO = project_round.rdField.filter(type='SO').first() + 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, + 'is_JD': True if project_qs.report_type == '9' else False, + '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': "\a".join(runtime_list), + 'devplant': "\a".join(devplant_list), + } + 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 = { + "security_level": get_str_dict(project_qs.security_level, 'security_level'), + "testTypes": "、".join(type_name_list), + "project_name": project_qs.name, + "type_index": type_index + } + 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 + # 获取第一轮所有测试项QuerySet + project_round_one = project_qs.pField.filter(key=0).first() + testDemand_qs = project_round_one.rtField.all() + # grouped_data的键是测试类型名称,值为测试项名称数组 + grouped_data = {} + for item in testDemand_qs: + grouped_data.setdefault(get_str_dict(item.testType, 'testType'), []).append(item.name) + # 获取当前测试项的测试类型 + test_types = testDemand_qs.values("testType").distinct() + type_name_list = list( + map(lambda qs_item: get_str_dict(qs_item['testType'], 'testType'), test_types)) + context = { + "project_name": project_qs.name, + # 查询关键等级-类似“关键”输出 + "security_level_str": get_str_abbr(security, 'security_level'), + "isDmsc": isDmsc, + "test_types": type_name_list, + "grouped_data": grouped_data, + } + 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, + 'test_item_count': testDemands.count(), + '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的思路进行追踪 + # 查询第一轮次 + project_round_one = project_qs.pField.filter(key=0).first() + testType_list, last_chapter_items = create_csx_chapter_dict(project_round_one) + # 找出第一轮的研总 + yz_dut = project_round_one.rdField.filter(type='YZ').first() + if yz_dut: + # 查询出验证所有design + yz_designs = yz_dut.rsField.all() + # 遍历所有研总的design + for design in yz_designs: + design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []} + # 获取一个design的所有测试项 + test_items = design.dtField.all() + # 连接两个QuerySet,默认去重 + test_items = test_items.union(design.odField.all()) + for test_item in test_items: + reveal_ident = "_".join( + ["XQ", get_testType(test_item.testType, "testType"), test_item.ident]) + # 查字典方式确认章节号最后一位 + test_item_last_chapter = last_chapter_items[test_item.testType].index( + test_item.key) + 1 + test_chapter = ".".join( + [test_item_prefix, str(testType_list.index(test_item.testType) + 1), + str(test_item_last_chapter)]) + test_item_dict = {'name': test_item.name, 'chapter': test_chapter, + 'ident': reveal_ident} + design_dict['test_demand'].append(test_item_dict) + design_list.append(design_dict) + try: + design_list = sorted(design_list, key=chapter_key) + except Exception as e: + print("研总的追踪排序报错,错误原因:", e) + context = { + 'design_list': design_list + } + 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) + # 根据design的chapter排序-为防止报错崩溃使用try-但难排查 + try: + design_list = sorted(design_list, key=chapter_key) + except Exception as e: + print("追踪排序报错,错误原因:", e) + 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) + +# 工具方法-给sorted排序使用-知识点:python里面可以元组排序 +def chapter_key(item): + big_num = [5000, 5000, 5000, 5000] + if "." in item['chapter']: + # 如果是有章节号的则排序 + return [int(part) for part in item['chapter'].split(".")] + if item['test_demand'][0]['name'] in ['文档审查', '静态分析', '代码审查', '代码走查']: + return [0, 0, 0, 0] + return big_num diff --git a/apps/createDocument/controllers/hjl.py b/apps/createDocument/controllers/hjl.py index bc13be6..df6f2a1 100644 --- a/apps/createDocument/controllers/hjl.py +++ b/apps/createDocument/controllers/hjl.py @@ -1,212 +1,216 @@ -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, autoescape=True) - 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, autoescape=True) - try: - doc.save(save_path) - except PermissionError: - return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试') - return ChenResponse(code=200, status=200, message='多轮回归测试用例记录生成完毕') +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] + runtimes = get_list_dict('runtime', project_obj.runtime) + runtime_list = [item['ident_version'] for item in runtimes] + devplants = get_list_dict('devplant', project_obj.devplant) + devplant_list = [item['ident_version'] for item in devplants] + # 取非第一轮次 + hround_list: QuerySet = project_obj.pField.exclude(key='0') + if len(hround_list) < 1: + # ***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': "、".join(runtime_list), + 'devplant': "、".join(devplant_list), + '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, autoescape=True) + 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, autoescape=True) + try: + doc.save(save_path) + except PermissionError: + return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试') + return ChenResponse(code=200, status=200, message='多轮回归测试用例记录生成完毕') diff --git a/apps/createDocument/controllers/hsm.py b/apps/createDocument/controllers/hsm.py index c1f26ce..46b46b6 100644 --- a/apps/createDocument/controllers/hsm.py +++ b/apps/createDocument/controllers/hsm.py @@ -1,607 +1,611 @@ -from pathlib import Path -from copy import deepcopy -from typing import Union -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 QuerySet, Q -from docxtpl import DocxTemplate -from docx import Document -# 导入模型 -from apps.project.models import Project, Round, Dut, InfluenceArea -from apps.dict.models import Dict -# 导入项目工具 -from utils.util import get_list_dict, get_str_dict, get_ident, get_case_ident, get_testType -from utils.chapter_tools.csx_chapter import create_csx_chapter_dict -from utils.chen_response import ChenResponse -from apps.createDocument.extensions import util -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 utils.util import get_str_abbr -from apps.createDocument.extensions.content_result_tool import create_influence_context -# 导入生成日志记录模块 -from apps.createSeiTaiDocument.extensions.logger import GenerateLogger -# 导入排序 -from apps.createDocument.extensions.tools import demand_sort_by_designKey - -chinese_round_name: list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'] - -# @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, autoescape=True) - 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, autoescape=True) - 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, autoescape=True) - 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 - # 这里插入影响域分析部分,并加入context - context_round['influence'] = create_influence_context(doc, hround, project_obj) # noqa - context_round['influence'] = None - # 如果存在这个轮次的需求文档,则查询上个版本 - 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, autoescape=True) - 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().select_related('design') - # 根据自己key排序 - sorted_demand_qs = sorted(testDemand_qs, key=demand_sort_by_designKey) - - for demand in sorted_demand_qs: - type_index = type_number_list.index(int(demand.testType)) - content_list = [] - for (index, content) in enumerate(demand.testQField.all()): - content_dict = { - "index": index + 1, - "rindex": str(index + 1).rjust(2, '0'), - "subName": content.subName, - # 修改遍历content下面的step,content变量是TestDemandContent表 - "subStep": [ - {'index': index + 1, 'operation': step_obj.operation, 'expect': step_obj.expect} - for (index, step_obj) in enumerate(content.testStepField.all()) - ], - } - content_list.append(content_dict) - testmethod_str = '' - for dict_item_qs in Dict.objects.get(code="testMethod").dictItem.all(): - for tm_item in demand.testMethod: - if tm_item == dict_item_qs.key: - testmethod_str += dict_item_qs.title + " " - # 设计需求的描述,富文本 - parser = RichParser(demand.design.description) - # 查询关联design以及普通design - doc_list = [{'dut_name': demand.dut.name, 'design_chapter': demand.design.chapter, - 'design_name': demand.design.name}] - for relate_design in demand.otherDesign.all(): - ddict = {'dut_name': relate_design.dut.name, 'design_chapter': relate_design.chapter, - 'design_name': relate_design.name} - doc_list.append(ddict) - # 组装单个测试项 - testdemand_dict = { - "name": demand.name, - "key": demand.key, - "ident": get_ident(demand), - "priority": get_str_dict(demand.priority, "priority"), - "doc_list": doc_list, - "design_description": parser.get_final_list(doc), - "test_demand_content": content_list, - "testMethod": testmethod_str.strip(), - "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, autoescape=True) - 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, autoescape=True) - 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, autoescape=True) - 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, autoescape=True) - 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='文档生成成功...') +from pathlib import Path +from copy import deepcopy +from typing import Union +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 QuerySet, Q +from docxtpl import DocxTemplate +from docx import Document +# 导入模型 +from apps.project.models import Project, Round, Dut, InfluenceArea +from apps.dict.models import Dict +# 导入项目工具 +from utils.util import get_list_dict, get_str_dict, get_ident, get_case_ident, get_testType +from utils.chapter_tools.csx_chapter import create_csx_chapter_dict +from utils.chen_response import ChenResponse +from apps.createDocument.extensions import util +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 utils.util import get_str_abbr +from apps.createDocument.extensions.content_result_tool import create_influence_context +# 导入生成日志记录模块 +from apps.createSeiTaiDocument.extensions.logger import GenerateLogger +# 导入排序 +from apps.createDocument.extensions.tools import demand_sort_by_designKey + +chinese_round_name: list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'] + +# @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] + runtimes = get_list_dict('runtime', project_obj.runtime) + runtime_list = [item['ident_version'] for item in runtimes] + devplants = get_list_dict('devplant', project_obj.devplant) + devplant_list = [item['ident_version'] for item in devplants] + # 取非第一轮次 + hround_list: QuerySet = project_obj.pField.exclude(key='0') + if len(hround_list) < 1: + # ***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': "、".join(runtime_list), + 'devplant': "、".join(devplant_list), + '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, autoescape=True) + 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, autoescape=True) + 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, autoescape=True) + 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 + # 这里插入影响域分析部分,并加入context + context_round['influence'] = create_influence_context(doc, hround, project_obj) # noqa + context_round['influence'] = None + # 如果存在这个轮次的需求文档,则查询上个版本 + 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, autoescape=True) + 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().select_related('design') + # 根据自己key排序 + sorted_demand_qs = sorted(testDemand_qs, key=demand_sort_by_designKey) + + for demand in sorted_demand_qs: + type_index = type_number_list.index(int(demand.testType)) + content_list = [] + for (index, content) in enumerate(demand.testQField.all()): + content_dict = { + "index": index + 1, + "rindex": str(index + 1).rjust(2, '0'), + "subName": content.subName, + # 修改遍历content下面的step,content变量是TestDemandContent表 + "subStep": [ + {'index': index + 1, 'operation': step_obj.operation, 'expect': step_obj.expect} + for (index, step_obj) in enumerate(content.testStepField.all()) + ], + } + content_list.append(content_dict) + testmethod_str = '' + for dict_item_qs in Dict.objects.get(code="testMethod").dictItem.all(): + for tm_item in demand.testMethod: + if tm_item == dict_item_qs.key: + testmethod_str += dict_item_qs.title + " " + # 设计需求的描述,富文本 + parser = RichParser(demand.design.description) + # 查询关联design以及普通design + doc_list = [{'dut_name': demand.dut.name, 'design_chapter': demand.design.chapter, + 'design_name': demand.design.name}] + for relate_design in demand.otherDesign.all(): + ddict = {'dut_name': relate_design.dut.name, 'design_chapter': relate_design.chapter, + 'design_name': relate_design.name} + doc_list.append(ddict) + # 组装单个测试项 + testdemand_dict = { + "name": demand.name, + "key": demand.key, + "ident": get_ident(demand), + "priority": get_str_dict(demand.priority, "priority"), + "doc_list": doc_list, + "design_description": parser.get_final_list(doc), + "test_demand_content": content_list, + "testMethod": testmethod_str.strip(), + "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, autoescape=True) + 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, autoescape=True) + 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, autoescape=True) + 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, autoescape=True) + 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='文档生成成功...') diff --git a/apps/createDocument/controllers/wtd.py b/apps/createDocument/controllers/wtd.py index 4958fe1..8796390 100644 --- a/apps/createDocument/controllers/wtd.py +++ b/apps/createDocument/controllers/wtd.py @@ -1,155 +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, autoescape=True) - 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)) +# 导入内置模块 +from pathlib import Path +from django.db.models import QuerySet +# 导入django、ninja等模块 +from ninja_extra import api_controller, ControllerBase, route +from django.db import transaction +from django.shortcuts import get_object_or_404 +# 导入文档处理模块 +from docxtpl import DocxTemplate, InlineImage +# 导入ORM模型 +from apps.project.models import Project, Case +# 导入工具 +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: QuerySet[Case] = 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(set(temp_name_version)) + problem_dict['case_ident'] = ",".join(set(case_ident_list)) + problem_dict['type'] = get_str_dict(problem.type, 'problemType') + problem_dict['grade'] = get_str_dict(problem.grade, 'problemGrade') + + # 依据要求-获取其设计需求 + print(case_design_list) + problem_dict['yaoqiu'] = "\a".join(case_design_list) + # 问题操作 - HTML解析 + desc_list = ['【问题操作】'] + 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, autoescape=True) + 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)) diff --git a/apps/createDocument/extensions/__pycache__/parse_rich_text.cpython-313.pyc b/apps/createDocument/extensions/__pycache__/parse_rich_text.cpython-313.pyc index bf3c223..5a44ce7 100644 Binary files a/apps/createDocument/extensions/__pycache__/parse_rich_text.cpython-313.pyc and b/apps/createDocument/extensions/__pycache__/parse_rich_text.cpython-313.pyc differ diff --git a/apps/createDocument/extensions/parse_rich_text.py b/apps/createDocument/extensions/parse_rich_text.py index 7718bbc..3519009 100644 --- a/apps/createDocument/extensions/parse_rich_text.py +++ b/apps/createDocument/extensions/parse_rich_text.py @@ -1,141 +1,143 @@ -""" -专门解析富文本插件tinymce的html内容 -""" -import pandas as pd -from bs4 import BeautifulSoup -from bs4.element import Tag, NavigableString -import base64 -import io -from docxtpl import InlineImage -from docx.shared import Mm -import re - -# text.replace('\xa0', ' ')) -class RichParser: - def __init__(self, rich_text): - # 将rich_text的None变为空字符串:鲁棒 - if rich_text is None: - rich_text = "" - # 对原始html解析后的bs对象 - self.bs = BeautifulSoup(rich_text, 'html.parser') - self.content = self.remove_n_in_contents() - # 最终的解析后的列表 - self.data_list = [] - self.line_parse() - # 匹配“表1-3”或“表1”等字符的正则 - self.biao_pattern = re.compile(r"表\d+(?:-\d+)?") - - # 1.函数:将self.bs.contents去掉\n,获取每行数据 - def remove_n_in_contents(self): - content_list = [] - for line in self.bs.contents: - if line != '\n': - content_list.append(line) - return content_list - - # 2.逐个遍历self.content,去掉table元素Tag对象单独解析 - def line_parse(self): - for tag in self.content: - if isinstance(tag, NavigableString): - self.data_list.append(tag.text) - elif isinstance(tag, Tag): - if tag.name == 'p': - img_list = tag.find_all('img') - if len(img_list) > 0: - for img_item in img_list: - self.data_list.append(img_item.get('src')) - else: - self.data_list.append(tag.text) - elif tag.name == 'table': - df_dict_list = self.parse_tag2list(tag) - self.data_list.append(df_dict_list) - elif tag.name == 'div': - table_list = tag.find_all('table') - if len(table_list) > 0: - for table in table_list: - df_dict_list = self.parse_tag2list(table) - self.data_list.append(df_dict_list) - - # 3.1.辅助方法,将的Tag对象转为[[]]二维列表格式 - def parse_tag2list(self, table_tag): - # str(tag)可直接变成
xxx
- pd_list = pd.read_html(io.StringIO(str(table_tag))) - # 将dataframe变为数组 - df = pd_list[0] - # 处理第一行为数字的情况,如果为数字则删除第一行,让第二行为列名 - if all(isinstance(col, int) for col in df.columns): - df.columns = df.iloc[0] - df = df.drop(0) # 删除原来的第一行 - # 转为列表的列表(二维列表) - # return df.values.tolist() - return df.fillna('').T.reset_index().T.values.tolist() - - # 3.2.辅助方法,打印解析后列表 - def print_content(self): - for line in self.data_list: - print(line) - - # 4.1.最终方法,生成给docxtpl可用的列表 -> 注意需要传递DocxTemplate对象,在接口函数里面初始化的 - def get_final_list(self, doc, /, *, img_size=100, height=80): - """注意关键字传参可修改图片大小img_size:int=100""" - final_list = [] - for oneline in self.data_list: - # 这里要单独处理下二维列表 - if isinstance(oneline, list): - final_list.append({'isTable': True, 'data': oneline}) - continue - if oneline.startswith("data:image/png;base64") or oneline.startswith("data:image/jpeg;base64,") or oneline.startswith( - "data:image/jpg;base64,"): - base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", "")) - # ~~~设置了固定宽度、高度~~~ - inline_image = InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=Mm(height)) - final_list.append(inline_image) - else: - final_list.append(oneline) - if len(final_list) <= 0: - final_list.append("") - # 针对tinymce中,粘贴表格最后一行显示句号问题,这里统一删除 - if final_list[-1] == '\xa0': - final_list.pop() - return final_list - - # 4.2.最终方法,在上面方法基础上,增加格式,例如

增加缩进,图片居中,

包含“图x”则居中 - def get_final_format_list(self, doc, /, *, img_size=115, height=80): - final_list = [] - for oneline in self.data_list: - # 这里要单独处理下二维列表 - if isinstance(oneline, list): - final_list.append({'isTable': True, 'data': oneline}) - continue - if oneline.startswith("data:image/png;base64"): - base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", "")) - # 1.和上面函数变化:图片更改为dict然后isCenter属性居中 - final_list.append( - {'isCenter': True, - 'data': InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=height)}) - else: - # 2.和上面区别:如果

带有“图”则居中 - if re.match(r"[表图]\d.*", oneline): - final_list.append({"isCenter": True, "data": oneline}) - else: - final_list.append({"isCenter": False, "data": oneline}) - if len(final_list) <= 0: - final_list.append("") - return final_list - - # 5.最终方法,去掉图片和table元素 -> 纯文本列表 - def get_final_p_list(self): - final_list = [] - for oneline in self.data_list: - if isinstance(oneline, list) or oneline.startswith("data:image/png;base64"): - continue - cleaned_line = oneline - cleaned_line = re.sub(r'\s+', '', cleaned_line) - cleaned_line = cleaned_line.replace(')', ')') - cleaned_line = cleaned_line.strip() - # 去掉以“表3”的行 - if self.biao_pattern.search(cleaned_line): - continue - if cleaned_line: - final_list.append(cleaned_line) - return final_list +""" +专门解析富文本插件tinymce的html内容 +""" +import pandas as pd +from bs4 import BeautifulSoup +from bs4.element import Tag, NavigableString +import base64 +import io +from docxtpl import InlineImage +from docx.shared import Mm +import re + +# text.replace('\xa0', ' ')) +class RichParser: + def __init__(self, rich_text): + # 将rich_text的None变为空字符串:鲁棒 + if rich_text is None: + rich_text = "" + # 对原始html解析后的bs对象 + self.bs = BeautifulSoup(rich_text, 'html.parser') + self.content = self.remove_n_in_contents() + # 最终的解析后的列表 + self.data_list = [] + self.line_parse() + # 匹配“表1-3”或“表1”等字符的正则 + self.biao_pattern = re.compile(r"表\d+(?:-\d+)?") + + # 1.函数:将self.bs.contents去掉\n,获取每行数据 + def remove_n_in_contents(self): + content_list = [] + for line in self.bs.contents: + if line != '\n': + content_list.append(line) + return content_list + + # 2.逐个遍历self.content,去掉table元素Tag对象单独解析 + def line_parse(self): + for tag in self.content: + if isinstance(tag, NavigableString): + self.data_list.append(tag.text) + elif isinstance(tag, Tag): + if tag.name == 'p': + img_list = tag.find_all('img') + if len(img_list) > 0: + for img_item in img_list: + self.data_list.append(img_item.get('src')) + else: + self.data_list.append(tag.text) + elif tag.name == 'table': + df_dict_list = self.parse_tag2list(tag) + self.data_list.append(df_dict_list) + elif tag.name == 'div': + table_list = tag.find_all('table') + if len(table_list) > 0: + for table in table_list: + df_dict_list = self.parse_tag2list(table) + self.data_list.append(df_dict_list) + + # 3.1.辅助方法,将的Tag对象转为[[]]二维列表格式 + def parse_tag2list(self, table_tag): + # str(tag)可直接变成
xxx
+ pd_list = pd.read_html(io.StringIO(str(table_tag))) + # 将dataframe变为数组 + df = pd_list[0] + # 处理第一行为数字的情况,如果为数字则删除第一行,让第二行为列名 + if all(isinstance(col, int) for col in df.columns): + df.columns = df.iloc[0] + df = df.drop(0) # 删除原来的第一行 + # 转为列表的列表(二维列表) + # return df.values.tolist() + return df.fillna('').T.reset_index().T.values.tolist() + + # 3.2.辅助方法,打印解析后列表 + def print_content(self): + for line in self.data_list: + print(line) + + # 4.1.最终方法,生成给docxtpl可用的列表 -> 注意需要传递DocxTemplate对象,在接口函数里面初始化的 + def get_final_list(self, doc, /, *, img_size=100, height=80): + """注意关键字传参可修改图片大小img_size:int=100""" + final_list = [] + for oneline in self.data_list: + # 这里要单独处理下二维列表 + if isinstance(oneline, list): + final_list.append({'isTable': True, 'data': oneline}) + continue + if oneline.startswith("data:image/png;base64") or oneline.startswith("data:image/jpeg;base64,") or oneline.startswith( + "data:image/jpg;base64,"): + base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", "")) + # ~~~设置了固定宽度、高度~~~ + inline_image = InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=Mm(height)) + final_list.append(inline_image) + else: + # ~~~新增:将\xa0修改为普通空格~~~ + oneline = oneline.replace('\xa0', ' ') + final_list.append(oneline) + if len(final_list) <= 0: + final_list.append("") + # 针对tinymce中,粘贴表格最后一行显示句号问题,这里统一删除 + if final_list[-1] == '\xa0': + final_list.pop() + return final_list + + # 4.2.最终方法,在上面方法基础上,增加格式,例如

增加缩进,图片居中,

包含“图x”则居中 + def get_final_format_list(self, doc, /, *, img_size=115, height=80): + final_list = [] + for oneline in self.data_list: + # 这里要单独处理下二维列表 + if isinstance(oneline, list): + final_list.append({'isTable': True, 'data': oneline}) + continue + if oneline.startswith("data:image/png;base64"): + base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", "")) + # 1.和上面函数变化:图片更改为dict然后isCenter属性居中 + final_list.append( + {'isCenter': True, + 'data': InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=height)}) + else: + # 2.和上面区别:如果

带有“图”则居中 + if re.match(r"[表图]\d.*", oneline): + final_list.append({"isCenter": True, "data": oneline.replace('\xa0', ' ')}) + else: + final_list.append({"isCenter": False, "data": oneline.replace('\xa0', ' ')}) + if len(final_list) <= 0: + final_list.append("") + return final_list + + # 5.最终方法,去掉图片和table元素 -> 纯文本列表 + def get_final_p_list(self): + final_list = [] + for oneline in self.data_list: + if isinstance(oneline, list) or oneline.startswith("data:image/png;base64"): + continue + cleaned_line = oneline + cleaned_line = re.sub(r'\s+', '', cleaned_line) + cleaned_line = cleaned_line.replace(')', ')') + cleaned_line = cleaned_line.strip() + # 去掉以“表3”的行 + if self.biao_pattern.search(cleaned_line): + continue + if cleaned_line: + final_list.append(cleaned_line) + return final_list diff --git a/apps/project/__pycache__/models.cpython-313.pyc b/apps/project/__pycache__/models.cpython-313.pyc index 75dc498..a3d78dd 100644 Binary files a/apps/project/__pycache__/models.cpython-313.pyc and b/apps/project/__pycache__/models.cpython-313.pyc differ diff --git a/apps/project/__pycache__/signals.cpython-313.pyc b/apps/project/__pycache__/signals.cpython-313.pyc index e66c44a..3a8565f 100644 Binary files a/apps/project/__pycache__/signals.cpython-313.pyc and b/apps/project/__pycache__/signals.cpython-313.pyc differ diff --git a/apps/project/controllers/__pycache__/case.cpython-313.pyc b/apps/project/controllers/__pycache__/case.cpython-313.pyc index 99a625b..569bfb3 100644 Binary files a/apps/project/controllers/__pycache__/case.cpython-313.pyc and b/apps/project/controllers/__pycache__/case.cpython-313.pyc differ diff --git a/apps/project/controllers/case.py b/apps/project/controllers/case.py index 064aa1d..c940308 100644 --- a/apps/project/controllers/case.py +++ b/apps/project/controllers/case.py @@ -257,7 +257,7 @@ class CaseController(ControllerBase): single_qs.key = case_key index = index + 1 single_qs.save() - return ChenResponse(message="测试用例删除成功!") + return ChenResponse(message="测试用例删除成功!影响域分析中如果有该关联用例则被删除。") # 右键测试项,根据测试子项生成用例 @route.post("/case/create_by_demand", url_name='case-create-by-demand') diff --git a/apps/project/migrations/0030_alter_project_devplant_alter_project_runtime.py b/apps/project/migrations/0030_alter_project_devplant_alter_project_runtime.py new file mode 100644 index 0000000..2462e90 --- /dev/null +++ b/apps/project/migrations/0030_alter_project_devplant_alter_project_runtime.py @@ -0,0 +1,24 @@ +# Generated by Django 6.0.4 on 2026-04-17 13:57 + +import apps.project.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0029_influenceitem_change_influ'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='devplant', + field=models.JSONField(blank=True, default=apps.project.models.create_list, help_text='开发环境', null=True, verbose_name='开发环境'), + ), + migrations.AlterField( + model_name='project', + name='runtime', + field=models.JSONField(blank=True, default=apps.project.models.create_list, help_text='运行环境', null=True, verbose_name='运行环境'), + ), + ] diff --git a/apps/project/migrations/0031_alter_testdemand_adequacy.py b/apps/project/migrations/0031_alter_testdemand_adequacy.py new file mode 100644 index 0000000..86518cd --- /dev/null +++ b/apps/project/migrations/0031_alter_testdemand_adequacy.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.4 on 2026-04-17 16:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0030_alter_project_devplant_alter_project_runtime'), + ] + + operations = [ + migrations.AlterField( + model_name='testdemand', + name='adequacy', + field=models.CharField(blank=True, help_text='充分条件', max_length=2048, null=True, verbose_name='充分条件'), + ), + ] diff --git a/apps/project/migrations/0032_alter_design_protocal.py b/apps/project/migrations/0032_alter_design_protocal.py new file mode 100644 index 0000000..b1a9f8a --- /dev/null +++ b/apps/project/migrations/0032_alter_design_protocal.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.4 on 2026-04-20 10:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0031_alter_testdemand_adequacy'), + ] + + operations = [ + migrations.AlterField( + model_name='design', + name='protocal', + field=models.CharField(blank=True, default='', help_text='接口数据', max_length=1024, null=True, verbose_name='接口数据'), + ), + ] diff --git a/apps/project/migrations/__pycache__/0030_alter_project_devplant_alter_project_runtime.cpython-313.pyc b/apps/project/migrations/__pycache__/0030_alter_project_devplant_alter_project_runtime.cpython-313.pyc new file mode 100644 index 0000000..49b4d1b Binary files /dev/null and b/apps/project/migrations/__pycache__/0030_alter_project_devplant_alter_project_runtime.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0031_alter_testdemand_adequacy.cpython-313.pyc b/apps/project/migrations/__pycache__/0031_alter_testdemand_adequacy.cpython-313.pyc new file mode 100644 index 0000000..24f2af6 Binary files /dev/null and b/apps/project/migrations/__pycache__/0031_alter_testdemand_adequacy.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0032_alter_design_protocal.cpython-313.pyc b/apps/project/migrations/__pycache__/0032_alter_design_protocal.cpython-313.pyc new file mode 100644 index 0000000..6cbe9b1 Binary files /dev/null and b/apps/project/migrations/__pycache__/0032_alter_design_protocal.cpython-313.pyc differ diff --git a/apps/project/models.py b/apps/project/models.py index 86f034a..a21ae34 100644 --- a/apps/project/models.py +++ b/apps/project/models.py @@ -27,50 +27,29 @@ class Project(CoreModel): vise_person = models.CharField(max_length=64, verbose_name="质量监督员", help_text="质量监督员") config_person = models.CharField(max_length=64, verbose_name="配置管理员", help_text="配置管理员") # ~~~~~~~~~~~ - security_level = models.CharField(max_length=8, blank=True, null=True, verbose_name="安全等级", - help_text="安全等级") - test_level = models.JSONField(null=True, blank=True, help_text="测试级别", verbose_name="测试级别", - default=create_list) - plant_type = models.JSONField(null=True, blank=True, help_text="平台类型", verbose_name="平台类型", - default=create_list) - report_type = models.CharField(max_length=64, blank=True, null=True, verbose_name="报告类型", - help_text="报告类型") - language = models.JSONField(null=True, blank=True, help_text="被测语言", verbose_name="被测语言", - default=create_list) - standard = models.JSONField(null=True, blank=True, help_text="依据标准", verbose_name="依据标准", - default=create_list) + security_level = models.CharField(max_length=8, blank=True, null=True, verbose_name="安全等级", help_text="安全等级") + test_level = models.JSONField(null=True, blank=True, help_text="测试级别", verbose_name="测试级别", default=create_list) + plant_type = models.JSONField(null=True, blank=True, help_text="平台类型", verbose_name="平台类型", default=create_list) + report_type = models.CharField(max_length=64, blank=True, null=True, verbose_name="报告类型", help_text="报告类型") + language = models.JSONField(null=True, blank=True, help_text="被测语言", verbose_name="被测语言", default=create_list) + standard = models.JSONField(null=True, blank=True, help_text="依据标准", verbose_name="依据标准", default=create_list) entrust_unit = models.CharField(max_length=64, verbose_name="委托方单位", help_text="委托方单位") - entrust_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方联系人", - help_text="委托方联系人") - entrust_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方电话", - help_text="委托方电话") - entrust_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方邮箱", - help_text="委托方邮箱") + entrust_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方联系人", help_text="委托方联系人") + entrust_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方电话", help_text="委托方电话") + entrust_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方邮箱", help_text="委托方邮箱") dev_unit = models.CharField(max_length=64, verbose_name="开发方单位", help_text="开发方单位") - dev_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方联系人", - help_text="研制方联系人") - dev_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方电话", - help_text="研制方电话") - dev_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方邮箱", - help_text="研制方邮箱") + dev_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方联系人", help_text="研制方联系人") + dev_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方电话", help_text="研制方电话") + dev_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方邮箱", help_text="研制方邮箱") test_unit = models.CharField(max_length=64, verbose_name="测试方单位", help_text="测试方单位") - test_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心联系人", - help_text="测评中心联系人") - test_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心电话", - help_text="测评中心电话") - test_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心邮箱", - help_text="测评中心邮箱") - step = models.CharField(max_length=8, blank=True, null=True, verbose_name="项目阶段", - help_text="项目阶段") - abbreviation = models.JSONField(null=True, blank=True, help_text="缩略语", verbose_name="缩略语", - default=create_list) - soft_type = models.SmallIntegerField(verbose_name='软件类型', - choices=((1, '新研'), (2, '改造'), (3, '沿用')), - default=1) - runtime = models.CharField(max_length=8, blank=True, null=True, verbose_name="运行环境", - help_text="运行环境") - devplant = models.CharField(max_length=8, blank=True, null=True, verbose_name="开发环境", - help_text="开发环境") + test_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心联系人", help_text="测评中心联系人") + test_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心电话", help_text="测评中心电话") + test_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心邮箱", help_text="测评中心邮箱") + step = models.CharField(max_length=8, blank=True, null=True, verbose_name="项目阶段", help_text="项目阶段") + abbreviation = models.JSONField(null=True, blank=True, help_text="缩略语", verbose_name="缩略语", default=create_list) + soft_type = models.SmallIntegerField(verbose_name='软件类型', choices=((1, '新研'), (2, '改造'), (3, '沿用')), default=1) + runtime = models.JSONField(null=True, blank=True, help_text="运行环境", verbose_name="运行环境", default=create_list) + devplant = models.JSONField(null=True, blank=True, help_text="开发环境", verbose_name="开发环境", default=create_list) # 9月2日新增字段:密级 secret = models.CharField(max_length=30, default='1', verbose_name='密级', help_text='密级') @@ -226,7 +205,7 @@ class Design(CoreModel): type = models.CharField(max_length=64, blank=True, null=True, default='', verbose_name='接口类型', help_text='接口类型') # 注意:该字段改为接口数据 - protocal = models.CharField(max_length=64, blank=True, null=True, default='', verbose_name='接口数据', + protocal = models.CharField(max_length=1024, blank=True, null=True, default='', verbose_name='接口数据', help_text='接口数据') def __str__(self): @@ -239,23 +218,15 @@ class Design(CoreModel): ordering = ('key',) class TestDemand(CoreModel): - objects = models.Manager() """测试项""" - ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="测试需求标识", - help_text="测试需求标识") - name = models.CharField(max_length=64, blank=True, null=True, verbose_name="测试需求名称", - help_text="测试需求名称") - adequacy = models.CharField(max_length=256, blank=True, null=True, verbose_name="充分条件", - help_text="充分条件") - priority = models.CharField(max_length=8, blank=True, null=True, verbose_name="优先级", - help_text="优先级") - testType = models.CharField(max_length=8, null=True, blank=True, help_text="测试类型", - verbose_name="测试类型", - default="1") - testMethod = models.JSONField(blank=True, help_text="测试方法", verbose_name="测试方法", - default=create_list) - title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称", - help_text="树-名称") + objects = models.Manager() + ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="测试需求标识", help_text="测试需求标识") + name = models.CharField(max_length=64, blank=True, null=True, verbose_name="测试需求名称", help_text="测试需求名称") + adequacy = models.CharField(max_length=2048, blank=True, null=True, verbose_name="充分条件", help_text="充分条件") + priority = models.CharField(max_length=8, blank=True, null=True, verbose_name="优先级", help_text="优先级") + testType = models.CharField(max_length=8, null=True, blank=True, help_text="测试类型", verbose_name="测试类型", default="1") + testMethod = models.JSONField(blank=True, help_text="测试方法", verbose_name="测试方法", default=create_list) + title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称", help_text="树-名称") key = models.CharField(max_length=64, blank=True, null=True, verbose_name="round-dut-designkey-testdemand", help_text="round-dut-designkey-testdemand") diff --git a/apps/project/signals.py b/apps/project/signals.py index 0b6ba75..eaef887 100644 --- a/apps/project/signals.py +++ b/apps/project/signals.py @@ -1,91 +1,132 @@ -import jwt -from django.conf import settings -from threading import local -from django.db.models.signals import post_save, post_delete -from django.dispatch import receiver -from django.utils.functional import SimpleLazyObject -from django.contrib.auth import get_user_model -# 导入日志的模型 -from apps.user.models import TableOperationLog, Users -# 导入其他模型用于排除 -from apps.project.models import CaseStep, TestDemandContent -# 导入异常处理 -from jwt.exceptions import ExpiredSignatureError -from utils.chen_response import ChenResponse -# 导入中间件记录日志模型 -from apps.system.models import LoginLog -from apps.system.models import OperationLog - -log_manager = TableOperationLog.objects - -_thread_local = local() - -def get_current_user(): - """ - 获取当前用户对象,调用则从local对象里面获取user - :return: Users实例 - """ - return getattr(_thread_local, 'user', None) - -def clear_request_locals(sender, **kwargs): - """ - 被request_finished连接的信号处理函数,请求结束后清除local里面的user信息 - """ - _thread_local.user = None - -def set_request_locals(sender, **kwargs): - """ - 被request_started连接的信号处理函数,_thread_local.user属性设置为当前登录用户 - """ - bearer_token = kwargs['environ'].get('HTTP_AUTHORIZATION', None) - if not bearer_token or bearer_token == 'Bearer null': - return - bearer_token = bearer_token.replace('Bearer ', '') - # 逻辑:先获取NINJA_JWT配置中秘钥、和加密算法信息 - jwt_settings = settings.NINJA_JWT - jwt_secret = jwt_settings.get('SIGNING_KEY', None) - jwt_algo = jwt_settings.get('ALGORITHM', None) - # 如果为None,则使用settings中的秘钥和['HS256']算法 - secret_key = jwt_secret or settings.SECRET_KEY - algorithms_str = jwt_algo or 'HS256' - # 解决bug:因为过期前面不跳转首页处理方式 - try: - jwt_dict = jwt.decode(bearer_token, secret_key, algorithms=[algorithms_str]) - except ExpiredSignatureError as exc: - return ChenResponse(status=403, code=500, message='您的token已过期,请重新登录') - user_id = jwt_dict.get('user_id', None) - if user_id: - _thread_local.user = SimpleLazyObject(lambda: get_user_model().objects.get(id=user_id)) - -# 1.注意可以不传sender,为监听所有模型,这里来记录日志 -# 2.使用get_current_user()获取当前请求用户 -@receiver(post_save) -def post_save_handler(sender, instance, created, **kwargs): - """模型新增-操作日志填写""" - # 注意排除日志模块、用例步骤表、测试项步骤表 - if (sender == TableOperationLog or sender == CaseStep or sender == TestDemandContent or sender == LoginLog or sender == OperationLog or sender - == Users): - return - user = get_current_user() - ope_dict = { - 'operate_obj': str(instance), - } - if created: - ope_dict['operate_des'] = '新增' - else: - ope_dict['operate_des'] = '修改' - log_manager.create(user=user, **ope_dict) - -@receiver(post_delete) -def post_delete_handler(sender, instance, **kwargs): - """模型删除-操作日志填写""" - # 注意排除日志模块、用例步骤表、测试项步骤表 - if (sender == TableOperationLog or sender == CaseStep or sender == TestDemandContent or sender == LoginLog or sender == OperationLog or sender - == Users): - return - user = get_current_user() - ope_dict = { - 'operate_obj': str(instance), - 'operate_des': '删除' - } - log_manager.create(user=user, **ope_dict) +import jwt +from django.conf import settings +from threading import local +from django.db.models.signals import post_save, post_delete +from django.dispatch import receiver +from django.utils.functional import SimpleLazyObject +from django.contrib.auth import get_user_model +# 导入用例和影响域分析Model-设计信号当删除用例对应影响域分析删除关联用例 +from apps.project.models import Case, InfluenceItem +# 导入日志的模型 +from apps.user.models import TableOperationLog, Users +# 导入其他模型用于排除 +from apps.project.models import CaseStep, TestDemandContent +# 导入异常处理 +from jwt.exceptions import ExpiredSignatureError +from utils.chen_response import ChenResponse +# 导入中间件记录日志模型 +from apps.system.models import LoginLog +from apps.system.models import OperationLog + +log_manager = TableOperationLog.objects + +_thread_local = local() + +def get_current_user(): + """ + 获取当前用户对象,调用则从local对象里面获取user + :return: Users实例 + """ + return getattr(_thread_local, 'user', None) + +def clear_request_locals(sender, **kwargs): + """ + 被request_finished连接的信号处理函数,请求结束后清除local里面的user信息 + """ + _thread_local.user = None + +def set_request_locals(sender, **kwargs): + """ + 被request_started连接的信号处理函数,_thread_local.user属性设置为当前登录用户 + """ + bearer_token = kwargs['environ'].get('HTTP_AUTHORIZATION', None) + if not bearer_token or bearer_token == 'Bearer null': + return + bearer_token = bearer_token.replace('Bearer ', '') + # 逻辑:先获取NINJA_JWT配置中秘钥、和加密算法信息 + jwt_settings = settings.NINJA_JWT + jwt_secret = jwt_settings.get('SIGNING_KEY', None) + jwt_algo = jwt_settings.get('ALGORITHM', None) + # 如果为None,则使用settings中的秘钥和['HS256']算法 + secret_key = jwt_secret or settings.SECRET_KEY + algorithms_str = jwt_algo or 'HS256' + # 解决bug:因为过期前面不跳转首页处理方式 + try: + jwt_dict = jwt.decode(bearer_token, secret_key, algorithms=[algorithms_str]) + except ExpiredSignatureError as exc: + return ChenResponse(status=403, code=500, message='您的token已过期,请重新登录') + user_id = jwt_dict.get('user_id', None) + if user_id: + _thread_local.user = SimpleLazyObject(lambda: get_user_model().objects.get(id=user_id)) + +# 1.注意可以不传sender,为监听所有模型,这里来记录日志 +# 2.使用get_current_user()获取当前请求用户 +@receiver(post_save) +def post_save_handler(sender, instance, created, **kwargs): + """模型新增-操作日志填写""" + # 注意排除日志模块、用例步骤表、测试项步骤表 + if (sender == TableOperationLog or sender == CaseStep or sender == TestDemandContent or sender == LoginLog or sender == OperationLog or sender + == Users): + return + user = get_current_user() + ope_dict = { + 'operate_obj': str(instance), + } + if created: + ope_dict['operate_des'] = '新增' + else: + ope_dict['operate_des'] = '修改' + log_manager.create(user=user, **ope_dict) + +@receiver(post_delete) +def post_delete_handler(sender, instance, **kwargs): + """模型删除-操作日志填写""" + # 注意排除日志模块、用例步骤表、测试项步骤表 + if (sender == TableOperationLog or sender == CaseStep or sender == TestDemandContent or sender == LoginLog or sender == OperationLog or sender + == Users): + return + user = get_current_user() + ope_dict = { + 'operate_obj': str(instance), + 'operate_des': '删除' + } + log_manager.create(user=user, **ope_dict) + +# 信号:删除影响域分析关联的用例,将影响域分析管理用例删除 +@receiver(post_delete, sender=Case) +def clean_up_deleted_case_reference_from_influence(sender, instance, **kwargs): + """ + 监听 Case 的删除信号。 + 仅在同一个 Project 范围内,从 InfluenceItem 的 effect_cases 中移除被删用例的 key。 + """ + deleted_key = instance.key + project_id = instance.project_id + + if not deleted_key or not project_id: + return + + # 查询当前项目的影响域分析并且包含该用例 + items = InfluenceItem.objects.filter( + influence__round__project_id=project_id, # 外键链锁定项目 + effect_cases__contains=[deleted_key] # JSON 字段包含该 key + ) + + # 未发现有关联的影响域分析则不处理 + if not items.exists(): + return + + # 更新影响域分析的关联用例 + updated_items = [] + for item in items: + original_keys = item.effect_cases + # 过滤掉被删除的 key,保留其他 + new_keys = [k for k in original_keys if k != deleted_key] + + if len(new_keys) != len(original_keys): + item.effect_cases = new_keys + updated_items.append(item) + + if updated_items: + InfluenceItem.objects.bulk_update(updated_items, ['effect_cases']) + else: + print("⚠️ 查询到记录但未发生变更,可能数据有误") \ No newline at end of file diff --git a/conf/__pycache__/env.cpython-313.pyc b/conf/__pycache__/env.cpython-313.pyc index 9ff8cc5..4c03b72 100644 Binary files a/conf/__pycache__/env.cpython-313.pyc and b/conf/__pycache__/env.cpython-313.pyc differ diff --git a/conf/base_document/form_template/bg/被测软件基本信息.docx b/conf/base_document/form_template/bg/被测软件基本信息.docx index 7dc9a6a..21f438f 100644 Binary files a/conf/base_document/form_template/bg/被测软件基本信息.docx and b/conf/base_document/form_template/bg/被测软件基本信息.docx differ diff --git a/conf/base_document/form_template/dg/被测软件基本信息.docx b/conf/base_document/form_template/dg/被测软件基本信息.docx index f7cbc5f..41612d8 100644 Binary files a/conf/base_document/form_template/dg/被测软件基本信息.docx and b/conf/base_document/form_template/dg/被测软件基本信息.docx differ diff --git a/conf/base_document/form_template/products/测评大纲.docx b/conf/base_document/form_template/products/测评大纲.docx index 3b7e8e6..e045cef 100644 Binary files a/conf/base_document/form_template/products/测评大纲.docx and b/conf/base_document/form_template/products/测评大纲.docx differ diff --git a/conf/base_document/form_template/wtd/问题详情表.docx b/conf/base_document/form_template/wtd/问题详情表.docx index d9d3369..ebf2ac0 100644 Binary files a/conf/base_document/form_template/wtd/问题详情表.docx and b/conf/base_document/form_template/wtd/问题详情表.docx differ diff --git a/conf/env.py b/conf/env.py index 48209fa..0959401 100644 --- a/conf/env.py +++ b/conf/env.py @@ -1,83 +1,83 @@ -"""全部是生产环境配置""" -import ldap -from django_auth_ldap.config import LDAPSearch -import environ -from pathlib import Path - -# ***************读取LDAP的.env文件配置*************** # -env_file = '.env' -env = environ.Env() -env.read_env(env_file=Path(__file__).resolve().parent.parent / env_file) - -# ================================================= # -# *************** mysql数据库 配置 *************** # -# ================================================= # -# 数据库地址 -DATABASE_HOST = "127.0.0.1" -# 数据库端口 -DATABASE_PORT = 3307 # 生成环境配置 -# 数据库用户名 -DATABASE_USER = "root" -# 数据库密码 -DATABASE_PASSWORD = "root" -# 数据库名 -DATABASE_NAME = "chengdu_test_plant_v1" - -# ================================================= # -# ******************** celery配置 **************** # -# ================================================= # -CELERY_BROKER_URL = "redis://127.0.0.1:6379/0" -CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379/1" -CELERY_ENABLE_UTC = False -CELERY_TIME_ZONE = "Asia/Shanghai" -CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间 -CELERY_REUSLT_SERIALIZER = "json" # celery结果序列化,接受mime类型,任务序列化形式 -CELERY_ACCEPT_CONTENT = ['application/json'] -CELERY_TASK_SERIALIZER = 'json' -DJANGO_CELERY_BEAT_TZ_AWARE = False -CELERY_WORKER_CONCURRENCY = 5 # 并发数量 -CELERY_MAX_TASKS_PER_CHILD = 10 # 每worker最多执行5个任务自动销毁 - -# ================================================= # -# ****************** 其他 配置 ****************** # -# ================================================= # -ALLOWED_HOSTS = ["*"] # 线上环境设置 -LOGIN_NO_CAPTCHA_AUTH = True # 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消 -ENABLE_LOGIN_ANALYSIS_LOG = True # 启动登录详细概略获取(通过调用api获取ip详细地址) -# ================================================= # -# *************** 接口throttle配置 *************** # -# ================================================= # - -# ================================================= # -# *************** LDAP认证配置 *************** # -# ================================================= # -AUTHENTICATION_BACKENDS = [ - 'django_auth_ldap.backend.LDAPBackend', - 'django.contrib.auth.backends.ModelBackend', -] -# ldap连接配置 -AUTH_LDAP_SERVER_URI = env('AUTH_LDAP_SERVER_URI', default='ldap://dns.paisat.cn:389') -# 绑定的DN,注意大小写敏感Administrator,Users -AUTH_LDAP_BIND_DN = env('AUTH_LDAP_BIND_DN', default="CN=Administrator,CN=Users,DC=sstc,DC=ctu") -# 管理员密码-生产环境 -AUTH_LDAP_BIND_PASSWORD = env('AUTH_LDAP_BIND_PASSWORD', default="WXWX2019!!!!!!") -AUTH_LDAP_USER_SEARCH = LDAPSearch( - env('BASE_DN', default='OU=all,DC=sstc,DC=ctu'), - ldap.SCOPE_SUBTREE, env('FILTER_STR', default='(sAMAccountName=%(user)s)') -) -# 如果ldap服务器是Windows的AD,需要配置上如下选项 -AUTH_LDAP_CONNECTION_OPTIONS = { - ldap.OPT_DEBUG_LEVEL: 1, - ldap.OPT_REFERRALS: 0, -} -# 每次LDAP认证后进行数据库更新,不包含密码 -AUTH_LDAP_ALWAYS_UPDATE_USER = True -# 看看下面是否需要password字段 -AUTH_LDAP_USER_ATTR_MAP = { - "username": "sAMAccountName", - "name": "name", - "email": "mail", -} -# ================================================= # -# *************** ...........配置 *************** # -# ================================================= # +"""全部是生产环境配置""" +import ldap +from django_auth_ldap.config import LDAPSearch +import environ +from pathlib import Path + +# ***************读取LDAP的.env文件配置*************** # +env_file = '.env' +env = environ.Env() +env.read_env(env_file=Path(__file__).resolve().parent.parent / env_file) + +# ================================================= # +# *************** mysql数据库 配置 *************** # +# ================================================= # +# 数据库地址 +DATABASE_HOST = "127.0.0.1" +# 数据库端口 +DATABASE_PORT = 3306 # 生成环境配置-注意打包时务必需改为3307!!!!!! +# 数据库用户名 +DATABASE_USER = "root" +# 数据库密码 +DATABASE_PASSWORD = "root" +# 数据库名 +DATABASE_NAME = "chengdu_test_plant_v1" + +# ================================================= # +# ******************** celery配置 **************** # +# ================================================= # +CELERY_BROKER_URL = "redis://127.0.0.1:6379/0" +CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379/1" +CELERY_ENABLE_UTC = False +CELERY_TIME_ZONE = "Asia/Shanghai" +CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间 +CELERY_REUSLT_SERIALIZER = "json" # celery结果序列化,接受mime类型,任务序列化形式 +CELERY_ACCEPT_CONTENT = ['application/json'] +CELERY_TASK_SERIALIZER = 'json' +DJANGO_CELERY_BEAT_TZ_AWARE = False +CELERY_WORKER_CONCURRENCY = 5 # 并发数量 +CELERY_MAX_TASKS_PER_CHILD = 10 # 每worker最多执行5个任务自动销毁 + +# ================================================= # +# ****************** 其他 配置 ****************** # +# ================================================= # +ALLOWED_HOSTS = ["*"] # 线上环境设置 +LOGIN_NO_CAPTCHA_AUTH = True # 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消 +ENABLE_LOGIN_ANALYSIS_LOG = True # 启动登录详细概略获取(通过调用api获取ip详细地址) +# ================================================= # +# *************** 接口throttle配置 *************** # +# ================================================= # + +# ================================================= # +# *************** LDAP认证配置 *************** # +# ================================================= # +AUTHENTICATION_BACKENDS = [ + 'django_auth_ldap.backend.LDAPBackend', + 'django.contrib.auth.backends.ModelBackend', +] +# ldap连接配置 +AUTH_LDAP_SERVER_URI = env('AUTH_LDAP_SERVER_URI', default='ldap://dns.paisat.cn:389') +# 绑定的DN,注意大小写敏感Administrator,Users +AUTH_LDAP_BIND_DN = env('AUTH_LDAP_BIND_DN', default="CN=Administrator,CN=Users,DC=sstc,DC=ctu") +# 管理员密码-生产环境 +AUTH_LDAP_BIND_PASSWORD = env('AUTH_LDAP_BIND_PASSWORD', default="WXWX2019!!!!!!") +AUTH_LDAP_USER_SEARCH = LDAPSearch( + env('BASE_DN', default='OU=all,DC=sstc,DC=ctu'), + ldap.SCOPE_SUBTREE, env('FILTER_STR', default='(sAMAccountName=%(user)s)') +) +# 如果ldap服务器是Windows的AD,需要配置上如下选项 +AUTH_LDAP_CONNECTION_OPTIONS = { + ldap.OPT_DEBUG_LEVEL: 1, + ldap.OPT_REFERRALS: 0, +} +# 每次LDAP认证后进行数据库更新,不包含密码 +AUTH_LDAP_ALWAYS_UPDATE_USER = True +# 看看下面是否需要password字段 +AUTH_LDAP_USER_ATTR_MAP = { + "username": "sAMAccountName", + "name": "name", + "email": "mail", +} +# ================================================= # +# *************** ...........配置 *************** # +# ================================================= # diff --git a/logs/generates_logs b/logs/generates_logs index e69de29..c098f8a 100644 --- a/logs/generates_logs +++ b/logs/generates_logs @@ -0,0 +1,2 @@ +[WARNING][2026-04-17 14:20:04,514][logger.py:25][回归测试记录模块][字典数据缺失]片段:字典数据runtime数据缺失,请检查相应数据是否存在 +[WARNING][2026-04-17 14:20:04,518][logger.py:25][回归测试记录模块][字典数据缺失]片段:字典数据devplant数据缺失,请检查相应数据是否存在 diff --git a/logs/root_log b/logs/root_log index ad7dfdd..7134d0f 100644 --- a/logs/root_log +++ b/logs/root_log @@ -322,3 +322,690 @@ Traceback (most recent call last): pic_nodes = new_element.xpath('.//pic:pic', namespaces=nsmap) TypeError: BaseOxmlElement.xpath() got an unexpected keyword argument 'namespaces' [ERROR][2026-04-16 17:21:20,341][log.py:249]Internal Server Error: /api/documentUpload/file +[WARNING][2026-04-17 13:45:31,769][log.py:249]Unauthorized: /api/system/getInfo +[WARNING][2026-04-17 13:45:31,835][log.py:249]Unauthorized: /api/system/logout +[WARNING][2026-04-17 13:45:36,826][backend.py:91]Caught LDAPError looking up user: SERVER_DOWN({'result': -1, 'desc': "Can't contact LDAP server", 'ctrls': []}) +[WARNING][2026-04-17 14:20:04,514][logger.py:25][回归测试记录模块][字典数据缺失]片段:字典数据runtime数据缺失,请检查相应数据是否存在 +[WARNING][2026-04-17 14:20:04,518][logger.py:25][回归测试记录模块][字典数据缺失]片段:字典数据devplant数据缺失,请检查相应数据是否存在 +[WARNING][2026-04-17 16:18:06,060][operation.py:136]"PUT - TestDemandController[update_testDemand] /api/project/testDemand/update/670" (1406, "Data too long for column 'adequacy' at row 1") +[ERROR][2026-04-17 16:18:06,060][errors.py:131](1406, "Data too long for column 'adequacy' at row 1") +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 105, in _execute + return self.cursor.execute(sql, params) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\mysql\base.py", line 78, in execute + return self.cursor.execute(query, args) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 179, in execute + res = self._query(mogrified_query) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 330, in _query + db.query(q) + ~~~~~~~~^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\connections.py", line 286, in query + _mysql.connection.query(self, query) + ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ +MySQLdb.DataError: (1406, "Data too long for column 'adequacy' at row 1") + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\operation.py", line 217, in run + result = self.view_func(request, **ctx.kwargs["view_func_kwargs"]) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\controllers\route\route_functions.py", line 108, in as_view + result = self.route.view_func( + ctx.controller_instance, *args, **ctx.view_func_kwargs + ) + File "D:\programs\uv\python\cpython-3.13.11-windows-x86_64-none\Lib\contextlib.py", line 85, in inner + return func(*args, **kwds) + File "E:\pycharmProjects\cdtestplant_v1\apps\project\controllers\testDemand.py", line 270, in update_testDemand + testDemand_qs.save() + ~~~~~~~~~~~~~~~~~~^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 874, in save + self.save_base( + ~~~~~~~~~~~~~~^ + using=using, + ^^^^^^^^^^^^ + ...<2 lines>... + update_fields=update_fields, + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 966, in save_base + updated = self._save_table( + raw, + ...<4 lines>... + update_fields, + ) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 1110, in _save_table + results = self._do_update( + base_qs, + ...<5 lines>... + returning_fields, + ) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 1213, in _do_update + return filtered._update(values, returning_fields) + ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\query.py", line 1327, in _update + return query.get_compiler(self.db).execute_returning_sql(returning_fields) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\sql\compiler.py", line 2140, in execute_returning_sql + row_count = self.execute_sql(ROW_COUNT) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\sql\compiler.py", line 2111, in execute_sql + row_count = super().execute_sql(result_type) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\sql\compiler.py", line 1624, in execute_sql + cursor.execute(sql, params) + ~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 122, in execute + return super().execute(sql, params) + ~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 79, in execute + return self._execute_with_wrappers( + ~~~~~~~~~~~~~~~~~~~~~~~~~~~^ + sql, params, many=False, executor=self._execute + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 92, in _execute_with_wrappers + return executor(sql, params, many, context) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 100, in _execute + with self.db.wrap_database_errors: + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\utils.py", line 94, in __exit__ + raise dj_exc_value.with_traceback(traceback) from exc_value + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 105, in _execute + return self.cursor.execute(sql, params) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\mysql\base.py", line 78, in execute + return self.cursor.execute(query, args) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 179, in execute + res = self._query(mogrified_query) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 330, in _query + db.query(q) + ~~~~~~~~^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\connections.py", line 286, in query + _mysql.connection.query(self, query) + ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ +django.db.utils.DataError: (1406, "Data too long for column 'adequacy' at row 1") +[ERROR][2026-04-17 16:18:06,071][log.py:249]Internal Server Error: /api/project/testDemand/update/670 +[WARNING][2026-04-17 16:35:01,341][log.py:249]Bad Request: /api/testmanage/project/save +[WARNING][2026-04-17 16:45:59,963][log.py:249]Bad Request: /api/testmanage/project/save +[WARNING][2026-04-17 16:46:01,209][log.py:249]Bad Request: /api/testmanage/project/save +[WARNING][2026-04-17 17:01:10,991][log.py:249]Bad Request: /api/testmanage/project/save +[WARNING][2026-04-20 09:31:28,826][log.py:249]Unauthorized: /api/system/getInfo +[WARNING][2026-04-20 09:31:28,893][log.py:249]Unauthorized: /api/system/logout +[WARNING][2026-04-20 09:31:36,102][backend.py:91]Caught LDAPError looking up user: SERVER_DOWN({'result': -1, 'desc': "Can't contact LDAP server", 'ctrls': []}) +[ERROR][2026-04-20 09:34:09,062][log.py:249]Internal Server Error: /api/project/testDemand/save +[ERROR][2026-04-20 09:34:10,386][log.py:249]Internal Server Error: /api/project/testDemand/save +[ERROR][2026-04-20 09:34:10,841][log.py:249]Internal Server Error: /api/project/testDemand/save +[WARNING][2026-04-20 10:25:04,532][operation.py:136]"GET - GenerateControllerDG[create_static_hard] /api/generate/create/static_hard" ('All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters',) +[ERROR][2026-04-20 10:25:04,532][errors.py:131]All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\operation.py", line 217, in run + result = self.view_func(request, **ctx.kwargs["view_func_kwargs"]) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\controllers\route\route_functions.py", line 108, in as_view + result = self.route.view_func( + ctx.controller_instance, *args, **ctx.view_func_kwargs + ) + File "E:\pycharmProjects\cdtestplant_v1\apps\createDocument\controllers\dg.py", line 567, in create_static_hard + res = self.uniform_static_dynamic_response(id, '静态硬件和固件项_2.docx', '静态硬件和固件项.docx', StaticSoftHardware) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\apps\createDocument\controllers\dg.py", line 535, in uniform_static_dynamic_response + subdoc = cls.create_table_context(table_data, doc) + File "E:\pycharmProjects\cdtestplant_v1\apps\createDocument\controllers\dg.py", line 498, in create_table_context + cell.text = cell.text.replace("\n", "\a") + ^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\table.py", line 284, in text + r.text = text + ^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 143, in text + _RunContentAppender.append_to_run_from_text(self, text) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 279, in append_to_run_from_text + appender.add_text(text) + ~~~~~~~~~~~~~~~~~^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 285, in add_text + self.flush() + ~~~~~~~~~~^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 306, in flush + self._r.add_t(text) + ~~~~~~~~~~~~~^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 42, in add_t + t = self._add_t(text=text) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\xmlchemy.py", line 288, in _add_child + setattr(child, key, value) + ~~~~~~~^^^^^^^^^^^^^^^^^^^ + File "src/lxml/etree.pyx", line 1168, in lxml.etree._Element.text.__set__ + File "src/lxml/apihelpers.pxi", line 763, in lxml.etree._setNodeText + File "src/lxml/apihelpers.pxi", line 751, in lxml.etree._createTextNode + File "src/lxml/apihelpers.pxi", line 1556, in lxml.etree._utf8 +ValueError: All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters +[ERROR][2026-04-20 10:25:04,710][log.py:249]Internal Server Error: /api/generate/create/static_hard +[WARNING][2026-04-20 10:25:26,146][operation.py:136]"GET - GenerateControllerDG[create_static_hard] /api/generate/create/static_hard" ('All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters',) +[ERROR][2026-04-20 10:25:26,150][errors.py:131]All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\operation.py", line 217, in run + result = self.view_func(request, **ctx.kwargs["view_func_kwargs"]) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\controllers\route\route_functions.py", line 108, in as_view + result = self.route.view_func( + ctx.controller_instance, *args, **ctx.view_func_kwargs + ) + File "E:\pycharmProjects\cdtestplant_v1\apps\createDocument\controllers\dg.py", line 567, in create_static_hard + res = self.uniform_static_dynamic_response(id, '静态硬件和固件项_2.docx', '静态硬件和固件项.docx', StaticSoftHardware) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\apps\createDocument\controllers\dg.py", line 535, in uniform_static_dynamic_response + subdoc = cls.create_table_context(table_data, doc) + File "E:\pycharmProjects\cdtestplant_v1\apps\createDocument\controllers\dg.py", line 498, in create_table_context + cell.text = cell.text.replace("\n", "\a") + ^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\table.py", line 284, in text + r.text = text + ^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 143, in text + _RunContentAppender.append_to_run_from_text(self, text) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 279, in append_to_run_from_text + appender.add_text(text) + ~~~~~~~~~~~~~~~~~^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 285, in add_text + self.flush() + ~~~~~~~~~~^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 306, in flush + self._r.add_t(text) + ~~~~~~~~~~~~~^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 42, in add_t + t = self._add_t(text=text) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\xmlchemy.py", line 288, in _add_child + setattr(child, key, value) + ~~~~~~~^^^^^^^^^^^^^^^^^^^ + File "src/lxml/etree.pyx", line 1168, in lxml.etree._Element.text.__set__ + File "src/lxml/apihelpers.pxi", line 763, in lxml.etree._setNodeText + File "src/lxml/apihelpers.pxi", line 751, in lxml.etree._createTextNode + File "src/lxml/apihelpers.pxi", line 1556, in lxml.etree._utf8 +ValueError: All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters +[ERROR][2026-04-20 10:25:26,215][log.py:249]Internal Server Error: /api/generate/create/static_hard +[WARNING][2026-04-20 10:25:46,224][operation.py:136]"GET - GenerateControllerDG[create_static_hard] /api/generate/create/static_hard" ('All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters',) +[ERROR][2026-04-20 10:25:46,224][errors.py:131]All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\operation.py", line 217, in run + result = self.view_func(request, **ctx.kwargs["view_func_kwargs"]) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\controllers\route\route_functions.py", line 108, in as_view + result = self.route.view_func( + ctx.controller_instance, *args, **ctx.view_func_kwargs + ) + File "E:\pycharmProjects\cdtestplant_v1\apps\createDocument\controllers\dg.py", line 565, in create_static_hard + res = self.uniform_static_dynamic_response(id, '静态硬件和固件项_2.docx', '静态硬件和固件项.docx', StaticSoftHardware) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\apps\createDocument\controllers\dg.py", line 533, in uniform_static_dynamic_response + subdoc = cls.create_table_context(table_data, doc) + File "E:\pycharmProjects\cdtestplant_v1\apps\createDocument\controllers\dg.py", line 496, in create_table_context + cell.text = table_data[row][col - 1].replace("\n", "\a") + ^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\table.py", line 284, in text + r.text = text + ^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 143, in text + _RunContentAppender.append_to_run_from_text(self, text) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 279, in append_to_run_from_text + appender.add_text(text) + ~~~~~~~~~~~~~~~~~^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 285, in add_text + self.flush() + ~~~~~~~~~~^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 306, in flush + self._r.add_t(text) + ~~~~~~~~~~~~~^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\text\run.py", line 42, in add_t + t = self._add_t(text=text) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docx\oxml\xmlchemy.py", line 288, in _add_child + setattr(child, key, value) + ~~~~~~~^^^^^^^^^^^^^^^^^^^ + File "src/lxml/etree.pyx", line 1168, in lxml.etree._Element.text.__set__ + File "src/lxml/apihelpers.pxi", line 763, in lxml.etree._setNodeText + File "src/lxml/apihelpers.pxi", line 751, in lxml.etree._createTextNode + File "src/lxml/apihelpers.pxi", line 1556, in lxml.etree._utf8 +ValueError: All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters +[ERROR][2026-04-20 10:25:46,266][log.py:249]Internal Server Error: /api/generate/create/static_hard +[WARNING][2026-04-20 10:34:26,463][operation.py:136]"POST - DesignController[create_design] /api/project/designDemand/save" (1406, "Data too long for column 'protocal' at row 1") +[ERROR][2026-04-20 10:34:26,463][errors.py:131](1406, "Data too long for column 'protocal' at row 1") +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 105, in _execute + return self.cursor.execute(sql, params) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\mysql\base.py", line 78, in execute + return self.cursor.execute(query, args) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 179, in execute + res = self._query(mogrified_query) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 330, in _query + db.query(q) + ~~~~~~~~^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\connections.py", line 286, in query + _mysql.connection.query(self, query) + ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ +MySQLdb.DataError: (1406, "Data too long for column 'protocal' at row 1") + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\operation.py", line 217, in run + result = self.view_func(request, **ctx.kwargs["view_func_kwargs"]) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\controllers\route\route_functions.py", line 108, in as_view + result = self.route.view_func( + ctx.controller_instance, *args, **ctx.view_func_kwargs + ) + File "D:\programs\uv\python\cpython-3.13.11-windows-x86_64-none\Lib\contextlib.py", line 85, in inner + return func(*args, **kwds) + File "E:\pycharmProjects\cdtestplant_v1\apps\project\controllers\design.py", line 100, in create_design + qs = Design.objects.create(**asert_dict) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\manager.py", line 87, in manager_method + return getattr(self.get_queryset(), name)(*args, **kwargs) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\query.py", line 669, in create + obj.save(force_insert=True, using=self.db) + ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 874, in save + self.save_base( + ~~~~~~~~~~~~~~^ + using=using, + ^^^^^^^^^^^^ + ...<2 lines>... + update_fields=update_fields, + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 966, in save_base + updated = self._save_table( + raw, + ...<4 lines>... + update_fields, + ) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 1169, in _save_table + results = self._do_insert( + cls._base_manager, using, insert_fields, returning_fields, raw + ) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 1220, in _do_insert + return manager._insert( + ~~~~~~~~~~~~~~~^ + [self], + ^^^^^^^ + ...<3 lines>... + raw=raw, + ^^^^^^^^ + ) + ^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\manager.py", line 87, in manager_method + return getattr(self.get_queryset(), name)(*args, **kwargs) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\query.py", line 1918, in _insert + return query.get_compiler(using=using).execute_sql(returning_fields) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\sql\compiler.py", line 1925, in execute_sql + cursor.execute(sql, params) + ~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 122, in execute + return super().execute(sql, params) + ~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 79, in execute + return self._execute_with_wrappers( + ~~~~~~~~~~~~~~~~~~~~~~~~~~~^ + sql, params, many=False, executor=self._execute + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 92, in _execute_with_wrappers + return executor(sql, params, many, context) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 100, in _execute + with self.db.wrap_database_errors: + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\utils.py", line 94, in __exit__ + raise dj_exc_value.with_traceback(traceback) from exc_value + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 105, in _execute + return self.cursor.execute(sql, params) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\mysql\base.py", line 78, in execute + return self.cursor.execute(query, args) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 179, in execute + res = self._query(mogrified_query) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 330, in _query + db.query(q) + ~~~~~~~~^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\connections.py", line 286, in query + _mysql.connection.query(self, query) + ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ +django.db.utils.DataError: (1406, "Data too long for column 'protocal' at row 1") +[ERROR][2026-04-20 10:34:26,484][log.py:249]Internal Server Error: /api/project/designDemand/save +[WARNING][2026-04-20 10:35:26,599][operation.py:136]"POST - DesignController[create_design] /api/project/designDemand/save" (1406, "Data too long for column 'protocal' at row 1") +[ERROR][2026-04-20 10:35:26,599][errors.py:131](1406, "Data too long for column 'protocal' at row 1") +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 105, in _execute + return self.cursor.execute(sql, params) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\mysql\base.py", line 78, in execute + return self.cursor.execute(query, args) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 179, in execute + res = self._query(mogrified_query) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 330, in _query + db.query(q) + ~~~~~~~~^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\connections.py", line 286, in query + _mysql.connection.query(self, query) + ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ +MySQLdb.DataError: (1406, "Data too long for column 'protocal' at row 1") + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\operation.py", line 217, in run + result = self.view_func(request, **ctx.kwargs["view_func_kwargs"]) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\controllers\route\route_functions.py", line 108, in as_view + result = self.route.view_func( + ctx.controller_instance, *args, **ctx.view_func_kwargs + ) + File "D:\programs\uv\python\cpython-3.13.11-windows-x86_64-none\Lib\contextlib.py", line 85, in inner + return func(*args, **kwds) + File "E:\pycharmProjects\cdtestplant_v1\apps\project\controllers\design.py", line 100, in create_design + qs = Design.objects.create(**asert_dict) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\manager.py", line 87, in manager_method + return getattr(self.get_queryset(), name)(*args, **kwargs) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\query.py", line 669, in create + obj.save(force_insert=True, using=self.db) + ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 874, in save + self.save_base( + ~~~~~~~~~~~~~~^ + using=using, + ^^^^^^^^^^^^ + ...<2 lines>... + update_fields=update_fields, + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 966, in save_base + updated = self._save_table( + raw, + ...<4 lines>... + update_fields, + ) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 1169, in _save_table + results = self._do_insert( + cls._base_manager, using, insert_fields, returning_fields, raw + ) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 1220, in _do_insert + return manager._insert( + ~~~~~~~~~~~~~~~^ + [self], + ^^^^^^^ + ...<3 lines>... + raw=raw, + ^^^^^^^^ + ) + ^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\manager.py", line 87, in manager_method + return getattr(self.get_queryset(), name)(*args, **kwargs) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\query.py", line 1918, in _insert + return query.get_compiler(using=using).execute_sql(returning_fields) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\sql\compiler.py", line 1925, in execute_sql + cursor.execute(sql, params) + ~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 122, in execute + return super().execute(sql, params) + ~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 79, in execute + return self._execute_with_wrappers( + ~~~~~~~~~~~~~~~~~~~~~~~~~~~^ + sql, params, many=False, executor=self._execute + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 92, in _execute_with_wrappers + return executor(sql, params, many, context) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 100, in _execute + with self.db.wrap_database_errors: + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\utils.py", line 94, in __exit__ + raise dj_exc_value.with_traceback(traceback) from exc_value + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 105, in _execute + return self.cursor.execute(sql, params) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\mysql\base.py", line 78, in execute + return self.cursor.execute(query, args) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 179, in execute + res = self._query(mogrified_query) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 330, in _query + db.query(q) + ~~~~~~~~^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\connections.py", line 286, in query + _mysql.connection.query(self, query) + ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ +django.db.utils.DataError: (1406, "Data too long for column 'protocal' at row 1") +[ERROR][2026-04-20 10:35:26,608][log.py:249]Internal Server Error: /api/project/designDemand/save +[WARNING][2026-04-20 10:35:27,464][operation.py:136]"POST - DesignController[create_design] /api/project/designDemand/save" (1406, "Data too long for column 'protocal' at row 1") +[ERROR][2026-04-20 10:35:27,464][errors.py:131](1406, "Data too long for column 'protocal' at row 1") +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 105, in _execute + return self.cursor.execute(sql, params) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\mysql\base.py", line 78, in execute + return self.cursor.execute(query, args) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 179, in execute + res = self._query(mogrified_query) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 330, in _query + db.query(q) + ~~~~~~~~^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\connections.py", line 286, in query + _mysql.connection.query(self, query) + ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ +MySQLdb.DataError: (1406, "Data too long for column 'protocal' at row 1") + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\operation.py", line 217, in run + result = self.view_func(request, **ctx.kwargs["view_func_kwargs"]) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\controllers\route\route_functions.py", line 108, in as_view + result = self.route.view_func( + ctx.controller_instance, *args, **ctx.view_func_kwargs + ) + File "D:\programs\uv\python\cpython-3.13.11-windows-x86_64-none\Lib\contextlib.py", line 85, in inner + return func(*args, **kwds) + File "E:\pycharmProjects\cdtestplant_v1\apps\project\controllers\design.py", line 100, in create_design + qs = Design.objects.create(**asert_dict) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\manager.py", line 87, in manager_method + return getattr(self.get_queryset(), name)(*args, **kwargs) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\query.py", line 669, in create + obj.save(force_insert=True, using=self.db) + ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 874, in save + self.save_base( + ~~~~~~~~~~~~~~^ + using=using, + ^^^^^^^^^^^^ + ...<2 lines>... + update_fields=update_fields, + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 966, in save_base + updated = self._save_table( + raw, + ...<4 lines>... + update_fields, + ) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 1169, in _save_table + results = self._do_insert( + cls._base_manager, using, insert_fields, returning_fields, raw + ) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 1220, in _do_insert + return manager._insert( + ~~~~~~~~~~~~~~~^ + [self], + ^^^^^^^ + ...<3 lines>... + raw=raw, + ^^^^^^^^ + ) + ^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\manager.py", line 87, in manager_method + return getattr(self.get_queryset(), name)(*args, **kwargs) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\query.py", line 1918, in _insert + return query.get_compiler(using=using).execute_sql(returning_fields) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\sql\compiler.py", line 1925, in execute_sql + cursor.execute(sql, params) + ~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 122, in execute + return super().execute(sql, params) + ~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 79, in execute + return self._execute_with_wrappers( + ~~~~~~~~~~~~~~~~~~~~~~~~~~~^ + sql, params, many=False, executor=self._execute + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 92, in _execute_with_wrappers + return executor(sql, params, many, context) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 100, in _execute + with self.db.wrap_database_errors: + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\utils.py", line 94, in __exit__ + raise dj_exc_value.with_traceback(traceback) from exc_value + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 105, in _execute + return self.cursor.execute(sql, params) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\mysql\base.py", line 78, in execute + return self.cursor.execute(query, args) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 179, in execute + res = self._query(mogrified_query) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 330, in _query + db.query(q) + ~~~~~~~~^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\connections.py", line 286, in query + _mysql.connection.query(self, query) + ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ +django.db.utils.DataError: (1406, "Data too long for column 'protocal' at row 1") +[ERROR][2026-04-20 10:35:27,473][log.py:249]Internal Server Error: /api/project/designDemand/save +[WARNING][2026-04-20 10:35:27,981][operation.py:136]"POST - DesignController[create_design] /api/project/designDemand/save" (1406, "Data too long for column 'protocal' at row 1") +[ERROR][2026-04-20 10:35:27,981][errors.py:131](1406, "Data too long for column 'protocal' at row 1") +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 105, in _execute + return self.cursor.execute(sql, params) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\mysql\base.py", line 78, in execute + return self.cursor.execute(query, args) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 179, in execute + res = self._query(mogrified_query) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 330, in _query + db.query(q) + ~~~~~~~~^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\connections.py", line 286, in query + _mysql.connection.query(self, query) + ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ +MySQLdb.DataError: (1406, "Data too long for column 'protocal' at row 1") + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\operation.py", line 217, in run + result = self.view_func(request, **ctx.kwargs["view_func_kwargs"]) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\controllers\route\route_functions.py", line 108, in as_view + result = self.route.view_func( + ctx.controller_instance, *args, **ctx.view_func_kwargs + ) + File "D:\programs\uv\python\cpython-3.13.11-windows-x86_64-none\Lib\contextlib.py", line 85, in inner + return func(*args, **kwds) + File "E:\pycharmProjects\cdtestplant_v1\apps\project\controllers\design.py", line 100, in create_design + qs = Design.objects.create(**asert_dict) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\manager.py", line 87, in manager_method + return getattr(self.get_queryset(), name)(*args, **kwargs) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\query.py", line 669, in create + obj.save(force_insert=True, using=self.db) + ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 874, in save + self.save_base( + ~~~~~~~~~~~~~~^ + using=using, + ^^^^^^^^^^^^ + ...<2 lines>... + update_fields=update_fields, + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 966, in save_base + updated = self._save_table( + raw, + ...<4 lines>... + update_fields, + ) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 1169, in _save_table + results = self._do_insert( + cls._base_manager, using, insert_fields, returning_fields, raw + ) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\base.py", line 1220, in _do_insert + return manager._insert( + ~~~~~~~~~~~~~~~^ + [self], + ^^^^^^^ + ...<3 lines>... + raw=raw, + ^^^^^^^^ + ) + ^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\manager.py", line 87, in manager_method + return getattr(self.get_queryset(), name)(*args, **kwargs) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\query.py", line 1918, in _insert + return query.get_compiler(using=using).execute_sql(returning_fields) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\models\sql\compiler.py", line 1925, in execute_sql + cursor.execute(sql, params) + ~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 122, in execute + return super().execute(sql, params) + ~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 79, in execute + return self._execute_with_wrappers( + ~~~~~~~~~~~~~~~~~~~~~~~~~~~^ + sql, params, many=False, executor=self._execute + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ) + ^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 92, in _execute_with_wrappers + return executor(sql, params, many, context) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 100, in _execute + with self.db.wrap_database_errors: + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\utils.py", line 94, in __exit__ + raise dj_exc_value.with_traceback(traceback) from exc_value + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\utils.py", line 105, in _execute + return self.cursor.execute(sql, params) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\django\db\backends\mysql\base.py", line 78, in execute + return self.cursor.execute(query, args) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 179, in execute + res = self._query(mogrified_query) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\cursors.py", line 330, in _query + db.query(q) + ~~~~~~~~^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\MySQLdb\connections.py", line 286, in query + _mysql.connection.query(self, query) + ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ +django.db.utils.DataError: (1406, "Data too long for column 'protocal' at row 1") +[ERROR][2026-04-20 10:35:27,991][log.py:249]Internal Server Error: /api/project/designDemand/save +[WARNING][2026-04-20 10:45:28,053][log.py:249]Bad Request: /api/project/editDesignDemand/4028 +[ERROR][2026-04-20 17:05:17,173][log.py:249]Internal Server Error: /api/project/case/create_by_demand diff --git a/media/R25999/final_seitai/测评大纲.docx b/media/R25999/final_seitai/测评大纲.docx index 6c53d65..815c8af 100644 Binary files a/media/R25999/final_seitai/测评大纲.docx and b/media/R25999/final_seitai/测评大纲.docx differ diff --git a/media/R25999/final_seitai/测评报告.docx b/media/R25999/final_seitai/测评报告.docx index 0186ad3..e33e809 100644 Binary files a/media/R25999/final_seitai/测评报告.docx and b/media/R25999/final_seitai/测评报告.docx differ diff --git a/media/R25999/form_template/bg/temporary/研总需归追踪_temp.docx b/media/R25999/form_template/bg/temporary/研总需归追踪_temp.docx index 285208c..89e3d9b 100644 Binary files a/media/R25999/form_template/bg/temporary/研总需归追踪_temp.docx and b/media/R25999/form_template/bg/temporary/研总需归追踪_temp.docx differ diff --git a/media/R25999/output_dir/bg/总体结论.docx b/media/R25999/output_dir/bg/总体结论.docx index 9221562..6e45081 100644 Binary files a/media/R25999/output_dir/bg/总体结论.docx and b/media/R25999/output_dir/bg/总体结论.docx differ diff --git a/media/R25999/output_dir/bg/技术依据文件.docx b/media/R25999/output_dir/bg/技术依据文件.docx index 4136a78..c4a8153 100644 Binary files a/media/R25999/output_dir/bg/技术依据文件.docx and b/media/R25999/output_dir/bg/技术依据文件.docx differ diff --git a/media/R25999/output_dir/bg/摸底清单.docx b/media/R25999/output_dir/bg/摸底清单.docx index 416b02e..3eeced4 100644 Binary files a/media/R25999/output_dir/bg/摸底清单.docx and b/media/R25999/output_dir/bg/摸底清单.docx differ diff --git a/media/R25999/output_dir/bg/测评完成情况.docx b/media/R25999/output_dir/bg/测评完成情况.docx index 1e7bd54..1bff148 100644 Binary files a/media/R25999/output_dir/bg/测评完成情况.docx and b/media/R25999/output_dir/bg/测评完成情况.docx differ diff --git a/media/R25999/output_dir/bg/测评时间和地点.docx b/media/R25999/output_dir/bg/测评时间和地点.docx index b1e060e..6be0b88 100644 Binary files a/media/R25999/output_dir/bg/测评时间和地点.docx and b/media/R25999/output_dir/bg/测评时间和地点.docx differ diff --git a/media/R25999/output_dir/bg/测试内容和结果_第一轮次.docx b/media/R25999/output_dir/bg/测试内容和结果_第一轮次.docx index 26be730..a043606 100644 Binary files a/media/R25999/output_dir/bg/测试内容和结果_第一轮次.docx and b/media/R25999/output_dir/bg/测试内容和结果_第一轮次.docx differ diff --git a/media/R25999/output_dir/bg/测试内容和结果_第二轮次.docx b/media/R25999/output_dir/bg/测试内容和结果_第二轮次.docx index 435e01a..c025f60 100644 Binary files a/media/R25999/output_dir/bg/测试内容和结果_第二轮次.docx and b/media/R25999/output_dir/bg/测试内容和结果_第二轮次.docx differ diff --git a/media/R25999/output_dir/bg/测试有效性充分性说明.docx b/media/R25999/output_dir/bg/测试有效性充分性说明.docx index ad83948..d105caa 100644 Binary files a/media/R25999/output_dir/bg/测试有效性充分性说明.docx and b/media/R25999/output_dir/bg/测试有效性充分性说明.docx differ diff --git a/media/R25999/output_dir/bg/研总需归追踪.docx b/media/R25999/output_dir/bg/研总需归追踪.docx index 845837f..9c4280b 100644 Binary files a/media/R25999/output_dir/bg/研总需归追踪.docx and b/media/R25999/output_dir/bg/研总需归追踪.docx differ diff --git a/media/R25999/output_dir/bg/综述.docx b/media/R25999/output_dir/bg/综述.docx index 3b6a0c2..af502a1 100644 Binary files a/media/R25999/output_dir/bg/综述.docx and b/media/R25999/output_dir/bg/综述.docx differ diff --git a/media/R25999/output_dir/bg/被测软件基本信息.docx b/media/R25999/output_dir/bg/被测软件基本信息.docx index 9f25e99..af2ad77 100644 Binary files a/media/R25999/output_dir/bg/被测软件基本信息.docx and b/media/R25999/output_dir/bg/被测软件基本信息.docx differ diff --git a/media/R25999/output_dir/bg/软件质量评价.docx b/media/R25999/output_dir/bg/软件质量评价.docx index a05475d..930d460 100644 Binary files a/media/R25999/output_dir/bg/软件质量评价.docx and b/media/R25999/output_dir/bg/软件质量评价.docx differ diff --git a/media/R25999/output_dir/bg/软件问题统计.docx b/media/R25999/output_dir/bg/软件问题统计.docx index 8f9ca82..4d01014 100644 Binary files a/media/R25999/output_dir/bg/软件问题统计.docx and b/media/R25999/output_dir/bg/软件问题统计.docx differ diff --git a/media/R25999/output_dir/bg/问题汇总表.docx b/media/R25999/output_dir/bg/问题汇总表.docx index f0327b0..79af8e1 100644 Binary files a/media/R25999/output_dir/bg/问题汇总表.docx and b/media/R25999/output_dir/bg/问题汇总表.docx differ diff --git a/media/R25999/output_dir/bg/需求指标符合性情况.docx b/media/R25999/output_dir/bg/需求指标符合性情况.docx index 4a62ad7..1bf50bb 100644 Binary files a/media/R25999/output_dir/bg/需求指标符合性情况.docx and b/media/R25999/output_dir/bg/需求指标符合性情况.docx differ diff --git a/media/R25999/output_dir/主要功能和性能指标.docx b/media/R25999/output_dir/主要功能和性能指标.docx index d0cb5f7..60d08d6 100644 Binary files a/media/R25999/output_dir/主要功能和性能指标.docx and b/media/R25999/output_dir/主要功能和性能指标.docx differ diff --git a/media/R25999/output_dir/代码质量度量分析表.docx b/media/R25999/output_dir/代码质量度量分析表.docx index dd93dfe..ab91fd0 100644 Binary files a/media/R25999/output_dir/代码质量度量分析表.docx and b/media/R25999/output_dir/代码质量度量分析表.docx differ diff --git a/media/R25999/output_dir/动态测试环境说明.docx b/media/R25999/output_dir/动态测试环境说明.docx index cdcc9d0..bf37cca 100644 Binary files a/media/R25999/output_dir/动态测试环境说明.docx and b/media/R25999/output_dir/动态测试环境说明.docx differ diff --git a/media/R25999/output_dir/动态硬件和固件项.docx b/media/R25999/output_dir/动态硬件和固件项.docx index 0f3e68d..1601667 100644 Binary files a/media/R25999/output_dir/动态硬件和固件项.docx and b/media/R25999/output_dir/动态硬件和固件项.docx differ diff --git a/media/R25999/output_dir/动态软件项.docx b/media/R25999/output_dir/动态软件项.docx index cb6d7ea..3216a22 100644 Binary files a/media/R25999/output_dir/动态软件项.docx and b/media/R25999/output_dir/动态软件项.docx differ diff --git a/media/R25999/output_dir/反向需求规格追踪表.docx b/media/R25999/output_dir/反向需求规格追踪表.docx index ec9ad8c..69c6f14 100644 Binary files a/media/R25999/output_dir/反向需求规格追踪表.docx and b/media/R25999/output_dir/反向需求规格追踪表.docx differ diff --git a/media/R25999/output_dir/技术依据文件.docx b/media/R25999/output_dir/技术依据文件.docx index a10fa87..f94e785 100644 Binary files a/media/R25999/output_dir/技术依据文件.docx and b/media/R25999/output_dir/技术依据文件.docx differ diff --git a/media/R25999/output_dir/标准依据文件.docx b/media/R25999/output_dir/标准依据文件.docx index e25b1f5..3043c56 100644 Binary files a/media/R25999/output_dir/标准依据文件.docx and b/media/R25999/output_dir/标准依据文件.docx differ diff --git a/media/R25999/output_dir/测评对象.docx b/media/R25999/output_dir/测评对象.docx index ded038d..05cbc99 100644 Binary files a/media/R25999/output_dir/测评对象.docx and b/media/R25999/output_dir/测评对象.docx differ diff --git a/media/R25999/output_dir/测评数据.docx b/media/R25999/output_dir/测评数据.docx index 62adb99..09a4de0 100644 Binary files a/media/R25999/output_dir/测评数据.docx and b/media/R25999/output_dir/测评数据.docx differ diff --git a/media/R25999/output_dir/测评时间和地点.docx b/media/R25999/output_dir/测评时间和地点.docx index 599b89a..e9bf9d9 100644 Binary files a/media/R25999/output_dir/测评时间和地点.docx and b/media/R25999/output_dir/测评时间和地点.docx differ diff --git a/media/R25999/output_dir/测评条件保障.docx b/media/R25999/output_dir/测评条件保障.docx index e9629f5..142a03c 100644 Binary files a/media/R25999/output_dir/测评条件保障.docx and b/media/R25999/output_dir/测评条件保障.docx differ diff --git a/media/R25999/output_dir/测评组织及任务分工.docx b/media/R25999/output_dir/测评组织及任务分工.docx index 91f41fb..12d862a 100644 Binary files a/media/R25999/output_dir/测评组织及任务分工.docx and b/media/R25999/output_dir/测评组织及任务分工.docx differ diff --git a/media/R25999/output_dir/测试内容充分性及测试方法有效性分析.docx b/media/R25999/output_dir/测试内容充分性及测试方法有效性分析.docx index 2628d65..eb998a9 100644 Binary files a/media/R25999/output_dir/测试内容充分性及测试方法有效性分析.docx and b/media/R25999/output_dir/测试内容充分性及测试方法有效性分析.docx differ diff --git a/media/R25999/output_dir/测试策略.docx b/media/R25999/output_dir/测试策略.docx index 744c58e..8131483 100644 Binary files a/media/R25999/output_dir/测试策略.docx and b/media/R25999/output_dir/测试策略.docx differ diff --git a/media/R25999/output_dir/测试级别和测试类型.docx b/media/R25999/output_dir/测试级别和测试类型.docx index 3d7d622..28234ab 100644 Binary files a/media/R25999/output_dir/测试级别和测试类型.docx and b/media/R25999/output_dir/测试级别和测试类型.docx differ diff --git a/media/R25999/output_dir/测试项及方法.docx b/media/R25999/output_dir/测试项及方法.docx index 3f2da12..368f99e 100644 Binary files a/media/R25999/output_dir/测试项及方法.docx and b/media/R25999/output_dir/测试项及方法.docx differ diff --git a/media/R25999/output_dir/环境差异性分析.docx b/media/R25999/output_dir/环境差异性分析.docx index 854fa04..3e59dfc 100644 Binary files a/media/R25999/output_dir/环境差异性分析.docx and b/media/R25999/output_dir/环境差异性分析.docx differ diff --git a/media/R25999/output_dir/研制总要求追踪表.docx b/media/R25999/output_dir/研制总要求追踪表.docx index d9ef678..0e17dd5 100644 Binary files a/media/R25999/output_dir/研制总要求追踪表.docx and b/media/R25999/output_dir/研制总要求追踪表.docx differ diff --git a/media/R25999/output_dir/缩略语.docx b/media/R25999/output_dir/缩略语.docx index 76a6259..dfdfecf 100644 Binary files a/media/R25999/output_dir/缩略语.docx and b/media/R25999/output_dir/缩略语.docx differ diff --git a/media/R25999/output_dir/联系人和方式.docx b/media/R25999/output_dir/联系人和方式.docx index f421e6d..f1c6ff5 100644 Binary files a/media/R25999/output_dir/联系人和方式.docx and b/media/R25999/output_dir/联系人和方式.docx differ diff --git a/media/R25999/output_dir/被测软件基本信息.docx b/media/R25999/output_dir/被测软件基本信息.docx index d8899ce..1af1942 100644 Binary files a/media/R25999/output_dir/被测软件基本信息.docx and b/media/R25999/output_dir/被测软件基本信息.docx differ diff --git a/media/R25999/output_dir/被测软件接口.docx b/media/R25999/output_dir/被测软件接口.docx index d1bdc59..73bf452 100644 Binary files a/media/R25999/output_dir/被测软件接口.docx and b/media/R25999/output_dir/被测软件接口.docx differ diff --git a/media/R25999/output_dir/需求规格说明追踪表.docx b/media/R25999/output_dir/需求规格说明追踪表.docx index 8ff42f8..697d89d 100644 Binary files a/media/R25999/output_dir/需求规格说明追踪表.docx and b/media/R25999/output_dir/需求规格说明追踪表.docx differ diff --git a/media/R25999/output_dir/静态测试环境说明.docx b/media/R25999/output_dir/静态测试环境说明.docx index 10d37b7..e89fe15 100644 Binary files a/media/R25999/output_dir/静态测试环境说明.docx and b/media/R25999/output_dir/静态测试环境说明.docx differ diff --git a/media/R25999/output_dir/静态硬件和固件项.docx b/media/R25999/output_dir/静态硬件和固件项.docx index fba3dc0..7468f27 100644 Binary files a/media/R25999/output_dir/静态硬件和固件项.docx and b/media/R25999/output_dir/静态硬件和固件项.docx differ diff --git a/media/R25999/output_dir/静态软件项.docx b/media/R25999/output_dir/静态软件项.docx index 4589817..b940213 100644 Binary files a/media/R25999/output_dir/静态软件项.docx and b/media/R25999/output_dir/静态软件项.docx differ diff --git a/media/R25999/output_dir/顶层技术文件.docx b/media/R25999/output_dir/顶层技术文件.docx index 912bd54..c46df6e 100644 Binary files a/media/R25999/output_dir/顶层技术文件.docx and b/media/R25999/output_dir/顶层技术文件.docx differ diff --git a/media/R25999/temp/测评大纲.docx b/media/R25999/temp/测评大纲.docx index eb1fc4b..f32a403 100644 Binary files a/media/R25999/temp/测评大纲.docx and b/media/R25999/temp/测评大纲.docx differ diff --git a/media/R25999/temp/测评报告.docx b/media/R25999/temp/测评报告.docx index 8004c18..27f368d 100644 Binary files a/media/R25999/temp/测评报告.docx and b/media/R25999/temp/测评报告.docx differ diff --git a/media/R2601/final_seitai/测评大纲.docx b/media/R2601/final_seitai/测评大纲.docx index ab311ef..cdf234d 100644 Binary files a/media/R2601/final_seitai/测评大纲.docx and b/media/R2601/final_seitai/测评大纲.docx differ diff --git a/media/R2601/final_seitai/测评报告.docx b/media/R2601/final_seitai/测评报告.docx new file mode 100644 index 0000000..7a915cf Binary files /dev/null and b/media/R2601/final_seitai/测评报告.docx differ diff --git a/media/R2601/final_seitai/测试记录.docx b/media/R2601/final_seitai/测试记录.docx index f77c539..07bc7a7 100644 Binary files a/media/R2601/final_seitai/测试记录.docx and b/media/R2601/final_seitai/测试记录.docx differ diff --git a/media/R2601/final_seitai/测试说明.docx b/media/R2601/final_seitai/测试说明.docx index e8efea7..e0ae7ce 100644 Binary files a/media/R2601/final_seitai/测试说明.docx and b/media/R2601/final_seitai/测试说明.docx differ diff --git a/media/R2601/final_seitai/第二轮回归测试记录.docx b/media/R2601/final_seitai/第二轮回归测试记录.docx new file mode 100644 index 0000000..afcd15e Binary files /dev/null and b/media/R2601/final_seitai/第二轮回归测试记录.docx differ diff --git a/media/R2601/final_seitai/第二轮回归测试说明.docx b/media/R2601/final_seitai/第二轮回归测试说明.docx new file mode 100644 index 0000000..ebf6aa8 Binary files /dev/null and b/media/R2601/final_seitai/第二轮回归测试说明.docx differ diff --git a/media/R2601/final_seitai/问题单.docx b/media/R2601/final_seitai/问题单.docx index 1fe0e0e..7255f7d 100644 Binary files a/media/R2601/final_seitai/问题单.docx and b/media/R2601/final_seitai/问题单.docx differ diff --git a/media/R2601/form_template/bg/temporary/研总需归追踪_temp.docx b/media/R2601/form_template/bg/temporary/研总需归追踪_temp.docx index 285208c..ca3a258 100644 Binary files a/media/R2601/form_template/bg/temporary/研总需归追踪_temp.docx and b/media/R2601/form_template/bg/temporary/研总需归追踪_temp.docx differ diff --git a/media/R2601/form_template/bg/被测软件基本信息.docx b/media/R2601/form_template/bg/被测软件基本信息.docx index 7dc9a6a..21f438f 100644 Binary files a/media/R2601/form_template/bg/被测软件基本信息.docx and b/media/R2601/form_template/bg/被测软件基本信息.docx differ diff --git a/media/R2601/form_template/dg/被测软件基本信息.docx b/media/R2601/form_template/dg/被测软件基本信息.docx index f7cbc5f..72294d8 100644 Binary files a/media/R2601/form_template/dg/被测软件基本信息.docx and b/media/R2601/form_template/dg/被测软件基本信息.docx differ diff --git a/media/R2601/form_template/hsm/temporary/第二轮用例追踪_temp.docx b/media/R2601/form_template/hsm/temporary/第二轮用例追踪_temp.docx index a8f3e8f..0028cde 100644 Binary files a/media/R2601/form_template/hsm/temporary/第二轮用例追踪_temp.docx and b/media/R2601/form_template/hsm/temporary/第二轮用例追踪_temp.docx differ diff --git a/media/R2601/form_template/products/测评大纲.docx b/media/R2601/form_template/products/测评大纲.docx index 002aaef..48a3b77 100644 Binary files a/media/R2601/form_template/products/测评大纲.docx and b/media/R2601/form_template/products/测评大纲.docx differ diff --git a/media/R2601/form_template/sm/temporary/说明追踪_temp.docx b/media/R2601/form_template/sm/temporary/说明追踪_temp.docx index e9b25d6..62f50d3 100644 Binary files a/media/R2601/form_template/sm/temporary/说明追踪_temp.docx and b/media/R2601/form_template/sm/temporary/说明追踪_temp.docx differ diff --git a/media/R2601/output_dir/bg/总体结论.docx b/media/R2601/output_dir/bg/总体结论.docx index 8f6d316..6ce00dc 100644 Binary files a/media/R2601/output_dir/bg/总体结论.docx and b/media/R2601/output_dir/bg/总体结论.docx differ diff --git a/media/R2601/output_dir/bg/技术依据文件.docx b/media/R2601/output_dir/bg/技术依据文件.docx index 089799f..7a521be 100644 Binary files a/media/R2601/output_dir/bg/技术依据文件.docx and b/media/R2601/output_dir/bg/技术依据文件.docx differ diff --git a/media/R2601/output_dir/bg/摸底清单.docx b/media/R2601/output_dir/bg/摸底清单.docx index 6841fcf..d1cdfb6 100644 Binary files a/media/R2601/output_dir/bg/摸底清单.docx and b/media/R2601/output_dir/bg/摸底清单.docx differ diff --git a/media/R2601/output_dir/bg/测评完成情况.docx b/media/R2601/output_dir/bg/测评完成情况.docx index a4bdc7f..8433378 100644 Binary files a/media/R2601/output_dir/bg/测评完成情况.docx and b/media/R2601/output_dir/bg/测评完成情况.docx differ diff --git a/media/R2601/output_dir/bg/测评时间和地点.docx b/media/R2601/output_dir/bg/测评时间和地点.docx index 0874ab6..607efc8 100644 Binary files a/media/R2601/output_dir/bg/测评时间和地点.docx and b/media/R2601/output_dir/bg/测评时间和地点.docx differ diff --git a/media/R2601/output_dir/bg/测试内容和结果_第一轮次.docx b/media/R2601/output_dir/bg/测试内容和结果_第一轮次.docx index 92567b1..17476b2 100644 Binary files a/media/R2601/output_dir/bg/测试内容和结果_第一轮次.docx and b/media/R2601/output_dir/bg/测试内容和结果_第一轮次.docx differ diff --git a/media/R2601/output_dir/bg/测试内容和结果_第二轮次.docx b/media/R2601/output_dir/bg/测试内容和结果_第二轮次.docx index 7ff43ca..4df2f95 100644 Binary files a/media/R2601/output_dir/bg/测试内容和结果_第二轮次.docx and b/media/R2601/output_dir/bg/测试内容和结果_第二轮次.docx differ diff --git a/media/R2601/output_dir/bg/测试有效性充分性说明.docx b/media/R2601/output_dir/bg/测试有效性充分性说明.docx index 29673b8..f81e939 100644 Binary files a/media/R2601/output_dir/bg/测试有效性充分性说明.docx and b/media/R2601/output_dir/bg/测试有效性充分性说明.docx differ diff --git a/media/R2601/output_dir/bg/研总需归追踪.docx b/media/R2601/output_dir/bg/研总需归追踪.docx index 868db32..dcf3b86 100644 Binary files a/media/R2601/output_dir/bg/研总需归追踪.docx and b/media/R2601/output_dir/bg/研总需归追踪.docx differ diff --git a/media/R2601/output_dir/bg/综述.docx b/media/R2601/output_dir/bg/综述.docx index 39518e3..cfc0511 100644 Binary files a/media/R2601/output_dir/bg/综述.docx and b/media/R2601/output_dir/bg/综述.docx differ diff --git a/media/R2601/output_dir/bg/被测软件基本信息.docx b/media/R2601/output_dir/bg/被测软件基本信息.docx index 0b9e261..991c03c 100644 Binary files a/media/R2601/output_dir/bg/被测软件基本信息.docx and b/media/R2601/output_dir/bg/被测软件基本信息.docx differ diff --git a/media/R2601/output_dir/bg/软件质量评价.docx b/media/R2601/output_dir/bg/软件质量评价.docx index 1744364..eccbef6 100644 Binary files a/media/R2601/output_dir/bg/软件质量评价.docx and b/media/R2601/output_dir/bg/软件质量评价.docx differ diff --git a/media/R2601/output_dir/bg/软件问题统计.docx b/media/R2601/output_dir/bg/软件问题统计.docx index 36d537c..a866fca 100644 Binary files a/media/R2601/output_dir/bg/软件问题统计.docx and b/media/R2601/output_dir/bg/软件问题统计.docx differ diff --git a/media/R2601/output_dir/bg/问题汇总表.docx b/media/R2601/output_dir/bg/问题汇总表.docx index db49af9..11aa177 100644 Binary files a/media/R2601/output_dir/bg/问题汇总表.docx and b/media/R2601/output_dir/bg/问题汇总表.docx differ diff --git a/media/R2601/output_dir/bg/需求指标符合性情况.docx b/media/R2601/output_dir/bg/需求指标符合性情况.docx index 6a6c4d7..c547e3a 100644 Binary files a/media/R2601/output_dir/bg/需求指标符合性情况.docx and b/media/R2601/output_dir/bg/需求指标符合性情况.docx differ diff --git a/media/R2601/output_dir/hjl/第二轮测试用例记录.docx b/media/R2601/output_dir/hjl/第二轮测试用例记录.docx new file mode 100644 index 0000000..e46e49f Binary files /dev/null and b/media/R2601/output_dir/hjl/第二轮测试用例记录.docx differ diff --git a/media/R2601/output_dir/hjl/第二轮被测软件基本信息.docx b/media/R2601/output_dir/hjl/第二轮被测软件基本信息.docx new file mode 100644 index 0000000..a235a31 Binary files /dev/null and b/media/R2601/output_dir/hjl/第二轮被测软件基本信息.docx differ diff --git a/media/R2601/output_dir/hsm/第二轮回归测试用例概述.docx b/media/R2601/output_dir/hsm/第二轮回归测试用例概述.docx index 8c28789..29e386f 100644 Binary files a/media/R2601/output_dir/hsm/第二轮回归测试用例概述.docx and b/media/R2601/output_dir/hsm/第二轮回归测试用例概述.docx differ diff --git a/media/R2601/output_dir/hsm/第二轮回归测试需求.docx b/media/R2601/output_dir/hsm/第二轮回归测试需求.docx index cc26c3c..80aa0ac 100644 Binary files a/media/R2601/output_dir/hsm/第二轮回归测试需求.docx and b/media/R2601/output_dir/hsm/第二轮回归测试需求.docx differ diff --git a/media/R2601/output_dir/hsm/第二轮技术依据文件.docx b/media/R2601/output_dir/hsm/第二轮技术依据文件.docx index e04d85c..1136b61 100644 Binary files a/media/R2601/output_dir/hsm/第二轮技术依据文件.docx and b/media/R2601/output_dir/hsm/第二轮技术依据文件.docx differ diff --git a/media/R2601/output_dir/hsm/第二轮文档概述.docx b/media/R2601/output_dir/hsm/第二轮文档概述.docx index 47b1c77..4528bbc 100644 Binary files a/media/R2601/output_dir/hsm/第二轮文档概述.docx and b/media/R2601/output_dir/hsm/第二轮文档概述.docx differ diff --git a/media/R2601/output_dir/hsm/第二轮测试用例.docx b/media/R2601/output_dir/hsm/第二轮测试用例.docx index 8011fc3..ae0f922 100644 Binary files a/media/R2601/output_dir/hsm/第二轮测试用例.docx and b/media/R2601/output_dir/hsm/第二轮测试用例.docx differ diff --git a/media/R2601/output_dir/hsm/第二轮用例追踪.docx b/media/R2601/output_dir/hsm/第二轮用例追踪.docx index 1545502..12fc0b8 100644 Binary files a/media/R2601/output_dir/hsm/第二轮用例追踪.docx and b/media/R2601/output_dir/hsm/第二轮用例追踪.docx differ diff --git a/media/R2601/output_dir/hsm/第二轮被测软件基本信息.docx b/media/R2601/output_dir/hsm/第二轮被测软件基本信息.docx index e2d3f2e..99fd729 100644 Binary files a/media/R2601/output_dir/hsm/第二轮被测软件基本信息.docx and b/media/R2601/output_dir/hsm/第二轮被测软件基本信息.docx differ diff --git a/media/R2601/output_dir/hsm/第二轮软件更改部分.docx b/media/R2601/output_dir/hsm/第二轮软件更改部分.docx index ae066b7..1893de7 100644 Binary files a/media/R2601/output_dir/hsm/第二轮软件更改部分.docx and b/media/R2601/output_dir/hsm/第二轮软件更改部分.docx differ diff --git a/media/R2601/output_dir/jl/测试用例记录.docx b/media/R2601/output_dir/jl/测试用例记录.docx index d83bc67..5dc0b88 100644 Binary files a/media/R2601/output_dir/jl/测试用例记录.docx and b/media/R2601/output_dir/jl/测试用例记录.docx differ diff --git a/media/R2601/output_dir/sm/技术依据文件.docx b/media/R2601/output_dir/sm/技术依据文件.docx index c6eeca5..d877af7 100644 Binary files a/media/R2601/output_dir/sm/技术依据文件.docx and b/media/R2601/output_dir/sm/技术依据文件.docx differ diff --git a/media/R2601/output_dir/sm/测试用例.docx b/media/R2601/output_dir/sm/测试用例.docx index b2f34ab..e9b9c3d 100644 Binary files a/media/R2601/output_dir/sm/测试用例.docx and b/media/R2601/output_dir/sm/测试用例.docx differ diff --git a/media/R2601/output_dir/sm/用例说明.docx b/media/R2601/output_dir/sm/用例说明.docx index 9a671ca..fae7f71 100644 Binary files a/media/R2601/output_dir/sm/用例说明.docx and b/media/R2601/output_dir/sm/用例说明.docx differ diff --git a/media/R2601/output_dir/sm/说明追踪.docx b/media/R2601/output_dir/sm/说明追踪.docx index 1bbc0dc..5bb6671 100644 Binary files a/media/R2601/output_dir/sm/说明追踪.docx and b/media/R2601/output_dir/sm/说明追踪.docx differ diff --git a/media/R2601/output_dir/wtd/问题详情表.docx b/media/R2601/output_dir/wtd/问题详情表.docx index fd922a7..bcc83d3 100644 Binary files a/media/R2601/output_dir/wtd/问题详情表.docx and b/media/R2601/output_dir/wtd/问题详情表.docx differ diff --git a/media/R2601/output_dir/主要功能和性能指标.docx b/media/R2601/output_dir/主要功能和性能指标.docx index 6498b03..fd0755b 100644 Binary files a/media/R2601/output_dir/主要功能和性能指标.docx and b/media/R2601/output_dir/主要功能和性能指标.docx differ diff --git a/media/R2601/output_dir/代码质量度量分析表.docx b/media/R2601/output_dir/代码质量度量分析表.docx index 2a9e03f..c1d623c 100644 Binary files a/media/R2601/output_dir/代码质量度量分析表.docx and b/media/R2601/output_dir/代码质量度量分析表.docx differ diff --git a/media/R2601/output_dir/动态测试环境说明.docx b/media/R2601/output_dir/动态测试环境说明.docx index f7731c9..4a5a539 100644 Binary files a/media/R2601/output_dir/动态测试环境说明.docx and b/media/R2601/output_dir/动态测试环境说明.docx differ diff --git a/media/R2601/output_dir/动态硬件和固件项.docx b/media/R2601/output_dir/动态硬件和固件项.docx index 32bbbee..d2029f1 100644 Binary files a/media/R2601/output_dir/动态硬件和固件项.docx and b/media/R2601/output_dir/动态硬件和固件项.docx differ diff --git a/media/R2601/output_dir/动态软件项.docx b/media/R2601/output_dir/动态软件项.docx index 0fc405f..569df48 100644 Binary files a/media/R2601/output_dir/动态软件项.docx and b/media/R2601/output_dir/动态软件项.docx differ diff --git a/media/R2601/output_dir/反向需求规格追踪表.docx b/media/R2601/output_dir/反向需求规格追踪表.docx index 1bbcdc7..bec180a 100644 Binary files a/media/R2601/output_dir/反向需求规格追踪表.docx and b/media/R2601/output_dir/反向需求规格追踪表.docx differ diff --git a/media/R2601/output_dir/技术依据文件.docx b/media/R2601/output_dir/技术依据文件.docx index 53c73e9..15c2620 100644 Binary files a/media/R2601/output_dir/技术依据文件.docx and b/media/R2601/output_dir/技术依据文件.docx differ diff --git a/media/R2601/output_dir/标准依据文件.docx b/media/R2601/output_dir/标准依据文件.docx index 0b89363..dd4ebcb 100644 Binary files a/media/R2601/output_dir/标准依据文件.docx and b/media/R2601/output_dir/标准依据文件.docx differ diff --git a/media/R2601/output_dir/测评对象.docx b/media/R2601/output_dir/测评对象.docx index 9ad5fa5..3c34091 100644 Binary files a/media/R2601/output_dir/测评对象.docx and b/media/R2601/output_dir/测评对象.docx differ diff --git a/media/R2601/output_dir/测评数据.docx b/media/R2601/output_dir/测评数据.docx index 4fa4424..3c78105 100644 Binary files a/media/R2601/output_dir/测评数据.docx and b/media/R2601/output_dir/测评数据.docx differ diff --git a/media/R2601/output_dir/测评时间和地点.docx b/media/R2601/output_dir/测评时间和地点.docx index 50b2c5c..0a44efc 100644 Binary files a/media/R2601/output_dir/测评时间和地点.docx and b/media/R2601/output_dir/测评时间和地点.docx differ diff --git a/media/R2601/output_dir/测评条件保障.docx b/media/R2601/output_dir/测评条件保障.docx index 526ee0b..9c2b037 100644 Binary files a/media/R2601/output_dir/测评条件保障.docx and b/media/R2601/output_dir/测评条件保障.docx differ diff --git a/media/R2601/output_dir/测评组织及任务分工.docx b/media/R2601/output_dir/测评组织及任务分工.docx index 19db17e..9fae8b4 100644 Binary files a/media/R2601/output_dir/测评组织及任务分工.docx and b/media/R2601/output_dir/测评组织及任务分工.docx differ diff --git a/media/R2601/output_dir/测试内容充分性及测试方法有效性分析.docx b/media/R2601/output_dir/测试内容充分性及测试方法有效性分析.docx index e6a62f7..18c4d2c 100644 Binary files a/media/R2601/output_dir/测试内容充分性及测试方法有效性分析.docx and b/media/R2601/output_dir/测试内容充分性及测试方法有效性分析.docx differ diff --git a/media/R2601/output_dir/测试策略.docx b/media/R2601/output_dir/测试策略.docx index e06bd70..2960680 100644 Binary files a/media/R2601/output_dir/测试策略.docx and b/media/R2601/output_dir/测试策略.docx differ diff --git a/media/R2601/output_dir/测试级别和测试类型.docx b/media/R2601/output_dir/测试级别和测试类型.docx index adff568..8f92f51 100644 Binary files a/media/R2601/output_dir/测试级别和测试类型.docx and b/media/R2601/output_dir/测试级别和测试类型.docx differ diff --git a/media/R2601/output_dir/测试项及方法.docx b/media/R2601/output_dir/测试项及方法.docx index 9640607..ff9364d 100644 Binary files a/media/R2601/output_dir/测试项及方法.docx and b/media/R2601/output_dir/测试项及方法.docx differ diff --git a/media/R2601/output_dir/环境差异性分析.docx b/media/R2601/output_dir/环境差异性分析.docx index c675aba..5f8f72f 100644 Binary files a/media/R2601/output_dir/环境差异性分析.docx and b/media/R2601/output_dir/环境差异性分析.docx differ diff --git a/media/R2601/output_dir/研制总要求追踪表.docx b/media/R2601/output_dir/研制总要求追踪表.docx index d3df505..5f965b8 100644 Binary files a/media/R2601/output_dir/研制总要求追踪表.docx and b/media/R2601/output_dir/研制总要求追踪表.docx differ diff --git a/media/R2601/output_dir/缩略语.docx b/media/R2601/output_dir/缩略语.docx index 686e17e..52119ae 100644 Binary files a/media/R2601/output_dir/缩略语.docx and b/media/R2601/output_dir/缩略语.docx differ diff --git a/media/R2601/output_dir/联系人和方式.docx b/media/R2601/output_dir/联系人和方式.docx index 8c577c3..10c8f07 100644 Binary files a/media/R2601/output_dir/联系人和方式.docx and b/media/R2601/output_dir/联系人和方式.docx differ diff --git a/media/R2601/output_dir/被测软件基本信息.docx b/media/R2601/output_dir/被测软件基本信息.docx index 5cce705..b396c41 100644 Binary files a/media/R2601/output_dir/被测软件基本信息.docx and b/media/R2601/output_dir/被测软件基本信息.docx differ diff --git a/media/R2601/output_dir/被测软件接口.docx b/media/R2601/output_dir/被测软件接口.docx index f808f52..20025ba 100644 Binary files a/media/R2601/output_dir/被测软件接口.docx and b/media/R2601/output_dir/被测软件接口.docx differ diff --git a/media/R2601/output_dir/需求规格说明追踪表.docx b/media/R2601/output_dir/需求规格说明追踪表.docx index 8bfe0be..e40e417 100644 Binary files a/media/R2601/output_dir/需求规格说明追踪表.docx and b/media/R2601/output_dir/需求规格说明追踪表.docx differ diff --git a/media/R2601/output_dir/静态测试环境说明.docx b/media/R2601/output_dir/静态测试环境说明.docx index f7f75f9..41b915f 100644 Binary files a/media/R2601/output_dir/静态测试环境说明.docx and b/media/R2601/output_dir/静态测试环境说明.docx differ diff --git a/media/R2601/output_dir/静态硬件和固件项.docx b/media/R2601/output_dir/静态硬件和固件项.docx index fcad79f..9485851 100644 Binary files a/media/R2601/output_dir/静态硬件和固件项.docx and b/media/R2601/output_dir/静态硬件和固件项.docx differ diff --git a/media/R2601/output_dir/静态软件项.docx b/media/R2601/output_dir/静态软件项.docx index 95c3811..887d8a7 100644 Binary files a/media/R2601/output_dir/静态软件项.docx and b/media/R2601/output_dir/静态软件项.docx differ diff --git a/media/R2601/output_dir/顶层技术文件.docx b/media/R2601/output_dir/顶层技术文件.docx index e632983..494f9e7 100644 Binary files a/media/R2601/output_dir/顶层技术文件.docx and b/media/R2601/output_dir/顶层技术文件.docx differ diff --git a/media/R2601/reuse/主要功能和性能指标.docx b/media/R2601/reuse/主要功能和性能指标.docx deleted file mode 100644 index 1eb922e..0000000 Binary files a/media/R2601/reuse/主要功能和性能指标.docx and /dev/null differ diff --git a/media/R2601/reuse/代码质量度量分析表.docx b/media/R2601/reuse/代码质量度量分析表.docx deleted file mode 100644 index ea6c61d..0000000 Binary files a/media/R2601/reuse/代码质量度量分析表.docx and /dev/null differ diff --git a/media/R2601/reuse/动态测试环境说明.docx b/media/R2601/reuse/动态测试环境说明.docx deleted file mode 100644 index a7c7e45..0000000 Binary files a/media/R2601/reuse/动态测试环境说明.docx and /dev/null differ diff --git a/media/R2601/reuse/动态硬件和固件项.docx b/media/R2601/reuse/动态硬件和固件项.docx deleted file mode 100644 index 9150eba..0000000 Binary files a/media/R2601/reuse/动态硬件和固件项.docx and /dev/null differ diff --git a/media/R2601/reuse/动态软件项.docx b/media/R2601/reuse/动态软件项.docx deleted file mode 100644 index 2d2bab7..0000000 Binary files a/media/R2601/reuse/动态软件项.docx and /dev/null differ diff --git a/media/R2601/reuse/反向需求规格追踪表.docx b/media/R2601/reuse/反向需求规格追踪表.docx deleted file mode 100644 index 9e720cc..0000000 Binary files a/media/R2601/reuse/反向需求规格追踪表.docx and /dev/null differ diff --git a/media/R2601/reuse/技术依据文件.docx b/media/R2601/reuse/技术依据文件.docx deleted file mode 100644 index 7ef0596..0000000 Binary files a/media/R2601/reuse/技术依据文件.docx and /dev/null differ diff --git a/media/R2601/reuse/标准依据文件.docx b/media/R2601/reuse/标准依据文件.docx deleted file mode 100644 index 395c111..0000000 Binary files a/media/R2601/reuse/标准依据文件.docx and /dev/null differ diff --git a/media/R2601/reuse/测评对象.docx b/media/R2601/reuse/测评对象.docx deleted file mode 100644 index 429855f..0000000 Binary files a/media/R2601/reuse/测评对象.docx and /dev/null differ diff --git a/media/R2601/reuse/测评数据.docx b/media/R2601/reuse/测评数据.docx deleted file mode 100644 index b8246d7..0000000 Binary files a/media/R2601/reuse/测评数据.docx and /dev/null differ diff --git a/media/R2601/reuse/测评时间和地点.docx b/media/R2601/reuse/测评时间和地点.docx deleted file mode 100644 index 3b3f410..0000000 Binary files a/media/R2601/reuse/测评时间和地点.docx and /dev/null differ diff --git a/media/R2601/reuse/测评条件保障.docx b/media/R2601/reuse/测评条件保障.docx deleted file mode 100644 index 078e32f..0000000 Binary files a/media/R2601/reuse/测评条件保障.docx and /dev/null differ diff --git a/media/R2601/reuse/测评组织及任务分工.docx b/media/R2601/reuse/测评组织及任务分工.docx deleted file mode 100644 index 93f4929..0000000 Binary files a/media/R2601/reuse/测评组织及任务分工.docx and /dev/null differ diff --git a/media/R2601/reuse/测试内容充分性及测试方法有效性分析.docx b/media/R2601/reuse/测试内容充分性及测试方法有效性分析.docx deleted file mode 100644 index e0b09c0..0000000 Binary files a/media/R2601/reuse/测试内容充分性及测试方法有效性分析.docx and /dev/null differ diff --git a/media/R2601/reuse/测试策略.docx b/media/R2601/reuse/测试策略.docx deleted file mode 100644 index 359a0b2..0000000 Binary files a/media/R2601/reuse/测试策略.docx and /dev/null differ diff --git a/media/R2601/reuse/测试级别和测试类型.docx b/media/R2601/reuse/测试级别和测试类型.docx deleted file mode 100644 index c1a459e..0000000 Binary files a/media/R2601/reuse/测试级别和测试类型.docx and /dev/null differ diff --git a/media/R2601/reuse/测试项及方法.docx b/media/R2601/reuse/测试项及方法.docx deleted file mode 100644 index eb555bc..0000000 Binary files a/media/R2601/reuse/测试项及方法.docx and /dev/null differ diff --git a/media/R2601/reuse/环境差异性分析.docx b/media/R2601/reuse/环境差异性分析.docx deleted file mode 100644 index bd640c8..0000000 Binary files a/media/R2601/reuse/环境差异性分析.docx and /dev/null differ diff --git a/media/R2601/reuse/研制总要求追踪表.docx b/media/R2601/reuse/研制总要求追踪表.docx deleted file mode 100644 index 398a7ed..0000000 Binary files a/media/R2601/reuse/研制总要求追踪表.docx and /dev/null differ diff --git a/media/R2601/reuse/联系人和方式.docx b/media/R2601/reuse/联系人和方式.docx deleted file mode 100644 index b00175a..0000000 Binary files a/media/R2601/reuse/联系人和方式.docx and /dev/null differ diff --git a/media/R2601/reuse/被测软件基本信息.docx b/media/R2601/reuse/被测软件基本信息.docx deleted file mode 100644 index 59bed24..0000000 Binary files a/media/R2601/reuse/被测软件基本信息.docx and /dev/null differ diff --git a/media/R2601/reuse/被测软件接口.docx b/media/R2601/reuse/被测软件接口.docx deleted file mode 100644 index d5903c5..0000000 Binary files a/media/R2601/reuse/被测软件接口.docx and /dev/null differ diff --git a/media/R2601/reuse/需求规格说明追踪表.docx b/media/R2601/reuse/需求规格说明追踪表.docx deleted file mode 100644 index 048aea5..0000000 Binary files a/media/R2601/reuse/需求规格说明追踪表.docx and /dev/null differ diff --git a/media/R2601/reuse/静态测试环境说明.docx b/media/R2601/reuse/静态测试环境说明.docx deleted file mode 100644 index e5461fb..0000000 Binary files a/media/R2601/reuse/静态测试环境说明.docx and /dev/null differ diff --git a/media/R2601/reuse/静态硬件和固件项.docx b/media/R2601/reuse/静态硬件和固件项.docx deleted file mode 100644 index 8f76ee3..0000000 Binary files a/media/R2601/reuse/静态硬件和固件项.docx and /dev/null differ diff --git a/media/R2601/reuse/静态软件项.docx b/media/R2601/reuse/静态软件项.docx deleted file mode 100644 index d9e7e2c..0000000 Binary files a/media/R2601/reuse/静态软件项.docx and /dev/null differ diff --git a/media/R2601/reuse/顶层技术文件.docx b/media/R2601/reuse/顶层技术文件.docx deleted file mode 100644 index ee9a2db..0000000 Binary files a/media/R2601/reuse/顶层技术文件.docx and /dev/null differ diff --git a/media/R2601/temp/测评大纲.docx b/media/R2601/temp/测评大纲.docx index 084fd9e..a3465ac 100644 Binary files a/media/R2601/temp/测评大纲.docx and b/media/R2601/temp/测评大纲.docx differ diff --git a/media/R2601/temp/测评报告.docx b/media/R2601/temp/测评报告.docx new file mode 100644 index 0000000..e33e95c Binary files /dev/null and b/media/R2601/temp/测评报告.docx differ diff --git a/media/R2601/temp/测试记录.docx b/media/R2601/temp/测试记录.docx index 7d97823..caab596 100644 Binary files a/media/R2601/temp/测试记录.docx and b/media/R2601/temp/测试记录.docx differ diff --git a/media/R2601/temp/测试说明.docx b/media/R2601/temp/测试说明.docx index 7742e21..a1b6025 100644 Binary files a/media/R2601/temp/测试说明.docx and b/media/R2601/temp/测试说明.docx differ diff --git a/media/R2601/temp/第二轮回归测试记录.docx b/media/R2601/temp/第二轮回归测试记录.docx new file mode 100644 index 0000000..b7364c1 Binary files /dev/null and b/media/R2601/temp/第二轮回归测试记录.docx differ diff --git a/media/R2601/temp/第二轮回归测试说明.docx b/media/R2601/temp/第二轮回归测试说明.docx new file mode 100644 index 0000000..ab117ba Binary files /dev/null and b/media/R2601/temp/第二轮回归测试说明.docx differ diff --git a/media/R2601/temp/问题单.docx b/media/R2601/temp/问题单.docx index 12e8982..46cc5f8 100644 Binary files a/media/R2601/temp/问题单.docx and b/media/R2601/temp/问题单.docx differ diff --git a/pyproject.toml b/pyproject.toml index 530df2a..cf688ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ dependencies = [ "ua-parser-builtins>=202601", "user-agents>=2.2.0", "waitress>=3.0.2", - "pydantic>=2.13.0", + "pydantic>=2.13.1", "lxml>=6.0.4", ] diff --git a/uv.lock b/uv.lock index 60e0089..019831f 100644 --- a/uv.lock +++ b/uv.lock @@ -133,7 +133,7 @@ requires-dist = [ { name = "numpy", specifier = "==2.4.4" }, { name = "orjson", specifier = ">=3.11.8" }, { name = "pandas", specifier = ">=3.0.2" }, - { name = "pydantic", specifier = ">=2.13.0" }, + { name = "pydantic", specifier = ">=2.13.1" }, { name = "pyinstaller", specifier = ">=6.19.0" }, { name = "python-docx", specifier = ">=1.2.0" }, { name = "python-ldap", path = "python_ldap-3.4.5-cp313-cp313-win_amd64.whl" }, @@ -919,7 +919,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.13.0" +version = "2.13.1" source = { registry = "https://mirrors.aliyun.com/pypi/simple" } dependencies = [ { name = "annotated-types" }, @@ -927,9 +927,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://mirrors.aliyun.com/pypi/packages/84/6b/69fd5c7194b21ebde0f8637e2a4ddc766ada29d472bfa6a5ca533d79549a/pydantic-2.13.0.tar.gz", hash = "sha256:b89b575b6e670ebf6e7448c01b41b244f471edd276cd0b6fe02e7e7aca320070" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/f3/6b/1353beb3d1cd5cf61cdec5b6f87a9872399de3bc5cae0b7ce07ff4de2ab0/pydantic-2.13.1.tar.gz", hash = "sha256:a0f829b279ddd1e39291133fe2539d2aa46cc6b150c1706a270ff0879e3774d2" } wheels = [ - { url = "https://mirrors.aliyun.com/pypi/packages/01/d7/c3a52c61f5b7be648e919005820fbac33028c6149994cd64453f49951c17/pydantic-2.13.0-py3-none-any.whl", hash = "sha256:ab0078b90da5f3e2fd2e71e3d9b457ddcb35d0350854fbda93b451e28d56baaf" }, + { url = "https://mirrors.aliyun.com/pypi/packages/81/5a/2225f4c176dbfed0d809e848b50ef08f70e61daa667b7fa14b0d311ae44d/pydantic-2.13.1-py3-none-any.whl", hash = "sha256:9557ecc2806faaf6037f85b1fbd963d01e30511c48085f0d573650fdeaad378a" }, ] [package.optional-dependencies] @@ -939,28 +939,28 @@ email = [ [[package]] name = "pydantic-core" -version = "2.46.0" +version = "2.46.1" source = { registry = "https://mirrors.aliyun.com/pypi/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://mirrors.aliyun.com/pypi/packages/6f/0a/9414cddf82eda3976b14048cc0fa8f5b5d1aecb0b22e1dcd2dbfe0e139b1/pydantic_core-2.46.0.tar.gz", hash = "sha256:82d2498c96be47b47e903e1378d1d0f770097ec56ea953322f39936a7cf34977" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/a1/93/f97a86a7eb28faa1d038af2fd5d6166418b4433659108a4c311b57128b2d/pydantic_core-2.46.1.tar.gz", hash = "sha256:d408153772d9f298098fb5d620f045bdf0f017af0d5cb6e309ef8c205540caa4" } wheels = [ - { url = "https://mirrors.aliyun.com/pypi/packages/df/05/ab3b0742bad1d51822f1af0c4232208408902bdcfc47601f3b812e09e6c2/pydantic_core-2.46.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a05900c37264c070c683c650cbca8f83d7cbb549719e645fcd81a24592eac788" }, - { url = "https://mirrors.aliyun.com/pypi/packages/98/08/30b43d9569d69094a0899a199711c43aa58fce6ce80f6a8f7693673eb995/pydantic_core-2.46.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de8e482fd4f1e3f36c50c6aac46d044462615d8f12cfafc6bebeaa0909eea22" }, - { url = "https://mirrors.aliyun.com/pypi/packages/db/a0/bf9a1ba34537c2ed3872a48195291138fdec8fe26c4009776f00d63cf0c8/pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c525ecf8a4cdf198327b65030a7d081867ad8e60acb01a7214fff95cf9832d47" }, - { url = "https://mirrors.aliyun.com/pypi/packages/71/70/0ba03c20e1e118219fc18c5417b008b7e880f0e3fb38560ec4465984d471/pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f14581aeb12e61542ce73b9bfef2bca5439d65d9ab3efe1a4d8e346b61838f9b" }, - { url = "https://mirrors.aliyun.com/pypi/packages/58/cf/1e320acefbde7fb7158a9e5def55e0adf9a4634636098ce28dc6b978e0d3/pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c108067f2f7e190d0dbd81247d789ec41f9ea50ccd9265a3a46710796ac60530" }, - { url = "https://mirrors.aliyun.com/pypi/packages/df/f5/ea8ba209756abe9eba891bb0ef3772b4c59a894eb9ad86cd5bd0dd4e3e52/pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ac10967e9a7bb1b96697374513f9a1a90a59e2fb41566b5e00ee45392beac59" }, - { url = "https://mirrors.aliyun.com/pypi/packages/e8/f8/5885350203b72e96438eee7f94de0d8f0442f4627237ca8ef75de34db1cd/pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7897078fe8a13b73623c0955dfb2b3d2c9acb7177aac25144758c9e5a5265aaa" }, - { url = "https://mirrors.aliyun.com/pypi/packages/bf/88/5930b0e828e371db5a556dd3189565417ddc3d8316bb001058168aadcf5f/pydantic_core-2.46.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:e69ce405510a419a082a78faed65bb4249cfb51232293cc675645c12f7379bf7" }, - { url = "https://mirrors.aliyun.com/pypi/packages/da/75/63d563d3035a0548e721c38b5b69fd5626fdd51da0f09ff4467503915b82/pydantic_core-2.46.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fd28d13eea0d8cf351dc1fe274b5070cc8e1cca2644381dee5f99de629e77cf3" }, - { url = "https://mirrors.aliyun.com/pypi/packages/a7/53/1958eacbfddc41aadf5ae86dd85041bf054b675f34a2fa76385935f96070/pydantic_core-2.46.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ee1547a6b8243e73dd10f585555e5a263395e55ce6dea618a078570a1e889aef" }, - { url = "https://mirrors.aliyun.com/pypi/packages/c7/17/098cc6d3595e4623186f2bc6604a6195eb182e126702a90517236391e9ce/pydantic_core-2.46.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c3dc68dcf62db22a18ddfc3ad4960038f72b75908edc48ae014d7ac8b391d57a" }, - { url = "https://mirrors.aliyun.com/pypi/packages/71/a7/abdb924620b1ac535c690b36ad5b8871f376104090f8842c08625cecf1d3/pydantic_core-2.46.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:004a2081c881abfcc6854a4623da6a09090a0d7c1398a6ae7133ca1256cee70b" }, - { url = "https://mirrors.aliyun.com/pypi/packages/d7/c9/2ddd10f50e4b7350d2574629a0f53d8d4eb6573f9c19a6b43e6b1487a31d/pydantic_core-2.46.0-cp313-cp313-win32.whl", hash = "sha256:59d24ec8d5eaabad93097525a69d0f00f2667cb353eb6cda578b1cfff203ceef" }, - { url = "https://mirrors.aliyun.com/pypi/packages/b5/e7/1efc38ed6f2680c032bcefa0e3ebd496a8c77e92dfdb86b07d0f2fc632b1/pydantic_core-2.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:71186dad5ac325c64d68fe0e654e15fd79802e7cc42bc6f0ff822d5ad8b1ab25" }, - { url = "https://mirrors.aliyun.com/pypi/packages/c3/1e/a325b4989e742bf7e72ed35fa124bc611fd76539c9f8cd2a9a7854473533/pydantic_core-2.46.0-cp313-cp313-win_arm64.whl", hash = "sha256:8e4503f3213f723842c9a3b53955c88a9cfbd0b288cbd1c1ae933aebeec4a1b4" }, + { url = "https://mirrors.aliyun.com/pypi/packages/ff/d2/bda39bad2f426cb5078e6ad28076614d3926704196efe0d7a2a19a99025d/pydantic_core-2.46.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:cdc8a5762a9c4b9d86e204d555444e3227507c92daba06259ee66595834de47a" }, + { url = "https://mirrors.aliyun.com/pypi/packages/ee/f3/69631e64d69cb3481494b2bddefe0ddd07771209f74e9106d066f9138c2a/pydantic_core-2.46.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ba381dfe9c85692c566ecb60fa5a77a697a2a8eebe274ec5e4d6ec15fafad799" }, + { url = "https://mirrors.aliyun.com/pypi/packages/53/1c/21cb3db6ae997df31be8e91f213081f72ffa641cb45c89b8a1986832b1f9/pydantic_core-2.46.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1593d8de98207466dc070118322fef68307a0cc6a5625e7b386f6fdae57f9ab6" }, + { url = "https://mirrors.aliyun.com/pypi/packages/91/9c/05c819f734318ce5a6ca24da300d93696c105af4adb90494ee571303afd8/pydantic_core-2.46.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8262c74a1af5b0fdf795f5537f7145785a63f9fbf9e15405f547440c30017ed8" }, + { url = "https://mirrors.aliyun.com/pypi/packages/cb/23/fadddf1c7f2f517f58731aea9b35c914e6005250f08dac9b8e53904cdbaa/pydantic_core-2.46.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b88949a24182e83fbbb3f7ca9b7858d0d37b735700ea91081434b7d37b3b444" }, + { url = "https://mirrors.aliyun.com/pypi/packages/23/07/0cd4f95cb0359c8b1ec71e89c3777e7932c8dfeb9cd54740289f310aaead/pydantic_core-2.46.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8f3708cd55537aeaf3fd0ea55df0d68d0da51dcb07cbc8508745b34acc4c6e0" }, + { url = "https://mirrors.aliyun.com/pypi/packages/0c/40/6fc24c3766a19c222a0d60d652b78f0283339d4cd4c173fab06b7ee76571/pydantic_core-2.46.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f79292435fff1d4f0c18d9cfaf214025cc88e4f5104bfaed53f173621da1c743" }, + { url = "https://mirrors.aliyun.com/pypi/packages/4b/af/f39795d1ce549e35d0841382b9c616ae211caffb88863147369a8d74fba9/pydantic_core-2.46.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:a2e607aeb59cf4575bb364470288db3b9a1f0e7415d053a322e3e154c1a0802e" }, + { url = "https://mirrors.aliyun.com/pypi/packages/e6/32/0d563f74582795779df6cc270c3fc220f49f4daf7860d74a5a6cda8491ff/pydantic_core-2.46.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec5ca190b75878a9f6ae1fc8f5eb678497934475aef3d93204c9fa01e97370b6" }, + { url = "https://mirrors.aliyun.com/pypi/packages/5c/07/1c10d5ce312fc4cf86d1e50bdcdbb8ef248409597b099cab1b4bb3a093f7/pydantic_core-2.46.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:1f80535259dcdd517d7b8ca588d5ca24b4f337228e583bebedf7a3adcdf5f721" }, + { url = "https://mirrors.aliyun.com/pypi/packages/92/01/e1f62d4cb39f0913dbf5c95b9b119ef30ddba9493dff8c2b012f0cdd67dc/pydantic_core-2.46.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:24820b3c82c43df61eca30147e42853e6c127d8b868afdc0c162df829e011eb4" }, + { url = "https://mirrors.aliyun.com/pypi/packages/44/ed/218dfeea6127fb1781a6ceca241ec6edf00e8a8933ff331af2215975a534/pydantic_core-2.46.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f12794b1dd8ac9fb66619e0b3a0427189f5d5638e55a3de1385121a9b7bf9b39" }, + { url = "https://mirrors.aliyun.com/pypi/packages/6c/1e/011e763cd059238249fbd5780e0f8d0b04b47f86c8925e22784f3e5fc977/pydantic_core-2.46.1-cp313-cp313-win32.whl", hash = "sha256:9bc09aed935cdf50f09e908923f9efbcca54e9244bd14a5a0e2a6c8d2c21b4e9" }, + { url = "https://mirrors.aliyun.com/pypi/packages/8c/06/b559a490d3ed106e9b1777b8d5c8112dd8d31716243cd662616f66c1f8ea/pydantic_core-2.46.1-cp313-cp313-win_amd64.whl", hash = "sha256:fac2d6c8615b8b42bee14677861ba09d56ee076ba4a65cfb9c3c3d0cc89042f2" }, + { url = "https://mirrors.aliyun.com/pypi/packages/9f/52/32a198946e2e19508532aa9da02a61419eb15bd2d96bab57f810f2713e31/pydantic_core-2.46.1-cp313-cp313-win_arm64.whl", hash = "sha256:f978329f12ace9f3cb814a5e44d98bbeced2e36f633132bafa06d2d71332e33e" }, ] [[package]]