commit 4faed52de575f5424024bd8bc6d039e0c59d1ef6 Author: chenjunyi <314298729@qq.com> Date: Tue Apr 29 18:09:00 2025 +0800 initial commit diff --git a/.env b/.env new file mode 100644 index 0000000..dd734d6 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +AUTH_LDAP_SERVER_URI='ldap://dns.paisat.cn:389' +AUTH_LDAP_BIND_DN='CN=Administrator,CN=Users,DC=sstc,DC=ctu' +AUTH_LDAP_BIND_PASSWORD='WXWX2019!!!!!!' +BASE_DN='OU=all,DC=sstc,DC=ctu' +FILTER_STR='(sAMAccountName=%(user)s)' \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8734d53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/dist +/venv +/.idea +/build +/static +*.zip +/uploads +.idea +/打包后配置 +/packagesInitialize \ No newline at end of file diff --git a/.idea/cdtestplant_v1.iml b/.idea/cdtestplant_v1.iml new file mode 100644 index 0000000..d9d2c05 --- /dev/null +++ b/.idea/cdtestplant_v1.iml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..3ca5a07 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://localhost:3306/chengdu_test_plant_v1 + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..dd4c951 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..153ea52 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..3980ba8 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..c8397c9 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2bf13f9 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# change_log + +## 内外V0.0.1版本 + +2024年7月3日 - V0.0.1版本首次导入内网并进行数据库迁移和部署 + + + +## 外V0.0.2版本 + +## 外V0.0.3版本 diff --git a/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/__pycache__/__init__.cpython-313.pyc b/apps/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..4849ac9 Binary files /dev/null and b/apps/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/__pycache__/__init__.cpython-38.pyc b/apps/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..d110cc2 Binary files /dev/null and b/apps/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/createDocument/__init__.py b/apps/createDocument/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/createDocument/__pycache__/__init__.cpython-313.pyc b/apps/createDocument/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..c655c64 Binary files /dev/null and b/apps/createDocument/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/createDocument/__pycache__/__init__.cpython-38.pyc b/apps/createDocument/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..aac5075 Binary files /dev/null and b/apps/createDocument/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/createDocument/__pycache__/admin.cpython-313.pyc b/apps/createDocument/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000..e61e1c1 Binary files /dev/null and b/apps/createDocument/__pycache__/admin.cpython-313.pyc differ diff --git a/apps/createDocument/__pycache__/admin.cpython-38.pyc b/apps/createDocument/__pycache__/admin.cpython-38.pyc new file mode 100644 index 0000000..18c1738 Binary files /dev/null and b/apps/createDocument/__pycache__/admin.cpython-38.pyc differ diff --git a/apps/createDocument/__pycache__/apps.cpython-313.pyc b/apps/createDocument/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000..cbe5ebc Binary files /dev/null and b/apps/createDocument/__pycache__/apps.cpython-313.pyc differ diff --git a/apps/createDocument/__pycache__/apps.cpython-38.pyc b/apps/createDocument/__pycache__/apps.cpython-38.pyc new file mode 100644 index 0000000..d3d68f2 Binary files /dev/null and b/apps/createDocument/__pycache__/apps.cpython-38.pyc differ diff --git a/apps/createDocument/__pycache__/models.cpython-313.pyc b/apps/createDocument/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..354cf51 Binary files /dev/null and b/apps/createDocument/__pycache__/models.cpython-313.pyc differ diff --git a/apps/createDocument/__pycache__/models.cpython-38.pyc b/apps/createDocument/__pycache__/models.cpython-38.pyc new file mode 100644 index 0000000..17d4295 Binary files /dev/null and b/apps/createDocument/__pycache__/models.cpython-38.pyc differ diff --git a/apps/createDocument/admin.py b/apps/createDocument/admin.py new file mode 100644 index 0000000..ea5d68b --- /dev/null +++ b/apps/createDocument/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/createDocument/apps.py b/apps/createDocument/apps.py new file mode 100644 index 0000000..f1dda67 --- /dev/null +++ b/apps/createDocument/apps.py @@ -0,0 +1,9 @@ +from django.apps import AppConfig + +class CreatedocumentConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.createDocument' + + # 在app准备好时候执行的代码,以后放async、signal等 + def ready(self): + pass diff --git a/apps/createDocument/controllers/__init__.py b/apps/createDocument/controllers/__init__.py new file mode 100644 index 0000000..7eac238 --- /dev/null +++ b/apps/createDocument/controllers/__init__.py @@ -0,0 +1,12 @@ +# 导入所有本目录控制器,为了自动导入 +from apps.createDocument.controllers.dg import GenerateControllerDG +from apps.createDocument.controllers.sm import GenerateControllerSM +from apps.createDocument.controllers.jl import GenerateControllerJL +from apps.createDocument.controllers.bg import GenerateControllerBG +from apps.createDocument.controllers.wtd import GenerateControllerWtd +from apps.createDocument.controllers.hsm import GenerateControllerHSM +from apps.createDocument.controllers.hjl import GenerateControllerHJL + +# 给外部导入 +__all__ = ['GenerateControllerDG', 'GenerateControllerSM', 'GenerateControllerJL', 'GenerateControllerBG', + 'GenerateControllerWtd', 'GenerateControllerHSM', 'GenerateControllerHJL'] diff --git a/apps/createDocument/controllers/__pycache__/__init__.cpython-313.pyc b/apps/createDocument/controllers/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..8e570ae Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/__init__.cpython-38.pyc b/apps/createDocument/controllers/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..d84ae1f Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/bg.cpython-313.pyc b/apps/createDocument/controllers/__pycache__/bg.cpython-313.pyc new file mode 100644 index 0000000..1fc29eb Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/bg.cpython-313.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/bg.cpython-38.pyc b/apps/createDocument/controllers/__pycache__/bg.cpython-38.pyc new file mode 100644 index 0000000..0ab30ae Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/bg.cpython-38.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/dg.cpython-313.pyc b/apps/createDocument/controllers/__pycache__/dg.cpython-313.pyc new file mode 100644 index 0000000..cb7c1d1 Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/dg.cpython-313.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/dg.cpython-38.pyc b/apps/createDocument/controllers/__pycache__/dg.cpython-38.pyc new file mode 100644 index 0000000..8f850b3 Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/dg.cpython-38.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/hjl.cpython-313.pyc b/apps/createDocument/controllers/__pycache__/hjl.cpython-313.pyc new file mode 100644 index 0000000..5c8b2b7 Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/hjl.cpython-313.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/hjl.cpython-38.pyc b/apps/createDocument/controllers/__pycache__/hjl.cpython-38.pyc new file mode 100644 index 0000000..0c739ce Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/hjl.cpython-38.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/hsm.cpython-313.pyc b/apps/createDocument/controllers/__pycache__/hsm.cpython-313.pyc new file mode 100644 index 0000000..83fb0b0 Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/hsm.cpython-313.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/hsm.cpython-38.pyc b/apps/createDocument/controllers/__pycache__/hsm.cpython-38.pyc new file mode 100644 index 0000000..1c0e49a Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/hsm.cpython-38.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/jl.cpython-313.pyc b/apps/createDocument/controllers/__pycache__/jl.cpython-313.pyc new file mode 100644 index 0000000..8b7e906 Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/jl.cpython-313.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/jl.cpython-38.pyc b/apps/createDocument/controllers/__pycache__/jl.cpython-38.pyc new file mode 100644 index 0000000..f5a98f0 Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/jl.cpython-38.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/sm.cpython-313.pyc b/apps/createDocument/controllers/__pycache__/sm.cpython-313.pyc new file mode 100644 index 0000000..3f6987f Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/sm.cpython-313.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/sm.cpython-38.pyc b/apps/createDocument/controllers/__pycache__/sm.cpython-38.pyc new file mode 100644 index 0000000..b09a85d Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/sm.cpython-38.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/wtd.cpython-313.pyc b/apps/createDocument/controllers/__pycache__/wtd.cpython-313.pyc new file mode 100644 index 0000000..bb6436a Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/wtd.cpython-313.pyc differ diff --git a/apps/createDocument/controllers/__pycache__/wtd.cpython-38.pyc b/apps/createDocument/controllers/__pycache__/wtd.cpython-38.pyc new file mode 100644 index 0000000..ba77011 Binary files /dev/null and b/apps/createDocument/controllers/__pycache__/wtd.cpython-38.pyc differ diff --git a/apps/createDocument/controllers/bg.py b/apps/createDocument/controllers/bg.py new file mode 100644 index 0000000..12c8fe7 --- /dev/null +++ b/apps/createDocument/controllers/bg.py @@ -0,0 +1,724 @@ +from datetime import date, timedelta +from pathlib import Path +from ninja_extra import api_controller, ControllerBase, route +from django.db import transaction +from django.shortcuts import get_object_or_404 +from django.db.models import Q +from docxtpl import DocxTemplate +from typing import Optional +from docx import Document +from ninja_extra.permissions import IsAuthenticated +from ninja_jwt.authentication import JWTAuth +# 导入模型 +from apps.project.models import Project, Dut, TestDemand, Problem +# 工具类函数 +from apps.createDocument.extensions import util +from utils.chen_response import ChenResponse +from apps.createDocument.extensions.util import create_bg_docx, get_round1_problem +from utils.util import get_str_dict, get_list_dict, create_problem_grade_str, create_str_testType_list, \ + create_demand_summary, create_problem_type_str, create_problem_table, create_problem_type_table, get_str_abbr +# 根据轮次生成测评内容文档context +from apps.createDocument.extensions.content_result_tool import create_round_context +from apps.createDocument.extensions.zhui import create_bg_round1_zhui +from apps.createDocument.extensions.solve_problem import create_one_problem_dit +from utils.path_utils import project_path +from apps.createDocument.extensions.util import delete_dir_files +from apps.createDocument.extensions.parse_rich_text import RichParser +from apps.createDocument.extensions.documentTime import DocTime +# 导入生成日志记录模块 +from apps.createSeiTaiDocument.extensions.logger import GenerateLogger + +# @api_controller("/generateBG", tags=['生成报告文档系列'], auth=JWTAuth(), permissions=[IsAuthenticated]) +@api_controller("/generateBG", tags=['生成报告文档系列']) +class GenerateControllerBG(ControllerBase): + logger = GenerateLogger('测评报告') + + # important:删除之前的文件 + @route.get('/create/deleteBGDocument', url_name='delete-bg-document') + def delete_bg_document(self, id: int): + project_path_str = project_path(id) + save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/bg' + delete_dir_files(save_path) + + @route.get("/create/techyiju", url_name="create-techyiju") + @transaction.atomic + def create_techyiju(self, id: int): + project_obj = get_object_or_404(Project, id=id) + duties_qs = project_obj.pdField.filter(Q(type='XQ') | Q(type='SJ') | Q(type='XY')) + std_documents = [] + for duty in duties_qs: + one_duty = {'doc_name': duty.name, 'ident_version': duty.ref + '-' + duty.version, + 'publish_date': duty.release_date, 'source': duty.release_union} + std_documents.append(one_duty) + # 添加大纲到这里 + ## 判断是否为鉴定 + doc_name = f'{project_obj.name}软件测评大纲' + if project_obj.report_type == '9': + doc_name = f'{project_obj.name}软件鉴定测评大纲' + # 时间控制类 + timer = DocTime(id) + # 这里大纲版本升级如何处理 + dg_duty = {'doc_name': doc_name, 'ident_version': f'PT-{project_obj.ident}-TO-1.00', + 'publish_date': timer.dg_cover_time, 'source': project_obj.test_unit} + std_documents.append(dg_duty) + # 需要添加说明、记录 + sm_duty = {'doc_name': f'{project_obj.name}软件测试说明', 'ident_version': f'PT-{project_obj.ident}-TD-1.00', + 'publish_date': timer.sm_cover_time, 'source': project_obj.test_unit} + jl_duty = {'doc_name': f'{project_obj.name}软件测试记录', 'ident_version': f'PT-{project_obj.ident}-TN', + 'publish_date': timer.jl_cover_time, 'source': project_obj.test_unit} + # 循环所有轮次,除了第一轮 + std_documents.extend([sm_duty, jl_duty]) + rounds = project_obj.pField.exclude(key='0') + name_list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'] + index = 1 + for r in rounds: + hsm_duty = {'doc_name': f'{project_obj.name}软件第{name_list[index]}轮测试说明', + 'ident_version': f'PT-{project_obj.ident}-TD{str(index + 1)}-1.00', + 'publish_date': r.beginTime, 'source': project_obj.test_unit} + hjl_duty = {'doc_name': f'{project_obj.name}软件第{name_list[index]}轮测试记录', + 'ident_version': f'PT-{project_obj.ident}-TN{str(index + 1)}', + 'publish_date': r.endTime, 'source': project_obj.test_unit} + std_documents.extend([hsm_duty, hjl_duty]) + index += 1 + # 生成二级文档 + context = { + 'std_documents': std_documents + } + return create_bg_docx("技术依据文件.docx", context, id) + + # 测评地点和时间接口 + @route.get('/create/timeaddress') + @transaction.atomic + def create_timeaddress(self, id: int): + timer = DocTime(id) + context = timer.bg_address_time() + return create_bg_docx('测评时间和地点.docx', context, id) + + # 在报告生成多个版本被测软件基本信息 + @route.get('/create/baseInformation', url_name='create-baseInformation') + def create_information(self, id: int): + project_obj = get_object_or_404(Project, id=id) + languages = get_list_dict('language', project_obj.language) + language_list = [] + for language in languages: + language_list.append(language.get('ident_version')) + + # 获取轮次 + rounds = project_obj.pField.all() + round_list = [] + for r in rounds: + round_dict = {} + # 获取SO的dut + so_dut: Dut = r.rdField.filter(type='SO').first() + if so_dut: + round_dict['version'] = so_dut.version + round_dict['line_count'] = int(so_dut.total_lines) + round_dict['effective_line'] = int(so_dut.effective_lines) + round_list.append(round_dict) + + context = { + 'project_name': project_obj.name, + 'soft_type': project_obj.get_soft_type_display(), + 'security_level': get_str_dict(project_obj.security_level, 'security_level'), + 'runtime': get_str_dict(project_obj.runtime, 'runtime'), + 'devplant': get_str_dict(project_obj.devplant, 'devplant'), + 'language': "\a".join(language_list), + 'recv_date': project_obj.beginTime.strftime("%Y-%m-%d"), + 'dev_unit': project_obj.dev_unit, + 'version_info': round_list + } + return create_bg_docx('被测软件基本信息.docx', context, id) + + # 生成测评完成情况 + @route.get('/create/completionstatus', url_name='create-completionstatus') + def create_completionstatus(self, id: int): + project_obj = get_object_or_404(Project, id=id) + # 找到第一轮轮次对象、第二轮轮次对象 + round1 = project_obj.pField.filter(key='0').first() + # 第一轮测试项个数 + round1_demand_qs = round1.rtField.all() + # 第一轮用例个数 + round1_case_qs = round1.rcField.all() + # 这部分找出第一轮的所有测试类型,输出字符串,并排序 + test_type_set: set = set() + for case in round1_case_qs: + demand: TestDemand = case.test + test_type_set.add(demand.testType) + round1_testType_list = list(map(lambda x: x['ident_version'], get_list_dict('testType', list(test_type_set)))) + # 这里找出第一轮,源代码被测件,并获取版本 + so_dut = round1.rdField.filter(type='SO').first() + so_dut_verson = "$请添加第一轮的源代码信息$" + if so_dut: + so_dut_verson = so_dut.version + # 这里找出除第一轮的其他轮次 + rounds = project_obj.pField.exclude(key='0') + rounds_str_chinese = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'] + round_list = [] + for r in rounds: + # 找所属dut的so-dut + so_dut = r.rdField.filter(type='SO').first() + # 找出上一轮dut的so-dut + last_problem_count = Problem.objects.filter(case__round__key=str(int(r.key) - 1)).distinct().count() + current_round_problem_count = Problem.objects.filter(case__round__key=r.key).distinct().count() + if current_round_problem_count > 0: + current_round_description = f'引入新问题{current_round_problem_count}个' + else: + current_round_description = '经测试软件更改正确,并且未引入新的问题' + r_dict = { + 'version': so_dut.version if so_dut else '$请添加该轮次源代码信息$', + 'round_index': rounds_str_chinese[int(r.key)], + 'last_problem_count': last_problem_count, + 'current_round_description': current_round_description, + 'start_year': r.beginTime.year, + 'start_month': r.beginTime.month, + 'end_year': (r.beginTime + timedelta(days=4)).year, # 这里只是简单+4有待商榷 + 'end_month': (r.beginTime + timedelta(days=4)).month, + } + round_list.append(r_dict) + + # 这部分找到第一轮的问题 + problem_qs = get_round1_problem(project_obj) + context = { + 'is_JD': True if project_obj.report_type == '9' else False, + 'project_name': project_obj.name, + 'start_time_year': project_obj.beginTime.year, + 'start_time_month': project_obj.beginTime.month, + 'round1_case_count': round1_case_qs.count(), + 'round1_demand_count': round1_demand_qs.count(), + 'round1_testType_str': '、'.join(round1_testType_list), + 'testType_count': len(round1_testType_list), + 'round1_version': so_dut_verson, + 'round1_problem_count': len(problem_qs), + 'end_time_year': date.today().year, + 'end_time_month': date.today().month, + 'round_list': round_list + } + # 注入时间 + timer = DocTime(id) + context.update(**timer.bg_completion_situation()) + return create_bg_docx('测评完成情况.docx', context, id) + + # 生成综述 + @route.get('/create/summary', url_name='create-summary') + def create_summary(self, id: int): + project_obj = get_object_or_404(Project, id=id) + # 找出所有问题单 + problem_qs = project_obj.projField.all() + problem_grade_dict = {} + problem_type_dict = {} + # 建议问题统计 + problem_suggest_count = 0 + problem_suggest_solved_count = 0 + for problem in problem_qs: + grade_key: str = get_str_dict(problem.grade, 'problemGrade') + type_key: str = get_str_dict(problem.type, 'problemType') + # 问题等级字典-计数 + if grade_key in problem_grade_dict.keys(): + problem_grade_dict[grade_key] += 1 + else: + problem_grade_dict[grade_key] = 1 + # 问题类型字典-计数 + if type_key in problem_type_dict.keys(): + problem_type_dict[type_key] += 1 + else: + problem_type_dict[type_key] = 1 + # 建议问题统计 + if problem.grade == '3': + problem_suggest_count += 1 + if problem.status == '1': + problem_suggest_solved_count += 1 + problem_grade_list = [] + problem_type_list = [] + for key, value in problem_grade_dict.items(): + problem_grade_list.append("".join([f"{key}问题", f"{value}个"])) + for key, value in problem_type_dict.items(): + problem_type_list.append("".join([f"{key}", f"{value}个"])) + # 用来生成建议问题信息 + if problem_suggest_count > 0 and problem_suggest_count - problem_suggest_solved_count > 0: + all_str = (f"测评过程中提出了{problem_suggest_count}个建议改进," + f"其中{problem_suggest_solved_count}个建议改进已修改," + f"剩余{problem_suggest_count - problem_suggest_solved_count}个未修改并经总体单位认可同意") + elif problem_suggest_count > 0 and problem_suggest_count - problem_suggest_solved_count == 0: + all_str = (f"测评过程中提出了{problem_suggest_count}个建议改进," + f"全部建议问题已修改") + else: + all_str = f"测评过程中未提出建议项。" + + context = { + 'problem_count': problem_qs.count(), + 'problem_grade_str': "、".join(problem_grade_list), + 'problem_type_str': '、'.join(problem_type_list), + 'all_str': all_str, + } + return create_bg_docx('综述.docx', context, id) + + # 生成测试内容和结果[报告非常关键的一环-大模块] + @route.get('/create/contentandresults_1', url_name='create-contentandresults_1') + @transaction.atomic + def create_content_results_1(self, id: int): + project_obj = get_object_or_404(Project, id=id) + project_ident = project_obj.ident + # ~~~~首轮信息~~~~ + round1 = project_obj.pField.filter(key='0').first() # !warning轮次1对象 + + # 1.处理首轮文档名称,新修改,这里取全部轮次的文档内容 + doc_list = [] + round1_duts = project_obj.pdField.filter(Q(type='SJ') | Q(type='XQ') | Q(type='XY')) + index = 1 + for dut in round1_duts: + dut_dict = { + 'name': dut.name, + 'ident': dut.ref, + 'version': dut.version, + 'index': index + } + doc_list.append(dut_dict) + index += 1 + + # 2.处理首轮文档问题的统计 - 注意去重 + problems = project_obj.projField.all().distinct() # !important:大变量-项目所有问题 + problems_r1 = problems.filter(case__round__key='0') # !important:大变量-首轮的所有问题 + problems_doc_r1 = problems_r1.filter(case__test__testType='8') # 第一轮所有文档问题 + + # 3.第一轮代码审查问题统计/版本 + source_r1_dut = round1.rdField.filter(type='SO').first() # !warning:小变量-第一轮源代码对象 + program_r1_problems = problems_r1.filter(case__test__testType='2') + + # 4.第一轮代码走查问题统计/版本 + zou_r1_problems = problems_r1.filter(case__test__testType='3') + # 找下是否存在代码走查测试项 + r1_demand_qs = round1.rtField.filter(testType='3') + has_zou = True if r1_demand_qs.count() > 0 else False + + # 5.第一轮静态分析问题统计 + static_problems = problems_r1.filter(case__test__testType='15') + + # 6.第一轮动态测试用例个数(动态测试-非静态分析、文档审查、代码审查、代码走查4个) + case_r1_qs = round1.rcField.filter(~Q(test__testType='2'), ~Q(test__testType='3'), ~Q(test__testType='8'), + ~Q(test__testType='15'), + round__key='0') # !warning:中变量-第一轮动态测试用例qs + testType_list, testType_count = create_str_testType_list(case_r1_qs) + ## 动态测试(第一轮)各个类型测试用例执行表/各个测试需求表 + demand_r1_dynamic_qs = round1.rtField.filter(~Q(testType='2'), ~Q(testType='3'), ~Q(testType='8'), + ~Q(testType='15')) # !warning:中变量:第一轮动态测试的测试项 + summary_r1_demand_info, summry_r1_demandType_info = create_demand_summary(demand_r1_dynamic_qs, project_ident) + + # N.第一轮所有动态问题统计 + problems_dynamic_r1 = problems_r1.filter(~Q(case__test__testType='2'), ~Q(case__test__testType='3'), + ~Q(case__test__testType='8'), + ~Q(case__test__testType='15')) # !critical:大变量:第一轮动态问题单qs + problem_dynamic_r1_type_str = create_problem_type_str(problems_dynamic_r1) + problem_dynamic_r1_grade_str = create_problem_grade_str(problems_dynamic_r1) + + context = { + 'project_name': project_obj.name, + 'doc_list': doc_list, + 'r1_doc_problem_count': problems_doc_r1.count(), + 'r1_doc_problem_str': + f"{',其中' + create_problem_grade_str(problems_doc_r1) if problems_doc_r1.count() > 0 else '即未发现问题'}", + 'r1_version': source_r1_dut.version if source_r1_dut else "未录入首轮版本信息", + 'r1_program_problem_count': program_r1_problems.count(), + 'r1_program_problem_str': + f'{",其中" + create_problem_grade_str(program_r1_problems) if program_r1_problems.count() > 0 else "即未发现问题"}', + 'r1_zou_problem_count': zou_r1_problems.count(), + 'r1_zou_problem_str': f'{",其中" + create_problem_grade_str(zou_r1_problems) if zou_r1_problems.count() > 0 else "即未发现问题"}', + 'has_zou': has_zou, + 'r1_static_problem_count': static_problems.count(), + 'r1_static_problem_str': f"{',其中' + create_problem_grade_str(static_problems) if static_problems.count() > 0 else '即未发现问题'}", + 'r1_case_count': case_r1_qs.count(), + 'r1_case_testType': "、".join(testType_list), + 'r1_case_testType_count': testType_count, + 'r1_problem_counts': len(problems_dynamic_r1), + 'r1_exe_info_all': summary_r1_demand_info, + 'r1_exe_info_type': summry_r1_demandType_info, + 'r1_dynamic_problem_str': problem_dynamic_r1_type_str, + 'r1_dynamic_problem_grade_str': problem_dynamic_r1_grade_str, + } + return create_bg_docx("测试内容和结果_第一轮次.docx", context, id) + + # 查询除第一轮以外,生成其他轮次测试内容和结果 + @route.get('/create/contentandresults_2', url_name='create-contentandresults_2') + @transaction.atomic + def create_content_results_2(self, id: int): + project_obj = get_object_or_404(Project, id=id) + # 查询除第一轮,其他有几轮 + round_qs = project_obj.pField.filter(~Q(key='0')) + round_str_list = [item.key for item in round_qs] + # 每个轮次都需要生成一个测试内容和标题 + project_path_str = project_path(id) + for round_str in round_str_list: + context = create_round_context(project_obj, round_str) + template_path = Path.cwd() / 'media' / project_path_str / 'form_template' / 'bg' / '测试内容和结果_第二轮次.docx' + doc = DocxTemplate(template_path) + doc.render(context) + try: + doc.save( + Path.cwd() / "media" / project_path_str / "output_dir/bg" / f"测试内容和结果_第{context['round_id']}轮次.docx") + except PermissionError: + ChenResponse(code=400, status=400, message='您已打开生成文件,请关闭后再试...') + + # 软件问题统计 + @route.get('/create/problem_statistics') + @transaction.atomic + def create_problem_statistics(self, id: int): + project_obj = get_object_or_404(Project, id=id) + problems = project_obj.projField.all().distinct() # 项目所有问题单 + context = { + 'closed_count': problems.filter(status='1').count(), + 'noclosed_count': problems.count() - problems.filter(status='1').count(), + 'problem_table': create_problem_table(problems), + 'problem_table_2': create_problem_type_table(problems) + } + return create_bg_docx("软件问题统计.docx", context, id) + + # 测试有效性充分性说明 + @route.get('/create/effect_and_adquacy', url_name='create-effect_and_adquacy') + @transaction.atomic + def create_effect_and_adquacy(self, id: int): + project_obj = get_object_or_404(Project, id=id) + # 判断是否为鉴定 + is_JD = False + if project_obj.report_type == '9': + is_JD = True + # 统计测试项数量 + demand_qs = project_obj.ptField + # 统计用例个数 + case_qs = project_obj.pcField + # 测试用例的类型统计个数 + testType_list, testType_count = create_str_testType_list(case_qs.all()) + # 问题单总个数 + problem_qs = project_obj.projField + + context = { + 'project_name': project_obj.name, + 'demand_count': demand_qs.count(), + 'case_count': case_qs.count(), + 'testType_list': "、".join(testType_list), + 'testType_count': testType_count, + 'problem_count': problem_qs.count(), + 'is_JD': is_JD, + } + return create_bg_docx('测试有效性充分性说明.docx', context, id) + + # 需求指标符合性情况 + @route.get('/create/demand_effective', url_name='create-demand_effective') + @transaction.atomic + def create_demand_effective(self, id: int): + project_obj = get_object_or_404(Project, id=id) + # 查询所有需求规格说明的 - 设计需求 + round1_design_qs = project_obj.psField.filter(round__key='0', dut__type='XQ') # qs:第一轮需求文档的设计需求 + # 将第一轮需求文档名称 + dut_name = f"《{project_obj.name}软件需求规格说明》" + data_list = [] + design_index = 1 + for design in round1_design_qs: + # 如果为“/”则写为隐含需求 + if design.chapter.strip() == '/': + design_dict = {'source': "隐含需求"} + else: + design_dict = {'source': "".join([dut_name, design.name, ':', design.chapter])} + # 将设计需求描述筛入 + rich_parser = RichParser(design.description) + p_list = rich_parser.get_final_p_list() + design_dict['description'] = '\a'.join(p_list) + # 找出其中所有demand + demand_qs = design.dtField.all() + if not demand_qs.exists(): + design_dict['demands'] = '未关联测试项' + else: + demand_list = [] + index = 0 + for demand in demand_qs: + index += 1 + demand_abbr = get_str_abbr(demand.testType, 'testType') + demand_list.append(f'{index}、XQ_{demand_abbr}_{demand.ident}-{demand.name}') + design_dict['demands'] = '\a'.join(demand_list) + # 通过还是未通过 + design_dict['pass'] = '通过' + design_dict['index'] = design_index + data_list.append(design_dict) + design_index += 1 + + # ~~~~指标符合性表~~~~ + data_yz_list = [] + # qs:第一轮需求文档的设计需求 + has_YZ = False + round1_design_yz_qs = project_obj.psField.filter(round__key='0', dut__type='YZ') + if round1_design_yz_qs.exists(): + has_YZ = True + # 如果有研制总要求的dut,继续 + for design in round1_design_yz_qs: + rich_parser2 = RichParser(design.description) + p_list = rich_parser2.get_final_p_list() + design_dict = {'yz_des': "".join([design.chapter, '章节:', design.name, '\a', '\a'.join(p_list)])} + # 找出其中所有demand + demand_qs = design.dtField.all() + if not demand_qs.exists(): + design_dict['demands'] = '未关联测评大纲条款' + else: + # 大纲条款的列表 + demand_list = [] + demand_step_list = [] + index = 0 + for demand in demand_qs: + index += 1 + demand_list.append(f'{index}、{demand.ident}-{demand.name}') + # 测试需求步骤的列表 + step_list = [] + for step in demand.testQField.all(): + step_list.append(step.subName) + demand_step_list.append('\a'.join(step_list)) + + design_dict['demands'] = '\a'.join(demand_list) + design_dict['steps'] = '\a'.join(demand_step_list) + + # 通过还是未通过 + design_dict['pass'] = '通过' + data_yz_list.append(design_dict) + # 处理没有steps字段 + if 'steps' not in design_dict: + design_dict['steps'] = '该设计需求未关联测评大纲条款' + + context = { + 'data_list': data_list, + 'data_yz_list': data_yz_list, + 'has_YZ': has_YZ, + } + return create_bg_docx('需求指标符合性情况.docx', context, id) + + # 软件质量评价 + @route.get('/create/quality_evaluate', url_name='create-quality_evaluate') + @transaction.atomic + def create_quality_evaluate(self, id: int): + project_obj = get_object_or_404(Project, id=id) + # 找出最后一轮 + rounds = project_obj.pField.order_by('-key') # qs:轮次 + last_dut_so: Optional[Dut] = None + for round in rounds: + # 查询其源代码dut + dut_so = round.rdField.filter(type='SO').first() + if dut_so: + last_dut_so = dut_so + break + # 计算千行缺陷率 + problem_count = project_obj.projField.count() + # 如果没有轮次信息则返回错误 + if not last_dut_so: + return ChenResponse(code=400, status=400, message='您还未创建轮次,请进入工作区创建') + # 计算注释率 + ## 总行数 + total_lines = int(last_dut_so.total_lines) + ## 有效注释行 + effective_comment_lines = int(last_dut_so.comment_lines) + comment_ratio = (effective_comment_lines / total_lines) * 100 + context = { + 'last_version': last_dut_so.version, # 最后轮次代码版本 + 'comment_percent': format(comment_ratio, '.4f'), # 最后轮次代码注释率 + 'qian_comment_rate': format(problem_count / int(last_dut_so.total_lines) * 1000, '.4f'), + 'avg_function_lines': "XXXX", + 'avg_cyclomatic': 'XXXX', + 'avg_fan_out': 'XXXX', + } + # 判断是否有metrics一对一模型关联 + if hasattr(last_dut_so, 'metrics'): + context['avg_function_lines'] = str(last_dut_so.metrics.avg_function_lines) + context['avg_cyclomatic'] = str(last_dut_so.metrics.avg_cyclomatic) + context['avg_fan_out'] = str(last_dut_so.metrics.avg_fan_out) + return create_bg_docx('软件质量评价.docx', context, id) + + # 软件总体结论 + @route.get('/create/entire', url_name='create-entire') + @transaction.atomic + def create_entire(self, id: int): + project_obj = get_object_or_404(Project, id=id) + # 是否鉴定 + is_JD = False + if project_obj.report_type == '9': + is_JD = True + # 找出最后一轮并且有源代码的dut + rounds = project_obj.pField.order_by('-key') # qs:轮次 + last_dut_so: Optional[Dut] = None + for round in rounds: + # 查询其源代码dut + dut_so = round.rdField.filter(type='SO').first() + if dut_so: + last_dut_so = dut_so + break + # 找出所有被测件协议(XY)、需求规格说明(XQ)、设计说明(SJ) + duties_qs: list[Dut] = project_obj.pdField.filter(Q(type='XQ') | Q(type='SJ') | Q(type='XY')) + # ***Inspect-start*** + if not last_dut_so: + self.logger.model = '测评报告' + self.logger.write_warning_log('总体结论', f'项目没创建轮次,请检查') + return None + # ***Inspect-end*** + context = { + 'name': project_obj.name, + 'last_version': last_dut_so.version, + 'is_JD': is_JD, + 'dut_list': [ + { + 'index': index + 1, + 'name': dut_single.name, + 'ref': dut_single.ref, + 'version': dut_single.version, + } for index, dut_single in enumerate(duties_qs) + ], + 'last_dut_so_ref': last_dut_so.ref, + } + return create_bg_docx('总体结论.docx', context, id) + + # 研总需求追踪 - 注意生成每个轮次的追踪 + @route.get('/create/yzxq_track', url_name='create-yzxq_track') + @transaction.atomic + def create_yzxq_track(self, id: int): + project_obj = get_object_or_404(Project, id=id) + # 是否是鉴定的变量,如果为鉴定,则需要研总的追踪 + is_JD = False + if project_obj.report_type == '9': + is_JD = True + # 查询多少个轮次 + round_count = project_obj.pField.count() + round_str_list = [str(i) for i in range(round_count)] + # 生成研总的design_list + design_list_all = [] + for round_str in round_str_list: + # 找寻轮次里面源代码版本 + dut_version = 'XXX' + dut_so = Dut.objects.filter(round__key=round_str, type='SO').first() + if dut_so: + dut_version = dut_so.version + if is_JD: + design_list_yz = create_bg_round1_zhui(project_obj, dut_str='YZ', round_str=round_str) + one_table_dict = { + 'design_list': design_list_yz, + 'version': 'V' + dut_version, + 'title': '研制总要求' + } + design_list_all.append(one_table_dict) + design_list_xq = create_bg_round1_zhui(project_obj, dut_str='XQ', round_str=round_str) + one_table_dict_xq = { + 'design_list': design_list_xq, + 'version': 'V' + dut_version, + 'title': '需求规格说明' + } + design_list_all.append(one_table_dict_xq) + else: + design_list_xq = create_bg_round1_zhui(project_obj, dut_str='XQ', round_str=round_str) + one_table_dict_xq = { + 'design_list': design_list_xq, + 'version': 'V' + dut_version, + 'title': '需求规格说明' + } + design_list_all.append(one_table_dict_xq) + context = { + 'design_list_all': design_list_all, + } + + # 手动渲染tpl文档 + project_path_str = project_path(id) + input_file = Path.cwd() / 'media' / project_path_str / 'form_template' / 'bg' / '研总需归追踪.docx' + temporary_file = Path.cwd() / 'media' / project_path_str / 'form_template' / 'bg' / 'temporary' / '研总需归追踪_temp.docx' + out_put_file = Path.cwd() / 'media' / project_path_str / 'output_dir' / 'bg' / '研总需归追踪.docx' + doc = DocxTemplate(input_file) + doc.render(context) + doc.save(temporary_file) + # 通过docx合并单元格 + if temporary_file.is_file(): + try: + docu = Document(temporary_file) + # 循环找到表格 + for table in docu.tables: + util.merge_all_cell(table) + # 储存到合适位置 + docu.save(out_put_file) + return ChenResponse(code=200, status=200, message='文档生成成功...') + except PermissionError: + return ChenResponse(code=400, status=400, message='请检查文件是否打开,如果打开则关闭...') + else: + return ChenResponse(code=400, status=400, message='中间文档未找到,请检查你模版是否存在...') + + # 生成问题汇总表 + @route.get('/create/problems_summary', url_name='create-problem_summary') + @transaction.atomic + def create_problem_summary(self, id: int): + tpl_doc = Path.cwd() / "media" / project_path(id) / "form_template" / "bg" / "问题汇总表.docx" + doc = DocxTemplate(tpl_doc) + project_obj = get_object_or_404(Project, id=id) + problem_prefix = "_".join(['PT', project_obj.ident]) + problems = project_obj.projField + # 先查询有多少轮次 + round_count = project_obj.pField.count() + round_str_list = [str(x) for x in range(round_count)] + data_list = [] + for round_str in round_str_list: + # 查询所属当前轮次的SO-dut + so_dut = Dut.objects.filter(round__key=round_str, type='SO').first() + round_dict = { + 'static': [], + 'dynamic': [], + 'version': so_dut.version if so_dut else "v1.0", + } + # 找出轮次中静态问题 + r1_static_problems = problems.filter(case__round__key=round_str, + case__test__testType__in=['2', '3', '8', '15']).distinct() + for problem in r1_static_problems: + problem_dict = create_one_problem_dit(problem, problem_prefix, doc) + round_dict['static'].append(problem_dict) + + # 找出轮次中动态问题 + r1_dynamic_problems = problems.filter(case__round__key=round_str).exclude( + case__test__testType__in=['2', '3', '8', '15']).distinct() + for problem in r1_dynamic_problems: + problem_dict = create_one_problem_dit(problem, problem_prefix, doc) + round_dict['dynamic'].append(problem_dict) + data_list.append(round_dict) + + context = { + 'data_list': data_list + } + + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/bg" / "问题汇总表.docx") + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + # 生成摸底清单 + @route.get('/create/modi_list', url_name='create-modi-list') + @transaction.atomic + def create_modi_list(self, id: int): + tpl_doc = Path.cwd() / "media" / project_path(id) / "form_template" / "bg" / "摸底清单.docx" + doc = DocxTemplate(tpl_doc) + project_obj = get_object_or_404(Project, id=id) + # 查询所有轮次“摸底测试”的测试项 + demands_qs = project_obj.ptField.all() + modi_list = [] + for demand in demands_qs: + one_modi = {} + testType_str = get_str_dict(demand.testType, 'testType') + if "摸底" in testType_str: + # 1.找到设计需求章节号以及描述 + design = demand.design + one_modi['source'] = f"{design.chapter}-{design.name}" if design.chapter != '/' else "隐含需求" + one_modi['desc'] = "\a".join(RichParser(design.description).get_final_p_list()) + # 2.找所有的case + case_qs = demand.tcField.all() + one_modi['result'] = [] + for case in case_qs: + # 找case的步骤 + for step in case.step.all(): + if step.passed == '1': # 只获取通过的 + one_modi['result'].append("\a".join(RichParser(step.result).get_final_p_list())) + modi_list.append(one_modi) + # 渲染上下文 + context = { + 'modi_list': modi_list, + } + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/bg" / "摸底清单.docx") + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) diff --git a/apps/createDocument/controllers/dg.py b/apps/createDocument/controllers/dg.py new file mode 100644 index 0000000..895e040 --- /dev/null +++ b/apps/createDocument/controllers/dg.py @@ -0,0 +1,784 @@ +from ninja.errors import HttpError +from ninja_extra import ControllerBase, api_controller, route +from ninja_extra.permissions import IsAuthenticated +from ninja_jwt.authentication import JWTAuth +from django.db import transaction +from django.db.models import Q +from docxtpl import DocxTemplate +from pathlib import Path +from utils.chen_response import ChenResponse +# 导入数据库ORM +from apps.project.models import Project, Contact, Abbreviation +from apps.dict.models import Dict +# 导入工具函数 +from utils.util import get_str_dict, get_list_dict, get_testType, get_ident, get_str_abbr +from utils.chapter_tools.csx_chapter import create_csx_chapter_dict +from utils.util import MyHTMLParser_p +from django.shortcuts import get_object_or_404 +from django.forms.models import model_to_dict +from apps.createDocument.extensions.util import create_dg_docx +from apps.createDocument.extensions.parse_rich_text import RichParser +from apps.createDocument.extensions.documentTime import DocTime +from utils.path_utils import project_path +# 记录生成日志 +from apps.createSeiTaiDocument.extensions.logger import GenerateLogger +# 导入mixins-处理文档片段 +from apps.createDocument.extensions.mixins import FragementToolsMixin + +# @api_controller("/generate", tags=['生成大纲文档'], auth=JWTAuth(), permissions=[IsAuthenticated]) +@api_controller("/generate", tags=['生成大纲文档']) +class GenerateControllerDG(ControllerBase, FragementToolsMixin): + logger = GenerateLogger('测评大纲') + + @route.get("/create/testdemand", url_name="create-testdemand") + @transaction.atomic + def create_testdemand(self, id: int): # type:ignore + """目前生成第一轮测试项""" + tplTestDemandGenerate_path = Path.cwd() / "media" / project_path(id) / "form_template" / "dg" / "测试项及方法.docx" + doc = DocxTemplate(tplTestDemandGenerate_path) + # 获取指定的项目对象 + project_qs = get_object_or_404(Project, id=id) + # 先查询dict字典,查出总共有多少个testType + test_type_len = Dict.objects.get(code='testType').dictItem.count() + type_number_list = [i for i in range(1, test_type_len + 1)] + list_list = [[] for _ in range(1, test_type_len + 1)] + + # 查出第一轮所有testdemand + project_round_one = project_qs.pField.filter(key=0).first() + testDemand_qs = project_round_one.rtField.all() + + # 遍历第一轮测试项:默认是ID排序 + for single_qs in testDemand_qs: + type_index = type_number_list.index(int(single_qs.testType)) + # 先查询其testDemandContent信息 + content_list = [] + for (index, content) in enumerate(single_qs.testQField.all()): + content_dict = { + "index": index + 1, + "rindex": str(index + 1).rjust(2, '0'), + "subName": content.subName, + # 修改遍历content下面的step,content变量是TestDemandContent表 + "subStep": [ + {'index': index + 1, 'operation': step_obj.operation, 'expect': step_obj.expect} + for (index, step_obj) in enumerate(content.testStepField.all()) + ], + } + content_list.append(content_dict) + # 查询测试项中testMethod + testmethod_str = '' + for dict_item_qs in Dict.objects.get(code="testMethod").dictItem.all(): + for tm_item in single_qs.testMethod: + if tm_item == dict_item_qs.key: + testmethod_str += dict_item_qs.title + " " + # 富文本解析 + # ***Inspect-start:检查设计需求的描述是否为空*** + if single_qs.design.description == '': + design_info = single_qs.design.ident + '-' + single_qs.design.name + self.logger.write_warning_log('测试项', f'设计需求中的描述为空,请检查 -> {design_info}') + # ***Inspect-end*** + html_parser = RichParser(single_qs.design.description) + desc_list = html_parser.get_final_list(doc) + + # 查询关联design以及普通design + doc_list = [{'dut_name': single_qs.dut.name, 'design_chapter': single_qs.design.chapter, + 'design_name': single_qs.design.name}] + for relate_design in single_qs.otherDesign.all(): + ddict = {'dut_name': relate_design.dut.name, 'design_chapter': relate_design.chapter, + 'design_name': relate_design.name} + doc_list.append(ddict) + + # 组装单个测试项 + testdemand_dict = { + "name": single_qs.name, + "key": single_qs.key, + "ident": get_ident(single_qs), + "priority": get_str_dict(single_qs.priority, "priority"), + "doc_list": doc_list, + "design_description": desc_list, + "test_demand_content": content_list, + "testMethod": testmethod_str, + "adequacy": single_qs.adequacy.replace("\n", "\a"), + "testDesciption": single_qs.testDesciption.replace("\n", "\a") # 测试项描述 + } + list_list[type_index].append(testdemand_dict) + + # 定义渲染context字典 + context = { + "project_name": project_qs.name + } + output_list = [] + + for (index, li) in enumerate(list_list): + qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1)) + context_str = qs.title + sort = qs.sort + table = { + "type": context_str, + "item": li, + "sort": sort + } + output_list.append(table) + + # 排序1:测试类型排序 + output_list = sorted(output_list, key=(lambda x: x["sort"])) + + context["data"] = output_list + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / "测试项及方法.docx") + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + @route.get("/create/yiju", url_name='create-yiju') + @transaction.atomic + def create_yiju(self, id: int): + # 先找出所属项目 + project_qs = get_object_or_404(Project, id=id) + # 找出该项目的真实依据文件qs + yiju_list = get_list_dict('standard', project_qs.standard) + context = { + 'std_documents': yiju_list + } + return create_dg_docx('标准依据文件.docx', context, id) + + @route.get("/create/techyiju", url_name='create-techyiju') + @transaction.atomic + def create_techyiju(self, id: int): + # 找出所属项目 + project_qs = get_object_or_404(Project, id=id) + # 根据项目找出被测件-只找第一轮次 + duties_qs = project_qs.pdField.filter(Q(type='XQ') | Q(type='SJ') | Q(type='XY') | Q(type='YZ')).filter( + round__key='0') + # 先定义个字典 + std_documents = [] + for duty in duties_qs: + one_duty = {'doc_name': duty.name, 'ident_version': duty.ref + '-' + duty.version, + 'publish_date': duty.release_date, 'source': duty.release_union} + std_documents.append(one_duty) + + # 生成二级文档 + context = { + 'std_documents': std_documents + } + return create_dg_docx('技术依据文件.docx', context, id) + + @route.get("/create/contact", url_name='create-contact') + @transaction.atomic + def create_contact(self, id: int): + # 先找出所属项目 + project_qs = get_object_or_404(Project, id=id) + contact_dict = model_to_dict(project_qs, + fields=['entrust_unit', 'entrust_contact', 'entrust_contact_phone', 'dev_unit', + 'dev_contact', 'dev_contact_phone', 'test_unit', 'test_contact', + 'test_contact_phone']) + # 根据entrust_unit、dev_unit、test_unit查找Contact中地址信息 + entrust_addr = Contact.objects.get(name=contact_dict['entrust_unit']).addr + dev_addr = Contact.objects.get(name=contact_dict['dev_unit']).addr + test_addr = Contact.objects.get(name=contact_dict['test_unit']).addr + contact_dict['entrust_addr'] = entrust_addr + contact_dict['dev_addr'] = dev_addr + contact_dict['test_addr'] = test_addr + context = { + 'datas': contact_dict + } + return create_dg_docx('联系人和方式.docx', context, id) + + # 生成测评时间和地点 + @route.get('/create/timeaddress', url_name='create-timeaddress') + @transaction.atomic + def create_timeaddress(self, id: int): + doc_timer = DocTime(id) + context = doc_timer.dg_address_time() + return create_dg_docx('测评时间和地点.docx', context, id) + + # 生成【主要功能和性能指标】文档片段 + @route.get('/create/indicators', url_name='create-indicators') + @transaction.atomic + def create_indicators(self, id: int): + # 获取文档片段模版路径 + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '主要功能和性能指标.docx' + doc = DocxTemplate(input_path) + # 获取项目对象 + project_obj: Project = get_object_or_404(Project, id=id) + # 定义JINJA上下文 + # 获取第一轮次所有功能、性能的设计需求[目前只支持XQ和YZ],因为要获取dut信息进行连表查询 + q_ex = Q(dut__type='XQ') | Q(dut__type='YZ') + design_qs = project_obj.psField.filter(q_ex, round__key='0').select_related('dut') + # 1.功能性能覆盖表 + # 定义功能/性能两个列表 + func_design_list = [] + performance_design_list = [] + # 遍历设计需求,然后放入两个列表 + for design_obj in design_qs: + # 功能指标描述 + description = RichParser(design_obj.description).get_final_p_list() + # 覆盖情况-【查询测试项-测试子项名称】 + demand_qs = design_obj.dtField.all() + str_list = [] + for demand in demand_qs: + # 再查询测试子项 + for subDemand in demand.testQField.all(): + str_list.append(subDemand.subName) + coverage_str = "、".join(str_list) + design_context_obj = { + 'chapter_info': f"《{design_obj.dut.name}》{design_obj.chapter}-{design_obj.name}", + 'indicator': "\a".join(description), + 'coverage': f"对{design_obj.name}进行全覆盖测试,包含{coverage_str},验证所描述内容是否满足需求等文档的要求" + } + demandType_str = get_str_dict(design_obj.demandType, 'demandType') + # 判断是否包含“功能”/“性能”字样 + if '功能' in demandType_str: + func_design_list.append(design_context_obj) + elif '性能' in demandType_str: + performance_design_list.append(design_context_obj) + # 2.摸底指标清单 + # 先查第一轮次所有测试项 + is_has_modi = False + md_demand_list = [] + round1_demand_qs = project_obj.ptField.filter(round__key='0') + for one_demand in round1_demand_qs: + testType_str = get_str_dict(one_demand.testType, 'testType') + if '摸底' in testType_str: + is_has_modi = True + md_demand_list.append({ + 'xq_source': "隐含需求", + 'desc': one_demand.testDesciption, + 'demand_name': one_demand.name, + 'demand_ident': "".join(["XQ_MD_", one_demand.ident]) + }) + # 上下文添加 + context = { + 'project_name': project_obj.name, + 'func_design_list': func_design_list, + 'performance_design_list': performance_design_list, + 'md_demand_list': md_demand_list, + 'is_has_modi': is_has_modi + } + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '主要功能和性能指标.docx') + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + # 生成测评对象 - 包括大纲、说明、回归说明和报告 + @route.get('/create/softComposition', url_name='create-softComposition') + @transaction.atomic + def create_softComposition(self, id: int): + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '测评对象.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '测评对象') + context = { + "replace": replace, # 指定是否由数据库文档片段进行生成 + "user_content": frag and rich_text_list + } + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '测评对象.docx') + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + # 生成被测软件接口章节 + @route.get('/create/interface', url_name='create-interface') + def create_interface(self, id: int): + project_qs = get_object_or_404(Project, id=id) + project_name = project_qs.name + interfaceNameList = [] + # 查询接口列表 + iters = project_qs.psField.filter(demandType=3) + iters_length = len(iters) + index = 0 + for inter in iters: + interfaceNameList.append(inter.name) + index += 1 + if index < iters_length: + interfaceNameList.append('、') + # 对每个接口进行字典处理 + interface_list = [] + for interface in iters: + interface_dict = { + 'name': interface.name, + 'ident': interface.ident, + 'source': interface.source, + 'to': interface.to, + 'type': interface.type, + 'protocal': interface.protocal, + } + interface_list.append(interface_dict) + context = { + 'project_name': project_name, + 'iters': interfaceNameList, + 'iter_list': interface_list, + } + return create_dg_docx('被测软件接口.docx', context, id) + + # 生成顶层技术文件 + @route.get('/create/top_file', url_name='create-performance') + def create_top_file(self, id: int): + project_obj: Project = get_object_or_404(Project, id=id) + is_JD = True if project_obj.report_type == '9' else False + dut_qs = project_obj.pdField.filter(type='YZ') + dut_list = [{ + 'index': index + 2 if is_JD else index + 1, + 'name': dut_obj.name, + 'ident_and_version': '-'.join([dut_obj.ref, dut_obj.version]), + 'publish_date': dut_obj.release_date, + 'source': dut_obj.release_union, + } for index, dut_obj in enumerate(dut_qs)] + context = { + 'project_name': project_obj.name, + 'is_JD': is_JD, + 'dut_list': dut_list, + } + return create_dg_docx('顶层技术文件.docx', context, id) + + # 静态测试环境说明 + @route.get('/create/static_env', url_name='create-static_env') + def create_static_env(self, id: int): + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '静态测试环境说明.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '静态测试环境说明') + context = { + "replace": replace, # 指定是否由数据库文档片段进行生成 + "user_content": frag and rich_text_list + } + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '静态测试环境说明.docx') + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + # 静态软件项 + @route.get('/create/static_soft', url_name='create-static_soft') + def create_static_soft(self, id: int): + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '静态软件项.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '静态软件项') + context = { + "replace": replace, # 指定是否由数据库文档片段进行生成 + "user_content": frag and rich_text_list + } + return create_dg_docx("静态软件项.docx", context, id) + + # 静态硬件和固件项 + @route.get('/create/static_hard', url_name='create-static_hard') + def create_static_hard(self, id: int): + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '静态硬件和固件项.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '静态硬件和固件项') + context = { + "replace": replace, # 指定是否由数据库文档片段进行生成 + "user_content": frag and rich_text_list + } + return create_dg_docx("静态硬件和固件项.docx", context, id) + + # 动态测评环境说明 + @route.get('/create/dynamic_env', url_name='create-dynamic_env') + def create_dynamic_env(self, id: int): + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '动态测试环境说明.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '动态测试环境说明') + context = { + "replace": replace, + "user_content": frag and rich_text_list + } + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / '动态测试环境说明.docx') + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + # 动态软件项 + @route.get('/create/dynamic_soft', url_name='create-dynamic_soft') + def create_dynamic_soft(self, id: int): + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '动态软件项.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '动态软件项') + context = { + "replace": replace, + "user_content": frag and rich_text_list + } + return create_dg_docx("动态软件项.docx", context, id) + + # 动态软件项 + @route.get('/create/dynamic_hard', url_name='create-dynamic_hard') + def create_dynamic_hard(self, id: int): + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '动态硬件和固件项.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '动态硬件和固件项') + context = { + "replace": replace, + "user_content": frag and rich_text_list + } + return create_dg_docx("动态硬件和固件项.docx", context, id) + + # 测试数据 + @route.get('/create/test_data', url_name='create-test_data') + def create_test_data(self, id: int): + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '测评数据.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '测评数据') + context = { + "replace": replace, + "user_content": frag and rich_text_list + } + return create_dg_docx("测评数据.docx", context, id) + + # 环境差异性分析 + @route.get('/create/env_diff', url_name='create-env_diff') + def create_env_diff(self, id: int): + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '环境差异性分析.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '环境差异性分析') + context = { + "replace": replace, + "user_content": frag and rich_text_list + } + return create_dg_docx("环境差异性分析.docx", context, id) + + # 生成被测软件-基本信息 + @route.get('/create/baseInformation', url_name='create-baseInformation') + def create_information(self, id: int): + project_qs = get_object_or_404(Project, id=id) + security = get_str_dict(project_qs.security_level, 'security_level') + languages = get_list_dict('language', project_qs.language) + runtime = get_str_dict(project_qs.runtime, 'runtime') + devplant = get_str_dict(project_qs.devplant, 'devplant') + language_list = [] + for language in languages: + language_list.append(language.get('ident_version')) + # 版本先找第一轮 + project_round = project_qs.pField.filter(key=0).first() + first_round_SO = project_round.rdField.filter(type='SO').first() + if not first_round_SO: + return ChenResponse(code=400, status=400, message='您还未创建轮次,请进入工作区创建') + version = first_round_SO.version + line_count = int(first_round_SO.total_lines) + dev_unit = project_qs.dev_unit + # 渲染上下文 + context = { + 'project_name': project_qs.name, + 'security_level': security, + 'language': "\a".join(language_list), + 'version': version, + 'line_count': line_count, + 'effective_line': int(first_round_SO.effective_lines), + 'recv_date': project_qs.beginTime.strftime("%Y-%m-%d"), + 'dev_unit': dev_unit, + 'soft_type': project_qs.get_soft_type_display(), + 'runtime': runtime, + 'devplant': devplant + } + return create_dg_docx('被测软件基本信息.docx', context, id) + + # 生成测试级别和测试类型 + @route.get('/create/levelAndType', url_name='create-levelAndType') + def create_levelAndType(self, id: int): + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '测试级别和测试类型.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '测试级别和测试类型') + if replace: + context = { + "replace": replace, + "user_content": frag and rich_text_list + } + else: + # 如果没有片段替换,则利用数据生成信息 + project_qs = get_object_or_404(Project, id=id) + # 获取所有已录入测试类型 + test_types = project_qs.ptField.values("testType").distinct() + # 通过测试类型查询字典中的中文 + type_name_list = list(map(lambda qs_item: get_str_dict(qs_item['testType'], 'testType'), test_types)) + # 定义测试类型一览的顺序,注意word里面也要一样 + word_types = ['文档审查', '静态分析', '代码审查', '逻辑测试', '功能测试', '性能测试', '边界测试', + '恢复性测试', '安装性测试', '数据处理测试', '余量测试', '强度测试', '接口测试', + '人机交互界面测试', '兼容性测试'] + type_index = [] + for index, test_type in enumerate(word_types): + for exist_type in type_name_list: + if exist_type == test_type: + type_index.append(str(index)) + context = { + "testTypes": "、".join(type_name_list), + "project_name": project_qs.name, + "type_index": type_index + } + return create_dg_docx("测试级别和测试类型.docx", context, id) + + # 生成测试策略 + @route.get('/create/strategy', url_name='create-strategy') + def create_strategy(self, id: int): + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '测试策略.docx' + doc = DocxTemplate(input_path) + replace, frag, rich_text_list = self._generate_frag(id, doc, '测试策略') + if replace: + context = { + "replace": replace, + "user_content": frag and rich_text_list + } + else: + # 如果没有片段替换,则利用数据生成信息 + project_qs = get_object_or_404(Project, id=id) + # 根据关键等级检查是否有代码审查 + security = project_qs.security_level + isDmsc = True if int(security) <= 2 else False + # 获取当前测试项的测试类型 + test_types = project_qs.ptField.values("testType").distinct() + type_name_list = list(map(lambda qs_item: get_str_dict(qs_item['testType'], 'testType'), test_types)) + context = { + "project_name": project_qs.name, + # 查询关键等级-类似“关键”输出 + "security_level_str": get_str_abbr(security, 'security_level'), + "isDmsc": isDmsc, + "test_types": type_name_list + } + return create_dg_docx("测试策略.docx", context, id) + + # 生成-测试内容充分性及测试方法有效性 + @route.get('/create/adequacy_effectiveness', url_name='create-adequacy_effectiveness') + def create_adequacy_effectiveness(self, id: int): + project_qs = get_object_or_404(Project, id=id) + # 统计测试种类数量-只统计第一轮测试 + project_round_one = project_qs.pField.filter(key=0).first() + if not project_round_one: + return ChenResponse(status=400, code=400, message="未找到首轮测试信息!") + # 通过字典获取-测试方法 + type_dict = {} # key为测试类型,value为数量 + testDemands = project_round_one.rtField.all() + for testDemand in testDemands: + # 获取每个测试项测试类型 + test_type = get_list_dict('testType', [testDemand.testType])[0].get('ident_version') + # 如果字典没有该key,则创建并value=1 + if not test_type in type_dict: + type_dict[test_type] = 1 + else: + type_dict[test_type] += 1 + length = len(type_dict) + type_str_list = [] + for key, value in type_dict.items(): + type_str_list.append(f"{key}{value}项") + context = { + 'project_name': project_qs.name, + 'length': length, + 'type_str': "、".join(type_str_list), + } + return create_dg_docx('测试内容充分性及测试方法有效性分析.docx', context, id) + + # 生成-测评项目组组成和分工 + @route.get('/create/group', url_name='create_group') + def create_group(self, id: int): + project_qs = get_object_or_404(Project, id=id) + context = { + 'duty_person': project_qs.duty_person, + 'member_str': "、".join(project_qs.member), + 'quality_person': project_qs.quality_person, + 'vise_person': project_qs.vise_person, + 'config_person': project_qs.config_person, + 'dev_unit': project_qs.dev_unit, + } + return create_dg_docx('测评组织及任务分工.docx', context, id) + + # 生成-测评条件保障 + @route.get('/create/guarantee', url_name='create-guarantee') + def create_guarantee(self, id: int): + project_qs = get_object_or_404(Project, id=id) + context = { + 'project': project_qs + } + return create_dg_docx('测评条件保障.docx', context, id) + + # 生成-缩略语 + @route.get('/create/abbreviation', url_name='create-abbreviation') + def create_abbreviation(self, id: int): + project_qs = get_object_or_404(Project, id=id) + abbreviations = [] + for abbr in project_qs.abbreviation: + abbr_dict = {'title': abbr, 'des': Abbreviation.objects.filter(title=abbr).first().des} + abbreviations.append(abbr_dict) + context = { + 'abbreviations': abbreviations + } + return create_dg_docx('缩略语.docx', context, id) + + # 生成研制总要求-测试项追踪关系表 + @route.get('/create/yzComparison', url_name='create-yzComparison') + def create_yzComparison(self, id: int): + """目前追踪需求项的章节号是硬编码,按6.2章节起步,6.2.1~x.x.x依次排序""" + # 规定测试项的章节号开头 + test_item_prefix = '6.2' + # 计算有多少种testType - '文档审查'/'功能测试' -> + # 形成一个数组['1','2','3','4','9']后面用来判断测试项的章节号 + project_qs = get_object_or_404(Project, id=id) + design_list = [] # 先按照design的思路进行追踪 + # 判断是否为鉴定测评,有则生成该表 + if project_qs.report_type == '9': + project_round_one = project_qs.pField.filter(key=0).first() + testType_list, last_chapter_items = create_csx_chapter_dict(project_round_one) + # 找出第一轮的研总 + yz_dut = project_round_one.rdField.filter(type='YZ').first() + if yz_dut: + # 查询出验证所有design + yz_designs = yz_dut.rsField.all() + # 遍历所有研总的design + for design in yz_designs: + design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []} + # 获取一个design的所有测试项 + test_items = design.dtField.all() + for test_item in test_items: + reveal_ident = "_".join( + ["XQ", get_testType(test_item.testType, "testType"), test_item.ident]) + # 查字典方式确认章节号最后一位 + test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1 + test_chapter = ".".join([test_item_prefix, str(testType_list.index(test_item.testType) + 1), + str(test_item_last_chapter)]) + test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident} + design_dict['test_demand'].append(test_item_dict) + design_list.append(design_dict) + context = { + 'design_list': design_list + } + return create_dg_docx('研制总要求追踪表.docx', context, id) + + # 生成需求规格说明-测试项追踪关系表 + @route.get('/create/xqComparison', url_name='create-xqComparison') + def create_xqComparison(self, id: int): + project_qs = get_object_or_404(Project, id=id) + test_item_prefix = '6.2' + design_list = [] + project_round_one = project_qs.pField.filter(key=0).first() + if project_round_one: + testType_list, last_chapter_items = create_csx_chapter_dict(project_round_one) + # 找出第一轮的被测件为'XQ' + xq_dut = project_round_one.rdField.filter(type='XQ').first() + # 找出第一轮被测件为'SO',其中的测试项 + so_dut = project_round_one.rdField.filter(type='SO').first() + if so_dut: + so_designs = so_dut.rsField.all() + for design in so_designs: + design_dict = {'name': "/", 'chapter': "/", 'test_demand': []} + # 获取一个design的所有测试项 + test_items = [] + test_items.extend(design.dtField.all()) + test_items.extend(design.odField.all()) + + for test_item in test_items: + # 只对文档审查、静态分析、代码走查、代码审查进行处理 + if test_item.testType in ['8', '15', '3', '2']: + reveal_ident = "_".join( + ["XQ", get_testType(test_item.testType, "testType"), test_item.ident]) + # 查字典方式确认章节号最后一位 + test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1 + test_chapter = ".".join([test_item_prefix, str(testType_list.index(test_item.testType) + 1), + str(test_item_last_chapter)]) + test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident} + design_dict['test_demand'].append(test_item_dict) + design_list.append(design_dict) + + if xq_dut: + xq_designs = xq_dut.rsField.all() + for design in xq_designs: + design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []} + # 获取一个design的所有测试项 + test_items = [] + test_items.extend(design.dtField.all()) + test_items.extend(design.odField.all()) + + for test_item in test_items: + reveal_ident = "_".join( + ["XQ", get_testType(test_item.testType, "testType"), test_item.ident]) + # 查字典方式确认章节号最后一位 + test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1 + test_chapter = ".".join([test_item_prefix, str(testType_list.index(test_item.testType) + 1), + str(test_item_last_chapter)]) + test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident} + design_dict['test_demand'].append(test_item_dict) + + design_list.append(design_dict) + context = { + 'design_list': design_list + } + return create_dg_docx('需求规格说明追踪表.docx', context, id) + raise HttpError(400, "生成需求追踪表出错") + + # 生成测试项-需求规格说明关系表【反向】 + @route.get('/create/fanXqComparison', url_name='create-fanXqComparison') + def create_fanXqComparison(self, id: int): + project_qs = get_object_or_404(Project, id=id) + test_item_prefix = '6.2' + # 取出第一轮所有测试项的章节处理列表和字典 + project_round_one = project_qs.pField.filter(key=0).first() + testType_list, last_chapter_items = create_csx_chapter_dict(project_round_one) + # 查询第一轮所有测试项 + test_items = [] + test_items.extend(project_round_one.rtField.all()) + # 最后渲染列表 + items_list = [] + for test_item in test_items: + # 第二个处理被测件为"XQ",第二个处理被测件为'SO',并且为测试项testType为['8', '15', '3', '2']的 + if test_item.dut.type == 'XQ' or (test_item.dut.type == 'SO' and test_item.testType in ['8', '15', '3', + '2']): + reveal_ident = "_".join( + ["XQ", get_testType(test_item.testType, "testType"), test_item.ident]) + # 查字典方式确认章节号最后一位 + test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1 + test_chapter = ".".join([test_item_prefix, str(testType_list.index(test_item.testType) + 1), + str(test_item_last_chapter)]) + # 如果是SO里面的 + if test_item.testType in ['8', '15', '3', '2'] and test_item.dut.type == 'SO': + test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident, + 'design': { + 'name': "/", 'chapter': "/" + }} + else: + test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident, + 'design': { + 'name': test_item.design.name, 'chapter': test_item.design.chapter + }} + items_list.append(test_item_dict) + context = { + 'items_list': items_list, + } + return create_dg_docx('反向需求规格追踪表.docx', context, id) + + # 生成代码质量度量分析表 + @route.get('/create/codeQuality', url_name='create-codeQuality') + def create_codeQuality(self, id: int): + project_qs = get_object_or_404(Project, id=id) + project_round_one = project_qs.pField.filter(key=0).first() + context = {} + context.update({'project_name': project_qs.name}) + if project_round_one: + source_dut: Dut = project_round_one.rdField.filter(type='SO').first() # type:ignore + if source_dut: + context.update({'version': source_dut.version}) # type:ignore + context.update({'size': int(source_dut.total_lines)}) + context.update({'total_code_line': int(source_dut.effective_lines)}) + context.update({'comment_line': int(source_dut.comment_lines)}) + comment_ratio = int(source_dut.comment_lines) / int(source_dut.total_lines) + context.update({ + 'comment_ratio': f"{comment_ratio * 100:.2f}%", + 'comment_ratio_right': '满足' if comment_ratio >= 0.2 else '不满足' + }) + # 如果已经有metrics + if hasattr(source_dut, 'metrics'): + context.update({ + 'black_line': source_dut.metrics.total_blanks, + 'function_count': source_dut.metrics.function_count, + 'avg_function_lines': source_dut.metrics.avg_function_lines, + 'avg_function_lines_right': '满足' if source_dut.metrics.avg_function_lines <= 200 else '不满足', + 'avg_fan_out': source_dut.metrics.avg_fan_out, + 'avg_fan_out_right': '满足' if source_dut.metrics.avg_fan_out <= 7 else '不满足', + 'avg_cyclomatic': source_dut.metrics.avg_cyclomatic, + 'avg_cyclomatic_right': '满足' if source_dut.metrics.avg_cyclomatic <= 10 else '不满足', + 'max_cyclomatic': source_dut.metrics.max_cyclomatic, + 'max_cyclomatic_right': '满足' if source_dut.metrics.max_cyclomatic <= 80 else '不满足', + 'high_cyclomatic_ratio': source_dut.metrics.high_cyclomatic_ratio, + 'high_cyclomatic_ratio_right': '满足' if source_dut.metrics.high_cyclomatic_ratio <= 0.2 else '不满足', + }) + else: + return ChenResponse(message='未找到源代码被测件', code=400) + return create_dg_docx('代码质量度量分析表.docx', context, id) diff --git a/apps/createDocument/controllers/hjl.py b/apps/createDocument/controllers/hjl.py new file mode 100644 index 0000000..5b12821 --- /dev/null +++ b/apps/createDocument/controllers/hjl.py @@ -0,0 +1,212 @@ +from copy import deepcopy +from pathlib import Path +from ninja_extra import api_controller, ControllerBase, route +from ninja_extra.permissions import IsAuthenticated +from ninja_jwt.authentication import JWTAuth +from django.db import transaction +from django.db.models import QuerySet +from docxtpl import DocxTemplate +from apps.dict.models import Dict +from utils.chen_response import ChenResponse +from django.shortcuts import get_object_or_404 +from typing import Union +from docxtpl import InlineImage +from apps.project.models import Dut, Project, Round +from utils.util import get_list_dict, get_str_dict, get_ident, get_case_ident +from utils.chapter_tools.csx_chapter import create_csx_chapter_dict +from utils.path_utils import project_path +from apps.createDocument.extensions.util import delete_dir_files +from apps.createDocument.extensions.parse_rich_text import RichParser +# 导入生成日志记录模块 +from apps.createSeiTaiDocument.extensions.logger import GenerateLogger + +chinese_round_name: list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'] + +# @api_controller("/generateHSM", tags=['生成回归记录系列文档'], auth=JWTAuth(), permissions=[IsAuthenticated]) +@api_controller("/generateHJL", tags=['生成回归记录系列文档']) +class GenerateControllerHJL(ControllerBase): + logger = GenerateLogger('回归测试记录') + + # important:删除之前的文件 + @route.get('/create/deleteHJLDocument', url_name='delete-hjl-document') + def delete_hjl_document(self, id: int): + project_path_str = project_path(id) + save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl' + delete_dir_files(save_path) + + @route.get("/create/basicInformation", url_name="create-basicInformation") + @transaction.atomic + def create_basicInformation(self, id: int): + """生成回归测试记录的被测软件基本信息""" + project_path_str = project_path(id) + tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hjl' / '被测软件基本信息.docx' + doc = DocxTemplate(tpl_path) + project_obj = get_object_or_404(Project, id=id) + # 第一轮次对象 + round1_obj: Union[Round, None] = project_obj.pField.filter(key='0').first() + # 第一轮源代码被测件对象 + round1_so_dut: Union[Dut, None] = round1_obj.rdField.filter(type='SO').first() + languages = get_list_dict('language', project_obj.language) + language_list = [item['ident_version'] for item in languages] + # 取非第一轮次 + hround_list: QuerySet = project_obj.pField.exclude(key='0') + if len(hround_list) < 1: + # ***Inspect-start*** + self.logger.model = '回归测试记录' + self.logger.write_warning_log('当前文档全部片段', f'该项目没有创建轮次') + # ***Inspect-end*** + return ChenResponse(code=400, status=400, message='您未创建轮次,请创建完毕后再试') + + context = { + 'project_name': project_obj.name, + 'language': "、".join(language_list), + 'soft_type': project_obj.get_soft_type_display(), + 'security_level': get_str_dict(project_obj.security_level, 'security_level'), + 'runtime': get_str_dict(project_obj.runtime, 'runtime'), + 'devplant': get_str_dict(project_obj.devplant, 'devplant'), + 'recv_date': project_obj.beginTime.strftime("%Y-%m-%d"), + 'dev_unit': project_obj.dev_unit, + } + + version_info = [{'version': round1_so_dut.version, + 'line_count': int(round1_so_dut.total_lines), + 'effective_line': int(round1_so_dut.effective_lines)}] + # 循环回归的轮次 + for hround in hround_list: + # 每个轮次独立渲染context + context_round = deepcopy(context) + # 取中文名称 + cname = chinese_round_name[int(hround.key)] # 输出二、三... + # 取该轮次源代码版本放入版本列表 + so_dut: Dut = hround.rdField.filter(type='SO').first() + if not so_dut: + return ChenResponse(code=400, status=400, message=f'您第{cname}轮次中缺少源代码被测件,请添加') + version_info.append( + {'version': so_dut.version, 'line_count': int(so_dut.total_lines), + 'effective_line': int(so_dut.effective_lines)}) + context_round['version_info'] = version_info + # 开始渲染每个轮次的二级文档 + save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl' / f"第{cname}轮被测软件基本信息.docx" + doc.render(context=context_round) + try: + doc.save(save_path) + except PermissionError: + return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试') + return ChenResponse(code=200, status=200, message='多轮回归说明文档基本信息生成完毕') + + @route.get("/create/caseinfo", url_name="create-caseinfo") + @transaction.atomic + def create_caseinfo(self, id: int): + """生成回归测试记录的-{测试用例记录}""" + project_path_str = project_path(id) + tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hjl' / '测试用例记录.docx' + doc = DocxTemplate(tpl_path) + project_obj = get_object_or_404(Project, id=id) + hround_list: QuerySet = project_obj.pField.exclude(key='0') + if len(hround_list) < 1: + return None + demand_prefix = '3.1' + # 循环每轮轮次对象 + for hround in hround_list: + cname = chinese_round_name[int(hround.key)] # var:输出二、三字样 + test_type_len = Dict.objects.get(code='testType').dictItem.count() # 测试类型的个数 + type_number_list = [i for i in range(1, test_type_len + 1)] # 测试类型编号对应的列表 + list_list = [[] for j in range(1, test_type_len + 1)] # 每个测试类型组合为一个列表[[],[],[],[]] + testType_list, last_chapter_items = create_csx_chapter_dict(hround) + testDemands = hround.rtField.all() # 本轮所有测试项 + for demand in testDemands: + type_index = type_number_list.index(int(demand.testType)) + demand_ident = get_ident(demand) + # ~~~组装测试项~~~ + demand_last_chapter = last_chapter_items[demand.testType].index(demand.key) + 1 + demand_chapter = ".".join([demand_prefix, str(testType_list.index(demand.testType) + 1), + str(demand_last_chapter)]) + demand_dict = { + 'name': demand.name, + 'ident': demand_ident, + 'chapter': demand_chapter, + 'item': [] + } + # ~~~这里组装测试项里面的测试用例~~~ + for case in demand.tcField.all(): + step_list = [] + index = 1 + for one in case.step.all(): + # 这里需要对operation富文本处理 + rich_parser = RichParser(one.operation) + desc_list = rich_parser.get_final_list(doc, img_size=68) + rich_parser2 = RichParser(one.result) + res_list = rich_parser2.get_final_list(doc, img_size=75) + # 组装用例里面的步骤dict + passed = '通过' + if one.passed == '2': + passed = '未通过' + if one.passed == '3': + passed = '未执行' + step_dict = { + 'index': index, + 'operation': desc_list, + 'expect': one.expect, + 'result': res_list, + 'passed': passed, + } + step_list.append(step_dict) + index += 1 + # 查询所有的problem + problem_list = [] + problem_prefix = "PT" + proj_ident = project_obj.ident + for problem in case.caseField.all(): + problem_list.append("_".join([problem_prefix, proj_ident, problem.ident])) + # fpga的时序图 + rich_parser3 = RichParser(case.timing_diagram) + timing_diagram = rich_parser3.get_final_list(doc, img_size=115, height=50) + has_timing_diagram = False + if len(timing_diagram) > 0: + if isinstance(timing_diagram[0], InlineImage): + has_timing_diagram = True + # 组装用例的dict + case_dict = { + 'name': case.name, + 'ident': get_case_ident(demand_ident, case), + 'summary': case.summarize, + 'initialization': case.initialization, + 'premise': case.premise, + 'design_person': case.designPerson, + 'test_person': case.testPerson, + 'monitor_person': case.monitorPerson, + 'step': step_list, + 'time': str(case.exe_time) if case.exe_time is not None else str(case.update_datetime), + 'problems': "、".join(problem_list), + 'round_num_chn': cname, + # 2025年4月24日新增 + 'has_timing_diagram': has_timing_diagram, + 'timing_diagram': timing_diagram, + } + demand_dict['item'].append(case_dict) + + list_list[type_index].append(demand_dict) + # 定义渲染上下文 + context = {} + output_list = [] + for (index, li) in enumerate(list_list): + qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1)) + context_str = qs.title + sort = qs.sort + table = { + "type": context_str, + "item": li, + "sort": sort + } + output_list.append(table) + # 排序 + output_list = sorted(output_list, key=(lambda x: x["sort"])) + context["data"] = output_list + # 最后渲染 + save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl' / f"第{cname}轮测试用例记录.docx" + doc.render(context) + try: + doc.save(save_path) + except PermissionError: + return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试') + return ChenResponse(code=200, status=200, message='多轮回归测试用例记录生成完毕') diff --git a/apps/createDocument/controllers/hsm.py b/apps/createDocument/controllers/hsm.py new file mode 100644 index 0000000..9592163 --- /dev/null +++ b/apps/createDocument/controllers/hsm.py @@ -0,0 +1,602 @@ +import base64 +import io +from pathlib import Path +from copy import deepcopy +from typing import Union +from ninja_extra import api_controller, ControllerBase, route +from ninja_extra.permissions import IsAuthenticated +from ninja_jwt.authentication import JWTAuth +from ninja.errors import HttpError +from django.db import transaction +from django.shortcuts import get_object_or_404 +from django.db.models import QuerySet, Q +from docxtpl import DocxTemplate, RichText, InlineImage +from docx.shared import Mm +from docx import Document +# 导入模型 +from apps.project.models import Project, Round, Dut +from apps.dict.models import Dict, DictItem +# 导入项目工具 +from utils.util import get_list_dict, get_str_dict, MyHTMLParser, get_ident, get_case_ident, get_testType +from utils.chapter_tools.csx_chapter import create_csx_chapter_dict +from utils.chen_response import ChenResponse +from apps.createDocument.extensions import util +from utils.path_utils import project_path +from apps.createDocument.extensions.util import delete_dir_files +from apps.createDocument.extensions.parse_rich_text import RichParser +from apps.createDocument.extensions.documentTime import DocTime +# 导入生成日志记录模块 +from apps.createSeiTaiDocument.extensions.logger import GenerateLogger + +chinese_round_name: list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'] + +# @api_controller("/generateHSM", tags=['生成回归说明系列文档'], auth=JWTAuth(), permissions=[IsAuthenticated]) +@api_controller("/generateHSM", tags=['生成回归说明系列文档']) +class GenerateControllerHSM(ControllerBase): + logger = GenerateLogger('回归测试说明') + + # important:删除之前的文件 + @route.get('/create/deleteHSMDocument', url_name='delete-hsm-document') + def delete_hsm_document(self, id: int): + project_path_str = project_path(id) + save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' + try: + delete_dir_files(save_path) + except PermissionError: + return ChenResponse(code=400, status=400, message='另一个程序正在占用文件,请关闭后重试') + + @route.get("/create/basicInformation", url_name="create-basicInformation") + @transaction.atomic + def create_basicInformation(self, id: int): + """生成回归测试说明的被测软件基本信息""" + project_path_str = project_path(id) + tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hsm' / '被测软件基本信息.docx' + doc = DocxTemplate(tpl_path) + project_obj: Project = get_object_or_404(Project, id=id) + # 第一轮次对象 + round1_obj: Union[Round, None] = project_obj.pField.filter(key='0').first() + # 第一轮源代码被测件对象 + round1_so_dut: Union[Dut, None] = round1_obj.rdField.filter(type='SO').first() + languages = get_list_dict('language', project_obj.language) + language_list = [item['ident_version'] for item in languages] + # 取非第一轮次 + hround_list: QuerySet = project_obj.pField.exclude(key='0') + if len(hround_list) < 1: + # ***Inspect-start*** + self.logger.model = '回归测试说明' + self.logger.write_warning_log('当前文档全部片段', f'该项目没有创建轮次') + # ***Inspect-end*** + return ChenResponse(code=400, status=400, message='您未创建轮次,请创建完毕后再试') + + context = { + 'project_name': project_obj.name, + 'language': "、".join(language_list), + 'soft_type': project_obj.get_soft_type_display(), + 'security_level': get_str_dict(project_obj.security_level, 'security_level'), + 'runtime': get_str_dict(project_obj.runtime, 'runtime'), + 'devplant': get_str_dict(project_obj.devplant, 'devplant'), + 'recv_date': project_obj.beginTime.strftime("%Y-%m-%d"), + 'dev_unit': project_obj.dev_unit, + } + version_info = [{ + 'version': round1_so_dut.version, + 'line_count': round1_so_dut.total_lines, + 'effective_count': round1_so_dut.effective_lines, + }] + # 循环回归的轮次 + for hround in hround_list: + # 每个轮次独立渲染context + context_round = deepcopy(context) + # 取中文名称 + cname = chinese_round_name[int(hround.key)] # 输出二、三... + # 取该轮次源代码版本放入版本列表 + so_dut: Dut = hround.rdField.filter(type='SO').first() + if not so_dut: + return ChenResponse(code=400, status=400, message=f'您第{cname}轮次中缺少源代码被测件,请添加') + version_info.append( + { + 'version': so_dut.version, + 'line_count': so_dut.total_lines, + 'effective_count': so_dut.effective_lines, + } + ) + context_round['version_info'] = version_info + # 开始渲染每个轮次的二级文档 + save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"第{cname}轮被测软件基本信息.docx" + doc.render(context=context_round) + try: + doc.save(save_path) + except PermissionError: + return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试') + return ChenResponse(code=200, status=200, message='多轮回归说明文档基本信息生成完毕') + + @route.get("/create/docsummary", url_name="create-docsummary") + @transaction.atomic + def create_docsummary(self, id: int): + """生成回归测试说明的文档概述""" + project_path_str = project_path(id) + tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hsm' / '文档概述.docx' + doc = DocxTemplate(tpl_path) + project_obj = get_object_or_404(Project, id=id) + # 非第一轮轮次对象 + hround_list: QuerySet = project_obj.pField.exclude(key='0') + if len(hround_list) < 1: + return None + + context = { + 'project_obj': project_obj.name, + } + + for hround in hround_list: + # 取出当前轮次key减1就是上一轮次 + cname = chinese_round_name[int(hround.key)] # 输出二、三... + so_dut: Dut = hround.rdField.filter(type='SO').first() + if not so_dut: + return ChenResponse(code=400, status=400, message=f'您第{cname}轮次中缺少源代码被测件,请添加') + # 取上一轮次 + so_dut_last: Dut = Dut.objects.filter(round__key=str(int(hround.key) - 1), project=project_obj, + type='SO').first() + round_context = deepcopy(context) + round_context['current_version'] = so_dut.version + round_context['last_version'] = so_dut_last.version + round_context['round_chinese'] = cname + save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"第{cname}轮文档概述.docx" + doc.render(context=round_context) + try: + doc.save(save_path) + except PermissionError: + return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试') + return ChenResponse(code=200, status=200, message='多轮回归文档概述生成完毕') + + @route.get("/create/jstech", url_name="create-jstech") + @transaction.atomic + def create_jstech(self, id: int): + """生成回归测试说明的技术依据文件""" + project_path_str = project_path(id) + tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hsm' / '技术依据文件.docx' + doc = DocxTemplate(tpl_path) + project_obj = get_object_or_404(Project, id=id) + duties_qs = project_obj.pdField.filter(Q(type='XQ') | Q(type='SJ') | Q(type='XY')) + std_documents = [] + for duty in duties_qs: + one_duty = {'doc_name': duty.name, 'ident_version': duty.ref + '-' + duty.version, + 'publish_date': duty.release_date, 'source': duty.release_union} + std_documents.append(one_duty) + doc_name = f'{project_obj.name}软件测评大纲' + if project_obj.report_type == '9': + doc_name = f'{project_obj.name}软件鉴定测评大纲' + # 时间控制类 + timer = DocTime(id) + dg_duty = {'doc_name': doc_name, 'ident_version': f'PT-{project_obj.ident}-TO-1.00', + 'publish_date': timer.dg_cover_time, 'source': project_obj.test_unit} + std_documents.append(dg_duty) + # 需要添加说明、记录 + sm_duty = {'doc_name': f'{project_obj.name}软件测试说明', 'ident_version': f'PT-{project_obj.ident}-TD-1.00', + 'publish_date': timer.sm_cover_time, 'source': project_obj.test_unit} + jl_duty = {'doc_name': f'{project_obj.name}软件测试记录', 'ident_version': f'PT-{project_obj.ident}-TN', + 'publish_date': timer.jl_cover_time, 'source': project_obj.test_unit} + std_documents.extend([sm_duty, jl_duty]) + + # 非第一轮的轮次 + hround_list: QuerySet = project_obj.pField.exclude(key='0') + if len(hround_list) < 1: + return None + for hround in hround_list: + std_documents_round = deepcopy(std_documents) + # 取出当前轮次key + cname = chinese_round_name[int(hround.key)] + hsm_duty = {'doc_name': f'{project_obj.name}软件第{cname}轮测试说明', + 'ident_version': f'PT-{project_obj.ident}-TD{int(hround.key) + 1}-1.00', + 'publish_date': hround.beginTime, 'source': project_obj.test_unit} + hjl_duty = {'doc_name': f'{project_obj.name}软件第{cname}轮测试记录', + 'ident_version': f'PT-{project_obj.ident}-TN{int(hround.key) + 1}', + 'publish_date': hround.endTime, 'source': project_obj.test_unit} + std_documents.extend([hsm_duty, hjl_duty]) + context = { + 'std_documents': std_documents_round + } + save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"第{cname}轮技术依据文件.docx" + doc.render(context=context) + try: + doc.save(save_path) + except PermissionError: + return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试') + return ChenResponse(code=200, status=200, message='多轮回归技术依据文件生成完毕') + + @route.get("/create/changePart", url_name="create-changePart") + @transaction.atomic + def create_changePart(self, id: int): + """ + 生成回归测试说明的软件更改部分 + 暂时没想到如何处理和报告里面软件更改部分关系 + """ + project_path_str = project_path(id) + tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hsm' / '软件更改部分.docx' + doc = DocxTemplate(tpl_path) + project_obj = get_object_or_404(Project, id=id) + context = { + 'project_name': project_obj.name, + } + # 非第一轮的轮次 + hround_list: QuerySet = project_obj.pField.exclude(key='0') + if len(hround_list) < 1: + return None + for hround in hround_list: + context_round = deepcopy(context) + cname = chinese_round_name[int(hround.key)] # 输出二、三... + so_dut: Dut = hround.rdField.filter(type='SO').first() + if not so_dut: + return ChenResponse(code=400, status=400, message=f'您第{cname}轮次中缺少源代码被测件,请添加') + xq_dut: Dut = hround.rdField.filter(type='XQ').first() + # 处理代码版本 + last_round_key = str(int(hround.key) - 1) + last_round: Round = project_obj.pField.filter(key=last_round_key).first() + last_round_so_dut = last_round.rdField.filter(type='SO').first() + if not last_round_so_dut: + return ChenResponse(code=400, status=400, + message=f'您第{chinese_round_name[int(hround.key)]}轮次中缺少源代码版本信息,请添加') + last_dm_version = last_round_so_dut.version + now_dm_version = so_dut.version + # 如果存在这个轮次的需求文档,则查询上个版本 + last_xq_version = "" + if xq_dut: + last_xq_dut = last_round.rdField.filter(type='XQ').first() + if not last_xq_dut: + return ChenResponse(code=400, status=400, + message=f'您第{chinese_round_name[int(hround.key)]}轮次中缺少需求文档信息') + last_xq_version = last_xq_dut.version + # 如果当前轮次有需求文档的修改 + now_xq_version = xq_dut.version + context_round['xq_str'] = f",以及软件需求规格说明{now_xq_version}版本和{last_xq_version}版本" + else: + # 如果当前轮次没有需求文档则xq_str为空 + context_round['xq_str'] = "" + + context_round['so_str'] = f"被测软件代码{now_dm_version}版本和{last_dm_version}版本" + save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"第{cname}轮软件更改部分.docx" + doc.render(context_round) + try: + doc.save(save_path) + except PermissionError: + return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试') + return ChenResponse(code=200, status=200, message='多轮回归文档概述生成完毕') + + @route.get("/create/hdemand", url_name="create-hdemand") + @transaction.atomic + def create_hdemand(self, id: int): + """ + 生成非第一轮的多个测试需求 + """ + project_path_str = project_path(id) + tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hsm' / '回归测试需求.docx' + doc = DocxTemplate(tpl_path) + project_obj = get_object_or_404(Project, id=id) + # 非第一轮轮次对象 + hround_list: QuerySet = project_obj.pField.exclude(key='0') + if len(hround_list) < 1: + return None + # 遍历非第一轮的轮次 + for hround in hround_list: + cname = chinese_round_name[int(hround.key)] # var:输出二、三字样 + # 先查询dict字典,查出总共有多少个testType + test_type_len = Dict.objects.get(code='testType').dictItem.count() + type_number_list = [i for i in range(1, test_type_len + 1)] + list_list = [[] for j in range(1, test_type_len + 1)] + # 获得本轮次所有testDemand + testDemand_qs = hround.rtField.all() + for demand in testDemand_qs: + type_index = type_number_list.index(int(demand.testType)) + content_list = [] + for (index, content) in enumerate(demand.testQField.all()): + content_dict = { + "index": index + 1, + "rindex": str(index + 1).rjust(2, '0'), + "subName": content.subName, + # 修改遍历content下面的step,content变量是TestDemandContent表 + "subStep": [ + {'index': index + 1, 'operation': step_obj.operation, 'expect': step_obj.expect} + for (index, step_obj) in enumerate(content.testStepField.all()) + ], + } + content_list.append(content_dict) + testmethod_str = '' + for dict_item_qs in Dict.objects.get(code="testMethod").dictItem.all(): + for tm_item in demand.testMethod: + if tm_item == dict_item_qs.key: + testmethod_str += dict_item_qs.title + " " + # 设计需求的描述,富文本 + parser = RichParser(demand.design.description) + # 查询关联design以及普通design + doc_list = [{'dut_name': demand.dut.name, 'design_chapter': demand.design.chapter, + 'design_name': demand.design.name}] + for relate_design in demand.otherDesign.all(): + ddict = {'dut_name': relate_design.dut.name, 'design_chapter': relate_design.chapter, + 'design_name': relate_design.name} + doc_list.append(ddict) + # 组装单个测试项 + testdemand_dict = { + "name": demand.name, + "key": demand.key, + "ident": get_ident(demand), + "priority": get_str_dict(demand.priority, "priority"), + "doc_list": doc_list, + "design_description": parser.get_final_list(doc), + "test_demand_content": content_list, + "testMethod": testmethod_str, + "adequacy": demand.adequacy.replace("\n", "\a"), + "testDesciption": demand.testDesciption.replace("\n", "\a") # 测试项描述 + } + list_list[type_index].append(testdemand_dict) + # 定义渲染context字典 + context = { + "project_name": project_obj.name + } + output_list = [] + for (index, li) in enumerate(list_list): + qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1)) + context_str = qs.title + sort = qs.sort + table = { + "type": context_str, + "item": li, + "sort": sort + } + output_list.append(table) + # 排序 + output_list = sorted(output_list, key=(lambda x: x["sort"])) + context["data"] = output_list + save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"第{cname}轮回归测试需求.docx" + doc.render(context) + try: + doc.save(save_path) + except PermissionError: + return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试') + return ChenResponse(code=200, status=200, message='多轮回归测试需求生成完毕') + + @route.get("/create/caseListDesc", url_name="create-caseListDesc") + @transaction.atomic + def create_caseListDesc(self, id: int): + """ + 生成非第一轮的用例说明 + """ + project_path_str = project_path(id) + tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hsm' / '回归测试用例概述.docx' + doc = DocxTemplate(tpl_path) + project_obj = get_object_or_404(Project, id=id) + # 非第一轮轮次对象 + hround_list: QuerySet = project_obj.pField.exclude(key='0') + if len(hround_list) < 1: + return None + for hround in hround_list: + # 先查询dict字典,查出总共有多少个testType + test_type_len = Dict.objects.get(code='testType').dictItem.count() + type_number_list = [i for i in range(1, test_type_len + 1)] + list_list = [[] for j in range(1, test_type_len + 1)] + cname = chinese_round_name[int(hround.key)] # 输出二、三... + testDemands = hround.rtField.all() + for demand in testDemands: + type_index = type_number_list.index(int(demand.testType)) + demand_ident = get_ident(demand) + demand_dict = { + 'name': demand.name, + 'item': [] + } + for case in demand.tcField.all(): + case_dict = { + 'name': case.name, + 'ident': get_case_ident(demand_ident, case), + 'summary': case.summarize, + } + demand_dict['item'].append(case_dict) + list_list[type_index].append(demand_dict) + # 定义渲染上下文 + context = {} + output_list = [] + for (index, li) in enumerate(list_list): + qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1)) + sort = qs.sort + table = { + "item": li, + "sort": sort + } + output_list.append(table) + output_list = sorted(output_list, key=(lambda x: x["sort"])) + context["data"] = output_list + save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"第{cname}轮回归测试用例概述.docx" + doc.render(context=context) + try: + doc.save(save_path) + except PermissionError: + return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试') + return ChenResponse(code=200, status=200, message='多轮回归测试用例概述生成完毕') + + @route.get("/create/caseList", url_name="create-caseList") + @transaction.atomic + def create_caseList(self, id: int): + """ + 生成非第一轮的测试用例 + """ + project_path_str = project_path(id) + tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hsm' / '测试用例.docx' + doc = DocxTemplate(tpl_path) + project_obj = get_object_or_404(Project, id=id) + # 非第一轮轮次对象 + hround_list: QuerySet = project_obj.pField.exclude(key='0') + if len(hround_list) < 1: + return None + for hround in hround_list: + cname = chinese_round_name[int(hround.key)] # 输出二、三... + # 先查询dict字典,查出总共有多少个testType + test_type_len = Dict.objects.get(code='testType').dictItem.count() + type_number_list = [i for i in range(1, test_type_len + 1)] + list_list = [[] for j in range(1, test_type_len + 1)] + demand_prefix = '3.1' + testType_list, last_chapter_items = create_csx_chapter_dict(hround) + testDemands = hround.rtField.all() + # 首先轮询所有测试需求 + for demand in testDemands: + type_index = type_number_list.index(int(demand.testType)) + demand_ident = get_ident(demand) + # ~~~~~这里组装测试项~~~~~ + ## 确定测试需求章节号(后面可提取后进行复用) + demand_last_chapter = last_chapter_items[demand.testType].index(demand.key) + 1 + demand_chapter = ".".join([demand_prefix, str(testType_list.index(demand.testType) + 1), + str(demand_last_chapter)]) + demand_dict = { + 'name': demand.name, + 'ident': demand_ident, + 'chapter': demand_chapter, + 'item': [] + } + # ~~~这里组装测试项里面的测试用例~~~ + for case in demand.tcField.all(): + step_list = [] + index = 1 + for one in case.step.all(): + # 这里需要对operation富文本处理 + rich_parser = RichParser(one.operation) + desc_list = rich_parser.get_final_list(doc, img_size=70) + step_dict = { + 'index': index, + 'operation': desc_list, + 'expect': one.expect, + } + step_list.append(step_dict) + index += 1 + + case_dict = { + 'name': case.name, + 'ident': get_case_ident(demand_ident, case), + 'summary': case.summarize, + 'initialization': case.initialization, + 'premise': case.premise, + 'design_person': case.designPerson, + 'step': step_list + } + demand_dict['item'].append(case_dict) + + list_list[type_index].append(demand_dict) + # 定义渲染上下文 + context = {} + output_list = [] + for (index, li) in enumerate(list_list): + qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1)) + context_str = qs.title + sort = qs.sort + table = { + "type": context_str, + "item": li, + "sort": sort + } + output_list.append(table) + # 排序 + output_list = sorted(output_list, key=(lambda x: x["sort"])) + context["data"] = output_list + context["round_han"] = cname + save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hsm' / f"第{cname}轮测试用例.docx" + doc.render(context=context) + try: + doc.save(save_path) + except PermissionError: + return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试') + return ChenResponse(code=200, status=200, message='多轮测试用例生成完毕') + + @route.get("/create/track", url_name="create-track") + @transaction.atomic + def create_track(self, id: int): + """ + 生成非第一轮的用例追踪 + """ + project_path_str = project_path(id) + project_obj = get_object_or_404(Project, id=id) + # 非第一轮轮次对象 + hround_list: QuerySet = project_obj.pField.exclude(key='0') + demand_prefix = '4.1' + if len(hround_list) < 1: + return + for hround in hround_list: + # 取出当前轮次key减1就是上一轮次 + cname = chinese_round_name[int(hround.key)] # 输出二、三... + design_list = [] + testType_list, last_chapter_items = create_csx_chapter_dict(hround) + # 找出当前轮次被测件为'SO'的 + so_dut = hround.rdField.filter(type='SO').first() + if not so_dut: + return ChenResponse(code=400, status=400, message=f'第{cname}轮次无源代码被测件') + so_designs = so_dut.rsField.filter() + for design in so_designs: + design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []} + test_items = [] + test_items.extend(design.dtField.all()) + test_items.extend(design.odField.all()) + for test_item in test_items: + if test_item.testType in ['2', '3', '15', '8']: + design_dict.update({'name': "/", 'chapter': "/"}) + reveal_ident = "_".join( + ["XQ", get_testType(test_item.testType, "testType"), test_item.ident]) + # 查字典方式确认章节号最后一位 + test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1 + test_chapter = ".".join([demand_prefix, str(testType_list.index(test_item.testType) + 1), + str(test_item_last_chapter)]) + test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident, + 'case_list': []} + for case in test_item.tcField.all(): + case_dict = { + 'name': case.name, + 'ident': get_case_ident(reveal_ident, case) + } + test_item_dict['case_list'].append(case_dict) + design_dict['test_demand'].append(test_item_dict) + design_list.append(design_dict) + # 找出当前轮次的被测件为'XQ'的第一个 + xq_dut = hround.rdField.filter(type='XQ').first() + if not xq_dut: + return ChenResponse(code=400, status=400, + message=f'第{cname}轮次没有找到需求被测件,只有放在被测件为<需求>的设计需求、测试项、用例才会被追踪') + xq_designs = xq_dut.rsField.all() + for design in xq_designs: + design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []} + test_items = [] + test_items.extend(design.dtField.all()) + test_items.extend(design.odField.all()) + for test_item in test_items: + reveal_ident = "_".join( + ["XQ", get_testType(test_item.testType, "testType"), test_item.ident]) + # 查字典方式确认章节号最后一位 + test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1 + test_chapter = ".".join([demand_prefix, str(testType_list.index(test_item.testType) + 1), + str(test_item_last_chapter)]) + test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident, + 'case_list': []} + for case in test_item.tcField.all(): + case_dict = { + 'name': case.name, + 'ident': get_case_ident(reveal_ident, case) + } + test_item_dict['case_list'].append(case_dict) + design_dict['test_demand'].append(test_item_dict) + design_list.append(design_dict) + context = { + 'design_list': design_list, + } + + # 手动渲染tpl生成文档 + input_file = Path.cwd() / 'media' / project_path_str / 'form_template' / 'hsm' / '用例追踪.docx' + temporary_file = Path.cwd() / 'media' / project_path_str / 'form_template' / 'hsm' / 'temporary' / f'第{cname}轮用例追踪_temp.docx' + out_put_file = Path.cwd() / 'media' / project_path_str / 'output_dir' / 'hsm' / f'第{cname}轮用例追踪.docx' + doc = DocxTemplate(input_file) + doc.render(context) + doc.save(temporary_file) + # 通过docx合并单元格 + if temporary_file.is_file(): + try: + docu = Document(temporary_file) + # 找到其中的表格 + util.merge_all_cell(docu.tables[0]) + # 储存到合适位置 + docu.save(out_put_file) + except PermissionError: + return ChenResponse(code=400, status=400, message='请检查文件是否打开,如果打开则关闭...') + else: + return ChenResponse(code=400, status=400, message='中间文档未找到,请检查你模版是否存在...') + return ChenResponse(code=200, status=200, message='文档生成成功...') diff --git a/apps/createDocument/controllers/jl.py b/apps/createDocument/controllers/jl.py new file mode 100644 index 0000000..c1c1da0 --- /dev/null +++ b/apps/createDocument/controllers/jl.py @@ -0,0 +1,154 @@ +# 内置模块导入 +from pathlib import Path +# 导入框架相关 +from django.db import transaction +from django.shortcuts import get_object_or_404 +from ninja_extra import api_controller, ControllerBase, route +# 导入权限相关 +from ninja_extra.permissions import IsAuthenticated +from ninja_jwt.authentication import JWTAuth +# 导入数据库模型 +from apps.project.models import Project +from apps.dict.models import Dict +from docxtpl import InlineImage +# 导入文档处理相关 +from docxtpl import DocxTemplate +# 导入自己工具 +from utils.chapter_tools.csx_chapter import create_csx_chapter_dict +from utils.util import get_ident, get_case_ident, get_testType +from utils.chen_response import ChenResponse +from utils.path_utils import project_path +from apps.createDocument.extensions.parse_rich_text import RichParser + +# @api_controller("/generateJL", tags=['生成测试记录系列'], auth=JWTAuth(), permissions=[IsAuthenticated]) +@api_controller("/generateJL", tags=['生成测试记录系列']) +class GenerateControllerJL(ControllerBase): + @route.get("/create/caserecord", url_name="create-caserecord") + @transaction.atomic + def create_caserecord(self, id: int): + """生成测试记录表格""" + project_path_str = project_path(id) + project_obj = get_object_or_404(Project, id=id) + # 测试用例记录模版位置 + record_template_path = Path.cwd() / 'media' / project_path_str / 'form_template/jl' / '测试用例记录.docx' + # 打开模版文档 + doc = DocxTemplate(record_template_path) + # 准备返回的测试类型数组的嵌套 + test_type_len = Dict.objects.get(code='testType').dictItem.count() # 测试类型的个数 + type_number_list = [i for i in range(1, test_type_len + 1)] # 测试类型编号对应的列表 + list_list = [[] for j in range(1, test_type_len + 1)] # 每个测试类型组合为一个列表[[],[],[],[]] + # 查询出第一轮次 + round_one = project_obj.pField.filter(key=0).first() + # 测试项的章节号预置处理 - 根据轮次生成list和dict的二维数据 + demand_prefix = '6.2' + testType_list, last_chapter_items = create_csx_chapter_dict(round_one) + # 找出所有测试项 + testDemands = round_one.rtField.all() + # 首先轮询所有测试需求 + for demand in testDemands: + type_index = type_number_list.index(int(demand.testType)) + demand_ident = get_ident(demand) + # ~~~组装测试项~~~ + demand_last_chapter = last_chapter_items[demand.testType].index(demand.key) + 1 + demand_chapter = ".".join([demand_prefix, str(testType_list.index(demand.testType) + 1), + str(demand_last_chapter)]) + demand_dict = { + 'name': demand.name, + 'ident': demand_ident, + 'chapter': demand_chapter, + 'item': [] + } + # ~~~这里组装测试项里面的测试用例~~~ + for case in demand.tcField.all(): + step_list = [] + index = 1 + for one in case.step.all(): + # 这里需要对operation富文本处理 + rich_parser = RichParser(one.operation) + desc_list = rich_parser.get_final_list(doc, img_size=68) + # 这里需要对result富文本处理 + rich_parser2 = RichParser(one.result) + res_list = rich_parser2.get_final_list(doc, img_size=75) + # 组装用例里面的步骤dict + passed = '通过' + if one.passed == '2': + passed = '未通过' + elif one.passed == '3': + passed = '未执行' + step_dict = { + 'index': index, + 'operation': desc_list, + 'expect': one.expect, + 'result': res_list, + 'passed': passed, + } + index += 1 + step_list.append(step_dict) + # 这里判断里面的单个步骤的执行情况,来输出一个整个用例的执行情况 + exe_noncount = 0 + execution_str = '已执行' + for ste in step_list: + if ste.get('execution') == '3': + exe_noncount += 1 + if exe_noncount > 0 and exe_noncount != len(step_list): + execution_str = '部分执行' + elif exe_noncount == len(step_list): + execution_str = '未执行' + else: + execution_str = '已执行' + # 查询所有的problem + problem_list = [] + problem_prefix = "PT" + proj_ident = project_obj.ident + for problem in case.caseField.all(): + problem_list.append("_".join([problem_prefix, proj_ident, problem.ident])) + # 组装用例的dict + rich_parser3 = RichParser(case.timing_diagram) + timing_diagram = rich_parser3.get_final_list(doc, img_size=115, height=50) + has_timing_diagram = False + if len(timing_diagram) > 0: + if isinstance(timing_diagram[0], InlineImage): + has_timing_diagram = True + case_dict = { + 'name': case.name, + 'ident': get_case_ident(demand_ident, case), + 'summary': case.summarize, + 'initialization': case.initialization, + 'premise': case.premise, + 'design_person': case.designPerson, + 'test_person': case.testPerson, + 'monitor_person': case.monitorPerson, + 'step': step_list, + 'execution': execution_str, + 'time': str(case.exe_time) if case.exe_time is not None else str(case.update_datetime), + 'problems': "、".join(problem_list), + # 2025年4月24日新增 + 'has_timing_diagram': has_timing_diagram, + 'timing_diagram': timing_diagram, + } + demand_dict['item'].append(case_dict) + + list_list[type_index].append(demand_dict) + # 定义渲染上下文 + context = {} + output_list = [] + for (index, li) in enumerate(list_list): + qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1)) # type:ignore + context_str = qs.title + sort = qs.sort + table = { + "type": context_str, + "item": li, + "sort": sort + } + output_list.append(table) + # 排序 + output_list = sorted(output_list, key=(lambda x: x["sort"])) + context["data"] = output_list + + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path_str / "output_dir/jl" / "测试用例记录.docx") + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) diff --git a/apps/createDocument/controllers/sm.py b/apps/createDocument/controllers/sm.py new file mode 100644 index 0000000..7d92336 --- /dev/null +++ b/apps/createDocument/controllers/sm.py @@ -0,0 +1,282 @@ +from pathlib import Path +# 导入框架东西 +from ninja_extra import ControllerBase, api_controller, route +# 框架组件 +from django.db import transaction +from django.shortcuts import get_object_or_404 +from django.db.models import Q +# 认证和权限 +from ninja_extra.permissions import IsAuthenticated +from ninja_jwt.authentication import JWTAuth +# 导入模型 +from apps.project.models import Project +from apps.dict.models import Dict +# 导入文档处理类 +from docxtpl import DocxTemplate +from docx import Document +# 导入自己工具 +from utils.chen_response import ChenResponse +from utils.util import get_ident, get_case_ident, get_testType +from utils.chapter_tools.csx_chapter import create_csx_chapter_dict +from apps.createDocument.extensions import util +from apps.createDocument.extensions.util import create_sm_docx +from utils.path_utils import project_path +from apps.createDocument.extensions.parse_rich_text import RichParser +from apps.createDocument.extensions.documentTime import DocTime + +# @api_controller("/generateSM", tags=['生成说明文档系列'], auth=JWTAuth(), permissions=[IsAuthenticated]) +@api_controller("/generateSM", tags=['生成说明文档系列']) +class GenerateControllerSM(ControllerBase): + @route.get("/create/techyiju", url_name="create-techyiju") + @transaction.atomic + def create_techyiju(self, id: int): + project_obj = get_object_or_404(Project, id=id) + duties_qs = project_obj.pdField.filter(Q(type='XQ') | Q(type='SJ') | Q(type='XY')) + std_documents = [] + for duty in duties_qs: + one_duty = {'doc_name': duty.name, 'ident_version': duty.ref + '-' + duty.version, + 'publish_date': duty.release_date, 'source': duty.release_union} + std_documents.append(one_duty) + # 添加大纲到这里 + ## 判断是否为鉴定 + doc_name = f'{project_obj.name}软件测评大纲' + if project_obj.report_type == '9': + doc_name = f'{project_obj.name}软件鉴定测评大纲' + # 这里大纲版本升级如何处理 - TODO:1.大纲版本升级后版本处理 + # 时间控制类 + timer = DocTime(id) + dg_duty = {'doc_name': doc_name, 'ident_version': f'PT-{project_obj.ident}-TO-1.00', + 'publish_date': timer.dg_cover_time, 'source': project_obj.test_unit} + std_documents.append(dg_duty) + # 生成二级文档 + context = { + 'std_documents': std_documents + } + return create_sm_docx("技术依据文件.docx", context, id) + + @route.get('/create/caseList', url_name='create-caseList') + @transaction.atomic + def create_caseList(self, id: int): + """创建第一轮文档""" + project_path_str = project_path(id) + # 生成测试用例需要doc对象 + case_template_doc_path = Path.cwd() / 'media' / project_path_str / 'form_template/sm' / '测试用例.docx' + doc = DocxTemplate(case_template_doc_path) + project_obj = get_object_or_404(Project, id=id) + # 先查询dict字典,查出总共有多少个testType + test_type_len = Dict.objects.get(code='testType').dictItem.count() + type_number_list = [i for i in + range(1, test_type_len + 1)] # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + list_list = [[] for j in + range(1, test_type_len + 1)] # [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []] + # 先找到第一轮次 + project_round_one = project_obj.pField.filter(key=0).first() + # 测试项的章节号预置处理 + demand_prefix = '6.2' + testType_list, last_chapter_items = create_csx_chapter_dict(project_round_one) + + testDemands = project_round_one.rtField.all() + # 首先轮询所有测试需求 + for demand in testDemands: + type_index = type_number_list.index(int(demand.testType)) + demand_ident = get_ident(demand) + # ~~~~~这里组装测试项~~~~~ + ## 确定测试需求章节号(后面可提取后进行复用) + demand_last_chapter = last_chapter_items[demand.testType].index(demand.key) + 1 + demand_chapter = ".".join([demand_prefix, str(testType_list.index(demand.testType) + 1), + str(demand_last_chapter)]) + demand_dict = { + 'name': demand.name, + 'ident': demand_ident, + 'chapter': demand_chapter, + 'item': [] + } + # ~~~这里组装测试项里面的测试用例~~~ + for case in demand.tcField.all(): + step_list = [] + index = 1 + for one in case.step.all(): + # 这里需要对operation富文本处理 + rich_parser = RichParser(one.operation) + desc_list = rich_parser.get_final_list(doc, img_size=70) + step_dict = { + 'index': index, + 'operation': desc_list, + 'expect': one.expect, + } + step_list.append(step_dict) + index += 1 + + case_dict = { + 'name': case.name, + 'ident': get_case_ident(demand_ident, case), + 'summary': case.summarize, + 'initialization': case.initialization, + 'premise': case.premise, + 'design_person': case.designPerson, + 'step': step_list + } + demand_dict['item'].append(case_dict) + + list_list[type_index].append(demand_dict) + # 定义渲染上下文 + context = {} + output_list = [] + for (index, li) in enumerate(list_list): + qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1)) + context_str = qs.title + sort = qs.sort + table = { + "type": context_str, + "item": li, + "sort": sort + } + output_list.append(table) + # 排序 + output_list = sorted(output_list, key=(lambda x: x["sort"])) + context["data"] = output_list + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path_str / "output_dir/sm" / "测试用例.docx") + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + @route.get('/create/caseBreifList', url_name='create-caseBreifList') + @transaction.atomic + def create_caseBreifList(self, id: int): + # 生成第一轮的测试说明 + project_obj = get_object_or_404(Project, id=id) + # 先查询dict字典,查出总共有多少个testType + test_type_len = Dict.objects.get(code='testType').dictItem.count() + type_number_list = [i for i in + range(1, test_type_len + 1)] # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + list_list = [[] for j in + range(1, test_type_len + 1)] # [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []] + # 先找到第一轮次 + project_round_one = project_obj.pField.filter(key=0).first() + # 找到第一轮的全部测试项 + testDemands = project_round_one.rtField.all() + for demand in testDemands: + type_index = type_number_list.index(int(demand.testType)) + demand_ident = get_ident(demand) + demand_dict = { + 'name': demand.name, + 'item': [] + } + for case in demand.tcField.all(): + case_dict = { + 'name': case.name, + 'ident': get_case_ident(demand_ident, case), + 'summary': case.summarize, + } + demand_dict['item'].append(case_dict) + list_list[type_index].append(demand_dict) + # 定义渲染上下文 + context = {} + output_list = [] + for (index, li) in enumerate(list_list): + qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1)) + sort = qs.sort + table = { + "item": li, + "sort": sort + } + output_list.append(table) + output_list = sorted(output_list, key=(lambda x: x["sort"])) + context["data"] = output_list + return create_sm_docx("用例说明.docx", context, id) + + @route.get('/create/smtrack', url_name='create-smtrack') + @transaction.atomic + def create_smtrack(self, id: int): + """生成说明的需求追踪表""" + project_path_str = project_path(id) + project_obj = get_object_or_404(Project, id=id) + demand_prefix = '6.2' + design_list = [] + project_round_one = project_obj.pField.filter(key='0').first() + if project_round_one: + testType_list, last_chapter_items = create_csx_chapter_dict(project_round_one) + # 找出第一轮被测件为'SO'的 + so_dut = project_round_one.rdField.filter(type='SO').first() + if so_dut: + so_designs = so_dut.rsField.all() + for design in so_designs: + design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []} + test_items = [] + test_items.extend(design.dtField.all()) + test_items.extend(design.odField.all()) + for test_item in test_items: + # 对4个测试类型单独处理:因为这4类肯定没有章节号 + if test_item.testType in ['2', '3', '15', '8']: + design_dict.update({'name': "/", 'chapter': "/"}) + reveal_ident = "_".join( + ["XQ", get_testType(test_item.testType, "testType"), test_item.ident]) + # 查字典方式确认章节号最后一位 + test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1 + test_chapter = ".".join([demand_prefix, str(testType_list.index(test_item.testType) + 1), + str(test_item_last_chapter)]) + test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident, + 'case_list': []} + for case in test_item.tcField.all(): + case_dict = { + 'name': case.name, + 'ident': get_case_ident(reveal_ident, case) + } + test_item_dict['case_list'].append(case_dict) + design_dict['test_demand'].append(test_item_dict) + design_list.append(design_dict) + + # 上面找出了源代码被测件,这里找XQ被测件 + xq_dut = project_round_one.rdField.filter(type='XQ').first() + if xq_dut: + xq_designs = xq_dut.rsField.all() + for design in xq_designs: + design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []} + # 获取一个design的所有测试项 + # 注意:这里有关联测试项!!!需要多对多关系拼接 + test_items = [] + test_items.extend(design.dtField.all()) + test_items.extend(design.odField.all()) + for test_item in test_items: + reveal_ident = "_".join( + ["XQ", get_testType(test_item.testType, "testType"), test_item.ident]) + # 查字典方式确认章节号最后一位 + test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1 + test_chapter = ".".join([demand_prefix, str(testType_list.index(test_item.testType) + 1), + str(test_item_last_chapter)]) + test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident, + 'case_list': []} + for case in test_item.tcField.all(): + case_dict = { + 'name': case.name, + 'ident': get_case_ident(reveal_ident, case) + } + test_item_dict['case_list'].append(case_dict) + design_dict['test_demand'].append(test_item_dict) + design_list.append(design_dict) + context = { + 'design_list': design_list, + } + + # 手动渲染tpl生成文档 + input_file = Path.cwd() / 'media' / project_path_str / 'form_template' / 'sm' / '说明追踪.docx' + temporary_file = Path.cwd() / 'media' / project_path_str / 'form_template' / 'sm' / 'temporary' / '说明追踪_temp.docx' + out_put_file = Path.cwd() / 'media' / project_path_str / 'output_dir' / 'sm' / '说明追踪.docx' + doc = DocxTemplate(input_file) + doc.render(context) + doc.save(temporary_file) + # 通过docx合并单元格 + if temporary_file.is_file(): + try: + docu = Document(temporary_file) + # 找到其中的表格 + util.merge_all_cell(docu.tables[0]) + # 储存到合适位置 + docu.save(out_put_file) + return ChenResponse(code=200, status=200, message='文档生成成功...') + except PermissionError as e: + return ChenResponse(code=400, status=400, message='请检查文件是否打开,如果打开则关闭...') + else: + return ChenResponse(code=400, status=400, message='中间文档未找到,请检查你模版是否存在...') diff --git a/apps/createDocument/controllers/wtd.py b/apps/createDocument/controllers/wtd.py new file mode 100644 index 0000000..3fa9ee7 --- /dev/null +++ b/apps/createDocument/controllers/wtd.py @@ -0,0 +1,155 @@ +# 导入内置模块 +from pathlib import Path +# 导入django、ninja等模块 +from ninja_extra import api_controller, ControllerBase, route +from django.db import transaction +from django.shortcuts import get_object_or_404 +# 导入文档处理模块 +from docxtpl import DocxTemplate, InlineImage +# 导入ORM模型 +from apps.project.models import Project +# 导入工具 +from utils.util import get_str_abbr, get_str_dict +from utils.chen_response import ChenResponse +from utils.path_utils import project_path +from apps.createDocument.extensions.parse_rich_text import RichParser +# 导入生成日志记录模块 +from apps.createSeiTaiDocument.extensions.logger import GenerateLogger + +gloger = GenerateLogger("问题单二段文档") + +# @api_controller("/generateWtd", tags=['生成问题单文档系列'], auth=JWTAuth(), permissions=[IsAuthenticated]) +@api_controller('/generateWtd', tags=['生成问题单文档系列']) +class GenerateControllerWtd(ControllerBase): + @route.get("/create/problem", url_name="create-problem") + @transaction.atomic + def create_problem(self, id: int): + """生成问题单""" + project_path_str = project_path(id) + tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/wtd' / '问题详情表.docx' + doc = DocxTemplate(tpl_path) + project_obj = get_object_or_404(Project, id=id) + problem_list = list(project_obj.projField.distinct()) # 去掉重复,因为和case是多对多 + problem_list.sort(key=lambda x: int(x.ident)) + data_list = [] + for problem in problem_list: + problem_dict = {'ident': problem.ident, 'name': problem.name} + # 1.生成被测对象名称、被测对象标识、被测对象版本 + cases = problem.case.all() + # generate_log:无关联问题单进入生成日志 + if cases.count() < 1: + gloger.write_warning_log('单个问题单表格', f'问题单{problem.ident}未关联用例,请检查') + str_dut_name_list = [] + str_dut_ident_list = [] + str_dut_version_list = [] + # 2.所属用例标识 + case_ident_list = [] + # 3.获取依据要求 + case_design_list = [] + for case in cases: + if case.test.testType == '8': + # 1.1.如果为文档审查,提取所属文档名称、文档被测件标识、文档被测件版本 + str_dut_name_list.append(case.dut.name) + str_dut_ident_list.append(case.dut.ref) + str_dut_version_list.append(case.dut.version) + # 对应dut名称,design章节号,design描述 + case_design_list.append("".join([case.dut.name, case.design.chapter])) + else: + # 1.2.如果不为文档审查,则提取该轮次源代码dut的信息 + so_dut = case.round.rdField.filter(type='SO').first() + if so_dut: + str_dut_name_list.append(project_obj.name + '软件') + str_dut_ident_list.append(so_dut.ref) + str_dut_version_list.append(so_dut.version) + # TODO:如何处理设计需求的内容,暂时设置为取出图片,只保留文字 + p_list = [] + rich_parse_remove_img = RichParser(case.design.description) + rich_list = rich_parse_remove_img.get_final_list(doc) + for rich in rich_list: + if isinstance(rich, dict) or isinstance(rich, InlineImage): + continue + else: + p_list.append(rich) + + case_design_list.append( + "-".join([case.dut.name, case.design.chapter + '章节' + ":" + ''.join(p_list)])) + # 2.用例标识修改-YL_测试项类型_测试项标识_用例key+1 + demand = case.test # 中间变量 + demand_testType = demand.testType # 中间变量 + testType_abbr = get_str_abbr(demand_testType, 'testType') # 输出FT + case_ident_list.append("_".join( + ['YL', testType_abbr, demand.ident, str(int(case.key[-1]) + 1).rjust(3, '0')])) + problem_dict['duts_name'] = "/".join(set(str_dut_name_list)) + problem_dict['duts_ref'] = "/".join(set(str_dut_ident_list)) + problem_dict['duts_version'] = "/".join(set(str_dut_version_list)) + temp_name_version = [] + for i in range(len(str_dut_name_list)): + temp_name_version.append( + "".join([str_dut_name_list[i] + str_dut_ident_list[i], '/V', str_dut_version_list[i]])) + problem_dict['dut_name_version'] = "\a".join(temp_name_version) + problem_dict['case_ident'] = ",".join(set(case_ident_list)) + problem_dict['type'] = get_str_dict(problem.type, 'problemType') + problem_dict['grade'] = get_str_dict(problem.grade, 'problemGrade') + + # 依据要求-获取其设计需求 + problem_dict['yaoqiu'] = "\a".join(case_design_list) + # 问题操作 - HTML解析 + desc_list = ['【问题操作】'] + rich_parser = RichParser(problem.operation) + desc_list.extend(rich_parser.get_final_list(doc)) + + # 问题影响 + desc_list_result = [f'\a【问题影响】\a{problem.result}'] + desc_list.extend(desc_list_result) + # 问题描述赋值 + problem_dict['desc'] = desc_list + + # 4.原因分析 + desc_list_3 = [f'【原因分析】\a{problem.analysis}'] + problem_dict['cause'] = desc_list_3 + + # 5.影响域分析~~~~ + desc_list_4 = [f'【影响域分析】\a{problem.effect_scope}'] + problem_dict['effect_scope'] = desc_list_4 + + # 6.改正措施 + problem_dict['solve'] = problem.solve + + # 7.回归验证结果 + desc_list_5 = [] + rich_parser5 = RichParser(problem.verify_result) + desc_list_5.extend(rich_parser5.get_final_list(doc)) + problem_dict['verify_result'] = desc_list_5 + + # 8.其他日期和人员 + problem_dict['postPerson'] = problem.postPerson + problem_dict['postDate'] = problem.postDate + close_str = '□修改文档 □修改程序 □不修改' + if len(problem.closeMethod) < 1: + close_str = '□修改文档 □修改程序 ■不修改' + elif len(problem.closeMethod) == 2: + close_str = '■修改文档 ■修改程序 □不修改' + else: + if problem.closeMethod[0] == '1': + close_str = '■修改文档 □修改程序 □不修改' + elif problem.closeMethod[0] == '2': + close_str = '□修改文档 ■修改程序 □不修改' + else: + close_str = '□修改文档 □修改程序 □不修改' + problem_dict['closeMethod'] = close_str + problem_dict['designer'] = problem.designerPerson + problem_dict['designDate'] = problem.designDate + problem_dict['verifyPerson'] = problem.verifyPerson + problem_dict['verifyDate'] = problem.verifyDate + data_list.append(problem_dict) + context = { + 'project_name': project_obj.name, + 'project_ident': project_obj.ident, + 'problem_list': data_list, + } + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path_str / "output_dir/wtd" / '问题详情表.docx') + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) diff --git a/apps/createDocument/extensions/__pycache__/content_result_tool.cpython-313.pyc b/apps/createDocument/extensions/__pycache__/content_result_tool.cpython-313.pyc new file mode 100644 index 0000000..0eb73b4 Binary files /dev/null and b/apps/createDocument/extensions/__pycache__/content_result_tool.cpython-313.pyc differ diff --git a/apps/createDocument/extensions/__pycache__/content_result_tool.cpython-38.pyc b/apps/createDocument/extensions/__pycache__/content_result_tool.cpython-38.pyc new file mode 100644 index 0000000..0143f64 Binary files /dev/null and b/apps/createDocument/extensions/__pycache__/content_result_tool.cpython-38.pyc differ diff --git a/apps/createDocument/extensions/__pycache__/documentTime.cpython-313.pyc b/apps/createDocument/extensions/__pycache__/documentTime.cpython-313.pyc new file mode 100644 index 0000000..95185b8 Binary files /dev/null and b/apps/createDocument/extensions/__pycache__/documentTime.cpython-313.pyc differ diff --git a/apps/createDocument/extensions/__pycache__/documentTime.cpython-38.pyc b/apps/createDocument/extensions/__pycache__/documentTime.cpython-38.pyc new file mode 100644 index 0000000..2ce4e63 Binary files /dev/null and b/apps/createDocument/extensions/__pycache__/documentTime.cpython-38.pyc differ diff --git a/apps/createDocument/extensions/__pycache__/mixins.cpython-313.pyc b/apps/createDocument/extensions/__pycache__/mixins.cpython-313.pyc new file mode 100644 index 0000000..04a3173 Binary files /dev/null and b/apps/createDocument/extensions/__pycache__/mixins.cpython-313.pyc differ diff --git a/apps/createDocument/extensions/__pycache__/mixins.cpython-38.pyc b/apps/createDocument/extensions/__pycache__/mixins.cpython-38.pyc new file mode 100644 index 0000000..787cc0d Binary files /dev/null and b/apps/createDocument/extensions/__pycache__/mixins.cpython-38.pyc differ diff --git a/apps/createDocument/extensions/__pycache__/parse_rich_text.cpython-313.pyc b/apps/createDocument/extensions/__pycache__/parse_rich_text.cpython-313.pyc new file mode 100644 index 0000000..b0bedd2 Binary files /dev/null and b/apps/createDocument/extensions/__pycache__/parse_rich_text.cpython-313.pyc differ diff --git a/apps/createDocument/extensions/__pycache__/parse_rich_text.cpython-38.pyc b/apps/createDocument/extensions/__pycache__/parse_rich_text.cpython-38.pyc new file mode 100644 index 0000000..dc6f983 Binary files /dev/null and b/apps/createDocument/extensions/__pycache__/parse_rich_text.cpython-38.pyc differ diff --git a/apps/createDocument/extensions/__pycache__/solve_problem.cpython-313.pyc b/apps/createDocument/extensions/__pycache__/solve_problem.cpython-313.pyc new file mode 100644 index 0000000..bc03117 Binary files /dev/null and b/apps/createDocument/extensions/__pycache__/solve_problem.cpython-313.pyc differ diff --git a/apps/createDocument/extensions/__pycache__/solve_problem.cpython-38.pyc b/apps/createDocument/extensions/__pycache__/solve_problem.cpython-38.pyc new file mode 100644 index 0000000..f8835fb Binary files /dev/null and b/apps/createDocument/extensions/__pycache__/solve_problem.cpython-38.pyc differ diff --git a/apps/createDocument/extensions/__pycache__/util.cpython-313.pyc b/apps/createDocument/extensions/__pycache__/util.cpython-313.pyc new file mode 100644 index 0000000..1c65620 Binary files /dev/null and b/apps/createDocument/extensions/__pycache__/util.cpython-313.pyc differ diff --git a/apps/createDocument/extensions/__pycache__/util.cpython-38.pyc b/apps/createDocument/extensions/__pycache__/util.cpython-38.pyc new file mode 100644 index 0000000..918f3bf Binary files /dev/null and b/apps/createDocument/extensions/__pycache__/util.cpython-38.pyc differ diff --git a/apps/createDocument/extensions/__pycache__/zhui.cpython-313.pyc b/apps/createDocument/extensions/__pycache__/zhui.cpython-313.pyc new file mode 100644 index 0000000..82d5b14 Binary files /dev/null and b/apps/createDocument/extensions/__pycache__/zhui.cpython-313.pyc differ diff --git a/apps/createDocument/extensions/__pycache__/zhui.cpython-38.pyc b/apps/createDocument/extensions/__pycache__/zhui.cpython-38.pyc new file mode 100644 index 0000000..71db1b8 Binary files /dev/null and b/apps/createDocument/extensions/__pycache__/zhui.cpython-38.pyc differ diff --git a/apps/createDocument/extensions/content_result_tool.py b/apps/createDocument/extensions/content_result_tool.py new file mode 100644 index 0000000..f8e9bbc --- /dev/null +++ b/apps/createDocument/extensions/content_result_tool.py @@ -0,0 +1,80 @@ +from apps.project.models import Project +from utils.util import * +from utils.chen_response import ChenResponse +from django.db.models import Q + +def create_round_context(project_obj: Project, round_id: str): + """根据轮次,生成测评报告中的测评结果""" + # 0. 首先定义个轮次对应中文 + round_chinese = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'] + round_id = int(round_id) + round_str_id = str(round_id) + # 1. 首先先查询给定轮次的round对向 + round_obj = project_obj.pField.filter(key=round_str_id).first() + # 如果没有轮次对象则返回错误 + if not round_obj: + return ChenResponse(code=400, status=400, message='注意您没有设置第二轮测试,请添加!') + dut_qs_r2 = round_obj.rdField # 当前轮被测件dut对象qs + dut_qs_r1 = project_obj.pdField.filter(round__key=str(round_id - 1)) # 上一个轮次被测件dut对象qs + + # 查询当前轮次duts,是否有源代码,如果没有返回错误 + so_dut = dut_qs_r2.filter(type='SO').first() # 当前轮次源代码被测件对象 + if not so_dut: + return ChenResponse(code=400, status=400, message='注意您某轮次没有编写源代码被测件信息,务必添加...') + + # 查询上一个轮次的dut中的源代码、需求文档 + r1_so_dut = dut_qs_r1.filter(type='SO').first() + + # 3. 文档审查清单 + doc_list = [] + round_duts = round_obj.rdField.filter(Q(type='SJ') | Q(type='XQ') | Q(type='XY')) + for dut in round_duts: + dut_dict = { + 'name': dut.name, + 'ident': dut.ref, + 'version': dut.version + } + doc_list.append(dut_dict) + + # 4. 发现多少个问题,什么类型多少个问题 + problems = project_obj.projField.all().distinct() # !important:大变量-项目所有问题 + problems_r2 = problems.filter(case__round__key=round_str_id) # 当前轮次所有问题 + + # 7. 第二轮动态测试用例个数(动态测试-非静态分析、文档审查、代码审查、代码走查4个) + case_r2_qs = round_obj.rcField.filter(~Q(test__testType='2'), ~Q(test__testType='3'), ~Q(test__testType='8'), + ~Q(test__testType='15')) # !warning:中变量-第一轮动态测试用例qs + testType_list, testType_count = create_str_testType_list(case_r2_qs) + ## 动态测试(第一轮)各个类型测试用例执行表/各个测试需求表 + demand_r2_dynamic_qs = round_obj.rtField.filter(~Q(testType='2'), ~Q(testType='3'), ~Q(testType='8'), + ~Q(testType='15')) # !warning:中变量:第一轮动态测试的测试项 + summary_r2_demand_info, summry_r2_demandType_info = create_demand_summary(demand_r2_dynamic_qs, + project_obj.ident) + + # 8.第二轮所有动态问题统计 + problems_dynamic_r2 = problems_r2.filter(~Q(case__test__testType='2'), ~Q(case__test__testType='3'), + ~Q(case__test__testType='8'), + ~Q(case__test__testType='15')) # !critical:大变量:第一轮动态问题单qs + problem_dynamic_r2_type_str = create_problem_type_str(problems_dynamic_r2) + problem_dynamic_r2_grade_str = create_problem_grade_str(problems_dynamic_r2) + r2_dynamic_str = "本轮测试未发现新问题。" + has_r2_dynamic = False + if len(problems_dynamic_r2) > 0: + has_r2_dynamic = True + r2_dynamic_str = (f"第{round_chinese[int(round_id)]}轮动态测试共发现问题{problems_dynamic_r2.count()}个," + f"其中{problem_dynamic_r2_type_str};" + f"{problem_dynamic_r2_grade_str}。") + + context = { + 'project_name': project_obj.name, + 'r1_so_version': r1_so_dut.version, + 'so_version': so_dut.version, + 'case_dynamic_r2_count': case_r2_qs.count(), + 'dynamic_testType_list': '、'.join(testType_list), + 'dynamic_testType_count': testType_count, + 'r2_exe_info_all': summary_r2_demand_info, + 'r2_exe_info_type': summry_r2_demandType_info, + 'has_r2_dynamic': has_r2_dynamic, + 'r2_dynamic_str': r2_dynamic_str, + 'round_id': round_chinese[round_id], + } + return context diff --git a/apps/createDocument/extensions/documentTime.py b/apps/createDocument/extensions/documentTime.py new file mode 100644 index 0000000..9cb77e0 --- /dev/null +++ b/apps/createDocument/extensions/documentTime.py @@ -0,0 +1,207 @@ +# 本模块主要以项目开始时间、结束时间、轮次开始时间、结束时间计算文档中的各个时间 +from datetime import timedelta, date +from apps.project.models import Project +from django.shortcuts import get_object_or_404 +from ninja.errors import HttpError # 从代码抛出该异常,被ninja截取变为response + +def format_remove_heng(dateT: date) -> str: + """该函数将date对象的横杠-去掉,输出str""" + return str(dateT).replace('-', '') + +def times_by_cover_time(cover_time: date) -> dict: + """该函数为每个产品文档根据封面时间,渲染签署页时间、文档变更记录时间""" + return { + 'preparation_time_no_format': cover_time - timedelta(days=2), + 'preparation_time': format_remove_heng(cover_time - timedelta(days=2)), # 拟制时间:为编制结束时间-2天 + 'inspect_time': format_remove_heng(cover_time - timedelta(days=1)), # 校对时间:为编制时间+1天 + 'auditing_time': format_remove_heng(cover_time), + 'ratify_time': format_remove_heng(cover_time), + 'create_doc_time': format_remove_heng(cover_time - timedelta(days=2)), + 'doc_v1_time': format_remove_heng(cover_time) + } + +class DocTime: + def __init__(self, project_id: int): + self.project = get_object_or_404(Project, id=project_id) + # 用户录入时间-项目 + self.p_start = self.project.beginTime # 被测件接收时间/ + self.p_end = self.project.endTime # 大纲测评时间周期结束时间/ + # 遍历轮次时间-多个 + self.round_count = self.project.pField.count() + self.round_time = [] # 轮次按顺序排序 + for round in self.project.pField.all(): + self.round_time.append({ + 'start': round.beginTime, + 'end': round.endTime, + 'location': round.location + }) + # ~~~~由上面时间二次计算得出时间~~~~ -> TODO:可由用户设置间隔时间!!!! + self.dg_bz_start = self.p_start + timedelta(days=1) # 大纲编制开始时间,项目开始时间+1天 + self.dg_bz_end = self.dg_bz_start + timedelta(days=6) # 大纲编制结束时间,大纲编制开始+6天 + self.test_sj_start = self.dg_bz_end + timedelta(days=1) # 测评设计与实现时间,在大纲编制结束+1天 + self.test_sj_end = self.test_sj_start + timedelta(days=5) # 测评设计与实现结束,在开始+5天 + # ~~~~储存每个文档的cover_time~~~~ + self.dg_cover_time = self.dg_bz_end + self.sm_cover_time = self.test_sj_end + self.jl_cover_time = self.round_time[0]['end'] + self.wtd_cover_time = self.round_time[-1]['end'] + + # 该函数生成大纲文档片段-测评时间和地点的时间和地点信息 + def dg_address_time(self): + """直接返回context去渲染""" + # 需要判断round_time是否有值 + if len(self.round_time) <= 0: + raise HttpError(status_code=400, message='您还未创建轮次时间,请填写后生成') + return { + 'start_year': self.p_start.year, + 'start_month': self.p_start.month, + 'end_year': self.p_end.year, + 'end_month': self.p_end.month, + 'beginTime_strf': format_remove_heng(self.p_start), + 'dgCompileStart': format_remove_heng(self.dg_bz_start), + 'dgCompileEnd': format_remove_heng(self.dg_bz_end), + 'designStart': format_remove_heng(self.test_sj_start), + 'designEnd': format_remove_heng(self.test_sj_end), + 'location': self.round_time[0]['location'] + } + + # 该函数生成报告文档片段-测评时间和地点【注意使用了dg_address_time -> 所以后续有修改注意前导】 + def bg_address_time(self): + if len(self.round_time) <= 0: + raise HttpError(status_code=400, message='您还未创建轮次时间,请填写后生成') + # 先使用大纲的时间行数作为前三行 + cname = ['首轮测试', '第二轮测试', '第三轮测试', '第四轮测试', '第五轮测试', '第六轮测试', '第七轮测试', + '第八轮测试', '第九轮测试', '第十轮测试'] + dg_address_time = self.dg_address_time() + round_time_list = [] + index = 0 + for round_dict in self.round_time: + one_dict = { + 'name': cname[index], + 'start': format_remove_heng(round_dict['start']), + 'end': format_remove_heng(round_dict['end']), + 'location': round_dict['location'] + } + index += 1 + round_time_list.append(one_dict) + return { + 'begin_year': dg_address_time['start_year'], + 'begin_month': dg_address_time['start_month'], + 'end_year': dg_address_time['end_year'], + 'end_month': dg_address_time['end_month'], + 'begin_time': dg_address_time['beginTime_strf'], + 'dg_weave_start_date': dg_address_time['dgCompileStart'], + 'dg_weave_end_date': dg_address_time['dgCompileEnd'], + 'sj_weave_start_date': dg_address_time['designStart'], + 'sj_weave_end_date': dg_address_time['designEnd'], + 'round_time_list': round_time_list, + # 测评总结 -> 依据项目结束时间-7 ~ 项目结束时间 + 'summary_start_date': format_remove_heng(self.p_end - timedelta(days=7)), + 'summary_end_date': format_remove_heng(self.p_end), + } + + # 生成报告中测评完成情况 -> 必须依据其他内容生成时间【注意使用了bg_address_time -> 所以后续有修改注意前导】 + def bg_completion_situation(self): + bg_timer_dict = self.bg_address_time() + xq_fx_time_end = self.dg_bz_start + timedelta(days=2) + ch_time_start = xq_fx_time_end + timedelta(days=1) + ch_time_end = self.dg_bz_end + if len(self.round_time) < 1: + raise HttpError(status_code=400, message='您还未创建第一轮测试的时间,请填写后再生成') + return { + 'start_time_year': bg_timer_dict['begin_year'], + 'start_time_month': bg_timer_dict['begin_month'], + 'xq_fx_time_start_year': self.dg_bz_start.year, + 'xq_fx_time_start_month': self.dg_bz_start.month, + 'xq_fx_time_start_day': self.dg_bz_start.day, + 'xq_fx_time_end_year': xq_fx_time_end.year, # 需求分析结束时间是大纲编制开始+2 + 'xq_fx_time_end_month': xq_fx_time_end.month, + 'xq_fx_time_end_day': xq_fx_time_end.day, + 'ch_start_year': ch_time_start.year, + 'ch_start_month': ch_time_start.month, + 'ch_start_day': ch_time_start.day, + 'ch_end_year': ch_time_end.year, + 'ch_end_month': ch_time_end.month, + 'ch_end_day': ch_time_end.day, + 'sj_start_year': self.test_sj_start.year, + 'sj_start_month': self.test_sj_start.month, + 'sj_start_day': self.test_sj_start.day, + 'sj_end_year': self.test_sj_end.year, + 'sj_end_month': self.test_sj_end.month, + 'sj_end_day': self.test_sj_end.day, + 'end_time_year': self.p_end.year, + 'end_time_month': self.p_end.month, + 'exec_start_time_year': self.round_time[0]['start'].year, + 'exec_start_time_month': self.round_time[0]['start'].month, + 'exec_start_time_day': self.round_time[0]['start'].day, + 'exec_end_time_year': self.round_time[0]['end'].year, + 'exec_end_time_month': self.round_time[0]['end'].month, + 'exec_end_time_day': self.round_time[0]['end'].day, + } + + # 该函数生成最终大纲的时间 + def dg_final_time(self): + cover_time = self.dg_bz_end + context = times_by_cover_time(cover_time) + context.update(cover_time=cover_time.strftime("%Y年%m月%d日")) + # 新增给大纲模版10.2章节context + context.update(basic_line1=cover_time.strftime("%Y年%m月"), basic_line2=self.p_end.strftime("%Y年%m月")) + # 新增给大纲模版10.3.2章节的context + sm_context = self.sm_final_time() + context.update(sm_end_time=sm_context['preparation_time_no_format'].strftime("%Y年%m月")) + return context + + # 该函数生成说明文档的时间 -> 依据项目时间而非用户第一轮填写时间! + def sm_final_time(self): + cover_time = self.test_sj_end # 封面时间:为大纲时间中“测评设计与实现”结束时间 + context = times_by_cover_time(cover_time) + context.update(cover_time=cover_time.strftime("%Y年%m月%d日")) + return context + + # 该函数生成记录文档的时间 -> 依据第一轮测试用户填写的事件 + def jl_final_time(self): + if len(self.round_time) < 1: + raise HttpError(status_code=400, message='您还未创建第一轮测试的时间,请填写后再生成') + cover_time = self.round_time[0]['end'] # 封面时间为用户填写第一轮结束时间 + context = times_by_cover_time(cover_time) + context.update(cover_time=cover_time.strftime("%Y年%m月%d日")) + return context + + # 问题单的时间 -> 依据最后一轮次的结束时间+1天 + def wtd_final_time(self): + if len(self.round_time) < 1: + raise HttpError(status_code=400, message='您还未创建第一轮测试的时间,请填写后再生成') + cover_time = self.round_time[-1]['end'] + context = times_by_cover_time(cover_time) + context.update(cover_time=cover_time.strftime("%Y年%m月%d日")) + return context + + # 回归测试说明时间 -> 根据第二轮、第三轮...的开始时间 + def hsm_final_time(self, round_key: str): + if len(self.round_time) < int(round_key) + 1: + raise HttpError(status_code=400, message='您填写的回归轮次时间不正确,请填写后再生成') + cover_time = self.round_time[int(round_key)]['start'] + context = times_by_cover_time(cover_time) + context.update(cover_time=cover_time.strftime("%Y年%m月%d日")) + return context + + # 回归测试记录时间 -> 根据第二轮、第三轮...的结束时间 + def hjl_final_time(self, round_key: str) -> dict: + if len(self.round_time) < int(round_key) + 1: + raise HttpError(status_code=400, message='您填写的回归轮次时间不正确,请填写后再生成') + cover_time = self.round_time[int(round_key)]['end'] + context = times_by_cover_time(cover_time) + context.update(cover_time=cover_time.strftime("%Y年%m月%d日")) + return context + + # 生成报告非过程时间 -> 根据项目结束时间来定 + def bg_final_time(self) -> dict: + if len(self.round_time) <= 0: + raise HttpError(status_code=400, message='您还未创建轮次时间,请填写后生成') + cover_time = self.p_end + # 这里做判断,如果项目结束时间/最后一轮结束时间 + if cover_time < self.round_time[-1]['end']: + raise HttpError(500, message='项目结束时间早于最后一轮次结束时间或等于开始时间,请修改项目结束时间') + context = times_by_cover_time(cover_time) + context.update(cover_time=cover_time.strftime("%Y年%m月%d日")) + return context diff --git a/apps/createDocument/extensions/mixins.py b/apps/createDocument/extensions/mixins.py new file mode 100644 index 0000000..e29d6d4 --- /dev/null +++ b/apps/createDocument/extensions/mixins.py @@ -0,0 +1,21 @@ +from abc import ABC +from apps.project.models import Project +from django.shortcuts import get_object_or_404 +from apps.dict.models import Fragment +from apps.createDocument.extensions.parse_rich_text import RichParser + +class FragementToolsMixin(ABC): + """该混合主要给文档片段进行功能封装""" + def _generate_frag(self, id: int, doc, doc_name: str): + """传入项目id/""" + project_qs = get_object_or_404(Project, id=id) + replace = False # 是否替换标志 + rich_text_list = [] + # [deprecated]判断是否有当前项目的文档片段 + fragments = project_qs.frag.all() + # 传入'片段名称'和判断is_main + frag: Fragment = fragments.filter(name=doc_name, is_main=True).first() + if frag: + replace = True + rich_text_list = RichParser(frag.content).get_final_format_list(doc) + return replace, frag, rich_text_list diff --git a/apps/createDocument/extensions/parse_rich_text.py b/apps/createDocument/extensions/parse_rich_text.py new file mode 100644 index 0000000..674b1ae --- /dev/null +++ b/apps/createDocument/extensions/parse_rich_text.py @@ -0,0 +1,127 @@ +""" +专门解析富文本插件tinymce的html内容 +""" +import pandas as pd +from bs4 import BeautifulSoup +from bs4.element import Tag, NavigableString +import base64 +import io +from docxtpl import InlineImage +from docx.shared import Mm, Cm +import re + +# text.replace('\xa0', ' ')) +class RichParser: + def __init__(self, rich_text): + # 将rich_text的None变为空字符串:鲁棒 + if rich_text is None: + rich_text = "" + # 对原始html解析后的bs对象 + self.bs = BeautifulSoup(rich_text, 'html.parser') + self.content = self.remove_n_in_contents() + # 最终的解析后的列表 + self.data_list = [] + self.line_parse() + + # 1.函数:将self.bs.contents去掉\n,获取每行数据 + def remove_n_in_contents(self): + content_list = [] + for line in self.bs.contents: + if line != '\n': + content_list.append(line) + return content_list + + # 2.逐个遍历self.content,去掉table元素Tag对象单独解析 + def line_parse(self): + for tag in self.content: + if isinstance(tag, NavigableString): + self.data_list.append(tag.text) + elif isinstance(tag, Tag): + if tag.name == 'p': + img_list = tag.find_all('img') + if len(img_list) > 0: + for img_item in img_list: + self.data_list.append(img_item.get('src')) + else: + self.data_list.append(tag.text) + elif tag.name == 'table': + df_dict_list = self.parse_tag2list(tag) + self.data_list.append(df_dict_list) + elif tag.name == 'div': + table_list = tag.find_all('table') + if len(table_list) > 0: + for table in table_list: + df_dict_list = self.parse_tag2list(table) + self.data_list.append(df_dict_list) + + # 3.1.辅助方法,将的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"): + base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", "")) + # ~~~设置了固定宽度、高度~~~ + final_list.append(InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=Mm(height))) + else: + final_list.append(oneline) + if len(final_list) <= 0: + final_list.append("") + return final_list + + # 4.2.最终方法,在上面方法基础上,增加格式,例如

增加缩进,图片居中,

包含“图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 + else: + final_list.append(oneline) + return final_list diff --git a/apps/createDocument/extensions/solve_problem.py b/apps/createDocument/extensions/solve_problem.py new file mode 100644 index 0000000..4e4d63f --- /dev/null +++ b/apps/createDocument/extensions/solve_problem.py @@ -0,0 +1,35 @@ +from apps.project.models import Problem +from utils.util import get_str_dict +from apps.createDocument.extensions.parse_rich_text import RichParser + +def parse_html(html_txt, a_list, doc): + """解析HTML字段的文字和图片到列表,输入是HTML字段的txt以及列表,输出列表""" + parser = RichParser(html_txt) + a_list.extend(parser.get_final_list(doc, img_size=80)) + return a_list + +def create_one_problem_dit(problem: Problem, problem_prefix: str, doc) -> dict: + """问题单汇总表每个问题作为一行的数据""" + problem_dict = { + 'ident': '_'.join([problem_prefix, problem.ident]), + 'grade': get_str_dict(problem.grade, 'problemGrade'), + 'type': get_str_dict(problem.type, 'problemType'), + 'status': get_str_dict(problem.status, 'problemStatu') + } + # 问题操作 - HTML解析 + desc_list = ['【问题描述】'] + desc_list = parse_html(problem.operation, desc_list, doc) + desc_list_yq = [f'\a【问题影响】\a{problem.result}'] + desc_list.extend(desc_list_yq) + problem_dict['desciption'] = desc_list + # 问题处理方式表格单元格 + solve_list = [f'【原因分析】\a{problem.analysis}'] + solve_list_effect = [f'\a【影响域】\a{problem.effect_scope}'] + solve_list_basic = ['\a【处理方式】', problem.solve] + solve_list_verify = ['\a【回归验证】'] + solve_list_verify = parse_html(problem.verify_result, solve_list_verify, doc) + solve_list.extend(solve_list_effect) + solve_list.extend(solve_list_basic) + solve_list.extend(solve_list_verify) + problem_dict['solve'] = solve_list + return problem_dict diff --git a/apps/createDocument/extensions/util.py b/apps/createDocument/extensions/util.py new file mode 100644 index 0000000..1eb06f8 --- /dev/null +++ b/apps/createDocument/extensions/util.py @@ -0,0 +1,97 @@ +from pathlib import Path +from docxtpl import DocxTemplate +from docx.table import Table +from utils.chen_response import ChenResponse +from typing import Any +from apps.project.models import Project +from utils.path_utils import project_path + +def merge_all_cell(table: Table) -> None: + """生成需求研总对照表工具:逐个找第二列和第三列单元格的text,如果一致则合并""" + col_list = [table.columns[1], table.columns[2]] + # 合并第二列相同的单元格 + for col_right in col_list: + index = 0 + temp_text = "" + for cell in col_right.cells: + if index == 0: + temp_text = cell.text + else: + if cell.text == temp_text: + if cell.text == '': # 不知道什么原因必须这样判断下 + cell.text = '/' + text_temp = cell.text + ce = cell.merge(col_right.cells[index - 1]) + ce.text = text_temp + else: + temp_text = cell.text + index += 1 + +def create_sm_docx(template_name: str, context: dict, id: int) -> ChenResponse: + """生成最终说明文档工具函数""" + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'sm' / template_name + doc = DocxTemplate(input_path) + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/sm" / template_name) + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + +def create_dg_docx(template_name: str, context: dict, id: int) -> ChenResponse: + """生成最终大纲文档工具函数""" + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / template_name + doc = DocxTemplate(input_path) + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / template_name) + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + +def create_bg_docx(template_name: str, context: dict, id: int) -> ChenResponse: + """生成最终报告文档工具函数""" + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'bg' / template_name + doc = DocxTemplate(input_path) + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/bg" / template_name) + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + +def create_wtd_docx(template_name: str, context: dict, id: int) -> ChenResponse: + """生成最终问题单文档工具函数""" + input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'wtd' / template_name + doc = DocxTemplate(input_path) + doc.render(context) + try: + doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/wtd" / template_name) + return ChenResponse(status=200, code=200, message="文档生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + +def get_round1_problem(project: Project) -> Any: + """ + 从项目返回第一轮问题单 + :param project: Project项目Model对象 + :return: 问题单的列表 + """ + all_problem_qs = project.projField.all() + # 遍历每个问题,找出第一轮的问题 + problem_set = set() + for problem in all_problem_qs: + flag = False + for case in problem.case.all(): + if case.round.key == '0': + flag = True + if flag: + problem_set.add(problem) + return list(problem_set) + +def delete_dir_files(path: Path) -> Any: + """传入一个Path对象,如果是文件夹则删除里面所有的文件(不删除文件夹)""" + if path.is_dir(): + for file in path.iterdir(): + if file.is_file(): + file.unlink() diff --git a/apps/createDocument/extensions/zhui.py b/apps/createDocument/extensions/zhui.py new file mode 100644 index 0000000..430c99a --- /dev/null +++ b/apps/createDocument/extensions/zhui.py @@ -0,0 +1,99 @@ +from apps.project.models import Project +from utils.chapter_tools.csx_chapter import create_csx_chapter_dict +from utils.util import get_testType, get_case_ident + +# 传入项目对象、dut的类型例如'XQ'、round_str字符串表示例如第一轮为'XQ',测试项其实章节前缀例如 +def create_bg_round1_zhui(project_obj: Project, dut_str='XQ', round_str='0'): + """传入项目对象,返回{仅第一轮}的design_list渲染到模版的列表""" + # 首先定义后面用问题单前缀 + problem_prefix = "".join(['PT_', project_obj.ident, '_']) + # 如果是第一轮,测试项章节号前缀则为6.2,其他轮次为4.1 + demand_prefix = '6.2' if round_str == '0' else "3.1" + design_list = [] + round_obj = project_obj.pField.filter(key=round_str).first() # 轮次对象 + case_index = 1 + if round_obj: + testType_list, last_chapter_items = create_csx_chapter_dict(round_obj) + specific_dut = round_obj.rdField.filter(type=dut_str).first() # design的列表 + if dut_str == 'XQ': + so_dut = round_obj.rdField.filter(type='SO').first() + if so_dut: + designs = so_dut.rsField.all() + for design in designs: + design_dict = {'name': "/", 'chapter': "/", 'test_demand': []} + # 获取一个design的所以测试项,包括关联测试项 + test_items = [] + test_items.extend(design.dtField.all()) + test_items.extend(design.odField.all()) + for test_item in test_items: + key_index = int(test_item.key.split("-")[-1]) + 1 + test_index = str(key_index).rjust(3, '0') + reveal_ident = "_".join( + ["XQ", get_testType(test_item.testType, "testType"), + test_item.ident, test_index]) + test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1 + test_chapter = ".".join([demand_prefix, str(testType_list.index(test_item.testType) + 1), + str(test_item_last_chapter)]) + test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident, + 'case_list': []} + for case in test_item.tcField.all(): + # 用例如果关联了问题单,那么直接判断未通过,如果没有关联问题单,则找步骤里面是否有未执行 + # 如果未执行,不显示未执行,显示“/”斜杠 + is_passed = '通过' + problem_ident_list = [] + for problem in case.caseField.all(): + problem_ident_list.append("".join([problem_prefix, problem.ident])) + if len(problem_ident_list) > 0: + is_passed = '未通过' + case_dict = { + 'index': case_index, + 'name': case.name, + 'ident': get_case_ident(reveal_ident, case), + 'passed': is_passed, + 'problem_ident_list': "\a".join(problem_ident_list) + } + test_item_dict['case_list'].append(case_dict) + case_index += 1 + design_dict['test_demand'].append(test_item_dict) + design_list.append(design_dict) + + if specific_dut: + designs = specific_dut.rsField.all() + for design in designs: + design_dict = {'name': design.name, 'chapter': design.chapter, 'test_demand': []} + # 获取一个design的所以测试项,包括关联测试项 + test_items = [] + test_items.extend(design.dtField.all()) + test_items.extend(design.odField.all()) + for test_item in test_items: + key_index = int(test_item.key.split("-")[-1]) + 1 + test_index = str(key_index).rjust(3, '0') + reveal_ident = "_".join( + ["XQ", get_testType(test_item.testType, "testType"), + test_item.ident, test_index]) + test_item_last_chapter = last_chapter_items[test_item.testType].index(test_item.key) + 1 + test_chapter = ".".join([demand_prefix, str(testType_list.index(test_item.testType) + 1), + str(test_item_last_chapter)]) + test_item_dict = {'name': test_item.name, 'chapter': test_chapter, 'ident': reveal_ident, + 'case_list': []} + for case in test_item.tcField.all(): + # 用例如果关联了问题单,那么直接判断未通过,如果没有关联问题单,则找步骤里面是否有未执行 + # 如果未执行,不显示未执行,显示“/”斜杠 + is_passed = '通过' + problem_ident_list = [] + for problem in case.caseField.all(): + problem_ident_list.append("".join([problem_prefix, problem.ident])) + if len(problem_ident_list) > 0: + is_passed = '未通过' + case_dict = { + "index": case_index, + 'name': case.name, + 'ident': get_case_ident(reveal_ident, case), + 'passed': is_passed, + 'problem_ident_list': "\a".join(problem_ident_list) + } + test_item_dict['case_list'].append(case_dict) + case_index += 1 + design_dict['test_demand'].append(test_item_dict) + design_list.append(design_dict) + return design_list diff --git a/apps/createDocument/migrations/__init__.py b/apps/createDocument/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/createDocument/migrations/__pycache__/__init__.cpython-313.pyc b/apps/createDocument/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..0dd5387 Binary files /dev/null and b/apps/createDocument/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/createDocument/migrations/__pycache__/__init__.cpython-38.pyc b/apps/createDocument/migrations/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..26847b1 Binary files /dev/null and b/apps/createDocument/migrations/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/createDocument/models.py b/apps/createDocument/models.py new file mode 100644 index 0000000..5482a0c --- /dev/null +++ b/apps/createDocument/models.py @@ -0,0 +1 @@ +from django.db import models diff --git a/apps/createDocument/schema/__init__.py b/apps/createDocument/schema/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/createDocument/schema/__pycache__/__init__.cpython-38.pyc b/apps/createDocument/schema/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..a434b4a Binary files /dev/null and b/apps/createDocument/schema/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/createDocument/tests.py b/apps/createDocument/tests.py new file mode 100644 index 0000000..de8bdc0 --- /dev/null +++ b/apps/createDocument/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/createDocument/views.py b/apps/createDocument/views.py new file mode 100644 index 0000000..200df7b --- /dev/null +++ b/apps/createDocument/views.py @@ -0,0 +1 @@ +from django.shortcuts import render diff --git a/apps/createSeiTaiDocument/__init__.py b/apps/createSeiTaiDocument/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/createSeiTaiDocument/__pycache__/__init__.cpython-313.pyc b/apps/createSeiTaiDocument/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..d1811ad Binary files /dev/null and b/apps/createSeiTaiDocument/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/createSeiTaiDocument/__pycache__/__init__.cpython-38.pyc b/apps/createSeiTaiDocument/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..68423d7 Binary files /dev/null and b/apps/createSeiTaiDocument/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/createSeiTaiDocument/__pycache__/admin.cpython-313.pyc b/apps/createSeiTaiDocument/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000..aa5b55a Binary files /dev/null and b/apps/createSeiTaiDocument/__pycache__/admin.cpython-313.pyc differ diff --git a/apps/createSeiTaiDocument/__pycache__/admin.cpython-38.pyc b/apps/createSeiTaiDocument/__pycache__/admin.cpython-38.pyc new file mode 100644 index 0000000..40184af Binary files /dev/null and b/apps/createSeiTaiDocument/__pycache__/admin.cpython-38.pyc differ diff --git a/apps/createSeiTaiDocument/__pycache__/apps.cpython-313.pyc b/apps/createSeiTaiDocument/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000..69c3bd4 Binary files /dev/null and b/apps/createSeiTaiDocument/__pycache__/apps.cpython-313.pyc differ diff --git a/apps/createSeiTaiDocument/__pycache__/apps.cpython-38.pyc b/apps/createSeiTaiDocument/__pycache__/apps.cpython-38.pyc new file mode 100644 index 0000000..6e962c3 Binary files /dev/null and b/apps/createSeiTaiDocument/__pycache__/apps.cpython-38.pyc differ diff --git a/apps/createSeiTaiDocument/__pycache__/controllers.cpython-313.pyc b/apps/createSeiTaiDocument/__pycache__/controllers.cpython-313.pyc new file mode 100644 index 0000000..b4fcda4 Binary files /dev/null and b/apps/createSeiTaiDocument/__pycache__/controllers.cpython-313.pyc differ diff --git a/apps/createSeiTaiDocument/__pycache__/controllers.cpython-38.pyc b/apps/createSeiTaiDocument/__pycache__/controllers.cpython-38.pyc new file mode 100644 index 0000000..51f2c96 Binary files /dev/null and b/apps/createSeiTaiDocument/__pycache__/controllers.cpython-38.pyc differ diff --git a/apps/createSeiTaiDocument/__pycache__/docXmlUtils.cpython-313.pyc b/apps/createSeiTaiDocument/__pycache__/docXmlUtils.cpython-313.pyc new file mode 100644 index 0000000..260e389 Binary files /dev/null and b/apps/createSeiTaiDocument/__pycache__/docXmlUtils.cpython-313.pyc differ diff --git a/apps/createSeiTaiDocument/__pycache__/docXmlUtils.cpython-38.pyc b/apps/createSeiTaiDocument/__pycache__/docXmlUtils.cpython-38.pyc new file mode 100644 index 0000000..ff49e8b Binary files /dev/null and b/apps/createSeiTaiDocument/__pycache__/docXmlUtils.cpython-38.pyc differ diff --git a/apps/createSeiTaiDocument/__pycache__/models.cpython-313.pyc b/apps/createSeiTaiDocument/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..08e92e8 Binary files /dev/null and b/apps/createSeiTaiDocument/__pycache__/models.cpython-313.pyc differ diff --git a/apps/createSeiTaiDocument/__pycache__/models.cpython-38.pyc b/apps/createSeiTaiDocument/__pycache__/models.cpython-38.pyc new file mode 100644 index 0000000..7130af3 Binary files /dev/null and b/apps/createSeiTaiDocument/__pycache__/models.cpython-38.pyc differ diff --git a/apps/createSeiTaiDocument/__pycache__/schema.cpython-313.pyc b/apps/createSeiTaiDocument/__pycache__/schema.cpython-313.pyc new file mode 100644 index 0000000..d338e98 Binary files /dev/null and b/apps/createSeiTaiDocument/__pycache__/schema.cpython-313.pyc differ diff --git a/apps/createSeiTaiDocument/admin.py b/apps/createSeiTaiDocument/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/createSeiTaiDocument/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/createSeiTaiDocument/apps.py b/apps/createSeiTaiDocument/apps.py new file mode 100644 index 0000000..05ac384 --- /dev/null +++ b/apps/createSeiTaiDocument/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class CreateseitaidocumentConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.createSeiTaiDocument' diff --git a/apps/createSeiTaiDocument/controllers.py b/apps/createSeiTaiDocument/controllers.py new file mode 100644 index 0000000..5ca4492 --- /dev/null +++ b/apps/createSeiTaiDocument/controllers.py @@ -0,0 +1,493 @@ +from pathlib import Path +from django.conf import settings +from django.core.files.storage import FileSystemStorage +from utils.path_utils import project_path +from ninja import File, UploadedFile +from ninja.errors import HttpError +from ninja_extra.controllers import api_controller, ControllerBase, route +from ninja_jwt.authentication import JWTAuth +from ninja_extra.permissions import IsAuthenticated +from django.db import transaction +from django.shortcuts import get_object_or_404 +from django.db.models import QuerySet +from docx import Document +from docxtpl import DocxTemplate +# 工具 +from apps.createSeiTaiDocument.docXmlUtils import generate_temp_doc, get_frag_from_document +from apps.createSeiTaiDocument.schema import SeitaiInputSchema +from utils.chen_response import ChenResponse +from apps.project.models import Project, Dut +from apps.createDocument.extensions.documentTime import DocTime +from utils.util import get_str_dict +from apps.createSeiTaiDocument.extensions.download_response import get_file_respone +# 图片工具docx +from apps.createSeiTaiDocument.extensions.shape_size_tool import set_shape_size +# 修改temp文本片段工具 +from apps.createSeiTaiDocument.docXmlUtils import get_jinja_stdContent_element, stdContent_modify + +main_download_path = Path(settings.BASE_DIR) / 'media' + +# @api_controller("/create", tags=['生成产品文档接口'], auth=JWTAuth(), permissions=[IsAuthenticated]) +@api_controller("/create", tags=['生成产品文档接口']) +class GenerateSeitaiController(ControllerBase): + chinese_round_name: list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.project_obj: Project | None = None + self.temp_context = {} + + @route.post("/dgDocument", url_name="create-dgDocument") + @transaction.atomic + def create_dgDocument(self, payload: SeitaiInputSchema): + # 获取项目Model + self.project_obj = get_object_or_404(Project, id=payload.id) + # 生成大纲需要的文本片段信息储存在字典里面 + sec_title = get_str_dict(self.project_obj.secret, 'secret') + duty_person = self.project_obj.duty_person + is_jd = True if self.project_obj.report_type == '9' else False + self.temp_context = { + 'is_jd': is_jd, + 'jd_or_third': "鉴定" if is_jd else "第三方", + 'project_ident': self.project_obj.ident, + 'project_name': self.project_obj.name, + 'test_purpose': "装备鉴定和列装定型" if is_jd else "软件交付和使用", + 'sec_title': sec_title, + 'sec': sec_title, + 'duty_person': duty_person, + 'member': self.project_obj.member[0] if len( + self.project_obj.member) > 0 else duty_person, + 'entrust_unit': self.project_obj.entrust_unit + } | DocTime(payload.id).dg_final_time() # python3.9以上推荐使用|运算符合并 + # 调用self添加temp_context信息 + self.get_first_round_code_ident() + self.get_xq_doc_informations() + result = generate_temp_doc('dg', payload.id, frag_list=payload.frag) + if isinstance(result, dict): + return ChenResponse(status=400, code=400, message=result.get('msg', 'dg未报出错误原因,反正在生成文档出错')) + dg_replace_path, dg_seitai_final_path = result + # ~~~~start:2025/04/19-新增渲染单个字段(可能封装为函数-对temp文件下的jinja字段处理)~~~~ + # 现在已经把alias和stdContent对应起来了 + text_frag_name_list, doc_docx = get_jinja_stdContent_element(dg_replace_path) + # 遍历找出来的文本片段进行修改 + self.text_frag_replace_handle(text_frag_name_list, doc_docx) + # ~~~~end~~~~ + try: + doc_docx.save(dg_seitai_final_path) + # 文件下载 + return get_file_respone(payload.id, '测评大纲') + except PermissionError as e: + return ChenResponse(status=400, code=400, message="文档未生成或生成错误!,{0}".format(e)) + + @route.post('/smDocument', url_name='create-smDocument') + @transaction.atomic + def create_smDocument(self, payload: SeitaiInputSchema): + """生成最后说明文档""" + # 获取项目对象 + self.project_obj = get_object_or_404(Project, id=payload.id) + # 首先第二层模版所需变量 + is_jd = True if self.project_obj.report_type == '9' else False + self.temp_context = { + 'project_name': self.project_obj.name, + 'project_ident': self.project_obj.ident, + 'is_jd': is_jd, + 'jd_or_third': "鉴定" if is_jd else "第三方", + 'ident': self.project_obj.ident, + 'sec_title': get_str_dict(self.project_obj.secret, 'secret'), + 'sec': get_str_dict(self.project_obj.secret, 'secret'), + 'duty_person': self.project_obj.duty_person, + 'member': self.project_obj.member[0] if len( + self.project_obj.member) > 0 else self.project_obj.duty_person, + } | DocTime(payload.id).sm_final_time() + self.get_first_round_code_ident() + # 文档片段操作 + result = generate_temp_doc('sm', payload.id, frag_list=payload.frag) + if isinstance(result, dict): + return ChenResponse(code=400, status=400, message=result.get('msg', '无错误原因')) + sm_to_tpl_file, sm_seitai_final_file = result + + # 文本片段操作 + text_frag_name_list, doc_docx = get_jinja_stdContent_element(sm_to_tpl_file) + self.text_frag_replace_handle(text_frag_name_list, doc_docx) + # 注册时间变量 + try: + doc_docx.save(sm_seitai_final_file) + return get_file_respone(payload.id, '测试说明') + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + @route.post('/jlDocument', url_name='create-jlDocument') + @transaction.atomic + def create_jlDocument(self, payload: SeitaiInputSchema): + self.project_obj = get_object_or_404(Project, id=payload.id) + # seitai文档所需变量 + is_jd = True if self.project_obj.report_type == '9' else False + member = self.project_obj.member[0] if len(self.project_obj.member) > 0 else self.project_obj.duty_person + self.temp_context = { + 'project_name': self.project_obj.name, + 'project_ident': self.project_obj.ident, + 'is_jd': is_jd, + 'name': self.project_obj.name, + 'ident': self.project_obj.ident, + 'sec_title': get_str_dict(self.project_obj.secret, 'secret'), + 'duty_person': self.project_obj.duty_person, 'member': member + } | DocTime(payload.id).jl_final_time() + self.get_xq_doc_informations() # 添加文本片段“xq_version” + result = generate_temp_doc('jl', payload.id, frag_list=payload.frag) + if isinstance(result, dict): + return ChenResponse(code=400, status=400, message=result.get('msg', '无错误原因')) + jl_to_tpl_file, jl_seitai_final_file = result + text_frag_name_list, doc_docx = get_jinja_stdContent_element(jl_to_tpl_file) + # 文本片段操作 + self.text_frag_replace_handle(text_frag_name_list, doc_docx) + # 重新设置时序图大小 + for shape in doc_docx.inline_shapes: + set_shape_size(shape) + try: + doc_docx.save(jl_seitai_final_file) + return get_file_respone(payload.id, '测试记录') + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + @route.post('/hsmDocument', url_name='create-hsmDocument') + @transaction.atomic + def create_hsmDocument(self, payload: SeitaiInputSchema): + """生成最后的回归测试说明-(多个文档)""" + self.project_obj = get_object_or_404(Project, id=payload.id) + hround_list: QuerySet = self.project_obj.pField.exclude(key='0') # 非第一轮次 + cname_list = [] + if len(hround_list) < 1: + return ChenResponse(code=400, status=400, message='无回归轮次,请添加后再生成') + for hround in hround_list: + # 获取当前轮次中文数字 + cname = self.chinese_round_name[int(hround.key)] + # 将cname存入一个list,以便后续拼接给下载函数 + cname_list.append(cname) + is_jd = True if self.project_obj.report_type == '9' else False + member = self.project_obj.member[0] if len(self.project_obj.member) > 0 else self.project_obj.duty_person + # 回归轮次的标识和版本 + so_dut: Dut = hround.rdField.filter(type='SO').first() + if not so_dut: + return ChenResponse(status=400, code=400, message=f'您缺少第{cname}轮的源代码被测件') + # 每次循环会更新temp_context + self.temp_context = { + 'project_name': self.project_obj.name, + 'project_ident': self.project_obj.ident, + 'is_jd': is_jd, + 'sec_title': get_str_dict(self.project_obj.secret, 'secret'), + 'duty_person': self.project_obj.duty_person, + 'member': member, + 'round_num': str(int(hround.key) + 1), + 'round_num_chn': cname, + 'soft_ident': so_dut.ref, + 'soft_version': so_dut.version, + 'location': hround.location, + } | DocTime(payload.id).hsm_final_time(hround.key) + # 注意回归测试说明、回归测试记录都生成多个文档 + result = generate_temp_doc('hsm', payload.id, round_num=cname, frag_list=payload.frag) + if isinstance(result, dict): + return ChenResponse(status=400, code=400, + message=result.get('msg', '回归测试说明生成报错...')) + hsm_replace_path, hsm_seitai_final_path = result + text_frag_name_list, doc_docx = get_jinja_stdContent_element(hsm_replace_path) + # 文本片段操作 + self.text_frag_replace_handle(text_frag_name_list, doc_docx) + try: + doc_docx.save(hsm_seitai_final_path) + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + # 因为回归说明、回归记录可能有多份,多份下载zip否则docx + if len(cname_list) == 1: + return get_file_respone(payload.id, '第二轮回归测试说明') + else: + return get_file_respone(payload.id, list(map(lambda x: f"第{x}轮回归测试说明", cname_list))) + + @route.post('/hjlDocument', url_name='create-hjlDocument') + @transaction.atomic + def create_hjlDocument(self, payload: SeitaiInputSchema): + """生成最后的回归测试记录-(多个文档)""" + self.project_obj: Project = get_object_or_404(Project, id=payload.id) + # 取非第一轮次 + hround_list: QuerySet = self.project_obj.pField.exclude(key='0') + cname_list = [] + if len(hround_list) < 1: + return ChenResponse(code=400, status=400, message='无回归测试轮次,请创建后再试') + for hround in hround_list: + # 取出当前轮次key减1就是上一轮次 + cname = self.chinese_round_name[int(hround.key)] # 输出二、三... + cname_list.append(cname) + member = self.project_obj.member[0] if len(self.project_obj.member) > 0 else self.project_obj.duty_person + is_jd = True if self.project_obj.report_type == '9' else False + so_dut: Dut = hround.rdField.filter(type='SO').first() + if not so_dut: + return ChenResponse(status=400, code=400, message=f'您缺少第{cname}轮的源代码被测件') + self.temp_context = { + 'project_name': self.project_obj.name, + 'project_ident': self.project_obj.ident, + 'is_jd': is_jd, + 'sec_title': get_str_dict(self.project_obj.secret, 'secret'), + 'duty_person': self.project_obj.duty_person, + 'member': member, + 'round_num': str(int(hround.key) + 1), + 'round_num_chn': cname, + 'soft_ident': so_dut.ref, + 'soft_version': so_dut.version, + } | DocTime(payload.id).hjl_final_time(hround.key) + + result = generate_temp_doc('hjl', payload.id, round_num=cname, frag_list=payload.frag) + if isinstance(result, dict): + return ChenResponse(status=400, code=400, + message=result.get('msg', '回归测试记录生成错误!')) + hjl_replace_path, hjl_seitai_final_path = result + text_frag_name_list, doc_docx = get_jinja_stdContent_element(hjl_replace_path) + # 文本片段操作 + self.text_frag_replace_handle(text_frag_name_list, doc_docx) + # 重新设置时序图大小(注意不变) + for shape in doc_docx.inline_shapes: + set_shape_size(shape) + try: + doc_docx.save(hjl_seitai_final_path) + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + if len(cname_list) == 1: + return get_file_respone(payload.id, '第二轮回归测试记录') + else: + return get_file_respone(payload.id, list(map(lambda x: f"第{x}轮回归测试说明", cname_list))) + + @route.post('/wtdDocument', url_name='create-wtdDocument') + @transaction.atomic + def create_wtdDocument(self, payload: SeitaiInputSchema): + """生成最后的问题单""" + self.project_obj = get_object_or_404(Project, id=payload.id) + # seitai文档所需变量 + member = self.project_obj.member[0] if len(self.project_obj.member) > 0 else self.project_obj.duty_person + is_jd = True if self.project_obj.report_type == '9' else False + self.temp_context = { + "project_name": self.project_obj.name, + 'project_ident': self.project_obj.ident, + 'is_jd': is_jd, + 'member': member, + 'duty_person': self.project_obj.duty_person, + 'sec_title': get_str_dict(self.project_obj.secret, 'secret'), + } | DocTime(payload.id).wtd_final_time() + result = generate_temp_doc('wtd', payload.id, frag_list=payload.frag) + if isinstance(result, dict): + return ChenResponse(status=400, code=400, message=result.get('msg', 'wtd未报出错误原因,反正在生成文档出错')) + wtd_replace_path, wtd_seitai_final_path = result + text_frag_name_list, doc_docx = get_jinja_stdContent_element(wtd_replace_path) + # 文本片段操作 + self.text_frag_replace_handle(text_frag_name_list, doc_docx) + try: + doc_docx.save(wtd_seitai_final_path) + return get_file_respone(payload.id, '问题单') + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + @route.post('/bgDocument', url_name='create-bgDocument') + @transaction.atomic + def create_bgDocument(self, payload: SeitaiInputSchema): + """生成最后的报告文档""" + self.project_obj = get_object_or_404(Project, id=payload.id) + # seitai文档所需变量 + ## 1.判断是否为JD + member = self.project_obj.member[0] if len(self.project_obj.member) > 0 else self.project_obj.duty_person + is_jd = True if self.project_obj.report_type == '9' else False + self.temp_context = { + 'project_name': self.project_obj.name, + 'project_ident': self.project_obj.ident, + 'test_purpose': "装备鉴定和列装定型" if is_jd else "软件交付和使用", + 'is_jd': is_jd, + 'sec_title': get_str_dict(self.project_obj.secret, 'secret'), + 'duty_person': self.project_obj.duty_person, + 'jd_or_third': "鉴定" if is_jd else "第三方", + 'entrust_unit': self.project_obj.entrust_unit, + 'member': member, + 'joined_part': f'驻{self.project_obj.dev_unit}军事代表室、{self.project_obj.dev_unit}', + } | DocTime(payload.id).bg_final_time() + result = generate_temp_doc('bg', payload.id, frag_list=payload.frag) + if isinstance(result, dict): + return ChenResponse(status=400, code=400, message=result.get('msg', 'bg未报出错误原因,反正在生成文档出错')) + bg_replace_path, bg_seitai_final_path = result + text_frag_name_list, doc_docx = get_jinja_stdContent_element(bg_replace_path) + # 文本片段操作 + self.text_frag_replace_handle(text_frag_name_list, doc_docx) + try: + doc_docx.save(bg_seitai_final_path) + return get_file_respone(payload.id, '测评报告') + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) + + # ~~~~模版设计模式~~~~ + # 1.传入sdtContent列表,和替换后的文档对象,进行替换操作 + def text_frag_replace_handle(self, text_frag_name_list, doc_docx: Document): + for text_frag in text_frag_name_list: + alias = text_frag['alias'] + if alias in self.temp_context: + sdtContent = text_frag['sdtContent'] + stdContent_modify(self.temp_context[alias], doc_docx, sdtContent) + else: + print('未查找的文本片段变量:', alias) + + # ~~~~下面是生成文档辅助文本片段取变量,统一设置报错信息等,后续看重复代码封装~~~~ + # self拥有变量:self.project_obj / self.temp_context(用于替换文本片段的字典) + # 1.获取项目第一轮round的源代码dut的用户标识/版本/第一轮测试地点 + def get_first_round_code_ident(self): + round_obj = self.project_obj.pField.filter(key='0').first() + if round_obj: + self.temp_context.update({ + 'location': round_obj.location, + }) + code_dut_obj = round_obj.rdField.filter(type='SO').first() + if code_dut_obj: + self.temp_context.update({ + 'soft_ident': code_dut_obj.ref, + 'soft_version': code_dut_obj.version, + }) + return + raise HttpError(500, "第一轮次未创建,或第一轮动态测试地点为填写,或源代码被测件未创建,请先创建") + + # 2.获取第一轮次需求规格说明dut + def get_xq_doc_informations(self): + round1_xq_dut = self.project_obj.pdField.filter(round__key='0', type='XQ').first() + if round1_xq_dut: + self.temp_context.update({'xq_version': round1_xq_dut.version}) + return + raise HttpError(500, "第一轮次被测件:需求规格说明可能未创建,生成文档失败") + +# documentType - 对应的目录名称 +documentType_to_dir = { + '测评大纲': '', + '测试说明': 'sm', + '测试记录': 'jl', + '回归测试说明': 'hsm', + '回归测试记录': 'hjl', + '问题单': 'wtd', + '测评报告': 'bg' +} + +# 处理文档片段相关请求 +@api_controller('/createfragment', tags=['生成文档-文档片段接口集合']) +class CreateFragmentController(ControllerBase): + @route.get("/get_fragments", url_name='get-fragments') + def get_fragements(self, id: int, documentType: str): + """根据项目id和文档类型获取有哪些文档片段""" + # 获取文档片段的字符串列表 + frags = self.get_fragment_name_by_document_name(id, documentType) + # 如果没有文档片段-说明没有生成二段文档 + if not frags: + return ChenResponse(status=500, code=500, message='文档片段还未生成,请关闭后再打开/或者先下载基础文档') + # 到这里说fragments_files数组有值,返回文件名数组 + return ChenResponse(data=[fragment for fragment in frags], message='返回文档片段成功') + + @staticmethod + def get_fragment_name_by_document_name(id: int, document_name: str): + # 1.找到模版的路径 - 不用异常肯定存在 + document_path = main_download_path / project_path(id) / 'form_template' / 'products' / f"{document_name}.docx" + # 2.识别其中的文档片段 + frag_list = get_frag_from_document(document_path) + # 3.这里处理报告里第十轮次前端展示问题 + ## 3.1先判断是否为报告 + if document_name == '测评报告': + ## 3.2然后判断有几个轮次 + project_obj = get_object_or_404(Project, id=id) + round_qs = project_obj.pField.all() + white_list_frag = [] + ## 3.3将希望有的片段名称加入白名单 + for round_obj in round_qs: + chn_num = digit_to_chinese(int(round_obj.key) + 1) + exclude_str = f"测试内容和结果_第{chn_num}轮次" # 组成识别字符串 + white_list_frag.append(exclude_str) + ## 3.4过滤包含“测试内容和结果的轮次在白名单的通过” + # 去掉所有“测试内容和结果_”的片段 + filter_frags = list(filter(lambda x: '测试内容和结果' not in x['frag_name'], frag_list)) + # 再找到白名单的“测试内容和结果_”的片段 + content_and_result_frags = list( + filter(lambda x: '测试内容和结果' in x['frag_name'] and x['frag_name'] in white_list_frag, frag_list)) + # 再组合起来返回 + filter_frags.extend(content_and_result_frags) + return filter_frags + return frag_list + + @route.get("/get_round_exit", url_name='get-round-exit') + def get_round_exit(self, id: int): + """该函数主要识别有几轮回归测试说明、几轮回归测试记录""" + project_obj: Project = get_object_or_404(Project, id=id) + # 取非第一轮次的轮次的个数 + round_count = project_obj.pField.exclude(key='0').count() + return {'count': round_count} + +# 自定义修改Django的文件系统-启动覆盖模式 +class OverwriteStorage(FileSystemStorage): + def __init__(self, *args, **kwargs): + kwargs['allow_overwrite'] = True # 启用覆盖模式 + super().__init__(*args, **kwargs) + +def digit_to_chinese(num): + num_dict = {'0': '零', '1': '一', '2': '二', '3': '三', '4': '四', + '5': '五', '6': '六', '7': '七', '8': '八', '9': '九', '10': '十'} + return ''.join(num_dict[d] for d in str(num)) + +# 处理用户上传有文档片段的产品文档文件:注意回归测试说明、回归测试记录需要单独处理 +@api_controller('/documentUpload', tags=['生成文档-上传模版文档接口']) +class UploadDocumentController(ControllerBase): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # 储存上传文件 + self.upload_file: UploadedFile | None = None + + @route.post("/file", url_name='upload-file') + def upload_file(self, id: int, documentType: str, file: File[UploadedFile], round_num: int = None): + self.upload_file = file + # 1.获取储存路径 + target_dir = main_download_path / project_path(id) / 'form_template' / 'products' + # 2.初始化文件系统 + fs = OverwriteStorage(location=target_dir) + + # 新:如果是大纲片段,则大纲所有片段以文档片段方式储存在/reuse文件夹下面 + if documentType == '测评大纲': + self.get_dg_to_reuse_dir(target_dir.parent.parent / 'reuse') + + if round_num is None: + # 处理非“回归测试说明”/“回归测试记录”文档的上传 + # warning:不校验文档内是否有文档片段,由用户保证上传内容 + # 3.覆盖储存,注意会返回文件的name属性 + fs.save(f"{documentType}.docx", self.upload_file) + else: + # 处理“回归测试说明”/“回归测试记录”文档的上传 + fs.save(f"第{digit_to_chinese(round_num)}轮{documentType}.docx", self.upload_file) + return ChenResponse(status=200, code=200, message=f'上传{documentType}成功!') + + # 主功能函数:将所有大纲的片段储存在reuse下面,以便其他文件使用 + def get_dg_to_reuse_dir(self, reuse_dir_path: Path): + """将大纲的文档片段储存在/reuse文件夹下面""" + doc = Document(self.upload_file) + frag_list = self.get_document_frag_list(doc) + for frag_item in frag_list: + # 目的是格式明确按照“测评大纲.docx”进行,后续文档一样必须按照这样 + new_doc = Document((reuse_dir_path / 'basic_doc.docx').as_posix()) + if frag_item['content'] is not None: + # XML元素可以直接append + new_doc.element.body.clear_content() + for frag_child in frag_item['content'].iterchildren(): + new_doc.element.body.append(frag_child) + filename = f"{frag_item['alias']}.docx" + new_doc.save((reuse_dir_path / filename).as_posix()) + + # 辅助函数:将上传文件的文档片段以列表形式返回 + def get_document_frag_list(self, doc: Document): + body = doc.element.body + sdt_element_list = body.xpath('./w:sdt') # 只查询文档片段,非文本片段 + frag_list = [] + for sdt_element in sdt_element_list: + alias_name = None + sdtContent = None + for sdt_child in sdt_element.iterchildren(): + if sdt_child.tag.endswith('sdtPr'): + for sdtPr_child in sdt_child.getchildren(): + if sdtPr_child.tag.endswith('alias'): + if len(sdtPr_child.attrib.values()) > 0: + alias_name = sdtPr_child.attrib.values()[0] + if sdt_child.tag.endswith("sdtContent"): + sdtContent = sdt_child + frag_list.append({'alias': alias_name, 'content': sdtContent}) + return list(filter(lambda x: x['alias'] is not None, frag_list)) diff --git a/apps/createSeiTaiDocument/docXmlUtils.py b/apps/createSeiTaiDocument/docXmlUtils.py new file mode 100644 index 0000000..57e4f3e --- /dev/null +++ b/apps/createSeiTaiDocument/docXmlUtils.py @@ -0,0 +1,348 @@ +"""该文件是:替换文档片段然后生成辅助生成最终文档""" +from io import BytesIO +from typing import List, Dict +from pathlib import Path +from docx import Document +from docx.text.paragraph import Paragraph +from docx.table import Table +from docx.oxml.table import CT_Tbl +from docx.oxml.text.paragraph import CT_P +from docx.oxml.text.run import CT_R +from docx.oxml.shape import CT_Picture +from docx.parts.image import ImagePart +from docx.text.run import Run +from docx.shared import Mm +from docx.enum.text import WD_PARAGRAPH_ALIGNMENT +from lxml.etree import _Element + +# 路径工具 +from utils.path_utils import project_path + +### 模块变量:定义常用图片所在区域的宽高 +Demand_table_xqms = Mm(134) # 1.测评大纲-测试项里面-需求描述单元格 +Timing_diagram_width = Mm(242) # 2.测试记录-时序图 +Test_result_width = Mm(78) # 3.测试记录-测试结果 +Horizatal_width = Mm(130) # 4.所有文档-页面图片的横向距离(图片宽度预设置) + +def getParentRunNode(node): + """传入oxml节点对象,获取其祖先节点的CT_R""" + if isinstance(node, CT_R): + return node + return getParentRunNode(node.getparent()) + +def generate_temp_doc(doc_type: str, project_id: int, round_num=None, frag_list=None): + """ 该函数参数: + :param frag_list: 储存用户不覆盖的片段列表 + :param round_num: 只有回归说明和回归记录有 + :param project_id: 项目id + :param doc_type:大纲 sm:说明 jl:记录 bg:报告 hsm:回归测试说明 hjl:回归测试记录,默认路径为dg -> 所以如果传错就生成生成大纲了 + :return (to_tpl_file路径, seitai_final_file路径) + """ + if frag_list is None: + frag_list = [] + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + project_path_str = project_path(project_id) + # 根据传入需要处理的文档类型,自动获路径 + prefix = Path.cwd() / 'media' / project_path_str + template_file: Path = prefix / 'form_template' / 'products' / '测评大纲.docx' + to_tpl_file: Path = prefix / 'temp' / '测评大纲.docx' + seitai_final_file: Path = prefix / 'final_seitai' / '测评大纲.docx' + if doc_type == 'sm': + template_file = prefix / 'form_template' / 'products' / '测试说明.docx' + to_tpl_file = prefix / 'temp' / '测试说明.docx' + seitai_final_file: Path = prefix / 'final_seitai' / '测试说明.docx' + elif doc_type == 'jl': + template_file = prefix / 'form_template' / 'products' / '测试记录.docx' + to_tpl_file = prefix / 'temp' / '测试记录.docx' + seitai_final_file: Path = prefix / 'final_seitai' / '测试记录.docx' + elif doc_type == 'bg': + template_file = prefix / 'form_template' / 'products' / '测评报告.docx' + to_tpl_file = prefix / 'temp' / '测评报告.docx' + seitai_final_file: Path = prefix / 'final_seitai' / '测评报告.docx' + elif doc_type == 'hsm': + # 如果products里面存在“用户上传的第n轮回归测试说明.docx,则使用它作为模版” + template_file = prefix / 'form_template' / 'products' / f'第{round_num}轮回归测试说明.docx' + if not template_file.exists(): + template_file = prefix / 'form_template' / 'products' / '回归测试说明.docx' + to_tpl_file = prefix / 'temp' / f'第{round_num}轮回归测试说明.docx' + seitai_final_file: Path = prefix / 'final_seitai' / f'第{round_num}轮回归测试说明.docx' + elif doc_type == 'hjl': + # 如果products里面存在“用户上传的第n轮回归测试记录.docx,则使用它作为模版” + template_file = prefix / 'form_template' / 'products' / f'第{round_num}轮回归测试记录.docx' + if not template_file.exists(): + template_file = prefix / 'form_template' / 'products' / '回归测试记录.docx' + to_tpl_file = prefix / 'temp' / f'第{round_num}轮回归测试记录.docx' + seitai_final_file: Path = prefix / 'final_seitai' / f'第{round_num}轮回归测试记录.docx' + elif doc_type == 'wtd': + template_file = prefix / 'form_template' / 'products' / '问题单.docx' + to_tpl_file = prefix / 'temp' / '问题单.docx' + seitai_final_file: Path = prefix / 'final_seitai' / '问题单.docx' + # 定义找寻被复制文件根路径 - 后续会根据type找子路径 + output_files_path = prefix / 'output_dir' + # 这里可能修改,储存大纲里面的文档片段 + dg_copied_files = [] + # 储存sm/jl/hsm/hjl/bg/wtd的文档片段 + exclusive_copied_files = [] + # 新:储存reuse的文档片段 + reuse_files = [] + # 将被拷贝文件分别放入不同两个数组 + for file in output_files_path.iterdir(): + if file.is_file(): + if file.suffix == '.docx': + dg_copied_files.append(file) + elif file.is_dir(): + # 如果文件夹名称为sm/jl/hsm/hjl/bg/wtd则进入该判断 + # 所以要求文件系统文件夹名称必须是sm/jl/hsm/hjl/bg/wtd不然无法生成 + if file.stem == doc_type: + for f in file.iterdir(): + if f.suffix == '.docx': + exclusive_copied_files.append(f) + for file in (prefix / 'reuse').iterdir(): + if file.is_file(): + if file.suffix == '.docx': + reuse_files.append(file) + # 找到基础模版的所有std域 + doc = Document(template_file.as_posix()) + body = doc.element.body + sdt_element_list = body.xpath('./w:sdt') + # 找到sdt域的名称 -> 为了对应output_dir文件 / 储存所有output_dir图片 + area_name_list = [] + image_part_list = [] # 修改为字典两个字段{ 'name':'测评对象', 'img':ImagePart } + # 筛选片段【二】:用户前端要求不要覆盖的文档片段 + frag_is_cover_dict = {item.name: item.isCover for item in frag_list} + # 遍历所有控件 -> 放入area_name_list【这里准备提取公共代码】 + for sdt_ele in sdt_element_list: + isLock = False + for elem in sdt_ele.iterchildren(): + # 【一】用户设置lock - 下面2个if将需要被替换的(控件名称)存入area_name_list + if elem.tag.endswith('sdtPr'): + for el in elem.getchildren(): + if el.tag.endswith('lock'): + isLock = True + if elem.tag.endswith('sdtPr'): + for el in elem.getchildren(): + if el.tag.endswith('alias'): + # 筛序【一】:取出用户设置lock的文档片段 + if len(el.attrib.values()) > 0 and (isLock == False): + area_name = el.attrib.values()[0] + # 筛选【二】:前端用户选择要覆盖的片段 + if frag_is_cover_dict.get(area_name): + area_name_list.append(area_name) + # 下面开始替换area_name_list的“域”(这时已经被筛选-因为sdtPr和sdtContent是成对出现) + if elem.tag.endswith('sdtContent'): + if len(area_name_list) > 0: + # 从第一个片段名称开始取,取到模版的“域”名称 + area_pop_name = area_name_list.pop(0) + # 这里先去找media/output_dir/xx下文件,然后找media/output下文件 + copied_file_path = "" + # 下面if...else是找output_dir下面文件与“域”名称匹配,匹配到存入copied_file_path + if doc_type == 'dg': + for file in dg_copied_files: + if file.stem == area_pop_name: + copied_file_path = file + else: + # 如果不是大纲 + if round_num is None: + # 如果非回归说明、记录 + for file in exclusive_copied_files: + if file.stem == area_pop_name: + copied_file_path = file + # 这里判断是否copied_file_path没取到文件,然后遍历reuse下文件 + if not copied_file_path: + for file in reuse_files: + if file.stem == area_pop_name: + copied_file_path = file + # 如果上面被复制文件还没找到,然后遍历output_dir下文件 + if not copied_file_path: + for file in dg_copied_files: + if file.stem == area_pop_name: + copied_file_path = file + else: + # 因为回归的轮次,前面会加 -> 第{round_num}轮 + for file in exclusive_copied_files: # 这里多了第{round_num}轮 + if file.stem == f"第{round_num}轮{area_pop_name}": + copied_file_path = file + if not copied_file_path: + for file in reuse_files: + if file.stem == area_pop_name: + copied_file_path = file + if not copied_file_path: + for file in dg_copied_files: + if file.stem == area_pop_name: + copied_file_path = file + # 找到文档片段.docx,将其数据复制到对应area_name的“域” + if copied_file_path: + doc_copied = Document(copied_file_path) + copied_element_list = [] + element_list = doc_copied.element.body.inner_content_elements + for elet in element_list: + if isinstance(elet, CT_P): + copied_element_list.append(Paragraph(elet, doc_copied)) + if isinstance(elet, CT_Tbl): + copied_element_list.append(Table(elet, doc_copied)) + elem.clear() + for para_copied in copied_element_list: + elem.append(para_copied._element) + + # 下面代码就是将图片全部提取到image_part_list,以便后续插入,注意这时候已经是筛选后的 + doc_copied = Document(copied_file_path) # 需要重新获取否则namespace错误 + copied_body = doc_copied.element.body + img_node_list = copied_body.xpath('.//pic:pic') + if not img_node_list: + pass + else: + for img_node in img_node_list: + img: CT_Picture = img_node + # 根据节点找到图片的关联id + embed = img.xpath('.//a:blip/@r:embed')[0] + # 这里得到ImagePart -> 马上要给新文档添加 + related_part: ImagePart = doc_copied.part.related_parts[embed] + # doc_copied.part.related_parts是一个字典 + image_part_list.append({'name': area_pop_name, 'img': related_part}) + + # 现在是替换后,找到替换后文档所有pic:pic,并对“域”名称进行识别 + graph_node_list = body.xpath('.//pic:pic') + graph_node_list_transform = [] + for picNode in graph_node_list: + # 遍历替换后模版的所有pic,去找祖先 + sdt_node = picNode.xpath('ancestor::w:sdt[1]')[0] + for sdt_node_child in sdt_node.iterchildren(): + # 找到sdt下一级的stdPr + if sdt_node_child.tag.endswith('sdtPr'): + for sdtPr_node_child in sdt_node_child.getchildren(): + if sdtPr_node_child.tag.endswith('alias'): + yu_name = sdtPr_node_child.attrib.values()[0] + graph_node_list_transform.append({'yu_name': yu_name, 'yu_node': picNode}) + for graph_node in graph_node_list_transform: + image_run_node = getParentRunNode(graph_node['yu_node']) + image_run_node.clear() + # 循环去image_part_list找name和yu_name相等的图片 + for img_part in image_part_list: + # 1.如果找到相等 + if img_part['name'] == graph_node['yu_name']: + # 2.找到即可添加图片到“域” + image_run_node.clear() + # 辅助:去找其父节点是否为段落,是段落则存起来,后面好居中 + image_run_parent_paragraph = image_run_node.getparent() + father_paragraph = None + if isinstance(image_run_parent_paragraph, CT_P): + father_paragraph = Paragraph(image_run_parent_paragraph, doc) + copied_bytes_io = BytesIO(img_part['img'].image.blob) + r_element = Run(image_run_node, doc) + inline_shape = r_element.add_picture(copied_bytes_io) + ## 2.1.统一:这里设置文档片段里面的图片大小和位置 + source_width = inline_shape.width + source_height = inline_shape.height + if source_width >= source_height: + inline_shape.width = Mm(120) + inline_shape.height = int(inline_shape.height * (inline_shape.width / source_width)) + else: + inline_shape.height = Mm(60) + inline_shape.width = int(inline_shape.width * (inline_shape.height / source_height)) + ## 2.2.设置图片所在段落居中对齐 + if father_paragraph: + father_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + r_element.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + # 3.因为按顺序的,所以移除image_part_list中已经替换的图片 + image_part_list.remove(img_part) + break + try: + # 这里直接生成产品文档 + doc.save(str(to_tpl_file)) + return to_tpl_file, seitai_final_file + except PermissionError as e: + return {'code': 'error', 'msg': '生成的temp文件已打开,请关闭后重试...'} + +def get_frag_from_document(doc_path: Path) -> List[Dict]: + """传入products的文件路径,识别出所有文档片段名称,数组返回:要求docx里面文档名称不能更变""" + doc = Document(doc_path.as_posix()) + sdt_element_list = doc.element.body.xpath('./w:sdt') + # 整个for循环识别文档片段名称 + area_name_list = [] + for sdt_ele in sdt_element_list: + isLock = False + alias_value = None + for elem in sdt_ele.iterchildren(): + if elem.tag.endswith('sdtPr'): + for el in elem.getchildren(): + if el.tag.endswith('alias'): + alias_value = el.attrib.values() + # 查找是否被用户在模版上标记了Lock + if el.tag.endswith('lock'): + isLock = True + if alias_value and len(alias_value): + area_name_list.append({'frag_name': alias_value[0], 'isLock': isLock}) + return area_name_list + +# 辅助函数-传入temp文件路径(已替换文档片段的temp文档),输出stdContent +def get_jinja_stdContent_element(temp_docx_path: Path): + doc_docx = Document(temp_docx_path.as_posix()) + body = doc_docx.element.body + # 储存文本片段 + text_frag_name_list = [] + sdt_element_list = body.xpath('//w:sdt') + + # 注意python-docx的页头的文本片段不在body里面,而在section.header里面 + # 所以定义辅助函数,统一处理 + def deel_sdt_content(*args): + """传入sdt_element列表,将其sdtContent加入外部的文本片段列表""" + for sdt_ele in args: + # 找出每个sdt下面的3个标签 + tag_value = None + alias_value = None + sdtContent_ele = None + for sdt_ele_child in sdt_ele.iterchildren(): + if sdt_ele_child.tag.endswith('sdtPr'): + for sdtPr_ele_child in sdt_ele_child.getchildren(): + if sdtPr_ele_child.tag.endswith('tag'): + if len(sdtPr_ele_child.attrib.values()) > 0: + tag_value = sdtPr_ele_child.attrib.values()[0] + if sdtPr_ele_child.tag.endswith('alias'): + if len(sdtPr_ele_child.attrib.values()) > 0: + alias_value = sdtPr_ele_child.attrib.values()[0] + if sdt_ele_child.tag.endswith('sdtContent'): + sdtContent_ele = sdt_ele_child + # 找出所有tag_value为jinja的文本片段 + if tag_value == 'jinja' and alias_value is not None and sdtContent_ele is not None: + text_frag_name_list.append({'alias': alias_value, 'sdtContent': sdtContent_ele}) + + deel_sdt_content(*sdt_element_list) + for section in doc_docx.sections: + header = section.header + header_sdt_list = header.part.element.xpath('//w:sdt') + deel_sdt_content(*header_sdt_list) + + return text_frag_name_list, doc_docx + +# 封装一个根据alias名称修改stdContent的函数 -> 在接口处理函数中取数据放入函数修改文档 +def stdContent_modify(modify_str: str | bool, doc_docx: Document, sdtContent: _Element): + # 正常处理 + for ele in sdtContent: + if isinstance(ele, CT_R): + run_ele = Run(ele, doc_docx) + if isinstance(modify_str, bool): + # 如果是True,则不修改原来 + if modify_str: + break + else: + modify_str = "" + # 有时候会int类型,转换一下防止报错 + if isinstance(modify_str, int): + modify_str = str(modify_str) + run_ele.text = modify_str + sdtContent.clear() + sdtContent.append(run_ele._element) + break + + if isinstance(ele, CT_P): + para_ele = Paragraph(ele, doc_docx) + if isinstance(modify_str, bool): + if modify_str: + break + else: + modify_str = "" + para_ele.clear() + para_ele.text = modify_str + sdtContent.clear() + sdtContent.append(para_ele._element) + break diff --git a/apps/createSeiTaiDocument/extensions/__pycache__/download_response.cpython-313.pyc b/apps/createSeiTaiDocument/extensions/__pycache__/download_response.cpython-313.pyc new file mode 100644 index 0000000..36f8023 Binary files /dev/null and b/apps/createSeiTaiDocument/extensions/__pycache__/download_response.cpython-313.pyc differ diff --git a/apps/createSeiTaiDocument/extensions/__pycache__/logger.cpython-313.pyc b/apps/createSeiTaiDocument/extensions/__pycache__/logger.cpython-313.pyc new file mode 100644 index 0000000..d328451 Binary files /dev/null and b/apps/createSeiTaiDocument/extensions/__pycache__/logger.cpython-313.pyc differ diff --git a/apps/createSeiTaiDocument/extensions/__pycache__/logger.cpython-38.pyc b/apps/createSeiTaiDocument/extensions/__pycache__/logger.cpython-38.pyc new file mode 100644 index 0000000..9c0e149 Binary files /dev/null and b/apps/createSeiTaiDocument/extensions/__pycache__/logger.cpython-38.pyc differ diff --git a/apps/createSeiTaiDocument/extensions/__pycache__/shape_size_tool.cpython-313.pyc b/apps/createSeiTaiDocument/extensions/__pycache__/shape_size_tool.cpython-313.pyc new file mode 100644 index 0000000..2008580 Binary files /dev/null and b/apps/createSeiTaiDocument/extensions/__pycache__/shape_size_tool.cpython-313.pyc differ diff --git a/apps/createSeiTaiDocument/extensions/download_response.py b/apps/createSeiTaiDocument/extensions/download_response.py new file mode 100644 index 0000000..5563c62 --- /dev/null +++ b/apps/createSeiTaiDocument/extensions/download_response.py @@ -0,0 +1,38 @@ +import os, io +from typing import List +import zipfile +from pathlib import Path +from django.conf import settings +from utils.path_utils import project_path +from utils.chen_response import ChenResponse +from django.http import FileResponse, HttpResponse + +main_download_path = Path(settings.BASE_DIR) / 'media' + +def get_file_respone(id: int, file_name: str | List[str]): + """将生成文档下载响应""" + # 1.如果传入的是str,直接是文件名 + if isinstance(file_name, str): + file_name = "".join([file_name, '.docx']) + file_abs_path = main_download_path / project_path(id) / 'final_seitai' / file_name + if not file_abs_path.is_file(): + return ChenResponse(status=404, code=404, message="文档未生成或生成错误!") + response = FileResponse(open(file_abs_path, 'rb')) + response['Content-Type'] = 'application/octet-stream' + response['Content-Disposition'] = f"attachment; filename={file_name}.docx" + return response + # 2.如果传入的是列表,多个文件名 + elif isinstance(file_name, list): + file_name_list = file_name + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: + for file_name in file_name_list: + file_name = "".join([file_name, '.docx']) + file_abs_path = main_download_path / project_path(id) / 'final_seitai' / file_name + zip_file.write(file_abs_path, os.path.basename(file_abs_path)) + zip_buffer.seek(0) + response = HttpResponse(zip_buffer, content_type='application/zip') + response['Content-Disposition'] = 'attachment; filename="回归测试说明文档.zip"' + return response + else: + return ChenResponse(code=500, status=500, message='下载文档出现错误,确认是否有多个轮次内容') diff --git a/apps/createSeiTaiDocument/extensions/logger.py b/apps/createSeiTaiDocument/extensions/logger.py new file mode 100644 index 0000000..1905ba0 --- /dev/null +++ b/apps/createSeiTaiDocument/extensions/logger.py @@ -0,0 +1,31 @@ +import logging +from conf.logConfig import LOG_GENERATE_FILE + +generate_logger = logging.getLogger("generate_document_logger") + +class GenerateLogger(object): + instance = None + + # 单例模式 + def __new__(cls, *args, **kwargs): + if cls.instance is None: + cls.instance = object.__new__(cls) + return cls.instance + else: + return cls.instance + + def __init__(self, model: str = '通用文档'): + self.logger = generate_logger + # 模块属性 + self.model = model + + def write_warning_log(self, fragment: str, message: str): + """警告日志记录,暂时简单点:model和message""" + whole_message = f"[{self.model}模块][{fragment}]片段:{message}" + self.logger.warning(whole_message) + + @staticmethod + def delete_one_logs(): + """删除生成文档logger的日志记录""" + with open(LOG_GENERATE_FILE, 'w') as f: + f.truncate() diff --git a/apps/createSeiTaiDocument/extensions/shape_size_tool.py b/apps/createSeiTaiDocument/extensions/shape_size_tool.py new file mode 100644 index 0000000..e08c0ae --- /dev/null +++ b/apps/createSeiTaiDocument/extensions/shape_size_tool.py @@ -0,0 +1,44 @@ +from docx.oxml.ns import qn # qn作用是元素的.tag属性,自动帮你处理namespace +from docx.shape import InlineShape +from apps.createSeiTaiDocument.docXmlUtils import ( + Demand_table_xqms, + Timing_diagram_width, + Test_result_width, + Horizatal_width +) + +def set_shape_size(shape: InlineShape): + """调用下面辅助函数,判断字典{'in_table': True, 'row_idx': 10, 'col_idx': 3}来设置大小""" + shape_location = get_shape_location(shape) + # 先判断是否在table中 + if shape_location['in_table']: + # 在table中看是否是第一列,第一列则是时序图 + if shape_location['col_idx'] == 0: + # 在第一列:说明是时序图 + shape.width = Timing_diagram_width + else: + shape.width = Test_result_width + else: + shape.width = Horizatal_width + +def get_shape_location(shape: InlineShape): + """传入图片直接处理,注意是python-docx库,不是docxtpl""" + # 获取父元素链 + parent_chain = list(shape._inline.iterancestors()) + # 检查是否在表格中 + for elem in parent_chain: + if elem.tag == qn("w:tbl"): + # 获取表格对象 + tbl = elem + # 获取行对象并计算行索引 + tr = next(e for e in parent_chain if e.tag == qn('w:tr')) + row_idx = tbl.index(tr) + # 获取单元格对象并计算列索引 + tc = next(e for e in parent_chain if e.tag == qn('w:tc')) + col_idx = tr.index(tc) + return { + 'in_table': True, + 'row_idx': row_idx, + 'col_idx': col_idx + } + return {'in_table': False} diff --git a/apps/createSeiTaiDocument/models.py b/apps/createSeiTaiDocument/models.py new file mode 100644 index 0000000..137941f --- /dev/null +++ b/apps/createSeiTaiDocument/models.py @@ -0,0 +1 @@ +from django.db import models diff --git a/apps/createSeiTaiDocument/schema.py b/apps/createSeiTaiDocument/schema.py new file mode 100644 index 0000000..8711c63 --- /dev/null +++ b/apps/createSeiTaiDocument/schema.py @@ -0,0 +1,13 @@ +"""定义生成最终文档的BaseModel""" +from typing import List +from ninja import Schema + +# 定义文档片段输入的Schema,用于输入Schema嵌套 +class FragmentItemInputSchema(Schema): + name: str + isCover: bool = True # 默认为需要覆盖生成文档 + +# 输入Schema +class SeitaiInputSchema(Schema): + id: int + frag: List[FragmentItemInputSchema] diff --git a/apps/createSeiTaiDocument/tests.py b/apps/createSeiTaiDocument/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/createSeiTaiDocument/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/createSeiTaiDocument/views.py b/apps/createSeiTaiDocument/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/apps/createSeiTaiDocument/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/apps/dict/__init__.py b/apps/dict/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/dict/__pycache__/__init__.cpython-313.pyc b/apps/dict/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..8b16d82 Binary files /dev/null and b/apps/dict/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/dict/__pycache__/__init__.cpython-38.pyc b/apps/dict/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..6c51342 Binary files /dev/null and b/apps/dict/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/dict/__pycache__/admin.cpython-313.pyc b/apps/dict/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000..508d0b8 Binary files /dev/null and b/apps/dict/__pycache__/admin.cpython-313.pyc differ diff --git a/apps/dict/__pycache__/admin.cpython-38.pyc b/apps/dict/__pycache__/admin.cpython-38.pyc new file mode 100644 index 0000000..9b40fba Binary files /dev/null and b/apps/dict/__pycache__/admin.cpython-38.pyc differ diff --git a/apps/dict/__pycache__/apps.cpython-313.pyc b/apps/dict/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000..e47071f Binary files /dev/null and b/apps/dict/__pycache__/apps.cpython-313.pyc differ diff --git a/apps/dict/__pycache__/apps.cpython-38.pyc b/apps/dict/__pycache__/apps.cpython-38.pyc new file mode 100644 index 0000000..03a24f8 Binary files /dev/null and b/apps/dict/__pycache__/apps.cpython-38.pyc differ diff --git a/apps/dict/__pycache__/models.cpython-313.pyc b/apps/dict/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..35e1177 Binary files /dev/null and b/apps/dict/__pycache__/models.cpython-313.pyc differ diff --git a/apps/dict/__pycache__/models.cpython-38.pyc b/apps/dict/__pycache__/models.cpython-38.pyc new file mode 100644 index 0000000..bbab28f Binary files /dev/null and b/apps/dict/__pycache__/models.cpython-38.pyc differ diff --git a/apps/dict/__pycache__/schema.cpython-313.pyc b/apps/dict/__pycache__/schema.cpython-313.pyc new file mode 100644 index 0000000..e792fce Binary files /dev/null and b/apps/dict/__pycache__/schema.cpython-313.pyc differ diff --git a/apps/dict/__pycache__/schema.cpython-38.pyc b/apps/dict/__pycache__/schema.cpython-38.pyc new file mode 100644 index 0000000..c62d0c1 Binary files /dev/null and b/apps/dict/__pycache__/schema.cpython-38.pyc differ diff --git a/apps/dict/__pycache__/throttle.cpython-38.pyc b/apps/dict/__pycache__/throttle.cpython-38.pyc new file mode 100644 index 0000000..95369d0 Binary files /dev/null and b/apps/dict/__pycache__/throttle.cpython-38.pyc differ diff --git a/apps/dict/admin.py b/apps/dict/admin.py new file mode 100644 index 0000000..59db1ff --- /dev/null +++ b/apps/dict/admin.py @@ -0,0 +1,2 @@ +from django.contrib import admin +# Register your models here. diff --git a/apps/dict/apps.py b/apps/dict/apps.py new file mode 100644 index 0000000..78c31db --- /dev/null +++ b/apps/dict/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class DictConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.dict' diff --git a/apps/dict/controllers/__init__.py b/apps/dict/controllers/__init__.py new file mode 100644 index 0000000..d3b4fc1 --- /dev/null +++ b/apps/dict/controllers/__init__.py @@ -0,0 +1,7 @@ +from apps.dict.controllers.abbreviation import AbbreviationController +from apps.dict.controllers.common import CommonController +from apps.dict.controllers.contact import ContactController +from apps.dict.controllers.dict import DictController +from apps.dict.controllers.fragment import UserFiledController + +__all__ = ['AbbreviationController', 'CommonController', 'ContactController', 'DictController', 'UserFiledController'] diff --git a/apps/dict/controllers/__pycache__/__init__.cpython-313.pyc b/apps/dict/controllers/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..911ade4 Binary files /dev/null and b/apps/dict/controllers/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/dict/controllers/__pycache__/__init__.cpython-38.pyc b/apps/dict/controllers/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..58c2bd0 Binary files /dev/null and b/apps/dict/controllers/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/dict/controllers/__pycache__/abbreviation.cpython-313.pyc b/apps/dict/controllers/__pycache__/abbreviation.cpython-313.pyc new file mode 100644 index 0000000..247de12 Binary files /dev/null and b/apps/dict/controllers/__pycache__/abbreviation.cpython-313.pyc differ diff --git a/apps/dict/controllers/__pycache__/abbreviation.cpython-38.pyc b/apps/dict/controllers/__pycache__/abbreviation.cpython-38.pyc new file mode 100644 index 0000000..40648ec Binary files /dev/null and b/apps/dict/controllers/__pycache__/abbreviation.cpython-38.pyc differ diff --git a/apps/dict/controllers/__pycache__/common.cpython-313.pyc b/apps/dict/controllers/__pycache__/common.cpython-313.pyc new file mode 100644 index 0000000..ab31de4 Binary files /dev/null and b/apps/dict/controllers/__pycache__/common.cpython-313.pyc differ diff --git a/apps/dict/controllers/__pycache__/common.cpython-38.pyc b/apps/dict/controllers/__pycache__/common.cpython-38.pyc new file mode 100644 index 0000000..73b2e59 Binary files /dev/null and b/apps/dict/controllers/__pycache__/common.cpython-38.pyc differ diff --git a/apps/dict/controllers/__pycache__/contact.cpython-313.pyc b/apps/dict/controllers/__pycache__/contact.cpython-313.pyc new file mode 100644 index 0000000..9165625 Binary files /dev/null and b/apps/dict/controllers/__pycache__/contact.cpython-313.pyc differ diff --git a/apps/dict/controllers/__pycache__/contact.cpython-38.pyc b/apps/dict/controllers/__pycache__/contact.cpython-38.pyc new file mode 100644 index 0000000..b65868d Binary files /dev/null and b/apps/dict/controllers/__pycache__/contact.cpython-38.pyc differ diff --git a/apps/dict/controllers/__pycache__/dict.cpython-313.pyc b/apps/dict/controllers/__pycache__/dict.cpython-313.pyc new file mode 100644 index 0000000..9301cc8 Binary files /dev/null and b/apps/dict/controllers/__pycache__/dict.cpython-313.pyc differ diff --git a/apps/dict/controllers/__pycache__/dict.cpython-38.pyc b/apps/dict/controllers/__pycache__/dict.cpython-38.pyc new file mode 100644 index 0000000..206366d Binary files /dev/null and b/apps/dict/controllers/__pycache__/dict.cpython-38.pyc differ diff --git a/apps/dict/controllers/__pycache__/fragment.cpython-313.pyc b/apps/dict/controllers/__pycache__/fragment.cpython-313.pyc new file mode 100644 index 0000000..f0b8ba0 Binary files /dev/null and b/apps/dict/controllers/__pycache__/fragment.cpython-313.pyc differ diff --git a/apps/dict/controllers/__pycache__/fragment.cpython-38.pyc b/apps/dict/controllers/__pycache__/fragment.cpython-38.pyc new file mode 100644 index 0000000..2ff1493 Binary files /dev/null and b/apps/dict/controllers/__pycache__/fragment.cpython-38.pyc differ diff --git a/apps/dict/controllers/abbreviation.py b/apps/dict/controllers/abbreviation.py new file mode 100644 index 0000000..0795b1e --- /dev/null +++ b/apps/dict/controllers/abbreviation.py @@ -0,0 +1,71 @@ +from ninja_extra import api_controller, ControllerBase, route +from ninja import Query +from apps.project.models import Abbreviation +from ninja_jwt.authentication import JWTAuth +from ninja_extra.permissions import IsAuthenticated +from ninja.pagination import paginate +from utils.chen_pagination import MyPagination +from django.db import transaction +from django.contrib.auth import get_user_model +from typing import List +from utils.chen_crud import multi_delete +from utils.chen_response import ChenResponse +from apps.dict.schema import DeleteSchema, AbbreviationOut, AbbreviationListInputSchema + +Users = get_user_model() + +@api_controller("/system", tags=['缩略语接口'], auth=JWTAuth(), permissions=[IsAuthenticated]) +class AbbreviationController(ControllerBase): + @route.get("/abbreviation/getlist", response=List[AbbreviationOut], url_name="abbreviation-search") + @transaction.atomic + @paginate(MyPagination) + def get_abbreviation_list(self, payload: AbbreviationListInputSchema = Query(...)): + for attr, value in payload.__dict__.items(): + if getattr(payload, attr) is None: + setattr(payload, attr, '') + qs = Abbreviation.objects.filter(title__icontains=payload.title, des__icontains=payload.des) + return qs + + # 单独获取 + @route.get("/abbreviation/index", response=List[AbbreviationOut], url_name="abbreviation-all") + @transaction.atomic + def get_contact_index(self): + qs = Abbreviation.objects.all() + return qs + + @route.post("/abbreviation/save", response=AbbreviationOut, url_name='abbreviation-create') + @transaction.atomic + def create_abbreviation(self, data: AbbreviationListInputSchema): + for attr, value in data.__dict__.items(): + if getattr(data, attr) is None: + setattr(data, attr, '') + # 判重key + assert_dict = data.dict() + key_qs = Abbreviation.objects.filter(title=data.title) + if len(key_qs) > 0: + return ChenResponse(code=400, status=400, message="缩略语重复,请修改...") + # 正常添加 + qs = Abbreviation.objects.create(**assert_dict) + return qs + + @route.put("/abbreviation/update/{id}", response=AbbreviationOut, url_name='abbreviation-update') + @transaction.atomic + def update_contact(self, id: int, data: AbbreviationListInputSchema): + for attr, value in data.__dict__.items(): + if getattr(data, attr) is None: + setattr(data, attr, '') + key_qs = Abbreviation.objects.filter(title=data.title) + if len(key_qs) > 1: + return ChenResponse(code=400, status=400, message="缩略语重复,请修改...") + # 查询id + qs = Abbreviation.objects.get(id=id) + for attr, value in data.__dict__.items(): + setattr(qs, attr, value) + qs.save() + return qs + + @route.delete('/abbreviation/delete', url_name='abbreviation-delete') + @transaction.atomic + def delete_contact(self, data: DeleteSchema): + multi_delete(data.ids, Abbreviation) + return ChenResponse(message='单位或公司删除成功') diff --git a/apps/dict/controllers/common.py b/apps/dict/controllers/common.py new file mode 100644 index 0000000..78f9f17 --- /dev/null +++ b/apps/dict/controllers/common.py @@ -0,0 +1,53 @@ +from datetime import date +from ninja_extra import api_controller, ControllerBase, route +from apps.project.models import Project +from django.db import transaction +from django.contrib.auth import get_user_model +from utils.chen_response import ChenResponse +from django.db.models import Q + +Users = get_user_model() + +# 这是其他common内容接口 +@api_controller("/system", tags=['通用接口']) +class CommonController(ControllerBase): + """通用接口类:工作台内的信息""" + + @route.get("/getNoticeList") + def get_notice(self, pageSize, orderBy, orderType): + item_list = [] + item1 = {"title": "测试管理平台V0.0.2测试发布", "created_at": "2023-09-23", + "content": "测试管理平台V0.0.2发布,正在进行内部测试.."} + item_list.append(item1) + item2 = {"title": "测试管理平台更新公共", "created_at": "2024-06-17", "content": "

1.修改大纲和报告模版

2.修复多个bug

"} + item_list.append(item2) + return item_list + + @route.get('/workplace/statistics') + @transaction.atomic + def get_statistics(self): + # 查询用户数量,进行的项目,项目总数,已完成项目数 + user_count = Users.objects.count() + project_qs = Project.objects.all() + project_count = project_qs.count() + project_done_count = project_qs.filter(step='3').count() + project_processing_count = project_qs.filter(Q(step='1') | Q(step='2')).count() + return ChenResponse(data={'pcount': project_count, 'ucount': user_count, + 'pdcount': project_done_count, 'ppcount': project_processing_count}) + + @route.get('/statistics/chart') + @transaction.atomic + def get_chart(self): + """该接口返回当前年份下,每月的项目统计,返回横坐标12个月的字符串以及12个月数据""" + current_year = date.today().year + month_list = [] + # 构造数组,里面是字典 + for i in range(12): + month_dict = {'month': i + 1, 'count': 0} + month_list.append(month_dict) + project_qs = Project.objects.all() + for project in project_qs: + for m in month_list: + if m['month'] == project.beginTime.month and project.beginTime.year == current_year: + m['count'] += 1 + return ChenResponse(status=200, code=200, data=month_list) diff --git a/apps/dict/controllers/contact.py b/apps/dict/controllers/contact.py new file mode 100644 index 0000000..46406cb --- /dev/null +++ b/apps/dict/controllers/contact.py @@ -0,0 +1,91 @@ +from ninja_extra import api_controller, ControllerBase, route +from ninja import Query +from apps.project.models import Contact +from ninja_jwt.authentication import JWTAuth +from ninja_extra.permissions import IsAuthenticated +from ninja.pagination import paginate +from utils.chen_pagination import MyPagination +from django.db import transaction +from typing import List +from utils.chen_crud import multi_delete +from utils.chen_response import ChenResponse +from apps.dict.schema import DeleteSchema, ContactListInputSchema, ContactOut + +# 公司信息处理接口 +@api_controller("/system", tags=['公司信息相关'], auth=JWTAuth(), permissions=[IsAuthenticated]) +class ContactController(ControllerBase): + @route.get("/contact/getlist", response=List[ContactOut], url_name="contact-search") + @transaction.atomic + @paginate(MyPagination) + def get_contact_list(self, payload: ContactListInputSchema = Query(...)): + for attr, value in payload.__dict__.items(): + if getattr(payload, attr) is None: + setattr(payload, attr, '') + if payload.key == '': + qs = Contact.objects.filter(name__icontains=payload.name, entrust_person__icontains=payload.entrust_person, + addr__icontains=payload.addr) + else: + qs = Contact.objects.filter(name__icontains=payload.name, entrust_person__icontains=payload.entrust_person, + key=int(payload.key), addr__icontains=payload.addr) + return qs + + # 单独获取 + @route.get("/contact/index", response=List[ContactOut], url_name="contact-all") + @transaction.atomic + def get_contact_index(self): + qs = Contact.objects.all() + return qs + + @route.post("/contact/save", response=ContactOut, url_name='contact-create') + @transaction.atomic + def create_contact(self, data: ContactListInputSchema): + for attr, value in data.__dict__.items(): + if getattr(data, attr) is None: + setattr(data, attr, '') + # 判重key -> key可能为空 + if data.key == '': + data.key = 0 + assert_dict = data.dict() + key_qs = Contact.objects.filter(key=str(data.key)) + if len(key_qs) > 0: + return ChenResponse(code=400, status=400, message="公司或单位的编号重复,请修改") + # 全称判重 + name_qs = Contact.objects.filter(name=data.name) + if len(name_qs) > 0: + return ChenResponse(code=400, status=400, message="全称重复,请修改") + + # 去掉key + assert_dict.pop("key") + assert_dict['key'] = 999999 + qs = Contact.objects.create(**assert_dict) + qs.key = qs.id + qs.save() + return qs + + @route.put("/contact/update/{id}", response=ContactOut, url_name='contact-update') + @transaction.atomic + def update_contact(self, id: int, data: ContactListInputSchema): + for attr, value in data.__dict__.items(): + if getattr(data, attr) is None: + setattr(data, attr, '') + qs = Contact.objects.filter(id=id).first() + if qs: + if qs.key != data.key: + key_qs = Contact.objects.filter(key=str(data.key)) + if len(key_qs) > 0: + return ChenResponse(code=400, status=400, message="公司或单位的编号重复,请修改") + if qs.name != data.name: + name_qs = Contact.objects.filter(name=data.name) + if len(name_qs) > 0: + return ChenResponse(code=400, status=400, message="全称重复,请修改") + # 更新联系人数据 + for attr, value in data.__dict__.items(): + setattr(qs, attr, value) + qs.save() + return qs + + @route.delete('/contact/delete', url_name='contact-delete') + @transaction.atomic + def delete_contact(self, data: DeleteSchema): + multi_delete(data.ids, Contact) + return ChenResponse(message='单位或公司删除成功') diff --git a/apps/dict/controllers/dict.py b/apps/dict/controllers/dict.py new file mode 100644 index 0000000..533d8af --- /dev/null +++ b/apps/dict/controllers/dict.py @@ -0,0 +1,171 @@ +from ninja_extra import api_controller, ControllerBase, route +from ninja import Query +from apps.dict.models import Dict, DictItem +from ninja_jwt.authentication import JWTAuth +from ninja_extra.permissions import IsAuthenticated, IsAdminUser +from ninja.pagination import paginate +from ninja.errors import HttpError +from utils.chen_pagination import MyPagination +from django.db import transaction +from typing import List +from utils.chen_crud import multi_delete +from utils.chen_response import ChenResponse +from apps.dict.schema import DictOut, DictIndexInput, ChangeStautsSchemaInput, DictItemInput, DictItemOut, \ + DictItemChangeSrotInput, DictItemCreateInputSchema, DictItemUpdateInputSchema, DeleteSchema, \ + DictItemFastCreateInputSchema, DictStdItemCreateInputSchema + +@api_controller("/system", tags=['字典相关'], auth=JWTAuth(), permissions=[IsAuthenticated]) +class DictController(ControllerBase): + @route.get("/dataDict/list", response=List[DictItemOut], url_name="dict-list") + def get_dict(self, code: str): + """传入code类型:例如testType,返回字典Item信息""" + dict_qs = Dict.objects.get(code=code) + items = dict_qs.dictItem.filter(status='1') + return items + + @route.get("/dataDict/index", response=List[DictOut], url_name="dict-index") + @transaction.atomic + @paginate(MyPagination) + def get_dict_index(self, payload: DictIndexInput = Query(...)): + for attr, value in payload.__dict__.items(): + if getattr(payload, attr) is None: + setattr(payload, attr, '') + # 处理时间 + if payload.update_datetime_start == '': + payload.update_datetime_start = "2000-01-01" + if payload.update_datetime_end == '': + payload.update_datetime_end = '5000-01-01' + date_list = [payload.update_datetime_start, payload.update_datetime_end] + qs = Dict.objects.filter(name__icontains=payload.name, remark__icontains=payload.remark, + code__icontains=payload.code, status__icontains=payload.status, + update_datetime__range=date_list) + return qs + + @route.put("/dataDict/changeStatus", url_name="dict-changeStatus", permissions=[IsAdminUser]) + @transaction.atomic + def change_dict_status(self, data: ChangeStautsSchemaInput): + qs = Dict.objects.get(id=data.id) + qs.status = data.status + qs.save() + return ChenResponse(code=200, status=200, message="修改状态成功") + + @route.put("/dataDict/changeItemStatus", url_name="dict-changeItemStatus", permissions=[IsAdminUser]) + @transaction.atomic + def change_dict_item_status(self, data: ChangeStautsSchemaInput): + qs = DictItem.objects.get(id=data.id) + qs.status = data.status + qs.save() + return ChenResponse(code=200, status=200, message="修改状态成功") + + # 有dict的id查询其中的dictItem数据 + @route.get("/dataDict/dictItemAll", response=List[DictItemOut], url_name='dictitem-list') + @transaction.atomic + @paginate(MyPagination) + def get_dictItem_list(self, payload: DictItemInput = Query(...)): + for attr, value in payload.__dict__.items(): + if getattr(payload, attr) is None: + setattr(payload, attr, '') + # 处理时间 + if payload.update_datetime_start == '': + payload.update_datetime_start = "2000-01-01" + if payload.update_datetime_end == '': + payload.update_datetime_end = '5000-01-01' + date_list = [payload.update_datetime_start, payload.update_datetime_end] + # 先对dict_id进行查询 + dict_qs = Dict.objects.get(id=payload.dict_id) + # 反向连接 + qs = dict_qs.dictItem.filter(update_datetime__range=date_list, status__icontains=payload.status, + key__icontains=payload.key, title__icontains=payload.title, + show_title__icontains=payload.show_title).order_by('sort') + return qs + + # 更改dictItem的sort字段接口 + @route.put("/dataDict/numberOperation", url_name="dictitem-changesort") + @transaction.atomic + def change_item_sort(self, data: DictItemChangeSrotInput): + qs = DictItem.objects.get(id=data.id) + qs.sort = data.numberValue + qs.save() + return ChenResponse(code=200, status=200, message='排序序号更新成功') + + # 新增dictItem + @route.post("/dataDict/saveitem", response=DictItemOut, url_name="dictitem-save") + @transaction.atomic + def save_item(self, payload: DictItemCreateInputSchema): + # 先根据dict_id查询出dict + dict_qs = Dict.objects.get(id=payload.dict_id) + qs1 = dict_qs.dictItem.filter(title=payload.title) + if len(qs1) > 0: + return ChenResponse(code=400, status=400, message='字典标签重复,请检查') + # 计算key值应该为多少 + key_number = str(len(dict_qs.dictItem.all()) + 1) + asert_dict = payload.dict(exclude_none=True) + asert_dict.pop('dict_id') + asert_dict.update({'dict': dict_qs, 'key': key_number}) + qs = DictItem.objects.create(**asert_dict) + return qs + + # 更新dictitem数据 + @route.put("/dataDict/update/{id}", response=DictItemOut, url_name='dictitem-update') + @transaction.atomic + def update(self, id: int, payload: DictItemUpdateInputSchema): + dictitem_qs = DictItem.objects.get(id=id) + for attr, value in payload.dict().items(): + setattr(dictitem_qs, attr, value) + dictitem_qs.save() + return dictitem_qs + + # 删除dictItem数据 + @route.delete("/dictType/realDeleteItem", url_name="dictitem-delete", permissions=[IsAdminUser]) + @transaction.atomic + def delete_dictitem(self, data: DeleteSchema): + # 根据其中一个id查询出dict的id + dictItem_single = DictItem.objects.filter(id=data.ids[0])[0] + dict_id = dictItem_single.dict.id + multi_delete(data.ids, DictItem) + index = 1 + qs = Dict.objects.get(id=dict_id).dictItem.all() + for qs_item in qs: + qs_item.key = str(index) + index = index + 1 + qs_item.save() + return ChenResponse(message="字典条目删除成功!") + + # 快速新增dictItem数据 + @route.post("/dataDict/fastSave", url_name="dictitem-save-fast", permissions=[IsAuthenticated]) + @transaction.atomic + def save_fast_dictitem(self, data: DictItemFastCreateInputSchema): + # 首先根据data.code查询出是哪个Dict + dict_single = Dict.objects.filter(code=data.code).first() + # 判断是否有该dict + if dict_single: + # 再判断是否dictItem重复 + qs = dict_single.dictItem.filter(title=data.title) + if len(qs) > 0: + return ChenResponse(code=400, status=400, message='字典标签重复,请检查') + # 查看key值应该为多少了 + key_number = str(len(dict_single.dictItem.all()) + 1) + DictItem.objects.create(title=data.title, + key=key_number, + show_title=data.title, + dict=dict_single) + else: + raise HttpError(404, "未查询到字典,请创建字典数据后进行") + return ChenResponse(message="新增成功") + + # 快速新增依据标准dictItem数据 - 输入更变为code + @route.post("/dataDict/saveStdItem", response=DictItemOut, url_name="dictitem-save") + @transaction.atomic + def save(self, payload: DictStdItemCreateInputSchema): + # 先根据dict_id查询出dict + dict_qs = Dict.objects.get(code=payload.code) + qs1 = dict_qs.dictItem.filter(title=payload.title) + if len(qs1) > 0: + return ChenResponse(code=400, status=400, message='字典标签重复,请检查') + # 计算key值应该为多少 + key_number = str(len(dict_qs.dictItem.all()) + 1) + asert_dict = payload.dict(exclude_none=True) + asert_dict.pop('code') + asert_dict.update({'dict': dict_qs, 'key': key_number}) + qs=DictItem.objects.create(**asert_dict) + return qs diff --git a/apps/dict/controllers/fragment.py b/apps/dict/controllers/fragment.py new file mode 100644 index 0000000..942c93c --- /dev/null +++ b/apps/dict/controllers/fragment.py @@ -0,0 +1,96 @@ +from typing import List,Optional +from ninja_extra import api_controller, ControllerBase, route +from ninja import Schema, Field, Query, ModelSchema +from ninja.errors import HttpError +from ninja_jwt.authentication import JWTAuth +from ninja_extra.permissions import IsAuthenticated +from pydantic import model_validator +# 小工具函数 +from utils.smallTools.interfaceTools import model_retrieve +from ninja.pagination import paginate +from utils.chen_pagination import MyPagination +from utils.chen_crud import updateWithoutRequestParam, multi_delete, createWithOutRequestParam +# ORM模型 +from apps.dict.models import Fragment + +# Schemas +## 查询fragment的输入 +class FragementListSchema(Schema): + name: Optional[str] = None # 片段名称 + is_main: Optional[bool] = None # 是否替换磁盘的片段 + project_id: int = Field(None, alias='projectId') + +## 查询结果 +class FragmentOutSchema(ModelSchema): + class Meta: + model = Fragment + fields = ['id', 'name', 'project', 'is_main', 'content'] + +## 新增 +class FragmentAddSchema(Schema): + name: str # 必填 + is_main: bool = False # 后端直接设置为False + project_id: int = Field(None, alias='projectId') + content: str = "" + + # username判重 + @model_validator(mode='after') + def unique_name(self): + if Fragment.objects.filter(name=self.name, project_id=self.project_id).exists(): + raise HttpError(400, "文档片段名称重复") + return self + +## 更新文档片段 +class FragmentUpdateSchema(Schema): + name: Optional[str] = None + is_main: Optional[bool] = None + project_id: int = Field(None, alias='projectId') + content: str = Field(None, alias='content') + + def validate_unique_update_fragName(self, id: int): + frag_filters = Fragment.objects.filter(name=self.name) + if len(frag_filters) > 1: + raise HttpError(400, "文档片段名称重复") + elif len(frag_filters) == 1: + if frag_filters[0].id == id: + return + else: + raise HttpError(400, "文档片段名称重复") + else: + return + +# 删除schema +class FragmentDeleteSchema(Schema): + ids: List[int] + +# 全局静态变量 +PIC_URL_PREFIX = "/uploads/" + +# Controller +@api_controller("/system/userField", tags=['文档片段'], auth=JWTAuth(), permissions=[IsAuthenticated]) +class UserFiledController(ControllerBase): + @route.get("/getFragment", response=List[FragmentOutSchema], url_name='fragment-list') + @paginate(MyPagination) + def get_fragement(self, condition: Query[FragementListSchema]): + fragment_qs = Fragment.objects.filter(project_id=condition.project_id) + res_qs = model_retrieve(condition, fragment_qs, ['project_id', 'is_main']) + res_qs = res_qs.filter(project_id=condition.project_id) + return res_qs + + @route.post("/add", url_name='fragment-add', response=FragmentOutSchema) + def add_fragement(self, data: FragmentAddSchema): + return createWithOutRequestParam(data, Fragment) + + @route.delete("/delete", url_name="fragment-delete") + def delete_fragment(self, data: FragmentDeleteSchema): + try: + multi_delete(data.ids, Fragment) + except Exception: + raise HttpError(500, "删除失败") + + @route.put("/update/{int:id}", url_name='fragment-update') + def update_fragment(self, id: int, data: FragmentUpdateSchema): + update_obj = updateWithoutRequestParam(id, data, Fragment) + if update_obj: + return '更新成功' + raise HttpError(500, "设置替换磁盘文件渲染失败") diff --git a/apps/dict/fragment/__init__.py b/apps/dict/fragment/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/dict/fragment/__pycache__/__init__.cpython-313.pyc b/apps/dict/fragment/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..5b69422 Binary files /dev/null and b/apps/dict/fragment/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/dict/fragment/__pycache__/__init__.cpython-38.pyc b/apps/dict/fragment/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..44e8792 Binary files /dev/null and b/apps/dict/fragment/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/dict/fragment/__pycache__/enums.cpython-313.pyc b/apps/dict/fragment/__pycache__/enums.cpython-313.pyc new file mode 100644 index 0000000..2e1f576 Binary files /dev/null and b/apps/dict/fragment/__pycache__/enums.cpython-313.pyc differ diff --git a/apps/dict/fragment/__pycache__/enums.cpython-38.pyc b/apps/dict/fragment/__pycache__/enums.cpython-38.pyc new file mode 100644 index 0000000..d8a9311 Binary files /dev/null and b/apps/dict/fragment/__pycache__/enums.cpython-38.pyc differ diff --git a/apps/dict/fragment/enums.py b/apps/dict/fragment/enums.py new file mode 100644 index 0000000..3b0561b --- /dev/null +++ b/apps/dict/fragment/enums.py @@ -0,0 +1,16 @@ +from enum import Enum, unique + +# 产品文档名称 +@unique +class DocNameEnum(Enum): + dg = 1 + sm = 2 + jl = 3 + hsm = 4 + hjl = 5 + bg = 6 + wtd = 7 + +if __name__ == '__main__': + print(DocNameEnum.dg) + print(DocNameEnum.dg.value) diff --git a/apps/dict/fragment/fragmentOperation.py b/apps/dict/fragment/fragmentOperation.py new file mode 100644 index 0000000..ae35472 --- /dev/null +++ b/apps/dict/fragment/fragmentOperation.py @@ -0,0 +1,12 @@ +# 该类主要传入文档片段列表,对列表进行数据组装等工作 +from django.db.models import QuerySet +# Models +from apps.dict.models import Fragment + +class FragmentOperation(object): + def __init__(self, fragments: QuerySet[Fragment] = None): + self.__fragments = fragments # 初始化必须:传入Fragment-Model对象 + + @property + def fragment(self): + return self.__fragments diff --git a/apps/dict/migrations/0001_initial.py b/apps/dict/migrations/0001_initial.py new file mode 100644 index 0000000..41d73d6 --- /dev/null +++ b/apps/dict/migrations/0001_initial.py @@ -0,0 +1,58 @@ +# Generated by Django 4.2.13 on 2024-07-03 10:38 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Dict', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('name', models.CharField(blank=True, help_text='字典名称', max_length=100, null=True, verbose_name='字典名称')), + ('code', models.CharField(blank=True, help_text='编码', max_length=100, null=True, verbose_name='编码')), + ('status', models.CharField(blank=True, default='1', help_text='状态', max_length=8, null=True, verbose_name='状态')), + ('remark', models.CharField(blank=True, help_text='备注', max_length=2000, null=True, verbose_name='备注')), + ], + options={ + 'verbose_name': '字典表', + 'verbose_name_plural': '字典表', + 'db_table': 'system_dict', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='DictItem', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('title', models.CharField(blank=True, help_text='显示名称', max_length=100, null=True, verbose_name='显示名称')), + ('key', models.CharField(blank=True, help_text='实际值', max_length=100, null=True, verbose_name='实际值')), + ('show_title', models.CharField(blank=True, help_text='类型转文字', max_length=64, verbose_name='类型转文字')), + ('status', models.CharField(blank=True, default='1', help_text='状态', max_length=8, null=True, verbose_name='状态')), + ('remark', models.CharField(blank=True, help_text='备注', max_length=2000, null=True, verbose_name='备注')), + ('doc_name', models.CharField(blank=True, help_text='文档名称', max_length=64, null=True, verbose_name='文档名称')), + ('publish_date', models.CharField(blank=True, help_text='发布日期', max_length=64, null=True, verbose_name='发布日期')), + ('source', models.CharField(blank=True, help_text='来源', max_length=32, null=True, verbose_name='来源')), + ('dict', models.ForeignKey(db_constraint=False, help_text='字典', on_delete=django.db.models.deletion.CASCADE, related_name='dictItem', to='dict.dict')), + ], + options={ + 'verbose_name': '字典表item表', + 'verbose_name_plural': '字典表item表', + 'db_table': 'system_dict_item', + 'ordering': ('-create_datetime',), + }, + ), + ] diff --git a/apps/dict/migrations/0002_userdictfield.py b/apps/dict/migrations/0002_userdictfield.py new file mode 100644 index 0000000..1d5ba02 --- /dev/null +++ b/apps/dict/migrations/0002_userdictfield.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.13 on 2024-07-23 11:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dict', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='UserDictField', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('name', models.CharField(max_length=64, verbose_name='字段名称')), + ('type', models.PositiveIntegerField(choices=[(1, '文字'), (2, '段落'), (3, '多段段落'), (4, '图片')], verbose_name='字段类型')), + ('ident', models.CharField(max_length=64, verbose_name='字段标识')), + ('status', models.BooleanField(default=False, verbose_name='字段启用状态')), + ('text', models.TextField(blank=True, null=True, verbose_name='字段的值')), + ('pic', models.ImageField(blank=True, null=True, upload_to='field_images', verbose_name='图片信息')), + ], + options={ + 'verbose_name': '用户字段表', + 'verbose_name_plural': '用户字段表', + 'db_table': 'user_dict_for_generate_doc', + 'ordering': ('-create_datetime', '-id'), + }, + ), + ] diff --git a/apps/dict/migrations/0003_fragment_picturefield_tablefield_textfield_wordfield_and_more.py b/apps/dict/migrations/0003_fragment_picturefield_tablefield_textfield_wordfield_and_more.py new file mode 100644 index 0000000..71e80f6 --- /dev/null +++ b/apps/dict/migrations/0003_fragment_picturefield_tablefield_textfield_wordfield_and_more.py @@ -0,0 +1,121 @@ +# Generated by Django 4.2.13 on 2024-07-24 14:43 + +import apps.dict.fragment.enums +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0003_alter_design_protocal'), + ('dict', '0002_userdictfield'), + ] + + operations = [ + migrations.CreateModel( + name='Fragment', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('name', models.CharField(max_length=128, verbose_name='片段名称-必须和文件名一致')), + ('belong_doc', models.PositiveSmallIntegerField(choices=[(1, apps.dict.fragment.enums.DocNameEnum['dg']), (2, apps.dict.fragment.enums.DocNameEnum['sm']), (3, apps.dict.fragment.enums.DocNameEnum['jl']), (4, apps.dict.fragment.enums.DocNameEnum['hsm']), (5, apps.dict.fragment.enums.DocNameEnum['hjl']), (6, apps.dict.fragment.enums.DocNameEnum['bg']), (7, apps.dict.fragment.enums.DocNameEnum['wtd'])], verbose_name='所属文档')), + ('field_seq', models.CharField(max_length=64, verbose_name='用户字段表的顺序')), + ('is_main', models.BooleanField(default=False, verbose_name='是否替换磁盘的片段')), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='frag', related_query_name='qFrag', to='project.project')), + ], + options={ + 'verbose_name': '文档片段', + 'verbose_name_plural': '文档片段', + 'db_table': 'fragment_core', + 'ordering': ('-create_datetime', '-id'), + }, + ), + migrations.CreateModel( + name='PictureField', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('name', models.CharField(max_length=64, verbose_name='字段名称-字母')), + ('img', models.ImageField(upload_to='field_images', verbose_name='图片')), + ('frag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='uPFeild', related_query_name='uPQField', to='dict.fragment')), + ], + options={ + 'verbose_name': '图片', + 'verbose_name_plural': '图片', + 'db_table': 'fragment_field_picture', + 'ordering': ('-create_datetime', '-id'), + }, + ), + migrations.CreateModel( + name='TableField', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('name', models.CharField(max_length=64, verbose_name='字段名称-字母')), + ('headers', models.CharField(blank=True, max_length=1024, null=True, verbose_name='表头')), + ('text', models.TextField(blank=True, null=True)), + ('frag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='uBFeild', related_query_name='uBQField', to='dict.fragment')), + ], + options={ + 'verbose_name': '图片', + 'verbose_name_plural': '图片', + 'db_table': 'fragment_field_table', + 'ordering': ('-create_datetime', '-id'), + }, + ), + migrations.CreateModel( + name='TextField', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('name', models.CharField(max_length=64, verbose_name='字段名称-字母')), + ('text', models.TextField(verbose_name='多行文本段落')), + ('frag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='uTFeild', related_query_name='uTQField', to='dict.fragment')), + ], + options={ + 'verbose_name': '储存当行文本', + 'verbose_name_plural': '储存当行文本', + 'db_table': 'fragment_field_text', + 'ordering': ('-create_datetime', '-id'), + }, + ), + migrations.CreateModel( + name='WordField', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('name', models.CharField(max_length=64, verbose_name='字段名称-字母')), + ('word', models.CharField(max_length=1024, verbose_name='单行文本')), + ('frag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='uWFeild', related_query_name='uWQField', to='dict.fragment')), + ], + options={ + 'verbose_name': '储存当行文本', + 'verbose_name_plural': '储存当行文本', + 'db_table': 'fragment_field_word', + 'ordering': ('-create_datetime', '-id'), + }, + ), + migrations.DeleteModel( + name='UserDictField', + ), + migrations.AddConstraint( + model_name='fragment', + constraint=models.UniqueConstraint(fields=('name', 'belong_doc'), name='unique_name_belong_doc'), + ), + ] diff --git a/apps/dict/migrations/0004_remove_tablefield_frag_remove_textfield_frag_and_more.py b/apps/dict/migrations/0004_remove_tablefield_frag_remove_textfield_frag_and_more.py new file mode 100644 index 0000000..4c1760b --- /dev/null +++ b/apps/dict/migrations/0004_remove_tablefield_frag_remove_textfield_frag_and_more.py @@ -0,0 +1,59 @@ +# Generated by Django 4.2.13 on 2024-07-25 18:21 + +from django.db import migrations, models +import django.db.models.deletion +import tinymce.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0003_alter_design_protocal'), + ('dict', '0003_fragment_picturefield_tablefield_textfield_wordfield_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='tablefield', + name='frag', + ), + migrations.RemoveField( + model_name='textfield', + name='frag', + ), + migrations.RemoveField( + model_name='wordfield', + name='frag', + ), + migrations.RemoveField( + model_name='fragment', + name='field_seq', + ), + migrations.AddField( + model_name='fragment', + name='content', + field=tinymce.models.HTMLField(blank=True, help_text='文档片段的富文本', null=True, verbose_name='片段富文本'), + ), + migrations.AlterField( + model_name='fragment', + name='belong_doc', + field=models.PositiveSmallIntegerField(choices=[(1, 'dg'), (2, 'sm'), (3, 'jl'), (4, 'hsm'), (5, 'hjl'), (6, 'bg'), (7, 'wtd')], verbose_name='所属文档'), + ), + migrations.AlterField( + model_name='fragment', + name='project', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frag', related_query_name='qFrag', to='project.project'), + ), + migrations.DeleteModel( + name='PictureField', + ), + migrations.DeleteModel( + name='TableField', + ), + migrations.DeleteModel( + name='TextField', + ), + migrations.DeleteModel( + name='WordField', + ), + ] diff --git a/apps/dict/migrations/0005_remove_fragment_unique_name_belong_doc_and_more.py b/apps/dict/migrations/0005_remove_fragment_unique_name_belong_doc_and_more.py new file mode 100644 index 0000000..adfbe87 --- /dev/null +++ b/apps/dict/migrations/0005_remove_fragment_unique_name_belong_doc_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.16 on 2024-10-16 14:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dict', '0004_remove_tablefield_frag_remove_textfield_frag_and_more'), + ] + + operations = [ + migrations.RemoveConstraint( + model_name='fragment', + name='unique_name_belong_doc', + ), + migrations.AddConstraint( + model_name='fragment', + constraint=models.UniqueConstraint(fields=('name', 'belong_doc', 'project_id'), name='unique_name_belong_doc'), + ), + ] diff --git a/apps/dict/migrations/0006_remove_fragment_unique_name_belong_doc_and_more.py b/apps/dict/migrations/0006_remove_fragment_unique_name_belong_doc_and_more.py new file mode 100644 index 0000000..c81a96b --- /dev/null +++ b/apps/dict/migrations/0006_remove_fragment_unique_name_belong_doc_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.17 on 2024-12-13 17:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dict', '0005_remove_fragment_unique_name_belong_doc_and_more'), + ] + + operations = [ + migrations.RemoveConstraint( + model_name='fragment', + name='unique_name_belong_doc', + ), + migrations.RemoveField( + model_name='fragment', + name='belong_doc', + ), + migrations.AddConstraint( + model_name='fragment', + constraint=models.UniqueConstraint(fields=('name', 'project_id'), name='unique_name'), + ), + ] diff --git a/apps/dict/migrations/__init__.py b/apps/dict/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/dict/migrations/__pycache__/0001_initial.cpython-313.pyc b/apps/dict/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000..dbc9190 Binary files /dev/null and b/apps/dict/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/apps/dict/migrations/__pycache__/0001_initial.cpython-38.pyc b/apps/dict/migrations/__pycache__/0001_initial.cpython-38.pyc new file mode 100644 index 0000000..2f7b9be Binary files /dev/null and b/apps/dict/migrations/__pycache__/0001_initial.cpython-38.pyc differ diff --git a/apps/dict/migrations/__pycache__/0002_userdictfield.cpython-313.pyc b/apps/dict/migrations/__pycache__/0002_userdictfield.cpython-313.pyc new file mode 100644 index 0000000..ed493e2 Binary files /dev/null and b/apps/dict/migrations/__pycache__/0002_userdictfield.cpython-313.pyc differ diff --git a/apps/dict/migrations/__pycache__/0002_userdictfield.cpython-38.pyc b/apps/dict/migrations/__pycache__/0002_userdictfield.cpython-38.pyc new file mode 100644 index 0000000..04c29ca Binary files /dev/null and b/apps/dict/migrations/__pycache__/0002_userdictfield.cpython-38.pyc differ diff --git a/apps/dict/migrations/__pycache__/0003_fragment_picturefield_tablefield_textfield_wordfield_and_more.cpython-313.pyc b/apps/dict/migrations/__pycache__/0003_fragment_picturefield_tablefield_textfield_wordfield_and_more.cpython-313.pyc new file mode 100644 index 0000000..1f9234a Binary files /dev/null and b/apps/dict/migrations/__pycache__/0003_fragment_picturefield_tablefield_textfield_wordfield_and_more.cpython-313.pyc differ diff --git a/apps/dict/migrations/__pycache__/0003_fragment_picturefield_tablefield_textfield_wordfield_and_more.cpython-38.pyc b/apps/dict/migrations/__pycache__/0003_fragment_picturefield_tablefield_textfield_wordfield_and_more.cpython-38.pyc new file mode 100644 index 0000000..7892ee2 Binary files /dev/null and b/apps/dict/migrations/__pycache__/0003_fragment_picturefield_tablefield_textfield_wordfield_and_more.cpython-38.pyc differ diff --git a/apps/dict/migrations/__pycache__/0004_remove_tablefield_frag_remove_textfield_frag_and_more.cpython-313.pyc b/apps/dict/migrations/__pycache__/0004_remove_tablefield_frag_remove_textfield_frag_and_more.cpython-313.pyc new file mode 100644 index 0000000..c076bc1 Binary files /dev/null and b/apps/dict/migrations/__pycache__/0004_remove_tablefield_frag_remove_textfield_frag_and_more.cpython-313.pyc differ diff --git a/apps/dict/migrations/__pycache__/0004_remove_tablefield_frag_remove_textfield_frag_and_more.cpython-38.pyc b/apps/dict/migrations/__pycache__/0004_remove_tablefield_frag_remove_textfield_frag_and_more.cpython-38.pyc new file mode 100644 index 0000000..1c9ecbb Binary files /dev/null and b/apps/dict/migrations/__pycache__/0004_remove_tablefield_frag_remove_textfield_frag_and_more.cpython-38.pyc differ diff --git a/apps/dict/migrations/__pycache__/0005_remove_fragment_unique_name_belong_doc_and_more.cpython-313.pyc b/apps/dict/migrations/__pycache__/0005_remove_fragment_unique_name_belong_doc_and_more.cpython-313.pyc new file mode 100644 index 0000000..b756e71 Binary files /dev/null and b/apps/dict/migrations/__pycache__/0005_remove_fragment_unique_name_belong_doc_and_more.cpython-313.pyc differ diff --git a/apps/dict/migrations/__pycache__/0005_remove_fragment_unique_name_belong_doc_and_more.cpython-38.pyc b/apps/dict/migrations/__pycache__/0005_remove_fragment_unique_name_belong_doc_and_more.cpython-38.pyc new file mode 100644 index 0000000..6de0db6 Binary files /dev/null and b/apps/dict/migrations/__pycache__/0005_remove_fragment_unique_name_belong_doc_and_more.cpython-38.pyc differ diff --git a/apps/dict/migrations/__pycache__/0006_remove_fragment_unique_name_belong_doc_and_more.cpython-313.pyc b/apps/dict/migrations/__pycache__/0006_remove_fragment_unique_name_belong_doc_and_more.cpython-313.pyc new file mode 100644 index 0000000..ebd67bc Binary files /dev/null and b/apps/dict/migrations/__pycache__/0006_remove_fragment_unique_name_belong_doc_and_more.cpython-313.pyc differ diff --git a/apps/dict/migrations/__pycache__/0006_remove_fragment_unique_name_belong_doc_and_more.cpython-38.pyc b/apps/dict/migrations/__pycache__/0006_remove_fragment_unique_name_belong_doc_and_more.cpython-38.pyc new file mode 100644 index 0000000..36510a1 Binary files /dev/null and b/apps/dict/migrations/__pycache__/0006_remove_fragment_unique_name_belong_doc_and_more.cpython-38.pyc differ diff --git a/apps/dict/migrations/__pycache__/__init__.cpython-313.pyc b/apps/dict/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..f6d07ec Binary files /dev/null and b/apps/dict/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/dict/migrations/__pycache__/__init__.cpython-38.pyc b/apps/dict/migrations/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..b79602e Binary files /dev/null and b/apps/dict/migrations/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/dict/models.py b/apps/dict/models.py new file mode 100644 index 0000000..4211361 --- /dev/null +++ b/apps/dict/models.py @@ -0,0 +1,63 @@ +from django.db import models +from utils.models import CoreModel +from apps.project.models import Project +from tinymce.models import HTMLField + +# ~~~~~~~~~~~~~字典以及字典item~~~~~~~~~~~~~ +class Dict(CoreModel): + objects = models.Manager() + name = models.CharField(max_length=100, blank=True, null=True, verbose_name="字典名称", help_text="字典名称") + code = models.CharField(max_length=100, blank=True, null=True, verbose_name="编码", help_text="编码") + status = models.CharField(max_length=8, blank=True, null=True, verbose_name="状态", help_text="状态", default='1') + remark = models.CharField(max_length=2000, blank=True, null=True, verbose_name="备注", help_text="备注") + + def __str__(self): + return f'字典名称:{self.name}-字典类码:{self.code}' + + class Meta: + db_table = 'system_dict' + verbose_name = "字典表" + verbose_name_plural = verbose_name + ordering = ('-create_datetime',) + +class DictItem(CoreModel): + objects = models.Manager() + title = models.CharField(max_length=100, blank=True, null=True, verbose_name="显示名称", help_text="显示名称") + key = models.CharField(max_length=100, blank=True, null=True, verbose_name="实际值", help_text="实际值") + show_title = models.CharField(max_length=64, blank=True, verbose_name="类型转文字", help_text="类型转文字") + status = models.CharField(max_length=8, blank=True, null=True, verbose_name="状态", help_text="状态", default='1') + dict = models.ForeignKey(to="Dict", db_constraint=False, related_name="dictItem", on_delete=models.CASCADE, + help_text="字典") + remark = models.CharField(max_length=2000, blank=True, null=True, verbose_name="备注", help_text="备注") + # 针对依据文件的字段 + doc_name = models.CharField(max_length=64, blank=True, null=True, verbose_name="文档名称", help_text="文档名称") + publish_date = models.CharField(max_length=64, blank=True, null=True, verbose_name="发布日期", help_text="发布日期") + source = models.CharField(max_length=32, blank=True, null=True, verbose_name='来源', help_text="来源") + + def __str__(self): + return f'字典项名称:{self.title}-字典项显示名称:{self.show_title}' + + class Meta: + db_table = 'system_dict_item' + verbose_name = "字典表item表" + verbose_name_plural = verbose_name + ordering = ('-create_datetime',) + +# ~~~~~~~~~~~~~用户文档片段~~~~~~~~~~~~~ +# fragment表 +class Fragment(CoreModel): + name = models.CharField(verbose_name='片段名称-必须和文件名一致', max_length=128) + is_main = models.BooleanField(default=False, verbose_name='是否替换磁盘的片段') + content = HTMLField(null=True, blank=True, verbose_name='片段富文本', help_text='文档片段的富文本') + # 关联的项目 + project = models.ForeignKey(Project, related_name='frag', related_query_name='qFrag', on_delete=models.CASCADE) + + class Meta: + db_table = 'fragment_core' + verbose_name = '文档片段' + verbose_name_plural = verbose_name + ordering = ('-create_datetime', '-id') + # 片段名称name和所属产品文档联合唯一 + constraints = [ + models.UniqueConstraint(fields=['name', 'project_id'], name='unique_name') + ] diff --git a/apps/dict/schema.py b/apps/dict/schema.py new file mode 100644 index 0000000..03af78b --- /dev/null +++ b/apps/dict/schema.py @@ -0,0 +1,109 @@ +from ninja_schema import ModelSchema +from apps.dict.models import Dict, DictItem +from apps.project.models import Contact, Abbreviation +from ninja import Field, Schema +from typing import List, Union + +class DictOut(ModelSchema): + class Config: + model = Dict + include = ('id', 'code', 'name', 'remark', 'status', 'update_datetime') + +class DictItemOut(ModelSchema): + class Config: + model = DictItem + include = ( + 'id', 'update_datetime', 'sort', 'title', 'key', 'status', 'remark', 'show_title', 'doc_name', + 'publish_date', + 'source') + +class DictIndexInput(Schema): + name: str = Field(None, alias='name') + remark: str = Field(None, alias='remark') + code: str = Field(None, alias='code') + status: str = Field(None, alias='status') + update_datetime_start: str = Field(None, alias='update_datetime[0]') + update_datetime_end: str = Field(None, alias='update_datetime[1]') + +class ChangeStautsSchemaInput(Schema): + id: int = Field(None, alias='id') + status: Union[str, int] = Field(None, alias='status') + +class DictItemInput(Schema): + dict_id: int = Field(None, alias='id') + title: str = Field(None, alias='title') + key: str = Field(None, alias='key') + status: str = Field(None, alias='status') + show_title: str = Field(None, alias='show_title') + update_datetime_start: str = Field(None, alias='update_datetime[0]') + update_datetime_end: str = Field(None, alias='update_datetime[1]') + +class DictItemChangeSrotInput(Schema): + id: int = Field(None, alias='id') + numberName: str = Field(None, alias='numberName') + numberValue: int = Field(None, alias='numberValue') + +class DictItemCreateInputSchema(Schema): + dict_id: int = Field(None, alias='id') + remark: str = Field(None, alias='remark') + sort: int = Field(None, alias='sort') + status: str = Field(None, alias='status') + title: str = Field(None, alias='title') + show_title: str = Field(None, alias='show_title') + doc_name: str = Field(None, alias='doc_name') + publish_date: str = Field(None, alias='publish_date') + source: str = Field(None, alias='source') + +# 定义快速新增依据标注接口 +class DictStdItemCreateInputSchema(Schema): + code: str = Field(None, alias='code') + remark: str = Field(None, alias='remark') + sort: int = Field(None, alias='sort') + status: str = Field(None, alias='status') + title: str = Field(None, alias='title') + show_title: str = Field(None, alias='show_title') + doc_name: str = Field(None, alias='doc_name') + publish_date: str = Field(None, alias='publish_date') + source: str = Field(None, alias='source') + +# 定义快速新增DictItem的接口 +class DictItemFastCreateInputSchema(Schema): + code: str = Field(alias='code') # Dict的code字段 + title: str = Field("", alias='title') + +class DictItemUpdateInputSchema(Schema): + remark: str = Field(None, alias='remark') + sort: int = Field(None, alias='sort') + status: str = Field(None, alias='status') + title: str = Field(None, alias='title') + show_title: str = Field(None, alias='show_title') + doc_name: str = Field(None, alias='doc_name') + publish_date: str = Field(None, alias='publish_date') + source: str = Field(None, alias='source') + +# 删除schema +class DeleteSchema(Schema): + ids: List[int] + +#############公司信息处理############## +class ContactOut(ModelSchema): + class Config: + model = Contact + include = ('id', 'entrust_person', 'name', 'refer_name', 'key', 'update_datetime', 'addr') + +class ContactListInputSchema(Schema): + key: Union[str, int] = Field(None, alias='key') + name: str = Field(None, alias='name') + refer_name: str = Field(None, alias='refer_name') + entrust_person: str = Field(None, alias='entrust_person') + addr: str = Field(None, alias='addr') + +#############缩略语处理############## +class AbbreviationOut(ModelSchema): + class Config: + model = Abbreviation + include = ('id', 'title', 'des') + +class AbbreviationListInputSchema(Schema): + title: str = Field(None, alias='title') + des: str = Field(None, alias='des') diff --git a/apps/dict/tests.py b/apps/dict/tests.py new file mode 100644 index 0000000..de8bdc0 --- /dev/null +++ b/apps/dict/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/dict/throttle.py b/apps/dict/throttle.py new file mode 100644 index 0000000..e9d96db --- /dev/null +++ b/apps/dict/throttle.py @@ -0,0 +1,6 @@ +from ninja_extra.throttling import UserRateThrottle +# ~~~~~~~~~~~注意:当前只对缩略语添加1分钟一次,用于Jmeter测试,记得删除~~~~~~~~~~~ +# 限流类 - 只能一分钟请求5次 +class User60MinRateThrottle(UserRateThrottle): + rate = '60/min' + scope = 'minutes' diff --git a/apps/dict/views.py b/apps/dict/views.py new file mode 100644 index 0000000..c60c790 --- /dev/null +++ b/apps/dict/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/apps/project/__init__.py b/apps/project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/project/__pycache__/__init__.cpython-313.pyc b/apps/project/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..fd8b8f6 Binary files /dev/null and b/apps/project/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/project/__pycache__/__init__.cpython-38.pyc b/apps/project/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..e620849 Binary files /dev/null and b/apps/project/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/project/__pycache__/admin.cpython-313.pyc b/apps/project/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000..bca5648 Binary files /dev/null and b/apps/project/__pycache__/admin.cpython-313.pyc differ diff --git a/apps/project/__pycache__/admin.cpython-38.pyc b/apps/project/__pycache__/admin.cpython-38.pyc new file mode 100644 index 0000000..28ae50d Binary files /dev/null and b/apps/project/__pycache__/admin.cpython-38.pyc differ diff --git a/apps/project/__pycache__/apps.cpython-313.pyc b/apps/project/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000..7d0557d Binary files /dev/null and b/apps/project/__pycache__/apps.cpython-313.pyc differ diff --git a/apps/project/__pycache__/apps.cpython-38.pyc b/apps/project/__pycache__/apps.cpython-38.pyc new file mode 100644 index 0000000..c93038e Binary files /dev/null and b/apps/project/__pycache__/apps.cpython-38.pyc differ diff --git a/apps/project/__pycache__/models.cpython-313.pyc b/apps/project/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..c11c711 Binary files /dev/null and b/apps/project/__pycache__/models.cpython-313.pyc differ diff --git a/apps/project/__pycache__/models.cpython-38.pyc b/apps/project/__pycache__/models.cpython-38.pyc new file mode 100644 index 0000000..0e0d91b Binary files /dev/null and b/apps/project/__pycache__/models.cpython-38.pyc differ diff --git a/apps/project/__pycache__/signals.cpython-313.pyc b/apps/project/__pycache__/signals.cpython-313.pyc new file mode 100644 index 0000000..e66c44a Binary files /dev/null and b/apps/project/__pycache__/signals.cpython-313.pyc differ diff --git a/apps/project/__pycache__/signals.cpython-38.pyc b/apps/project/__pycache__/signals.cpython-38.pyc new file mode 100644 index 0000000..ca441ae Binary files /dev/null and b/apps/project/__pycache__/signals.cpython-38.pyc differ diff --git a/apps/project/admin.py b/apps/project/admin.py new file mode 100644 index 0000000..ea5d68b --- /dev/null +++ b/apps/project/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/project/apps.py b/apps/project/apps.py new file mode 100644 index 0000000..e7d625f --- /dev/null +++ b/apps/project/apps.py @@ -0,0 +1,11 @@ +from django.apps import AppConfig + +class ProjectConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.project' + + def ready(self) -> None: + from apps.project.signals import set_request_locals, clear_request_locals + from django.core.signals import request_finished, request_started + request_started.connect(set_request_locals) + request_finished.connect(clear_request_locals) diff --git a/apps/project/controllers/__init__.py b/apps/project/controllers/__init__.py new file mode 100644 index 0000000..6171f58 --- /dev/null +++ b/apps/project/controllers/__init__.py @@ -0,0 +1,13 @@ +# 导入所有控制器 +from apps.project.controllers.project import ProjectController +from apps.project.controllers.round import RoundController +from apps.project.controllers.dut import DutController +from apps.project.controllers.design import DesignController +from apps.project.controllers.testDemand import TestDemandController +from apps.project.controllers.case import CaseController +from apps.project.controllers.problem import ProblemController +from apps.project.controllers.treeOperation import TreeController + +# 将导入的控制器以列表方式放入下面数组 +__all__ = ['ProjectController', 'RoundController', 'DutController', 'DesignController', 'TestDemandController', + 'CaseController', 'ProblemController', 'TreeController'] diff --git a/apps/project/controllers/__pycache__/__init__.cpython-313.pyc b/apps/project/controllers/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..ae36e30 Binary files /dev/null and b/apps/project/controllers/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/project/controllers/__pycache__/__init__.cpython-38.pyc b/apps/project/controllers/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..4bcc6ae Binary files /dev/null and b/apps/project/controllers/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/project/controllers/__pycache__/case.cpython-313.pyc b/apps/project/controllers/__pycache__/case.cpython-313.pyc new file mode 100644 index 0000000..333f809 Binary files /dev/null and b/apps/project/controllers/__pycache__/case.cpython-313.pyc differ diff --git a/apps/project/controllers/__pycache__/case.cpython-38.pyc b/apps/project/controllers/__pycache__/case.cpython-38.pyc new file mode 100644 index 0000000..4d9c81d Binary files /dev/null and b/apps/project/controllers/__pycache__/case.cpython-38.pyc differ diff --git a/apps/project/controllers/__pycache__/design.cpython-313.pyc b/apps/project/controllers/__pycache__/design.cpython-313.pyc new file mode 100644 index 0000000..bc09e85 Binary files /dev/null and b/apps/project/controllers/__pycache__/design.cpython-313.pyc differ diff --git a/apps/project/controllers/__pycache__/design.cpython-38.pyc b/apps/project/controllers/__pycache__/design.cpython-38.pyc new file mode 100644 index 0000000..91c1699 Binary files /dev/null and b/apps/project/controllers/__pycache__/design.cpython-38.pyc differ diff --git a/apps/project/controllers/__pycache__/dut.cpython-313.pyc b/apps/project/controllers/__pycache__/dut.cpython-313.pyc new file mode 100644 index 0000000..b0b20f4 Binary files /dev/null and b/apps/project/controllers/__pycache__/dut.cpython-313.pyc differ diff --git a/apps/project/controllers/__pycache__/dut.cpython-38.pyc b/apps/project/controllers/__pycache__/dut.cpython-38.pyc new file mode 100644 index 0000000..e58360b Binary files /dev/null and b/apps/project/controllers/__pycache__/dut.cpython-38.pyc differ diff --git a/apps/project/controllers/__pycache__/problem.cpython-313.pyc b/apps/project/controllers/__pycache__/problem.cpython-313.pyc new file mode 100644 index 0000000..b9d6154 Binary files /dev/null and b/apps/project/controllers/__pycache__/problem.cpython-313.pyc differ diff --git a/apps/project/controllers/__pycache__/problem.cpython-38.pyc b/apps/project/controllers/__pycache__/problem.cpython-38.pyc new file mode 100644 index 0000000..dc57823 Binary files /dev/null and b/apps/project/controllers/__pycache__/problem.cpython-38.pyc differ diff --git a/apps/project/controllers/__pycache__/project.cpython-313.pyc b/apps/project/controllers/__pycache__/project.cpython-313.pyc new file mode 100644 index 0000000..f98eede Binary files /dev/null and b/apps/project/controllers/__pycache__/project.cpython-313.pyc differ diff --git a/apps/project/controllers/__pycache__/project.cpython-38.pyc b/apps/project/controllers/__pycache__/project.cpython-38.pyc new file mode 100644 index 0000000..97fc0ed Binary files /dev/null and b/apps/project/controllers/__pycache__/project.cpython-38.pyc differ diff --git a/apps/project/controllers/__pycache__/round.cpython-313.pyc b/apps/project/controllers/__pycache__/round.cpython-313.pyc new file mode 100644 index 0000000..24b97de Binary files /dev/null and b/apps/project/controllers/__pycache__/round.cpython-313.pyc differ diff --git a/apps/project/controllers/__pycache__/round.cpython-38.pyc b/apps/project/controllers/__pycache__/round.cpython-38.pyc new file mode 100644 index 0000000..bd7d261 Binary files /dev/null and b/apps/project/controllers/__pycache__/round.cpython-38.pyc differ diff --git a/apps/project/controllers/__pycache__/testDemand.cpython-313.pyc b/apps/project/controllers/__pycache__/testDemand.cpython-313.pyc new file mode 100644 index 0000000..d480ac4 Binary files /dev/null and b/apps/project/controllers/__pycache__/testDemand.cpython-313.pyc differ diff --git a/apps/project/controllers/__pycache__/testDemand.cpython-38.pyc b/apps/project/controllers/__pycache__/testDemand.cpython-38.pyc new file mode 100644 index 0000000..38ba4df Binary files /dev/null and b/apps/project/controllers/__pycache__/testDemand.cpython-38.pyc differ diff --git a/apps/project/controllers/__pycache__/treeOperation.cpython-313.pyc b/apps/project/controllers/__pycache__/treeOperation.cpython-313.pyc new file mode 100644 index 0000000..7a55bd5 Binary files /dev/null and b/apps/project/controllers/__pycache__/treeOperation.cpython-313.pyc differ diff --git a/apps/project/controllers/__pycache__/treeOperation.cpython-38.pyc b/apps/project/controllers/__pycache__/treeOperation.cpython-38.pyc new file mode 100644 index 0000000..087f117 Binary files /dev/null and b/apps/project/controllers/__pycache__/treeOperation.cpython-38.pyc differ diff --git a/apps/project/controllers/case.py b/apps/project/controllers/case.py new file mode 100644 index 0000000..2771928 --- /dev/null +++ b/apps/project/controllers/case.py @@ -0,0 +1,245 @@ +from ninja_extra import api_controller, ControllerBase, route +from ninja import Query +from ninja_jwt.authentication import JWTAuth +from ninja_extra.permissions import IsAuthenticated +from ninja.pagination import paginate +from ninja.errors import HttpError +from utils.chen_pagination import MyPagination +from django.db import transaction +from django.shortcuts import get_object_or_404 +from typing import List +from utils.chen_response import ChenResponse +from utils.chen_crud import multi_delete_case +from apps.project.models import Design, Dut, Round, TestDemand, Case, CaseStep, Project, Problem +from apps.project.schemas.case import DeleteSchema, CaseModelOutSchema, CaseFilterSchema, CaseTreeReturnSchema, \ + CaseTreeInputSchema, CaseCreateOutSchema, CaseCreateInputSchema, DemandNodeSchema +from utils.util import get_testType +from utils.codes import HTTP_INDEX_ERROR, HTTP_EXISTS_CASES +from apps.project.tools.copyCase import case_move_to_test, case_copy_to_test, case_to_case_copy_or_move +from utils.smallTools.interfaceTools import conditionNoneToBlank +# 导入case的schema +from apps.project.schemas.case import CaseModelOutSchemaWithoutProblem + +@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['测试用例接口']) +class CaseController(ControllerBase): + @route.get("/getCaseList", response=List[CaseModelOutSchema], exclude_none=True, + url_name="case-list") + @transaction.atomic + @paginate(MyPagination) + def get_case_list(self, data: CaseFilterSchema = Query(...)): + """有id则查询一个case,无id则查询多个""" + data_dict = data.dict() + case_id = data_dict.pop('id') + if case_id: + # 当传入了id,则查询单个 + qs = Case.objects.filter(id=case_id) # type:ignore + else: + conditionNoneToBlank(data) + test_key = "".join([data.round_id, '-', data.dut_id, '-', data.design_id, '-', data.test_id]) + qs = Case.objects.filter(project__id=data.project_id, test__key=test_key, # type:ignore + ident__icontains=data.ident, + name__icontains=data.name, + designPerson__icontains=data.designPerson, + testPerson__icontains=data.testPerson, + monitorPerson__icontains=data.monitorPerson, + summarize__icontains=data.summarize, + ).order_by("key") + # 由于有嵌套query_set存在,把每个用例的schema加上一个字段 + query_list = [] + for query_single in qs: + setattr(query_single, "testStep", query_single.step.all().values()) + # 增加一个字段,测试类型例如:FT + setattr(query_single, 'testType', get_testType(query_single.test.testType, dict_code='testType')) + # 如果有问题单字段则添加上 + related_problem: Problem = query_single.caseField.first() + if query_single.caseField.all(): + setattr(query_single, 'problem', related_problem) + query_list.append(query_single) + return query_list + + @route.get("/getCaseOne", response=CaseModelOutSchemaWithoutProblem, url_name='case-one') + @transaction.atomic + def get_case_one(self, key: str, projectId: int): + """用于在用例树状页面,获取promblem信息,这里根据key获取信息""" + project_obj = get_object_or_404(Project, id=projectId) + case = project_obj.pcField.filter(key=key).first() + if case: + setattr(case, "testStep", case.step.all().values()) + setattr(case, 'testType', get_testType(case.test.testType, dict_code='testType')) + return case + raise HttpError(500, "您获取的数据不存在") + + # 处理树状数据 + @route.get("/getCaseInfo", response=List[CaseTreeReturnSchema], url_name="case-info") + @transaction.atomic + def get_case_tree(self, payload: CaseTreeInputSchema = Query(...)): + qs = Case.objects.filter(project__id=payload.project_id, test__key=payload.key) # type:ignore + for q in qs: + # 遍历每个用例节点,查看是否有关联问题单 + if q.caseField.count() > 0: + q.isRelatedProblem = True + # 遍历用例的step查看是否有未通过 + q.isNotPassed = False + for step in q.step.all(): + if step.passed == '2': + q.isNotPassed = True + return qs + + # 添加测试用例 + @route.post("/case/save", response=CaseCreateOutSchema, url_name="case-create") + @transaction.atomic + def create_case(self, payload: CaseCreateInputSchema): + asert_dict = payload.dict(exclude_none=True) + # 构造design_key + test_whole_key = "".join( + [payload.round_key, "-", payload.dut_key, '-', payload.design_key, '-', payload.test_key]) + # 查询当前key应该为多少 + case_count = Case.objects.filter(project__id=payload.project_id, # type:ignore + test__key=test_whole_key).count() + key_string = ''.join([test_whole_key, "-", str(case_count)]) + # 查询当前各个前面节点的instance + round_instance = Round.objects.get(project__id=payload.project_id, key=payload.round_key) + dut_instance = Dut.objects.get(project__id=payload.project_id, # type:ignore + key="".join([payload.round_key, "-", payload.dut_key])) + design_instance = Design.objects.get(project__id=payload.project_id, key="".join( # type:ignore + [payload.round_key, "-", payload.dut_key, '-', payload.design_key])) + test_instance = TestDemand.objects.get(project__id=payload.project_id, key="".join( # type:ignore + [payload.round_key, "-", payload.dut_key, '-', payload.design_key, '-', payload.test_key])) + # 直接把测试项的标识给前端处理显示 + asert_dict['ident'] = test_instance.ident + # ~~~~~~~~~end~~~~~~~~~ + asert_dict.update({'key': key_string, 'round': round_instance, 'dut': dut_instance, 'design': design_instance, + "test": test_instance, 'title': payload.name}) + asert_dict.pop("round_key") + asert_dict.pop("dut_key") + asert_dict.pop("design_key") + asert_dict.pop("test_key") + asert_dict.pop("testStep") + qs = Case.objects.create(**asert_dict) # type:ignore + # 对testStep单独处理 + data_list = [] + for item in payload.dict()["testStep"]: + if not isinstance(item, dict): + item = item.dict() + item["case"] = qs + data_list.append(CaseStep(**item)) + CaseStep.objects.bulk_create(data_list) # type:ignore + return qs + + # 更新测试用例 + @route.put("/case/update/{id}", response=CaseCreateOutSchema, url_name="case-update") + @transaction.atomic + def update_case(self, id: int, payload: CaseCreateInputSchema): + # 查到当前 + case_qs = Case.objects.get(id=id) # type:ignore + for attr, value in payload.dict().items(): + if attr == 'project_id' or attr == 'round_key' or attr == 'dut_key' or attr == 'design_key' or attr == 'test_key': + continue + if attr == 'name': + setattr(case_qs, "title", value) + # testStep处理 + if attr == 'testStep': + content_list = case_qs.step.all() + for content_single in content_list: + content_single.delete() + data_list = [] + for item in value: + if item['operation'] or item['expect'] or item['result'] or item['passed'] or item['status']: + item["case"] = case_qs + data_list.append(CaseStep(**item)) + CaseStep.objects.bulk_create(data_list) # type:ignore + + setattr(case_qs, attr, value) + # 处理标识-统一设置为YL + case_qs.ident = case_qs.test.ident + # ~~~~~~~~~end~~~~~~~~~ + case_qs.save() + return case_qs + + # 删除测试用例 + @route.delete("/case/delete", url_name="case-delete") + @transaction.atomic + def delete_case(self, data: DeleteSchema): + # 根据其中一个id查询出dut_id,注意这里解决前端框架问题:删除后还报错选择的行id + try: + case_single = Case.objects.filter(id=data.ids[0])[0] # type:ignore + except IndexError: + return ChenResponse(status=500, code=HTTP_INDEX_ERROR, message='您未选择需要删除的内容') + test_id = case_single.test.id + test_key = case_single.test.key + multi_delete_case(data.ids, Case) + index = 0 + case_all_qs = Case.objects.filter(test__id=test_id).order_by('id') # type:ignore + for single_qs in case_all_qs: + case_key = "".join([test_key, '-', str(index)]) + single_qs.key = case_key + index = index + 1 + single_qs.save() + return ChenResponse(message="测试用例删除成功!") + + # 右键测试项,根据测试子项生成用例 + @route.post("/case/create_by_demand", url_name='case-create-by-demand') + def create_case_by_demand(self, demand_node: DemandNodeSchema): + project_qs = get_object_or_404(Project, id=demand_node.project_id) + if demand_node.key and demand_node.key != '': + demand = get_object_or_404(TestDemand, key=demand_node.key, project=project_qs) + # 先查询当前测试项下面有无case + case_exists = demand.tcField.exists() + if case_exists: + return ChenResponse(status=500, code=HTTP_EXISTS_CASES, message='测试项下面有用例,请删除后生成') + # 查询所有测试子项 + sub_items = demand.testQField.all() + # 每一个子项都创建一个用例,先声明一个列表,后面可以bulk_create + index = 0 + for sub in sub_items: + user_name = self.context.request.user.name # type:ignore + case_dict = { + 'ident': demand.ident, + 'name': sub.subName, + 'initialization': '软件正常启动,正常运行', + 'premise': '软件正常启动,外部接口运行正常', + 'summarize': demand.testDesciption, + 'designPerson': user_name, + 'testPerson': user_name, + 'monitorPerson': user_name, + 'project': project_qs, + 'round': demand.round, + 'dut': demand.dut, + 'design': demand.design, + 'test': demand, + 'title': sub.subName, + 'key': ''.join([demand_node.key, '-', str(index)]), + 'level': '4', + } + case_model = Case.objects.create(**case_dict) # type:ignore + # 创建用例步骤 + for demand_step_obj in sub.testStepField.all(): + operation = demand_step_obj.operation + case_step_dict = { + 'operation': "".join([operation if operation is not None else ""]), + 'expect': demand_step_obj.expect, + 'result': '', # 暂时为空 + 'case': case_model, # 指定父级Case模型 + } + CaseStep.objects.create(**case_step_dict) # type:ignore + index += 1 + # 这里返回一个demand的key用于前端刷新树状图 + return ChenResponse(data={'key': demand_node.key}, status=200, code=200, message='测试项自动生成用例成功') + + # 测试用例复制/移动到测试项上 + @route.get("/case/copy_or_move_to_demand", url_name='case-copy-move-demand') + @transaction.atomic + def copy_move_case_to_demand(self, project_id: int, case_key: str, demand_key: str, move: bool): + if move: # 移动 + old_key, new_key = case_move_to_test(project_id, case_key, demand_key) + else: # 复制 + old_key, new_key = case_copy_to_test(project_id, case_key, demand_key) + # 返回刷新树状信息-需要刷新2个,原来的case_key和现在的case_key + return ChenResponse(data={'oldCaseKey': {'key': old_key}, 'newCaseKey': {'key': new_key}}) + + # 测试用例复制/移动到用例 + @route.get("/case/copy_or_move_by_case", url_name='case-copy-move-case') + @transaction.atomic + def copy_move_case_by_case(self, project_id: int, drag_key: str, drop_key: str, move: bool, position: int): + case_to_case_copy_or_move(project_id, drag_key, drop_key, move, position) + return ChenResponse(data={'old': {'key': drag_key}, 'new': {'key': drop_key}}) diff --git a/apps/project/controllers/design.py b/apps/project/controllers/design.py new file mode 100644 index 0000000..33e6079 --- /dev/null +++ b/apps/project/controllers/design.py @@ -0,0 +1,158 @@ +from ninja_extra import api_controller, ControllerBase, route +from ninja import Query +from ninja_jwt.authentication import JWTAuth +from ninja_extra.permissions import IsAuthenticated +from ninja.pagination import paginate +from ninja.errors import HttpError +from utils.chen_pagination import MyPagination +from django.db import transaction +from django.shortcuts import get_object_or_404 +from typing import List +from utils.chen_response import ChenResponse +from utils.chen_crud import multi_delete_design +from utils.codes import HTTP_INDEX_ERROR +from apps.project.models import Design, Dut, Round, Project +from apps.project.schemas.design import DeleteSchema, DesignFilterSchema, DesignModelOutSchema, DesignTreeReturnSchema, \ + DesignTreeInputSchema, DesignCreateOutSchema, DesignCreateInputSchema, MultiDesignCreateInputSchema +from apps.project.tools.delete_change_key import design_delete_sub_node_key +from utils.smallTools.interfaceTools import conditionNoneToBlank + +@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['设计需求数据']) +class DesignController(ControllerBase): + @route.get("/getDesignDemandList", response=List[DesignModelOutSchema], exclude_none=True, url_name="design-list") + @transaction.atomic + @paginate(MyPagination) + def get_design_list(self, datafilter: DesignFilterSchema = Query(...)): + conditionNoneToBlank(datafilter) + dut_key = "".join([datafilter.round_id, '-', datafilter.dut_id]) + qs = Design.objects.filter(project__id=datafilter.project_id, dut__key=dut_key, + ident__icontains=datafilter.ident, + name__icontains=datafilter.name, + demandType__contains=datafilter.demandType, + chapter__icontains=datafilter.chapter).order_by('id') + return qs + + @route.get("/getDesignOne", response=DesignModelOutSchema, url_name='design-one') + def get_dut(self, project_id: int, key: str): + design_qs = Design.objects.filter(project_id=project_id, key=key).first() + if design_qs: + return design_qs + raise HttpError(500, "未找到相应的数据") + + # 处理树状数据 + @route.get("/getDesignDemandInfo", response=List[DesignTreeReturnSchema], url_name="design-info") + def get_design_tree(self, payload: DesignTreeInputSchema = Query(...)): + qs = Design.objects.filter(project__id=payload.project_id, dut__key=payload.key).order_by('id') + return qs + + # 添加设计需求 + @route.post("/designDemand/save", response=DesignCreateOutSchema, url_name="design-create") + @transaction.atomic + def create_design(self, payload: DesignCreateInputSchema): + asert_dict = payload.dict(exclude_none=True) + # 如果识别description为None变为空字符串 + description = asert_dict.get('description') + # 构造dut_key + dut_key = "".join([payload.round_key, "-", payload.dut_key]) + # 判重标识-不需要再查询round以后的 + if Design.objects.filter(project__id=payload.project_id, round__key=payload.round_key, dut__key=dut_key, + ident=payload.ident).exists() and asert_dict['ident'] != "": + return ChenResponse(code=400, status=400, message='研制需求的标识重复,请检查') + # 查询当前key应该为多少 + design_count = Design.objects.filter(project__id=payload.project_id, dut__key=dut_key).count() + key_string = ''.join([dut_key, "-", str(design_count)]) + # 查询当前的round_id + round_instance = Round.objects.get(project__id=payload.project_id, key=payload.round_key) + dut_instance = Dut.objects.get(project__id=payload.project_id, key=dut_key) + asert_dict.update({'key': key_string, 'round': round_instance, 'dut': dut_instance, 'title': payload.name}) + asert_dict.pop("round_key") + asert_dict.pop("dut_key") + qs = Design.objects.create(**asert_dict) + return qs + + # 批量增加设计需求,对应前端批量增加页面modal + @route.post('/designDemand/multi_save', url_name='design-multi-create') + @transaction.atomic + def multi_create_design(self, payload: MultiDesignCreateInputSchema): + project_obj = get_object_or_404(Project, id=payload.project_id) + dut_obj = project_obj.pdField.filter(key=payload.dut_key).first() + round_obj = dut_obj.round + # 当前dut下的design个数 + design_count = Design.objects.filter(project=project_obj, dut=dut_obj).count() + key_index = design_count + # 这里根据payload.data批量增加 + bulk_list = [] + for desgin_obj in payload.data: + design_one = Design(**desgin_obj.model_dump()) + design_one.title = design_one.name + # 计算出当前key应该为多少 + design_one.key = ''.join([dut_obj.key, "-", str(key_index)]) + key_index += 1 + design_one.level = '2' + design_one.project = project_obj + design_one.round = round_obj + design_one.dut = dut_obj + bulk_list.append(design_one) + Design.objects.bulk_create(bulk_list) + # 为了前端更新,需要返回一个dut_key + return ChenResponse(status=200, code=200, data={'key': dut_obj.key + '-1'}) + + # 更新设计需求 + @route.put("/editDesignDemand/{id}", response=DesignCreateOutSchema, url_name="design-update") + @transaction.atomic + def update_design(self, id: int, payload: DesignCreateInputSchema): + design_search = Design.objects.filter(project__id=payload.project_id, ident=payload.ident, + round__key=payload.round_key) + # 判断是否和同项目同轮次的标识重复 + if len(design_search) > 1 and payload.ident != '': + return ChenResponse(code=400, status=400, message='研制需求的标识重复,请检查') + # 查到当前 + design_qs = Design.objects.get(id=id) + for attr, value in payload.dict().items(): + if attr == 'project_id' or attr == 'round_key' or attr == 'dut_key': + continue + if attr == 'name': + setattr(design_qs, "title", value) + setattr(design_qs, attr, value) + design_qs.save() + return design_qs + + # 删除设计需求 + @route.delete("/designDemand/delete", url_name="design-delete") + @transaction.atomic + def delete_design(self, data: DeleteSchema): + # 根据其中一个id查询出dut_id + try: + design_single = Design.objects.filter(id=data.ids[0])[0] + except IndexError: + return ChenResponse(status=500, code=HTTP_INDEX_ERROR, message='您未选择需要删除的内容') + dut_id = design_single.dut.id + dut_key = design_single.dut.key + multi_delete_design(data.ids, Design) + index = 0 + design_all_qs = Design.objects.filter(dut__id=dut_id).order_by('id') + for single_qs in design_all_qs: + design_key = "".join([dut_key, '-', str(index)]) + single_qs.key = design_key + index = index + 1 + single_qs.save() + design_delete_sub_node_key(single_qs) + return ChenResponse(message="设计需求删除成功!") + + # 给复制功能级联选择器查询所有的设计需求 + @route.get("/designDemand/getRelatedDesign", url_name='dut-relatedDesign') + def getRelatedDesign(self, id: int): + project_qs = get_object_or_404(Project, id=id) + # 依次找出round -> dut -> design + round_qs = project_qs.pField.all() + data_list = [] + for round in round_qs: + round_dict = {'label': round.name, 'value': round.id, 'children': []} + for dut in round.rdField.all(): + dut_dict = {'label': dut.name, 'value': dut.id, 'children': []} + for design in dut.rsField.all(): + design_dict = {'label': design.name, 'value': design.id, 'key': design.key} + dut_dict['children'].append(design_dict) + round_dict['children'].append(dut_dict) + data_list.append(round_dict) + return ChenResponse(message='获取成功', data=data_list) diff --git a/apps/project/controllers/dut.py b/apps/project/controllers/dut.py new file mode 100644 index 0000000..591e27f --- /dev/null +++ b/apps/project/controllers/dut.py @@ -0,0 +1,246 @@ +import os +import tempfile +from copy import deepcopy +from ninja_extra import api_controller, ControllerBase, route +from ninja import Query, File, UploadedFile +from ninja.errors import HttpError +from ninja_jwt.authentication import JWTAuth +from ninja_extra.permissions import IsAuthenticated +from ninja.pagination import paginate +from utils.chen_pagination import MyPagination +from django.db import transaction +from typing import List +from utils.chen_response import ChenResponse +from utils.chen_crud import multi_delete_dut +from utils.codes import HTTP_INDEX_ERROR +from apps.project.models import Dut, Round, Project, DutMetrics +from django.shortcuts import get_object_or_404 +from apps.project.schemas.dut import DutModelOutSchema, DutFilterSchema, DutTreeReturnSchema, DutTreeInputSchema, \ + DutCreateInputSchema, DutCreateOutSchema, DeleteSchema, DutCreateR1SoDutSchema +# 导入自动生成design、demand、case的辅助函数 +from apps.project.tools.auto_create_data import auto_create_jt_and_dm, auto_create_wd +from apps.project.tools.delete_change_key import dut_delete_sub_node_key +from utils.smallTools.interfaceTools import model_retrieve +# 导入代码统计函数 +from apps.project.tool.source_counter import analyze_code_directory, extract_and_get_paths +# 导入需求解析类 +from apps.project.tool.xq_parse import DocxChapterExtractor + +@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['被测件数据']) +class DutController(ControllerBase): + @route.get("/getDutList", response=List[DutModelOutSchema], exclude_none=True, url_name="dut-list") + @transaction.atomic + @paginate(MyPagination) + def get_dut_list(self, filters: DutFilterSchema = Query(...)): + qs = model_retrieve(filters, Dut.objects, ['project_id', 'round_id']).order_by("-create_datetime") + qs = qs.filter(project__id=filters.project_id, round__key=filters.round_id) + return qs + + # 处理树状数据 + @route.get("/getDutInfo", response=List[DutTreeReturnSchema], url_name="dut-info") + def get_round_tree(self, payload: DutTreeInputSchema = Query(...)): + qs = Dut.objects.filter(project__id=payload.project_id, round__key=payload.key) + return qs + + # 获取单个dut + @route.get("/getDutOne", response=DutModelOutSchema, url_name="dut-one") + @transaction.atomic + def get_dut(self, project_id: int, key: str): + dut_qs = Dut.objects.filter(project_id=project_id, key=key).first() + if dut_qs: + return dut_qs + raise HttpError(500, "未找到相应的数据") + + # 添加被测件 + @route.post("/dut/save", url_name="dut-create", response=DutCreateOutSchema) + @transaction.atomic + def create_dut(self, payload: DutCreateInputSchema): + asert_dict = payload.dict(exclude_none=True) + # 当被测件为SO时,一个轮次只运行有一个 + if payload.type == 'SO': + if Dut.objects.filter(project__id=payload.project_id, round__key=payload.round_key, type='SO').exists(): + return ChenResponse(code=400, status=400, message='源代码被测件一个轮次只能添加一个') + # 判重标识 + if Dut.objects.filter(project__id=payload.project_id, round__key=payload.round_key, + ident=payload.ident).exists(): + return ChenResponse(code=400, status=400, message='被测件的标识重复,请检查') + # 查询当前key应该为多少 + dut_count = Dut.objects.filter(project__id=payload.project_id, round__key=payload.round_key).count() + key_string = ''.join([payload.round_key, "-", str(dut_count)]) + # 然后在标识后面加上UT+KEY -> 注意删除时也改了key要对应修改blink1->>>>>> + asert_dict['ident'] = ''.join([asert_dict['ident'], str(dut_count + 1)]) + # 查询当前的round_id + round_instance = Round.objects.get(project__id=payload.project_id, key=payload.round_key) + asert_dict.update({'key': key_string, 'round': round_instance, 'title': payload.name}) + asert_dict.pop("round_key") + qs = Dut.objects.create(**asert_dict) + return qs + + # 更新被测件 + @route.put("/dut/update/{id}", url_name="dut-update", response=DutCreateOutSchema) + @transaction.atomic + def update_dut(self, id: int, payload: DutCreateInputSchema): + dut_search = Dut.objects.filter(project__id=payload.project_id, ident=payload.ident) + # 判断是否和同项目同轮次的标识重复 + if len(dut_search) > 1: + return ChenResponse(code=400, status=400, message='被测件的标识重复,请检查') + # 查到当前 + if payload.type == 'SO': + dut_qs = Dut.objects.get(id=id) + for attr, value in payload.dict().items(): + if attr == 'project_id' or attr == 'round_key': + continue + if attr == 'name': + setattr(dut_qs, "title", value) + setattr(dut_qs, attr, value) + dut_qs.save() + return dut_qs + else: + dut_qs = Dut.objects.get(id=id) + for attr, value in payload.dict().items(): + if attr == 'project_id' or attr == 'round_key': + continue + if attr == 'total_lines' or attr == 'effective_lines' or attr == 'comment_lines': + setattr(dut_qs, attr, "") + continue + if attr == 'name': + setattr(dut_qs, "title", value) + setattr(dut_qs, attr, value) + dut_qs.save() + return dut_qs + + # 删除被测件 - 1.重新对key排序 2.重新对表示尾号排序 + @route.delete("/dut/delete", url_name="dut-delete") + @transaction.atomic + def delete_dut(self, data: DeleteSchema): + # 查询某一个dut对象 + try: + dut_single = Dut.objects.filter(id=data.ids[0])[0] + except IndexError: + return ChenResponse(status=500, code=HTTP_INDEX_ERROR, message='您未选择需要删除的内容') + # 查询出dut所属的轮次id、key + round_id = dut_single.round.id + round_key = dut_single.round.key + # blink1->>>>>> 这里不仅重排key,还要重排ident中编号,先取出前面的RXXXX-RX等信息,这里必须要在删除之前 + # 查询出当前轮次所有dut + ids = deepcopy(data.ids) + message = '被测件删除成功' + for id in data.ids: + dut_obj = Dut.objects.filter(type='SO', id=id).first() + if dut_obj: + ids.remove(id) + message = '源代码被测件不能删除' + multi_delete_dut(ids, Dut) + dut_all_qs = Dut.objects.filter(round__id=round_id).order_by('id') + ident_before_string = dut_all_qs[0].ident.split("UT")[0] # 输出类似于“R2233-R1-” + index = 0 + for single_qs in dut_all_qs: + dut_key = "".join([round_key, '-', str(index)]) # 重排现有的dut的key + single_qs.key = dut_key + single_qs.ident = ident_before_string + "UT" + str(index + 1) + index = index + 1 + single_qs.save() + # 不仅重排自己的还要改所有子类的key,因为还是之前的key + dut_delete_sub_node_key(single_qs) + + return ChenResponse(message=message) + + # 查询项目中第一轮次是否存在源代码的被测件 -> 5月16日更改:查每一轮是否有源代码被测件 + @route.get("/dut/soExist", url_name="dut-soExist") + @transaction.atomic + def delete_soExist(self, id: int): + project_obj = get_object_or_404(Project, id=id) + # 先查询项目的所有轮次 + round_qs = project_obj.pField.all() + data = { + 'round_count': round_qs.count(), + 'round_list': [] + } + for round_obj in round_qs: + so_dut_exists = round_obj.rdField.filter(type='SO').exists() + round_dict = { + 'key': round_obj.key, + 'isExists': so_dut_exists + } + data['round_list'].append(round_dict) + return ChenResponse(code=200, status=200, message='在data展示轮次是否有源代码信息', data=data) + + # 弹窗添加第一轮被测件源代码信息,另外创建测试项(静态分析、代码审查),测试用例(静态分析、代码审查) + @route.post("/dut/createR1Sodut", response=DutCreateOutSchema, url_name='dut-r1SoDut') + @transaction.atomic + def create_r1_so_dut(self, data: DutCreateR1SoDutSchema): + asert_dict = data.dict(exclude_none=True) # asert_dict['round_key']可以获取是第几轮次 + round_key = asert_dict.pop('round_key') + project_obj = get_object_or_404(Project, id=data.project_id) + if Dut.objects.filter(project__id=data.project_id, round__key=round_key, type='SO').exists(): + return ChenResponse(code=400, status=400, message='源代码被测件一个轮次只能添加一个') + # 查询当前key应该为多少 + dut_count = Dut.objects.filter(project__id=data.project_id, round__key=round_key).count() + key_string = ''.join([round_key, "-", str(dut_count)]) + asert_dict['ident'] = "-".join( + [project_obj.ident, ''.join(['R', str(int(round_key) + 1)]), 'UT', str(dut_count + 1)]).replace("UT-", "UT") + # 查询round_id + round_id = project_obj.pField.filter(key=round_key).first().id + asert_dict['round_id'] = round_id + asert_dict.update({'key': key_string, 'title': '软件源代码', 'type': 'SO', 'name': '软件源代码', 'level': '1'}) + dut_qs: Dut = Dut.objects.create(**asert_dict) + # 到这里就自动生成了第一轮的源代码dut,下面使用辅助函数自动生成(静态分析、代码审查) + user_name = self.context.request.user.name + # 注意判断如果非第一轮次 + # 1.自动生成静态分析、代码审查 + auto_create_jt_and_dm(user_name, dut_qs, project_obj) + # 2.自动生成文档审查在源代码被测件中 + auto_create_wd(user_name, dut_qs, project_obj) + return dut_qs + + # 进入dut页面,返回dut的类型,例如XQ/XY/SO + @route.get('/dut/dut_type', url_name='testDemand-type') + @transaction.atomic + def get_dut_type(self, project_id: int, key: str): + project_qs = get_object_or_404(Project, id=project_id) + dut = project_qs.pdField.filter(key=key).first() + return ChenResponse(code=200, status=200, data={'dut_type': dut.type}) + +@api_controller("/dut_upload", tags=['上传源代码/上传需求规格说明解析']) +class UploadController(ControllerBase): + # 上传zip、7z、rar压缩文件然后计算圈复杂度等信息 + @route.post("/upload_file", url_name='dut-upload-file') + def upload_code_lines(self, dut_id: int, file: File[UploadedFile]): + # 获取dut对象 + dut_qs: Dut = get_object_or_404(Dut, id=dut_id) + # 创建临时目录 + with tempfile.TemporaryDirectory() as tmp_dir: + # 保存上传的ZIP文件 + zip_path = os.path.join(tmp_dir, file.name) + with open(zip_path, 'wb') as f: + for chunk in file.chunks(): + f.write(chunk) + # 解压并获取文件路径 + source_root = extract_and_get_paths(zip_path, tmp_dir) + results = analyze_code_directory(source_root) + # 判断是否录入了metrics,并且去掉ORM不需要字段 + key_to_remove = {'comment_rate', 'total_lines', 'effective_lines', 'comment_lines', 'code_ratio'} + create_results = {k: v for k, v in results.items() if k not in key_to_remove} + # 这是判断反向外键是否存在的关键 + if not hasattr(dut_qs, 'metrics'): + DutMetrics.objects.create(**create_results, dut=dut_qs) + DutMetrics.objects.filter(dut=dut_qs).update(**create_results) + # 进行储存 + dut_qs.total_lines = results['total_lines'] + dut_qs.effective_lines = results['effective_lines'] + dut_qs.comment_lines = results['comment_lines'] + dut_qs.save() + return results + + # 上传需求规格说明.docx进行解析 + @route.post("/upload_xq_docx/", url_name='dut-xq-docx') + def upload_xq_docx(self, dut_key: str, project_id: int, file: File[UploadedFile]): + # 构建临时目录 + with tempfile.TemporaryDirectory() as tmp_dir: + # 保存到临时目录 + docx_path = os.path.join(tmp_dir, file.name) + with open(docx_path, 'wb') as f: + for chunk in file.chunks(): + f.write(chunk) + extractor = DocxChapterExtractor(docx_path) + extractor.main('需求') diff --git a/apps/project/controllers/problem.py b/apps/project/controllers/problem.py new file mode 100644 index 0000000..295d598 --- /dev/null +++ b/apps/project/controllers/problem.py @@ -0,0 +1,332 @@ +import datetime +import numpy as np +from ninja_extra import api_controller, ControllerBase, route +from ninja import Query +from ninja_jwt.authentication import JWTAuth +from ninja_extra.permissions import IsAuthenticated +from ninja.pagination import paginate +from apps.dict.models import DictItem +from utils.chen_pagination import MyPagination +from django.db import transaction +from typing import List, Optional +from utils.chen_response import ChenResponse +from utils.codes import HTTP_INDEX_ERROR +from django.shortcuts import get_object_or_404 +from apps.project.models import Case, Problem, Project, TestDemand +from apps.project.schemas.problem import ( + DeleteSchema, + ProblemModelOutSchema, + ProblemFilterSchema, + ProblemCreateOutSchema, + ProblemCreateInputSchema, + ProblemSingleInputSchema, + ProblemUpdateInputSchema, + ProblemFilterWithHangSchema +) +from utils.util import get_str_abbr +from utils.smallTools.interfaceTools import conditionNoneToBlank + +@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['问题单系列']) +class ProblemController(ControllerBase): + @route.get("/getProblemList", response=List[ProblemModelOutSchema], exclude_none=True, + url_name="problem-list") + @transaction.atomic + @paginate(MyPagination) + def get_problem_list(self, data: ProblemFilterSchema = Query(...)): + project_id = data.project_id + conditionNoneToBlank(data) + case_key = "".join([data.round_id, '-', data.dut_id, '-', data.design_id, '-', data.test_id, '-', data.case_id]) + # 先查询出对应的case + case_obj = Case.objects.filter(project_id=project_id, key=case_key).first() + # 然后进行过滤 + qs = case_obj.caseField.filter(project__id=data.project_id, + ident__icontains=data.ident, + name__icontains=data.name, + status__icontains=data.status, + type__icontains=data.type, + grade__icontains=data.grade, + operation__icontains=data.operation, + postPerson__icontains=data.postPerson, + ).order_by("id") + + # 遍历通过代码不通过ORM查询闭环方式-巧妙使用numpy中array对象的in方法来判断 + closeMethod1 = self.context.request.GET.get("closeMethod[0]") + closeMethod2 = self.context.request.GET.get("closeMethod[1]") + query_add_closeMethod = [] + for query in qs: + arr = np.array(query.closeMethod) + if closeMethod1 is None and closeMethod2 is None: + query_add_closeMethod.append(query) + continue + if closeMethod1 in arr: + query_add_closeMethod.append(query) + continue + if closeMethod2 in arr: + query_add_closeMethod.append(query) + continue + return query_add_closeMethod + + # 搜索全部问题单/或查询轮次下的问题单 + @route.get('/problem/searchAllProblem', response=List[ProblemModelOutSchema], exclude_none=True, + url_name="problem-allList") + @transaction.atomic + @paginate(MyPagination) + def get_all_problems(self, round_key: Optional[str] = False, data: ProblemFilterWithHangSchema = Query(...)): + project_id = data.project_id + for attr, value in data.__dict__.items(): + if getattr(data, attr) is None: + setattr(data, attr, '') + # 先查询当前项目 + qs = Problem.objects.filter(project__id=data.project_id, + ident__icontains=data.ident, + name__icontains=data.name, + status__icontains=data.status, + type__icontains=data.type, + grade__icontains=data.grade, + operation__icontains=data.operation, + postPerson__icontains=data.postPerson, + ).order_by("id") + closeMethod1 = self.context.request.GET.get("closeMethod[0]") + closeMethod2 = self.context.request.GET.get("closeMethod[1]") + query_final = [] + for query in qs: + arr = np.array(query.closeMethod) + if closeMethod1 is None and closeMethod2 is None: + query_final.append(query) + continue + if closeMethod1 in arr: + query_final.append(query) + continue + if closeMethod2 in arr: + query_final.append(query) + continue + # 遍历所有problem,查询是有否有关联case,如果有则设置hang为True,否则False + hang = True + # 过滤不是该轮次的问题单对象列表 + deleted_problem_list = [] + for pro_obj in query_final: + case_exists = pro_obj.case.exists() + if not case_exists: + setattr(pro_obj, "hang", hang) + # 如果有关联用例还要看是否是查询轮次的问题单,过滤出去 + elif case_exists: + hang = False + setattr(pro_obj, "hang", hang) + hang = True + if round_key: + if not pro_obj.case.filter(round__key=round_key).exists(): + deleted_problem_list.append(pro_obj) + for dq in deleted_problem_list: + query_final.remove(dq) + # !!!如果是轮次查询则返回轮次,如果是关联查询则查询关联当前的case + if round_key: + pass + else: + case_obj = Case.objects.filter(project_id=project_id, key=data.key).first() + if case_obj: + for pro_obj in query_final: + # 查询关联的case + related = False + for re_case in pro_obj.case.all(): + if case_obj.id == re_case.id: + related = True + setattr(pro_obj, "related", related) + # 过滤查询悬挂逻辑 + query_last = [] + if data.hang == '3' or data.hang == '': # 疑问:为什么会是空字符串 + query_last = query_final + if data.hang == '2': + for pp in query_final: + if not pp.hang: + query_last.append(pp) + if data.hang == '1': + for pp in query_final: + if pp.hang is True: + query_last.append(pp) + return query_last + + @staticmethod + def __date_solve(payload: ProblemCreateInputSchema): + """辅助函数: + 1.设置问题单时间,而不是默认进入时间,传入schema对象,返回schema对象,只对里面时间进行处理 + """ + project_obj = get_object_or_404(Project, id=payload.project_id) + round_obj = project_obj.pField.filter(key=payload.round_key).first() + if round_obj: + if payload.postDate is None: + payload.postDate = round_obj.beginTime + datetime.timedelta(days=1) + if payload.designDate is None: + payload.designDate = round_obj.beginTime + datetime.timedelta(days=2) + return payload + + # 添加问题单 + @route.post("/problem/save", response=ProblemCreateOutSchema, url_name="problem-create") + @transaction.atomic + def create_case_demand(self, payload: ProblemCreateInputSchema): + payload = self.__date_solve(payload) + asert_dict = payload.dict() + project_id = payload.project_id + # 查询problem的总数 + problem_count = Problem.objects.filter(project_id=project_id).count() + # 查询当前各个前面节点的instance + pop_keys: List[str] = ["round_key", "dut_key", "design_key", "test_key", "case_key"] + for pkey in pop_keys: + asert_dict.pop(pkey) + # 处理问题单标识PT_项目ident_数目依次增加 + asert_dict["ident"] = str(problem_count + 1) + qs = Problem.objects.create(**asert_dict) + # 处理时间 + qs.postDate = payload.postDate + qs.designDate = payload.designDate + qs.save() + # 分两个逻辑处理,无关联创建问题单/case下面创建问题单 + if payload.case_key: + # 构造case_key + case_key = "".join( + [payload.round_key, "-", payload.dut_key, '-', payload.design_key, '-', payload.test_key, '-', + payload.case_key]) + # 查询出所属的case + case_obj = Case.objects.filter(project_id=project_id, key=case_key).first() + qs.case.add(case_obj) + qs.save() + # 对problem的ident排序 + self.reset_problem_ident(project_id) + return qs + + # 更新问题单 + @route.put("/problem/update/{id}", response=ProblemCreateOutSchema, url_name="problem-update") + @transaction.atomic + def update_problem(self, id: int, payload: ProblemCreateInputSchema): + # 查到当前 + problem_qs = Problem.objects.get(id=id) + for attr, value in payload.dict().items(): + setattr(problem_qs, attr, value) + problem_qs.save() + return ChenResponse(message="问题单更新成功") + + # 弹窗的-更新问题单 + @route.put("/problem/modalupdate/{id}", response=ProblemCreateOutSchema, url_name="problem-update") + @transaction.atomic + def update_modal_problem(self, id: int, payload: ProblemUpdateInputSchema): + # 查到当前 + problem_qs = Problem.objects.get(id=id) + for attr, value in payload.dict().items(): + setattr(problem_qs, attr, value) + problem_qs.save() + return ChenResponse(message="问题单更新成功") + + # 删除问题单 + @route.delete("/problem/delete", url_name="problem-delete") + @transaction.atomic + def delete_problem(self, data: DeleteSchema): + # 1.查询出所有被删除id + problems = Problem.objects.filter(id__in=data.ids) + if not problems.exists(): + return ChenResponse(status=500, code=HTTP_INDEX_ERROR, message='您未选取删除内容') + # 4.查询出当前项目id + project_id = None + # 2.循环该取出problem + for problem in problems: + project_id = problem.project_id + # 3. 直接删除case关联,然后删除自己 + problem.case.clear() + problem.delete() + # 4.找到对应项目的所有problems进行排序 + if project_id is not None: + self.reset_problem_ident(project_id) + return ChenResponse(message="问题单删除成功!") + + # 根据问题单id,返回关联的用例s + @route.get('/getRelativeCases', url_name='problem-relative-case') + @transaction.atomic + def get_relative_cases(self, id: int): + problem_qs = get_object_or_404(Problem, id=id) + cases = problem_qs.case.all() + case_list = [] + for case in cases: + case_dict = { + 'id': case.id, + 'case': case.title, + 'round': case.round.title, + 'dut': case.dut.title, + 'design': case.design.title, + } + demand = case.test + case_dict['demand'] = demand.title + demand_testType_showtitle = get_str_abbr(demand.testType, 'testType') + case_dict['demand_ident'] = "-".join(['XQ', demand_testType_showtitle, demand.ident]) + case_list.append(case_dict) + return case_list + + # 单独显示问题单页面需要数据 + @route.get("/getSingleProblem", url_name="problem-single", response=ProblemCreateOutSchema) + @transaction.atomic + def search_single_problem(self, data: ProblemSingleInputSchema = Query(...)): + key_string = "".join( + [data.round_id, '-', data.dut_id, '-', data.design_id, '-', data.test_id, '-', data.case_id, '-', + data.problem_id]) + qs = Problem.objects.get(project__id=data.project_id, key=key_string) + return qs + + # 让测试用例关联/取消问题单 + @route.get('/problem/relateProblem', exclude_none=True, url_name="problem-allList") + @transaction.atomic + def relate_problem(self, case_key: str, problem_id: int, val: bool): # val是将要变成的值 + # 先判断将要变成的值是否为True + problem_obj: Problem = Problem.objects.filter(id=problem_id).first() + project_id = problem_obj.project_id # 根据问题单反推项目id + case_obj = Case.objects.filter(project_id=project_id, key=case_key).first() + flag = False # 是否操作成功的标志 + if val: + # 这分支是进行关联操作 + # 5月15日新需求:一个用例只能关联一个问题单 + if case_obj.caseField.count() >= 1: + return ChenResponse(code=400, status=400, message='请注意:一个用例只允许关联一个问题单', + data={'isOK': False}) + case_obj.caseField.add(problem_obj) + flag = True + else: + case_obj.caseField.remove(problem_obj) + flag = True + # 排序ident + if project_id: + self.reset_problem_ident(project_id) + return ChenResponse(code=200, status=200, message='关联或取消关联成功...', + data={'isOK': flag, 'key': case_obj.key}) + + # 类方法:操作后对problem的ident排序:先基于轮次排序,然后基于测试项类型排序 + @classmethod + def reset_problem_ident(cls, project_id: int): + project_obj: Project = get_object_or_404(Project, id=project_id) + # 获取所有问题单 + problem_qs = project_obj.projField.prefetch_related('case').prefetch_related('case__test') + # 待排序列表 + not_sorted_problems = [] + # 处理为List[Dict]以便后续排序修改ident + for problem in problem_qs: + cases = problem.case.all() + if len(cases): + # 如果关联了case + belong_demand: TestDemand = cases[0].test + # 找到对应测试类型 + test_type = DictItem.objects.get(dict__code='testType', key=belong_demand.testType) + # 找到测试类型的sort/找到轮次key + not_sorted_problems.append({ + 'problem': problem, + 'sort': test_type.sort, + 'round_key': belong_demand.round.key, + }) + else: + # 如果没有关联case + not_sorted_problems.append({ + 'problem': problem, + 'sort': 1024, + 'round_key': 1024, + }) + # 排序后修改ident + round_sorted_problems = sorted(not_sorted_problems, key=lambda x: int(x['round_key'])) + last_sorted_problems = sorted(round_sorted_problems, key=lambda x: int(x['sort'])) + # 根据排序修改problem的ident + for index, problem_dict in enumerate(last_sorted_problems): + problem_dict['problem'].ident = str(index + 1) + problem_dict['problem'].save() diff --git a/apps/project/controllers/project.py b/apps/project/controllers/project.py new file mode 100644 index 0000000..f9f235a --- /dev/null +++ b/apps/project/controllers/project.py @@ -0,0 +1,266 @@ +from pathlib import Path +from datetime import date +from typing import List +from shutil import copytree, rmtree +from django.shortcuts import get_object_or_404 +from django.db import transaction +from ninja_extra import api_controller, ControllerBase, route +from ninja_extra.permissions import IsAuthenticated +from ninja_jwt.authentication import JWTAuth +from apps.user.models import Users +from utils.chen_pagination import MyPagination +from ninja.pagination import paginate +from ninja import Query +from utils.chen_response import ChenResponse +from utils.chen_crud import create, multi_delete_project +from apps.project.models import Project, Round +from apps.project.schemas.project import ProjectRetrieveSchema, ProjectFilterSchema, ProjectCreateInput, DeleteSchema +from utils.util import get_str_dict +# 时间处理模块 +from apps.project.tool.timeList import time_return_to +# 反射工具 +from utils.smallTools.interfaceTools import conditionNoneToBlank + +media_path = Path.cwd() / 'media' +base_document_path = Path.cwd() / 'conf/base_document' + +@api_controller("/testmanage/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['项目表相关']) +class ProjectController(ControllerBase): + @route.get("/index", response=List[ProjectRetrieveSchema]) + @paginate(MyPagination) + def list_project(self, filters: ProjectFilterSchema = Query(...)): + conditionNoneToBlank(filters) + # 处理时间范围 + start_time = self.context.request.GET.get('searchOnlyTimeRange[0]') + if start_time is None: + start_time = "2000-01-01" + end_time = self.context.request.GET.get('searchOnlyTimeRange[1]') + if end_time is None: + end_time = '9999-01-01' + date_list = [start_time, end_time] + # 前端返回的member + member_list = [] + for key, value in self.context.request.GET.items(): + if key.find('member') != -1: + member_list.append(self.context.request.GET[key]) + qs = Project.objects.filter( + ident__icontains=filters.ident, name__icontains=filters.name, + beginTime__range=date_list, duty_person__icontains=filters.duty_person, + security_level__icontains=filters.security_level, + report_type__icontains=filters.report_type, step__icontains=filters.step, + member__contains=member_list, secret__icontains=filters.secret).order_by( + "-create_datetime") + # 对软件类型进行处理 + if filters.soft_type != '': + qs = qs.filter(soft_type=filters.soft_type) + + # ~~role:查询项目的负责人和成员:普通用户只能看到自己参加的项目~~ + final_qs = [] + auth_info: Users = self.context.request.auth + if auth_info: + if auth_info.role != 'admin': + for proj in qs: + if proj.duty_person == auth_info.name or auth_info.name in proj.member: + final_qs.append(proj) + return final_qs + return qs + + @route.get("/findOneById/{int:project_id}", response=ProjectRetrieveSchema) + @transaction.atomic + def get_project_by_id(self, project_id: int): + project_obj = get_object_or_404(Project, id=project_id) + return project_obj + + @route.post("/save") + @transaction.atomic + def create_project(self, data: ProjectCreateInput): + data_dict = data.dict() + ident_qucover = Project.objects.filter(ident=data.dict()['ident']) + if ident_qucover: + return ChenResponse(code=400, status=400, message="项目标识重复,请重新设置") + qs = create(self.context.request, data_dict, Project) + # 创建项目时候自动添加第一轮测试 + if qs: + Round.objects.create(project_id=qs.id, key='0', level='0', title='第1轮测试', name='第1轮测试', + remark='第一轮测试', ident=''.join([qs.ident, '-R1'])) + # 在新增项目时,将/conf/base_document 移动到 /media/{项目ident}/下面 + src_dir = base_document_path + dist_dir = media_path / qs.ident + try: + copytree(src_dir, dist_dir) # shutil模块直接是复制并命名,如果命名文件存在则抛出FileExists异常 + except PermissionError: + return ChenResponse(code=500, status=500, message="错误,检查是否打开了服务器的conf中的文档,关闭后重试") + except FileExistsError: + return ChenResponse(code=500, status=500, message='文件标识已存在或输入为空格,请修改') + except FileNotFoundError: + return ChenResponse(code=500, status=500, message='文件不存在,请检查') + return ChenResponse(code=200, status=200, message="添加项目成功,并添加第一轮测试") + + @route.put("/update/{project_id}") + @transaction.atomic + def update_project(self, project_id: int, payload: ProjectCreateInput): + # 判断标识是否是被允许的字符串 + project = self.get_object_or_exception(Project, id=project_id) + old_ident = project.ident + # 更新操作 + for attr, value in payload.dict().items(): + setattr(project, attr, value) + project.save() + new_ident = project.ident + # 如果新ident不等于老ident,则做 1.更新文件夹名称 2.更新所有轮次中的ident + if new_ident != old_ident: + try: + Path(media_path / old_ident).rename(media_path / project.ident) + # 同时要更改round和dut的标识 + for r in project.pField.all(): + r.ident = r.ident.replace(old_ident, new_ident) + r.save() + for d in project.pdField.all(): + d.ident = d.ident.replace(old_ident, new_ident) + d.save() + except PermissionError: + return ChenResponse(code=500, status=500, message="错误,请关闭文件资源管理器再试") + except FileExistsError: + return ChenResponse(code=500, status=500, message='文件标识已存在或输入为空格,请修改') + except FileNotFoundError: + return ChenResponse(code=500, status=500, message='文件不存在,请检查') + return ChenResponse(code=200, status=200, message="项目更新成功") + + @route.delete("/delete") + @transaction.atomic + def delete(self, data: DeleteSchema): + idents = multi_delete_project(data.ids, Project) + # 查询media所属项目文件夹,并删除 + for ident in idents: + project_media_path = media_path / ident + try: + rmtree(project_media_path) + except FileNotFoundError as e: + return ChenResponse(status=400, code=400, message='项目模版目录可能不存在,可能之前已删除') + return ChenResponse(message="删除成功!") + + # 看板页面接口 + @route.get('/board') + @transaction.atomic + def board(self, id: int): + project_obj = get_object_or_404(Project, id=id) + # 1.项目阶段直接转字符串 + step_str = get_str_dict(project_obj.step, 'step') + # 2.返回时间信息 + # 3.返回人员信息 + # 4.返回研制方信息 + # 5.返回用例信息 + case_qs = project_obj.pcField.all() + exe_count = 0 # 已执行数量 + noexe_count = 0 # 未执行数量 + partexe_count = 0 # 部分执行数量 + ## 5.1 计算已执行的用例数 -> 所以的都通过/未通过才算执行,否则部分执行 + for case in case_qs: + steps = case.step.all() + steps_count = steps.count() # 步骤总数 + passed_steps_count = steps.filter(passed='1').count() + notPassed_steps_count = steps.filter(passed='2').count() + notExe_steps_count = steps_count - passed_steps_count - notPassed_steps_count + if notExe_steps_count > 0: + # 步骤全是未执行,则用例未执行 + if notExe_steps_count == steps_count: + noexe_count += 1 + else: + partexe_count += 1 + else: + exe_count += 1 + + # 6.计算问题单数 + problems = project_obj.projField.all() + close_count = 0 + open_count = 0 + for problem in problems: + if problem.status != '1': + open_count += 1 + else: + close_count += 1 + + # 7.将时间提取 todo:后续将计算的事件放入该页面 + timers = {'round_time': []} + rounds = project_obj.pField.all() + timers['start_time'] = project_obj.beginTime + timers['end_time'] = project_obj.endTime + for round in rounds: + round_number = int(round.key) + 1 + timers['round_time'].append({ + 'name': f'第{round_number}轮次', + 'start': round.beginTime, + 'end': round.endTime + }) + + # 8.提取所有需求下面测试项、用例数量 + # 9.提取测试类型下面测试项数量、用例数量 + data_list = [] + for round in rounds: + round_dict = {'name': f'第{int(round.key) + 1}轮次', 'desings': [], 'method_demand': {}, 'method_case': {}} + designs = round.dsField.all() + for design in designs: + design_dict = { + 'name': design.name, + 'demand_count': design.dtField.count(), + 'case_count': design.dcField.count() + } + round_dict['desings'].append(design_dict) + demands = round.rtField.all() + for demand in demands: + test_type = get_str_dict(demand.testType, 'testType') + if test_type not in round_dict['method_demand']: + round_dict['method_demand'][test_type] = 1 + else: + round_dict['method_demand'][test_type] += 1 + cases = round.rcField.all() + for case in cases: + testDemand = case.test + case_type = get_str_dict(testDemand.testType, 'testType') + if case_type not in round_dict['method_case']: + round_dict['method_case'][case_type] = 1 + else: + round_dict['method_case'][case_type] += 1 + data_list.append(round_dict) + + return { + 'ident': project_obj.ident, + 'name': project_obj.name, + 'step': step_str, + 'title_info': { + '时间': { + '开始时间': project_obj.beginTime, + '结束时间': project_obj.endTime, + '到现在时间': f"{(date.today() - project_obj.beginTime).days}天", + }, + '人员': { + '负责人': project_obj.duty_person, + '成员数': len(project_obj.member), + }, + '开发方信息': { + '联系人': project_obj.dev_contact, + '电话': project_obj.dev_contact_phone, + '邮箱': project_obj.dev_email + }, + '用例数': { + '总数': case_qs.count(), + '已执行': exe_count, + '未执行': noexe_count, + '部分执行': partexe_count, + }, + '问题数': { + '总数': problems.count(), + '已闭环': close_count, + '未闭环': open_count, + } + }, + 'time_line': timers, + 'statistics': data_list, + } + + # 看板页面的生成文档时间接口 + @route.get('/document_time_show') + @transaction.atomic + def document_time_show(self, id: int): + time = time_return_to(id) + return time diff --git a/apps/project/controllers/round.py b/apps/project/controllers/round.py new file mode 100644 index 0000000..46a9e1d --- /dev/null +++ b/apps/project/controllers/round.py @@ -0,0 +1,83 @@ +from ninja_extra import api_controller, ControllerBase, route +from ninja_jwt.authentication import JWTAuth +from ninja_extra.permissions import IsAuthenticated +from django.db import transaction +from apps.project.models import Round +from apps.project.schemas.round import TreeReturnRound, RoundInfoOutSchema, EditSchemaIn, DeleteSchema, \ + CreateRoundOutSchema, CreateRoundInputSchema +from typing import List +from utils.chen_response import ChenResponse +from apps.project.tools.delete_change_key import round_delete_sub_node_key + +@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['轮次数据']) +class RoundController(ControllerBase): + @route.get("/getRoundInfo/{project_id}", response=List[TreeReturnRound], url_name="round-info") + def get_round_tree(self, project_id): + qs = Round.objects.filter(project__id=project_id).order_by('key') + return qs + + @route.get("/getOneRoundInfo", response=RoundInfoOutSchema, url_name="round-one-info") + def get_round_info(self, projectId: str, round: str): + qs = Round.objects.filter(project__id=projectId).order_by('id') + # 这里问题是如果删除中间轮次会出现问题 + qs = qs.get(key=round) + return qs + + # 更新轮次信息 + @route.put("/round/update/{id}", response=RoundInfoOutSchema, url_name="round-update") + def update_round(self, id, payload: EditSchemaIn): + round = self.get_object_or_exception(Round, project__id=payload.project, id=id) + # 去重功能 + exist_round = Round.objects.filter(project__id=payload.project) + for exist_r in exist_round: + if exist_r.id != int(id): + if exist_r.ident == payload.ident: + return ChenResponse(code=400, status=400, message='标识和其他重复') + for attr, value in payload.dict().items(): + # 不知道为什么多个project + if attr != "project": + setattr(round, attr, value) + round.save() + return ChenResponse(message="轮次信息更新成功") + + @route.delete("/round/delete", url_name="round-delete") + @transaction.atomic + def delete_round(self, project_id: str, data: DeleteSchema): + # 先查询该project下面的值 + instance = self.get_object_or_exception(Round, project__id=project_id, key=data.key) + if instance.key == '0': + return ChenResponse(code=400, status=400, message="无法删除第一轮次数据") + # (多对多)删除下面case关联的problem关系 + cases = instance.rcField.all() + for case in cases: + case.caseField.clear() + instance.delete() + # 注意:删除中间key必须发生变化,重写key + ## 先查询出当前有多少轮次 + round_all_qs = Round.objects.filter(project__id=project_id).order_by('id') + ## 1.按顺序将轮次的key从1~N排序 2.并且将ident改为key值一样 3.将名称改为对应 + index = 0 + for single_qs in round_all_qs: + old_key = single_qs.key + single_qs.key = str(index) + single_qs.ident = single_qs.ident.replace(f'R{int(old_key) + 1}', f'R{index + 1}') + single_qs.name = single_qs.name.replace(str(int(old_key) + 1), str(index + 1)) + single_qs.title = single_qs.name + index = index + 1 + single_qs.save() + round_delete_sub_node_key(single_qs) + return ChenResponse(message="删除成功") + + @route.post("/round/save", response=CreateRoundOutSchema, url_name="round-create") + def create_round(self, project_id: str, data: CreateRoundInputSchema): + asert_dict = data.dict() + asert_dict['project_id'] = int(project_id) + asert_dict['title'] = asert_dict['name'] + # 标识去重 + exist_round = Round.objects.filter(project__id=project_id) + for exist_r in exist_round: + if exist_r.id != int(project_id): + if exist_r.ident == asert_dict['ident']: + return ChenResponse(code=400, status=400, message='标识和其他重复') + Round.objects.create(**asert_dict) + return ChenResponse(message="新增轮次成功") diff --git a/apps/project/controllers/testDemand.py b/apps/project/controllers/testDemand.py new file mode 100644 index 0000000..c04d912 --- /dev/null +++ b/apps/project/controllers/testDemand.py @@ -0,0 +1,253 @@ +from ninja_extra import api_controller, ControllerBase, route +from ninja import Query +from ninja_jwt.authentication import JWTAuth +from ninja_extra.permissions import IsAuthenticated +from ninja.pagination import paginate +from ninja.errors import HttpError +from utils.chen_pagination import MyPagination +from django.db import transaction +from django.shortcuts import get_object_or_404 +from typing import List +from utils.chen_response import ChenResponse +from utils.chen_crud import multi_delete_testDemand +from utils.codes import HTTP_INDEX_ERROR +from apps.project.models import Design, Dut, Round, TestDemand, TestDemandContent, TestDemandContentStep +from apps.project.schemas.testDemand import DeleteSchema, TestDemandModelOutSchema, TestDemandFilterSchema, \ + TestDemandTreeReturnSchema, TestDemandTreeInputSchema, TestDemandCreateOutSchema, TestDemandCreateInputSchema, \ + TestDemandRelatedSchema, TestDemandExistRelatedSchema, DemandCopyToDesignSchema +# 导入ORM +from apps.project.models import Project +# 导入工具 +from apps.project.tools.copyDemand import demand_copy_to_design +from apps.project.tools.delete_change_key import demand_delete_sub_node_key +from utils.smallTools.interfaceTools import conditionNoneToBlank + +@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['测试项接口']) +class TestDemandController(ControllerBase): + @route.get("/getTestDemandList", response=List[TestDemandModelOutSchema], exclude_none=True, + url_name="testDemand-list") + @transaction.atomic + @paginate(MyPagination) + def get_test_demand_list(self, datafilter: TestDemandFilterSchema = Query(...)): + conditionNoneToBlank(datafilter) + design_key = "".join([datafilter.round_id, '-', datafilter.dut_id, '-', datafilter.design_id]) + qs = TestDemand.objects.filter(project__id=datafilter.project_id, design__key=design_key, + ident__icontains=datafilter.ident, + name__icontains=datafilter.name, + testType__contains=datafilter.testType, + priority__icontains=datafilter.priority).order_by("key") + # 由于有嵌套query_set存在,把每个测试需求的schema加上一个字段 + query_list = [] + for query_single in qs: + # 遍历每一个测试子项 + sub_list = [] + for step_obj in query_single.testQField.all(): + setattr(step_obj, "subStep", step_obj.testStepField.all().values()) + sub_list.append(step_obj) + setattr(query_single, "testContent", sub_list) + query_list.append(query_single) + return query_list + + @route.get("/getTestDemandOne", response=TestDemandModelOutSchema, url_name='testDemand-one') + @transaction.atomic + def get_test_demand_one(self, project_id: int, key: str): + demand_qs = TestDemand.objects.filter(project_id=project_id, key=key).first() + if demand_qs: + sub_list = [] + for step_obj in demand_qs.testQField.all(): + setattr(step_obj, "subStep", step_obj.testStepField.all().values()) + sub_list.append(step_obj) + setattr(demand_qs, "testContent", sub_list) + return demand_qs + raise HttpError(500, "未找到相应的数据") + + # 处理树状数据 + @route.get("/getTestdemandInfo", response=List[TestDemandTreeReturnSchema], url_name="testDemand-info") + @transaction.atomic + def get_testDemand_tree(self, payload: TestDemandTreeInputSchema = Query(...)): + qs = TestDemand.objects.filter(project__id=payload.project_id, design__key=payload.key) + return qs + + # 添加测试项 + @route.post("/testDemand/save", response=TestDemandCreateOutSchema, url_name="testDemand-create") + @transaction.atomic + def create_test_demand(self, payload: TestDemandCreateInputSchema): + asert_dict = payload.dict(exclude_none=True) + # ident判重 + project_qs = Project.objects.filter(id=payload.project_id).first() + if payload.ident and project_qs: + exists = project_qs.ptField.filter(ident=payload.ident).exists() + if exists: + return ChenResponse(code=500, status=500, message='测试项标识和其他测试项重复,请更换测试项标识!!!') + # 构造design_key + design_key = "".join([payload.round_key, "-", payload.dut_key, '-', payload.design_key]) + # 查询当前key应该为多少 + test_demand_count = TestDemand.objects.filter(project__id=payload.project_id, design__key=design_key).count() + key_string = ''.join([design_key, "-", str(test_demand_count)]) + # 查询当前各个前面节点的instance + round_instance = Round.objects.get(project__id=payload.project_id, key=payload.round_key) + dut_instance = Dut.objects.get(project__id=payload.project_id, + key="".join([payload.round_key, "-", payload.dut_key])) + design_instance = Design.objects.get(project__id=payload.project_id, key="".join( + [payload.round_key, "-", payload.dut_key, '-', payload.design_key])) + asert_dict.update({'key': key_string, 'round': round_instance, 'dut': dut_instance, 'design': design_instance, + 'title': payload.name}) + asert_dict.pop("round_key") + asert_dict.pop("dut_key") + asert_dict.pop("design_key") + asert_dict.pop("testContent") + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # 创建测试项 - 以及子项/子项步骤 + qs = TestDemand.objects.create(**asert_dict) + for item in payload.dict()['testContent']: + content_obj = TestDemandContent.objects.create( + testDemand=qs, + subName=item['subName'] + ) + TestDemandContentStep.objects.bulk_create([ + TestDemandContentStep( + testDemandContent=content_obj, + **step.dict() if not isinstance(step, dict) else step + ) + for step in item['subStep'] + ]) + return qs + + # 更新测试项 + @route.put("/testDemand/update/{id}", response=TestDemandCreateOutSchema, url_name="testDemand-update") + @transaction.atomic + def update_testDemand(self, id: int, payload: TestDemandCreateInputSchema): + project_qs = get_object_or_404(Project, id=payload.project_id) + # 查到当前 + testDemand_qs = TestDemand.objects.get(id=id) + old_ident = testDemand_qs.ident # 用于判断是否要集体修改case的ident + for attr, value in payload.dict().items(): + # 判重复 + if attr == 'ident': + if testDemand_qs.ident != value: # 如果ident不和原来相等,则要判重复 + exists = project_qs.ptField.filter(ident=payload.ident).exists() + if exists: + return ChenResponse(code=500, status=500, message='更换的标识和其他测试项重复') + if attr == 'project_id' or attr == 'round_key' or attr == 'dut_key' or attr == 'design_key': + continue # 如果发现是key则不处理 + if attr == 'name': + setattr(testDemand_qs, "title", value) + # 找到attr为testContent的 + if attr == 'testContent': + content_list = testDemand_qs.testQField.all() + for content_single in content_list: + # 删除TestDemandContent,会把CASCADE的TestDemandContentStep也删除 + content_single.delete() + # 添加测试项步骤 + for item in value: # 遍历的是testContent字段,所以每个item是TestDemandContent的数据 + # 存在subName就添加一个测试子项 + if item['subName']: + content_obj = TestDemandContent.objects.create( + testDemand=testDemand_qs, + subName=item["subName"] + ) + TestDemandContentStep.objects.bulk_create([ + TestDemandContentStep( + testDemandContent=content_obj, + **step.dict() if not isinstance(step, dict) else step + ) + for step in item["subStep"] + ]) + # ~~~2024年5月9日:测试项更新标识后还要更新下面用例的标识~~~ + if testDemand_qs.ident != old_ident: + for case in testDemand_qs.tcField.all(): + case.ident = testDemand_qs.ident + case.save() + return testDemand_qs + + # 删除测试项 + @route.delete("/testDemand/delete", url_name="testDemand-delete") + @transaction.atomic + def delete_testDemand(self, data: DeleteSchema): + # 根据其中一个id查询出dut_id + try: + test_demand_single = TestDemand.objects.filter(id=data.ids[0])[0] + except IndexError: + return ChenResponse(status=500, code=HTTP_INDEX_ERROR, message='您未选择需要删除的内容') + design_id = test_demand_single.design.id + design_key = test_demand_single.design.key + multi_delete_testDemand(data.ids, TestDemand) + index = 0 + test_demand_all_qs = TestDemand.objects.filter(design__id=design_id).order_by('id') + for single_qs in test_demand_all_qs: + test_demand_key = "".join([design_key, '-', str(index)]) + single_qs.key = test_demand_key + index = index + 1 + single_qs.save() + demand_delete_sub_node_key(single_qs) # 删除后需重排子节点 + return ChenResponse(message="测试需求删除成功!") + + # 查询一个项目的所有测试项 + @route.get("/testDemand/getRelatedTestDemand", url_name="testDemand-getRelatedTestDemand") + @transaction.atomic + def getRelatedTestDemand(self, id: int, round: str): + project_qs = get_object_or_404(Project, id=id) + # 找出属于该轮次的所有测试项 + round_qs = project_qs.pField.filter(key=round).first() + designs = round_qs.dsField.all() + data_list = [] + for design in designs: + design_dict = {'label': design.name, 'value': design.id, 'children': []} + for test_item in design.dtField.all(): + test_item_dict = {'label': test_item.name, 'value': test_item.id} + design_dict['children'].append(test_item_dict) + data_list.append(design_dict) + return ChenResponse(message='获取成功', data=data_list) + + # 处理desgin关联testDemand接口 + @route.post('/testDemand/solveRelatedTestDemand', url_name="testDemand-solveRelatedTestDemand") + @transaction.atomic + def solveRelatedTestDemand(self, data: TestDemandRelatedSchema): + test_item_ids = data.data + non_exist_ids = [x for x in test_item_ids] + project_qs = get_object_or_404(Project, id=data.project_id) + key_str = "-".join([data.round_key, data.dut_key, data.design_key]) + design_item = project_qs.psField.filter(key=key_str).first() + if design_item: + # 将test_item_ids中本身具有的测试项从id数组中移除 + for test_id in test_item_ids: + for ti in design_item.dtField.all(): + if ti.pk == test_id: + non_exist_ids.remove(test_id) + if len(non_exist_ids) <= 0 < len(test_item_ids): + return ChenResponse(status=400, code=200, message='选择的测试项全部存在于当前设计需求中,请重新选择...') + # 先查询现在有的关联测试项 + for item in design_item.odField.values('id'): + item_id = item.get('id', None) + if not item_id in test_item_ids: + test_item_obj = TestDemand.objects.filter(id=item_id).first() + design_item.odField.remove(test_item_obj) + for test_item_id in non_exist_ids: + test_items = design_item.odField.filter(id=test_item_id) + if len(test_items) <= 0: + # 查询testDemand + design_item.odField.add(TestDemand.objects.filter(id=test_item_id).first()) + else: + return ChenResponse(status=400, code=400, message='设计需求不存在,请检查...') + return ChenResponse(status=200, code=200, message='添加关联测试项成功...') + + # 找出已关联的测试项给前端的cascader + @route.post('/testDemand/getExistRelatedTestDemand', url_name="testDemand-getExistRelatedTestDemand") + @transaction.atomic + def getExistRelatedTestDemand(self, data: TestDemandExistRelatedSchema): + project_qs = get_object_or_404(Project, id=data.project_id) + key_str = "-".join([data.round_key, data.dut_key, data.design_key]) + design_item = project_qs.psField.filter(key=key_str).first() + ids = [] + if design_item: + for item in design_item.odField.all(): + ids.append(item.id) + return ids + + # 前端测试项右键复制到某个设计需求下面 + @route.post('/testDemand/copy_to_design', url_name='testDemand-copy') + @transaction.atomic + def copy_to_design(self, data: DemandCopyToDesignSchema): + """前端测试项右键复制到某个设计需求下面""" + new_demand_key = demand_copy_to_design(data.project_id, data.demand_key, data.design_id, data.depth) + return ChenResponse(data={'key': new_demand_key}) diff --git a/apps/project/controllers/treeOperation.py b/apps/project/controllers/treeOperation.py new file mode 100644 index 0000000..fe54fe1 --- /dev/null +++ b/apps/project/controllers/treeOperation.py @@ -0,0 +1,27 @@ +from ninja_extra import api_controller, ControllerBase, route +from ninja_jwt.authentication import JWTAuth +from ninja_extra.permissions import IsAuthenticated +from django.db import transaction +from django.shortcuts import get_object_or_404 +# 导入schema +from apps.project.schemas.treeOperation import CopySchema +# 导入模型 +from apps.project.models import Project +# 导入本app工具 +from apps.project.tools.keyTools import TreeKey +# 导入项目工具 +from utils.chen_response import ChenResponse + +@api_controller("/treeOperation", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['树的操作']) +class TreeController(ControllerBase): + @route.post("/copy", url_name="tree-copy") + @transaction.atomic + def tree_copy(self, data: CopySchema): + """新建下一个轮次,并复制选中的节点""" + project_obj = get_object_or_404(Project, id=data.pid) + round_count = project_obj.pField.count() + tree_keys = data.data + # 逻辑是:如果大节点有值,则复制整个大节点而不关心其子节点 + key_tree = TreeKey(tree_keys) + key_tree.copy_tree(round_count, project_obj) + return ChenResponse(code=200, status=200, message='生成轮次成功') diff --git a/apps/project/migrations/0001_initial.py b/apps/project/migrations/0001_initial.py new file mode 100644 index 0000000..f259a8e --- /dev/null +++ b/apps/project/migrations/0001_initial.py @@ -0,0 +1,381 @@ +# Generated by Django 4.2.13 on 2024-07-03 10:38 + +import apps.project.models +from django.db import migrations, models +import django.db.models.deletion +import tinymce.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Abbreviation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(help_text='缩略语', max_length=64, verbose_name='缩略语')), + ('des', models.CharField(help_text='描述', max_length=256, verbose_name='描述')), + ], + options={ + 'verbose_name': '缩略语和行业词汇', + 'verbose_name_plural': '缩略语和行业词汇', + 'db_table': 'project_abbreviation', + }, + ), + migrations.CreateModel( + name='Case', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('ident', models.CharField(blank=True, help_text='用例标识', max_length=64, null=True, verbose_name='用例标识')), + ('name', models.CharField(blank=True, help_text='用例名称', max_length=64, null=True, verbose_name='用例名称')), + ('initialization', models.CharField(blank=True, help_text='初始化条件', max_length=128, null=True, verbose_name='初始条件')), + ('premise', models.CharField(blank=True, help_text='前提和约束', max_length=128, null=True, verbose_name='前提和约束')), + ('summarize', models.CharField(blank=True, help_text='用例综述', max_length=256, null=True, verbose_name='用例综述')), + ('designPerson', models.CharField(blank=True, help_text='设计人员', max_length=16, null=True, verbose_name='设计人员')), + ('testPerson', models.CharField(blank=True, help_text='测试人员', max_length=16, null=True, verbose_name='测试人员')), + ('monitorPerson', models.CharField(blank=True, help_text='审核人员', max_length=16, null=True, verbose_name='审核人员')), + ('isLeaf', models.BooleanField(default=True, help_text='树状图最后一个节点', verbose_name='树状图最后一个节点')), + ('title', models.CharField(blank=True, help_text='树-名称', max_length=64, null=True, verbose_name='树-名称')), + ('key', models.CharField(blank=True, help_text='round-dut-designkey-testdemand-case', max_length=64, null=True, verbose_name='round-dut-designkey-testdemand-case')), + ('level', models.CharField(blank=True, default=4, help_text='树-level', max_length=64, null=True, verbose_name='树-level')), + ('exe_time', models.DateField(blank=True, help_text='执行时间', null=True, verbose_name='执行时间')), + ], + options={ + 'verbose_name': '测试用例', + 'verbose_name_plural': '测试用例', + 'db_table': 'project_case', + 'ordering': ('key',), + }, + ), + migrations.CreateModel( + name='Contact', + fields=[ + ('key', models.IntegerField(auto_created=True, help_text='公司编号', verbose_name='公司编号')), + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('entrust_person', models.CharField(blank=True, help_text='法人', max_length=16, verbose_name='法人')), + ('name', models.CharField(blank=True, help_text='公司名称', max_length=64, verbose_name='公司名称')), + ('addr', models.CharField(blank=True, help_text='公司地址', max_length=64, verbose_name='公司地址')), + ], + options={ + 'verbose_name': '委托方、研制方、测试方信息', + 'verbose_name_plural': '委托方、研制方、测试方信息', + 'db_table': 'contact_gongsi', + 'ordering': ('create_datetime',), + }, + ), + migrations.CreateModel( + name='Design', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('ident', models.CharField(blank=True, help_text='设计需求标识', max_length=64, null=True, verbose_name='设计需求标识')), + ('name', models.CharField(blank=True, help_text='设计需求名称', max_length=64, null=True, verbose_name='设计需求名称')), + ('demandType', models.CharField(blank=True, help_text='设计需求类型', max_length=8, null=True, verbose_name='设计需求类型')), + ('description', tinymce.models.HTMLField(blank=True, help_text='设计需求描述', null=True, verbose_name='设计需求描述')), + ('title', models.CharField(blank=True, help_text='树-名称', max_length=64, null=True, verbose_name='树-名称')), + ('key', models.CharField(blank=True, help_text='round-dut-designkey', max_length=64, null=True, verbose_name='round-dut-designkey')), + ('level', models.CharField(blank=True, default=2, help_text='树-level', max_length=64, null=True, verbose_name='树-level')), + ('chapter', models.CharField(blank=True, help_text='设计需求章节号', max_length=64, verbose_name='设计需求章节号')), + ('source', models.CharField(blank=True, default='', help_text='接口来源', max_length=64, null=True, verbose_name='接口来源')), + ('to', models.CharField(blank=True, default='', help_text='接口目的地', max_length=64, null=True, verbose_name='接口目的地')), + ('type', models.CharField(blank=True, default='', help_text='接口类型', max_length=64, null=True, verbose_name='接口类型')), + ('protocal', models.CharField(blank=True, default='', help_text='接口协议', max_length=64, null=True, verbose_name='接口协议')), + ], + options={ + 'verbose_name': '测试需求', + 'verbose_name_plural': '测试需求', + 'db_table': 'project_design', + 'ordering': ('key',), + }, + ), + migrations.CreateModel( + name='Dut', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('ident', models.CharField(blank=True, help_text='被测件标识', max_length=64, null=True, verbose_name='被测件标识')), + ('type', models.CharField(blank=True, help_text='被测件类型', max_length=16, null=True, verbose_name='被测件类型')), + ('name', models.CharField(blank=True, help_text='被测件名称', max_length=64, null=True, verbose_name='被测件名称')), + ('black_line', models.CharField(blank=True, help_text='空行代码数', max_length=64, null=True, verbose_name='空行代码数')), + ('code_line', models.CharField(blank=True, help_text='纯代码行数', max_length=64, null=True, verbose_name='纯代码行数')), + ('mix_line', models.CharField(blank=True, help_text='混合行数', max_length=64, null=True, verbose_name='混合行数')), + ('comment_line', models.CharField(blank=True, help_text='纯注释行', max_length=64, null=True, verbose_name='纯注释行')), + ('title', models.CharField(blank=True, help_text='树-名称', max_length=64, null=True, verbose_name='树-名称')), + ('key', models.CharField(blank=True, help_text='树-key', max_length=64, null=True, verbose_name='树-key')), + ('version', models.CharField(blank=True, help_text='发布版本', max_length=64, null=True, verbose_name='发布版本')), + ('release_union', models.CharField(blank=True, help_text='发布版本', max_length=64, null=True, verbose_name='发布版本')), + ('release_date', models.DateField(auto_now_add=True, help_text='发布时间', null=True, verbose_name='发布时间')), + ('ref', models.CharField(blank=True, help_text='文档编号', max_length=32, null=True, verbose_name='文档编号')), + ('level', models.CharField(blank=True, default=1, help_text='树-level', max_length=64, null=True, verbose_name='树-level')), + ], + options={ + 'verbose_name': '被测件信息', + 'verbose_name_plural': '被测件信息', + 'db_table': 'project_dut', + 'ordering': ('key',), + }, + ), + migrations.CreateModel( + name='Project', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('ident', models.CharField(blank=True, help_text='项目标识', max_length=64, null=True, verbose_name='项目标识')), + ('name', models.CharField(blank=True, help_text='项目名称', max_length=100, null=True, verbose_name='项目名称')), + ('engin_model', models.CharField(blank=True, help_text='工程型号', max_length=64, null=True, verbose_name='工程型号')), + ('section_system', models.CharField(blank=True, help_text='分系统', max_length=64, null=True, verbose_name='分系统')), + ('sub_system', models.CharField(blank=True, help_text='子系统', max_length=64, null=True, verbose_name='子系统')), + ('device', models.CharField(blank=True, help_text='设备', max_length=64, null=True, verbose_name='设备')), + ('beginTime', models.DateField(auto_now_add=True, help_text='开始时间', null=True, verbose_name='开始时间')), + ('endTime', models.DateField(auto_now_add=True, help_text='结束时间', null=True, verbose_name='结束时间')), + ('duty_person', models.CharField(help_text='负责人', max_length=64, verbose_name='负责人')), + ('member', models.JSONField(blank=True, default=apps.project.models.create_list, help_text='项目成员', null=True, verbose_name='项目成员')), + ('quality_person', models.CharField(help_text='质量保证员', max_length=64, verbose_name='质量保证员')), + ('vise_person', models.CharField(help_text='质量监督员', max_length=64, verbose_name='质量监督员')), + ('config_person', models.CharField(help_text='配置管理员', max_length=64, verbose_name='配置管理员')), + ('security_level', models.CharField(blank=True, help_text='安全等级', max_length=8, null=True, verbose_name='安全等级')), + ('test_level', models.JSONField(blank=True, default=apps.project.models.create_list, help_text='测试级别', null=True, verbose_name='测试级别')), + ('plant_type', models.JSONField(blank=True, default=apps.project.models.create_list, help_text='平台类型', null=True, verbose_name='平台类型')), + ('report_type', models.CharField(blank=True, help_text='报告类型', max_length=64, null=True, verbose_name='报告类型')), + ('language', models.JSONField(blank=True, default=apps.project.models.create_list, help_text='被测语言', null=True, verbose_name='被测语言')), + ('standard', models.JSONField(blank=True, default=apps.project.models.create_list, help_text='依据标准', null=True, verbose_name='依据标准')), + ('entrust_unit', models.CharField(help_text='委托方单位', max_length=64, verbose_name='委托方单位')), + ('entrust_contact', models.CharField(blank=True, help_text='委托方联系人', max_length=64, null=True, verbose_name='委托方联系人')), + ('entrust_contact_phone', models.CharField(blank=True, help_text='委托方电话', max_length=64, null=True, verbose_name='委托方电话')), + ('entrust_email', models.CharField(blank=True, help_text='委托方邮箱', max_length=64, null=True, verbose_name='委托方邮箱')), + ('dev_unit', models.CharField(help_text='开发方单位', max_length=64, verbose_name='开发方单位')), + ('dev_contact', models.CharField(blank=True, help_text='研制方联系人', max_length=64, null=True, verbose_name='研制方联系人')), + ('dev_contact_phone', models.CharField(blank=True, help_text='研制方电话', max_length=64, null=True, verbose_name='研制方电话')), + ('dev_email', models.CharField(blank=True, help_text='研制方邮箱', max_length=64, null=True, verbose_name='研制方邮箱')), + ('test_unit', models.CharField(help_text='测试方单位', max_length=64, verbose_name='测试方单位')), + ('test_contact', models.CharField(blank=True, help_text='测评中心联系人', max_length=64, null=True, verbose_name='测评中心联系人')), + ('test_contact_phone', models.CharField(blank=True, help_text='测评中心电话', max_length=64, null=True, verbose_name='测评中心电话')), + ('test_email', models.CharField(blank=True, help_text='测评中心邮箱', max_length=64, null=True, verbose_name='测评中心邮箱')), + ('step', models.CharField(blank=True, help_text='项目阶段', max_length=8, null=True, verbose_name='项目阶段')), + ('abbreviation', models.JSONField(blank=True, default=apps.project.models.create_list, help_text='缩略语', null=True, verbose_name='缩略语')), + ('soft_type', models.SmallIntegerField(choices=[(1, '新研'), (2, '改造'), (3, '沿用')], default=1, verbose_name='软件类型')), + ('runtime', models.CharField(blank=True, help_text='运行环境', max_length=8, null=True, verbose_name='运行环境')), + ('devplant', models.CharField(blank=True, help_text='开发环境', max_length=8, null=True, verbose_name='开发环境')), + ], + options={ + 'verbose_name': '项目信息', + 'verbose_name_plural': '项目信息', + 'db_table': 'project_project', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='Round', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('ident', models.CharField(blank=True, help_text='轮次标识', max_length=64, null=True, verbose_name='轮次标识')), + ('name', models.CharField(blank=True, help_text='轮次名称', max_length=64, null=True, verbose_name='轮次名称')), + ('beginTime', models.DateField(auto_now_add=True, help_text='开始时间', null=True, verbose_name='开始时间')), + ('endTime', models.DateField(auto_now_add=True, help_text='结束时间', null=True, verbose_name='结束时间')), + ('speedGrade', models.CharField(blank=True, help_text='速度等级', max_length=64, null=True, verbose_name='速度等级')), + ('package', models.CharField(blank=True, help_text='封装', max_length=64, null=True, verbose_name='封装')), + ('grade', models.CharField(blank=True, help_text='等级', max_length=64, null=True, verbose_name='等级')), + ('best_condition_voltage', models.CharField(blank=True, help_text='最优工况电压', max_length=64, null=True, verbose_name='最优工况电压')), + ('best_condition_tem', models.CharField(blank=True, help_text='最优工况温度', max_length=64, null=True, verbose_name='最优工况温度')), + ('typical_condition_voltage', models.CharField(blank=True, help_text='典型工况电压', max_length=64, null=True, verbose_name='典型工况电压')), + ('typical_condition_tem', models.CharField(blank=True, help_text='典型工况温度', max_length=64, null=True, verbose_name='典型工况温度')), + ('low_condition_voltage', models.CharField(blank=True, help_text='最低工况电压', max_length=64, null=True, verbose_name='最低工况电压')), + ('low_condition_tem', models.CharField(blank=True, help_text='最低工况温度', max_length=64, null=True, verbose_name='最低工况温度')), + ('level', models.CharField(default='0', help_text='树状级别第一级', max_length=15, verbose_name='树状级别第一级')), + ('key', models.CharField(help_text='给前端的树状级别', max_length=15, verbose_name='给前端的树状级别')), + ('title', models.CharField(help_text='给前端的name', max_length=15, verbose_name='给前端的name')), + ('location', models.CharField(help_text='测评执行地点', max_length=30, verbose_name='测评执行地点')), + ('project', models.ForeignKey(db_constraint=False, help_text='归属项目', on_delete=django.db.models.deletion.CASCADE, related_name='pField', related_query_name='pQuery', to='project.project', verbose_name='归属项目')), + ], + options={ + 'verbose_name': '轮次信息', + 'verbose_name_plural': '轮次信息', + 'db_table': 'project_round', + 'ordering': ('key',), + }, + ), + migrations.CreateModel( + name='TestDemand', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('ident', models.CharField(blank=True, help_text='测试需求标识', max_length=64, null=True, verbose_name='测试需求标识')), + ('name', models.CharField(blank=True, help_text='测试需求名称', max_length=64, null=True, verbose_name='测试需求名称')), + ('adequacy', models.CharField(blank=True, help_text='充分条件', max_length=256, null=True, verbose_name='充分条件')), + ('priority', models.CharField(blank=True, help_text='优先级', max_length=8, null=True, verbose_name='优先级')), + ('testType', models.CharField(blank=True, default='1', help_text='测试类型', max_length=8, null=True, verbose_name='测试类型')), + ('testMethod', models.JSONField(blank=True, default=apps.project.models.create_list, help_text='测试方法', verbose_name='测试方法')), + ('title', models.CharField(blank=True, help_text='树-名称', max_length=64, null=True, verbose_name='树-名称')), + ('key', models.CharField(blank=True, help_text='round-dut-designkey-testdemand', max_length=64, null=True, verbose_name='round-dut-designkey-testdemand')), + ('level', models.CharField(blank=True, default=3, help_text='树-level', max_length=64, null=True, verbose_name='树-level')), + ('design', models.ForeignKey(db_constraint=False, help_text='归属设计需求', on_delete=django.db.models.deletion.CASCADE, related_name='dtField', related_query_name='dtQuery', to='project.design', verbose_name='归属设计需求')), + ('dut', models.ForeignKey(db_constraint=False, help_text='归属被测件', on_delete=django.db.models.deletion.CASCADE, related_name='dutField', related_query_name='dtQuery', to='project.dut', verbose_name='归属被测件')), + ('otherDesign', models.ManyToManyField(blank=True, db_constraint=False, related_name='odField', related_query_name='odQuery', to='project.design')), + ('project', models.ForeignKey(db_constraint=False, help_text='归属项目', on_delete=django.db.models.deletion.CASCADE, related_name='ptField', related_query_name='ptQuery', to='project.project', verbose_name='归属项目')), + ('round', models.ForeignKey(db_constraint=False, help_text='归属轮次', on_delete=django.db.models.deletion.CASCADE, related_name='rtField', related_query_name='dutQuery', to='project.round', verbose_name='归属轮次')), + ], + options={ + 'verbose_name': '核心模型', + 'verbose_name_plural': '核心模型', + 'abstract': False, + }, + ), + migrations.CreateModel( + name='TestDemandContent', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('subName', models.CharField(blank=True, max_length=1024, null=True, verbose_name='测试子项名称')), + ('subDesc', models.CharField(blank=True, max_length=1024, null=True, verbose_name='测试子项描述-对应表格测试项描述')), + ('condition', models.CharField(blank=True, max_length=1024, null=True, verbose_name='测试子项具体条件')), + ('operation', models.CharField(blank=True, max_length=3072, null=True, verbose_name='测试子项操作')), + ('observe', models.CharField(blank=True, max_length=1024, null=True, verbose_name='测试子项观察')), + ('expect', models.CharField(blank=True, max_length=1024, null=True, verbose_name='期望')), + ('testDemand', models.ForeignKey(db_constraint=False, help_text='归属的测试项', on_delete=django.db.models.deletion.CASCADE, related_name='testQField', related_query_name='testQField', to='project.testdemand', verbose_name='归属的测试项')), + ], + options={ + 'verbose_name': '核心模型', + 'verbose_name_plural': '核心模型', + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Problem', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('ident', models.CharField(blank=True, help_text='问题单标识', max_length=64, null=True, verbose_name='问题单标识')), + ('name', models.CharField(blank=True, help_text='问题单名称', max_length=64, null=True, verbose_name='问题单名称')), + ('status', models.CharField(blank=True, help_text='缺陷状态', max_length=8, null=True, verbose_name='缺陷状态')), + ('grade', models.CharField(blank=True, help_text='缺陷等级', max_length=8, null=True, verbose_name='缺陷等级')), + ('type', models.CharField(blank=True, help_text='缺陷类型', max_length=8, null=True, verbose_name='缺陷类型')), + ('closeMethod', models.JSONField(blank=True, default=apps.project.models.create_list_1, help_text='闭环方式', null=True, verbose_name='闭环方式')), + ('operation', tinymce.models.HTMLField(blank=True, help_text='问题描述', null=True, verbose_name='问题描述')), + ('result', tinymce.models.HTMLField(blank=True, help_text='问题结果/影响', null=True, verbose_name='问题结果/影响')), + ('postPerson', models.CharField(blank=True, help_text='提出人员', max_length=16, null=True, verbose_name='提出人员')), + ('postDate', models.DateField(auto_now_add=True, help_text='提单日期', null=True, verbose_name='提单日期')), + ('designerPerson', models.CharField(blank=True, help_text='开发人员', max_length=16, null=True, verbose_name='开发人员')), + ('designDate', models.DateField(auto_now_add=True, help_text='确认日期', null=True, verbose_name='确认日期')), + ('verifyPerson', models.CharField(blank=True, help_text='验证人员', max_length=16, null=True, verbose_name='验证人员')), + ('verifyDate', models.DateField(auto_now_add=True, help_text='验证日期', null=True, verbose_name='验证日期')), + ('solve', models.TextField(blank=True, help_text='开发人员填写-改正措施,该字段需要关联“status=1”', null=True, verbose_name='开发人员填写-改正措施')), + ('analysis', tinymce.models.HTMLField(blank=True, help_text='开发人员填写-原因分析', null=True, verbose_name='开发人员填写-原因分析')), + ('effect_scope', tinymce.models.HTMLField(blank=True, help_text='开发人员填写-影响域分析', null=True, verbose_name='开发人员填写-影响域分析')), + ('verify_result', tinymce.models.HTMLField(blank=True, help_text='回归结果', null=True, verbose_name='回归结果')), + ('case', models.ManyToManyField(db_constraint=False, help_text='归属测试用例-多对多', related_name='caseField', related_query_name='caseQuery', to='project.case', verbose_name='归属测试用例')), + ('project', models.ForeignKey(db_constraint=False, help_text='归属项目', on_delete=django.db.models.deletion.CASCADE, related_name='projField', related_query_name='projQuery', to='project.project', verbose_name='归属项目')), + ], + options={ + 'verbose_name': '问题单', + 'verbose_name_plural': '问题单', + 'db_table': 'project_problem', + 'ordering': ('id',), + }, + ), + migrations.AddField( + model_name='dut', + name='project', + field=models.ForeignKey(db_constraint=False, help_text='归属项目', on_delete=django.db.models.deletion.CASCADE, related_name='pdField', related_query_name='pdQuery', to='project.project', verbose_name='归属项目'), + ), + migrations.AddField( + model_name='dut', + name='round', + field=models.ForeignKey(db_constraint=False, help_text='归属轮次', on_delete=django.db.models.deletion.CASCADE, related_name='rdField', related_query_name='rdQuery', to='project.round', verbose_name='归属轮次'), + ), + migrations.AddField( + model_name='design', + name='dut', + field=models.ForeignKey(db_constraint=False, help_text='归属轮次', on_delete=django.db.models.deletion.CASCADE, related_name='rsField', related_query_name='rsQuery', to='project.dut', verbose_name='归属轮次'), + ), + migrations.AddField( + model_name='design', + name='project', + field=models.ForeignKey(db_constraint=False, help_text='归属项目', on_delete=django.db.models.deletion.CASCADE, related_name='psField', related_query_name='psQuery', to='project.project', verbose_name='归属项目'), + ), + migrations.AddField( + model_name='design', + name='round', + field=models.ForeignKey(db_constraint=False, help_text='归属轮次', on_delete=django.db.models.deletion.CASCADE, related_name='dsField', related_query_name='rsQuery', to='project.round', verbose_name='归属轮次'), + ), + migrations.CreateModel( + name='CaseStep', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('operation', tinymce.models.HTMLField(blank=True, help_text='测试步骤-操作', null=True, verbose_name='测试步骤-操作')), + ('expect', models.CharField(blank=True, help_text='用例预期', max_length=64, null=True, verbose_name='用例预期')), + ('result', tinymce.models.HTMLField(blank=True, help_text='测试步骤-结果', null=True, verbose_name='测试步骤-结果')), + ('passed', models.CharField(blank=True, default='3', help_text='是否通过', max_length=8, null=True, verbose_name='是否通过')), + ('status', models.CharField(blank=True, default='3', help_text='执行状态', max_length=8, null=True, verbose_name='执行状态')), + ('case', models.ForeignKey(db_constraint=False, help_text='归属的测试用例', on_delete=django.db.models.deletion.CASCADE, related_name='step', related_query_name='stepQ', to='project.case', verbose_name='归属的测试用例')), + ], + options={ + 'verbose_name': '核心模型', + 'verbose_name_plural': '核心模型', + 'abstract': False, + }, + ), + migrations.AddField( + model_name='case', + name='design', + field=models.ForeignKey(db_constraint=False, help_text='归属设计需求', on_delete=django.db.models.deletion.CASCADE, related_name='dcField', related_query_name='dcQuery', to='project.design', verbose_name='归属设计需求'), + ), + migrations.AddField( + model_name='case', + name='dut', + field=models.ForeignKey(db_constraint=False, help_text='归属被测件', on_delete=django.db.models.deletion.CASCADE, related_name='ducField', related_query_name='ducQuery', to='project.dut', verbose_name='归属被测件'), + ), + migrations.AddField( + model_name='case', + name='project', + field=models.ForeignKey(db_constraint=False, help_text='归属项目', on_delete=django.db.models.deletion.CASCADE, related_name='pcField', related_query_name='pcQuery', to='project.project', verbose_name='归属项目'), + ), + migrations.AddField( + model_name='case', + name='round', + field=models.ForeignKey(db_constraint=False, help_text='归属轮次', on_delete=django.db.models.deletion.CASCADE, related_name='rcField', related_query_name='rcQuery', to='project.round', verbose_name='归属轮次'), + ), + migrations.AddField( + model_name='case', + name='test', + field=models.ForeignKey(db_constraint=False, help_text='归属测试需求', on_delete=django.db.models.deletion.CASCADE, related_name='tcField', related_query_name='tcQuery', to='project.testdemand', verbose_name='归属测试需求'), + ), + ] diff --git a/apps/project/migrations/0002_contact_refer_name.py b/apps/project/migrations/0002_contact_refer_name.py new file mode 100644 index 0000000..ea53c09 --- /dev/null +++ b/apps/project/migrations/0002_contact_refer_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.13 on 2024-07-15 16:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='contact', + name='refer_name', + field=models.CharField(blank=True, help_text='公司简称', max_length=32, verbose_name='公司简称'), + ), + ] diff --git a/apps/project/migrations/0003_alter_design_protocal.py b/apps/project/migrations/0003_alter_design_protocal.py new file mode 100644 index 0000000..7d3106b --- /dev/null +++ b/apps/project/migrations/0003_alter_design_protocal.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.13 on 2024-07-23 11:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0002_contact_refer_name'), + ] + + operations = [ + migrations.AlterField( + model_name='design', + name='protocal', + field=models.CharField(blank=True, default='', help_text='接口数据', max_length=64, null=True, verbose_name='接口数据'), + ), + ] diff --git a/apps/project/migrations/0004_project_secret.py b/apps/project/migrations/0004_project_secret.py new file mode 100644 index 0000000..51c274d --- /dev/null +++ b/apps/project/migrations/0004_project_secret.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.15 on 2024-09-02 14:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0003_alter_design_protocal'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='secret', + field=models.CharField(default='1', help_text='密级', max_length=30, verbose_name='密级'), + ), + ] diff --git a/apps/project/migrations/0005_remove_casestep_status.py b/apps/project/migrations/0005_remove_casestep_status.py new file mode 100644 index 0000000..bf179ba --- /dev/null +++ b/apps/project/migrations/0005_remove_casestep_status.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.15 on 2024-09-04 17:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0004_project_secret'), + ] + + operations = [ + migrations.RemoveField( + model_name='casestep', + name='status', + ), + ] diff --git a/apps/project/migrations/0006_testdemand_testdesciption.py b/apps/project/migrations/0006_testdemand_testdesciption.py new file mode 100644 index 0000000..9679bf9 --- /dev/null +++ b/apps/project/migrations/0006_testdemand_testdesciption.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.19 on 2025-03-12 13:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0005_remove_casestep_status'), + ] + + operations = [ + migrations.AddField( + model_name='testdemand', + name='testDesciption', + field=models.CharField(blank=True, default='', help_text='老版本-测试项描述', max_length=1024, null=True, verbose_name='测试项描述'), + ), + ] diff --git a/apps/project/migrations/0007_alter_round_grade.py b/apps/project/migrations/0007_alter_round_grade.py new file mode 100644 index 0000000..3cd5d9e --- /dev/null +++ b/apps/project/migrations/0007_alter_round_grade.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2 on 2025-04-16 10:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0006_testdemand_testdesciption'), + ] + + operations = [ + migrations.AlterField( + model_name='round', + name='grade', + field=models.CharField(blank=True, default='1', help_text='等级', max_length=64, null=True, verbose_name='等级'), + ), + ] diff --git a/apps/project/migrations/0008_remove_project_device_remove_project_engin_model_and_more.py b/apps/project/migrations/0008_remove_project_device_remove_project_engin_model_and_more.py new file mode 100644 index 0000000..4341c38 --- /dev/null +++ b/apps/project/migrations/0008_remove_project_device_remove_project_engin_model_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2 on 2025-04-16 13:27 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0007_alter_round_grade'), + ] + + operations = [ + migrations.RemoveField( + model_name='project', + name='device', + ), + migrations.RemoveField( + model_name='project', + name='engin_model', + ), + migrations.RemoveField( + model_name='project', + name='section_system', + ), + migrations.RemoveField( + model_name='project', + name='sub_system', + ), + ] diff --git a/apps/project/migrations/0009_remove_round_package_remove_round_speedgrade.py b/apps/project/migrations/0009_remove_round_package_remove_round_speedgrade.py new file mode 100644 index 0000000..935bce3 --- /dev/null +++ b/apps/project/migrations/0009_remove_round_package_remove_round_speedgrade.py @@ -0,0 +1,21 @@ +# Generated by Django 5.2 on 2025-04-16 15:34 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0008_remove_project_device_remove_project_engin_model_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='round', + name='package', + ), + migrations.RemoveField( + model_name='round', + name='speedGrade', + ), + ] diff --git a/apps/project/migrations/0010_remove_testdemandcontent_condition_and_more.py b/apps/project/migrations/0010_remove_testdemandcontent_condition_and_more.py new file mode 100644 index 0000000..a0657a6 --- /dev/null +++ b/apps/project/migrations/0010_remove_testdemandcontent_condition_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 5.2 on 2025-04-16 15:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0009_remove_round_package_remove_round_speedgrade'), + ] + + operations = [ + migrations.RemoveField( + model_name='testdemandcontent', + name='condition', + ), + migrations.RemoveField( + model_name='testdemandcontent', + name='observe', + ), + migrations.RemoveField( + model_name='testdemandcontent', + name='subDesc', + ), + migrations.AlterField( + model_name='testdemand', + name='testDesciption', + field=models.CharField(blank=True, default='', help_text='测试项描述', max_length=1024, null=True, verbose_name='测试项描述'), + ), + ] diff --git a/apps/project/migrations/0011_remove_testdemandcontent_expect_and_more.py b/apps/project/migrations/0011_remove_testdemandcontent_expect_and_more.py new file mode 100644 index 0000000..1174cd9 --- /dev/null +++ b/apps/project/migrations/0011_remove_testdemandcontent_expect_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 5.2 on 2025-04-17 14:57 + +import django.db.models.deletion +import shortuuidfield.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0010_remove_testdemandcontent_condition_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='testdemandcontent', + name='expect', + ), + migrations.RemoveField( + model_name='testdemandcontent', + name='operation', + ), + migrations.CreateModel( + name='TestDemandContentStep', + fields=[ + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('id', shortuuidfield.fields.ShortUUIDField(blank=True, editable=False, help_text='Id', max_length=22, primary_key=True, serialize=False, verbose_name='Id')), + ('operation', models.CharField(blank=True, max_length=3072, null=True, verbose_name='测试子项操作')), + ('expect', models.CharField(blank=True, max_length=1024, null=True, verbose_name='期望')), + ('testDemandContent', models.ForeignKey(db_constraint=False, help_text='归属的测试项', on_delete=django.db.models.deletion.CASCADE, related_name='testStepField', related_query_name='testStepField', to='project.testdemandcontent', verbose_name='归属的测试项')), + ], + options={ + 'verbose_name': '核心模型', + 'verbose_name_plural': '核心模型', + 'abstract': False, + }, + ), + ] diff --git a/apps/project/migrations/0012_alter_project_ident.py b/apps/project/migrations/0012_alter_project_ident.py new file mode 100644 index 0000000..93f79c6 --- /dev/null +++ b/apps/project/migrations/0012_alter_project_ident.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2 on 2025-04-23 09:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0011_remove_testdemandcontent_expect_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='ident', + field=models.CharField(blank=True, help_text='项目标识', max_length=64, null=True, unique=True, verbose_name='项目标识'), + ), + ] diff --git a/apps/project/migrations/0013_case_timing_diagram.py b/apps/project/migrations/0013_case_timing_diagram.py new file mode 100644 index 0000000..ed0e2d1 --- /dev/null +++ b/apps/project/migrations/0013_case_timing_diagram.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2 on 2025-04-24 11:01 + +import tinymce.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0012_alter_project_ident'), + ] + + operations = [ + migrations.AddField( + model_name='case', + name='timing_diagram', + field=tinymce.models.HTMLField(blank=True, help_text='FPGA时序图', null=True, verbose_name='FPGA时序图'), + ), + ] diff --git a/apps/project/migrations/0014_alter_casestep_expect.py b/apps/project/migrations/0014_alter_casestep_expect.py new file mode 100644 index 0000000..1225e92 --- /dev/null +++ b/apps/project/migrations/0014_alter_casestep_expect.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2 on 2025-04-27 10:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0013_case_timing_diagram'), + ] + + operations = [ + migrations.AlterField( + model_name='casestep', + name='expect', + field=models.CharField(blank=True, help_text='用例预期', max_length=3072, null=True, verbose_name='用例预期'), + ), + ] diff --git a/apps/project/migrations/0015_remove_dut_black_line_remove_dut_code_line_and_more.py b/apps/project/migrations/0015_remove_dut_black_line_remove_dut_code_line_and_more.py new file mode 100644 index 0000000..7ab43ba --- /dev/null +++ b/apps/project/migrations/0015_remove_dut_black_line_remove_dut_code_line_and_more.py @@ -0,0 +1,44 @@ +# Generated by Django 5.2 on 2025-04-28 10:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0014_alter_casestep_expect'), + ] + + operations = [ + migrations.RemoveField( + model_name='dut', + name='black_line', + ), + migrations.RemoveField( + model_name='dut', + name='code_line', + ), + migrations.RemoveField( + model_name='dut', + name='comment_line', + ), + migrations.RemoveField( + model_name='dut', + name='mix_line', + ), + migrations.AddField( + model_name='dut', + name='comment_lines', + field=models.CharField(blank=True, max_length=64, null=True, verbose_name='注释行数'), + ), + migrations.AddField( + model_name='dut', + name='effective_lines', + field=models.CharField(blank=True, max_length=64, null=True, verbose_name='有效代码行数'), + ), + migrations.AddField( + model_name='dut', + name='total_lines', + field=models.CharField(blank=True, max_length=64, null=True, verbose_name='总行数'), + ), + ] diff --git a/apps/project/migrations/0016_dutmetrics.py b/apps/project/migrations/0016_dutmetrics.py new file mode 100644 index 0000000..edfc84d --- /dev/null +++ b/apps/project/migrations/0016_dutmetrics.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2 on 2025-04-28 15:25 + +import django.db.models.deletion +import shortuuidfield.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0015_remove_dut_black_line_remove_dut_code_line_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='DutMetrics', + fields=[ + ('id', shortuuidfield.fields.ShortUUIDField(blank=True, editable=False, help_text='id', max_length=22, primary_key=True, serialize=False, verbose_name='id')), + ('avg_function_lines', models.IntegerField(verbose_name='平均模块大小')), + ('avg_cyclomatic', models.IntegerField(verbose_name='平均圈复杂度')), + ('avg_fan_out', models.IntegerField(verbose_name='平均扇出数')), + ('function_count', models.IntegerField(verbose_name='模块数量')), + ('max_cyclomatic', models.IntegerField(verbose_name='最大圈复杂度')), + ('high_cyclomatic_ratio', models.IntegerField(verbose_name='圈复杂度>20模块占比')), + ('total_blanks', models.IntegerField(verbose_name='空行数')), + ('dut', models.OneToOneField(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='metrics', related_query_name='metrics', to='project.dut', verbose_name='归属源代码被测件')), + ], + ), + ] diff --git a/apps/project/migrations/__init__.py b/apps/project/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/project/migrations/__pycache__/0001_initial.cpython-313.pyc b/apps/project/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000..0db917e Binary files /dev/null and b/apps/project/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0001_initial.cpython-38.pyc b/apps/project/migrations/__pycache__/0001_initial.cpython-38.pyc new file mode 100644 index 0000000..48d74c7 Binary files /dev/null and b/apps/project/migrations/__pycache__/0001_initial.cpython-38.pyc differ diff --git a/apps/project/migrations/__pycache__/0002_contact_refer_name.cpython-313.pyc b/apps/project/migrations/__pycache__/0002_contact_refer_name.cpython-313.pyc new file mode 100644 index 0000000..5f2c160 Binary files /dev/null and b/apps/project/migrations/__pycache__/0002_contact_refer_name.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0002_contact_refer_name.cpython-38.pyc b/apps/project/migrations/__pycache__/0002_contact_refer_name.cpython-38.pyc new file mode 100644 index 0000000..18c0f20 Binary files /dev/null and b/apps/project/migrations/__pycache__/0002_contact_refer_name.cpython-38.pyc differ diff --git a/apps/project/migrations/__pycache__/0003_alter_design_protocal.cpython-313.pyc b/apps/project/migrations/__pycache__/0003_alter_design_protocal.cpython-313.pyc new file mode 100644 index 0000000..d7ccd5d Binary files /dev/null and b/apps/project/migrations/__pycache__/0003_alter_design_protocal.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0003_alter_design_protocal.cpython-38.pyc b/apps/project/migrations/__pycache__/0003_alter_design_protocal.cpython-38.pyc new file mode 100644 index 0000000..1f696a4 Binary files /dev/null and b/apps/project/migrations/__pycache__/0003_alter_design_protocal.cpython-38.pyc differ diff --git a/apps/project/migrations/__pycache__/0004_project_secret.cpython-313.pyc b/apps/project/migrations/__pycache__/0004_project_secret.cpython-313.pyc new file mode 100644 index 0000000..61fd12c Binary files /dev/null and b/apps/project/migrations/__pycache__/0004_project_secret.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0004_project_secret.cpython-38.pyc b/apps/project/migrations/__pycache__/0004_project_secret.cpython-38.pyc new file mode 100644 index 0000000..bf1c26e Binary files /dev/null and b/apps/project/migrations/__pycache__/0004_project_secret.cpython-38.pyc differ diff --git a/apps/project/migrations/__pycache__/0005_remove_casestep_status.cpython-313.pyc b/apps/project/migrations/__pycache__/0005_remove_casestep_status.cpython-313.pyc new file mode 100644 index 0000000..086a38e Binary files /dev/null and b/apps/project/migrations/__pycache__/0005_remove_casestep_status.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0005_remove_casestep_status.cpython-38.pyc b/apps/project/migrations/__pycache__/0005_remove_casestep_status.cpython-38.pyc new file mode 100644 index 0000000..e72c8a0 Binary files /dev/null and b/apps/project/migrations/__pycache__/0005_remove_casestep_status.cpython-38.pyc differ diff --git a/apps/project/migrations/__pycache__/0006_testdemand_testdesciption.cpython-313.pyc b/apps/project/migrations/__pycache__/0006_testdemand_testdesciption.cpython-313.pyc new file mode 100644 index 0000000..f575842 Binary files /dev/null and b/apps/project/migrations/__pycache__/0006_testdemand_testdesciption.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0006_testdemand_testdesciption.cpython-38.pyc b/apps/project/migrations/__pycache__/0006_testdemand_testdesciption.cpython-38.pyc new file mode 100644 index 0000000..9b589d4 Binary files /dev/null and b/apps/project/migrations/__pycache__/0006_testdemand_testdesciption.cpython-38.pyc differ diff --git a/apps/project/migrations/__pycache__/0007_alter_round_grade.cpython-313.pyc b/apps/project/migrations/__pycache__/0007_alter_round_grade.cpython-313.pyc new file mode 100644 index 0000000..7a4040d Binary files /dev/null and b/apps/project/migrations/__pycache__/0007_alter_round_grade.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0008_remove_project_device_remove_project_engin_model_and_more.cpython-313.pyc b/apps/project/migrations/__pycache__/0008_remove_project_device_remove_project_engin_model_and_more.cpython-313.pyc new file mode 100644 index 0000000..9f3b49e Binary files /dev/null and b/apps/project/migrations/__pycache__/0008_remove_project_device_remove_project_engin_model_and_more.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0009_remove_round_package_remove_round_speedgrade.cpython-313.pyc b/apps/project/migrations/__pycache__/0009_remove_round_package_remove_round_speedgrade.cpython-313.pyc new file mode 100644 index 0000000..f8e9ef4 Binary files /dev/null and b/apps/project/migrations/__pycache__/0009_remove_round_package_remove_round_speedgrade.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0010_remove_testdemandcontent_condition_and_more.cpython-313.pyc b/apps/project/migrations/__pycache__/0010_remove_testdemandcontent_condition_and_more.cpython-313.pyc new file mode 100644 index 0000000..ab1ffc8 Binary files /dev/null and b/apps/project/migrations/__pycache__/0010_remove_testdemandcontent_condition_and_more.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0011_remove_testdemandcontent_expect_and_more.cpython-313.pyc b/apps/project/migrations/__pycache__/0011_remove_testdemandcontent_expect_and_more.cpython-313.pyc new file mode 100644 index 0000000..79587f9 Binary files /dev/null and b/apps/project/migrations/__pycache__/0011_remove_testdemandcontent_expect_and_more.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0012_alter_project_ident.cpython-313.pyc b/apps/project/migrations/__pycache__/0012_alter_project_ident.cpython-313.pyc new file mode 100644 index 0000000..1d3aefd Binary files /dev/null and b/apps/project/migrations/__pycache__/0012_alter_project_ident.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0013_case_timing_diagram.cpython-313.pyc b/apps/project/migrations/__pycache__/0013_case_timing_diagram.cpython-313.pyc new file mode 100644 index 0000000..32640c9 Binary files /dev/null and b/apps/project/migrations/__pycache__/0013_case_timing_diagram.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0014_alter_casestep_expect.cpython-313.pyc b/apps/project/migrations/__pycache__/0014_alter_casestep_expect.cpython-313.pyc new file mode 100644 index 0000000..f93ad58 Binary files /dev/null and b/apps/project/migrations/__pycache__/0014_alter_casestep_expect.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0015_remove_dut_black_line_remove_dut_code_line_and_more.cpython-313.pyc b/apps/project/migrations/__pycache__/0015_remove_dut_black_line_remove_dut_code_line_and_more.cpython-313.pyc new file mode 100644 index 0000000..1cc4b8a Binary files /dev/null and b/apps/project/migrations/__pycache__/0015_remove_dut_black_line_remove_dut_code_line_and_more.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/0016_dutmetrics.cpython-313.pyc b/apps/project/migrations/__pycache__/0016_dutmetrics.cpython-313.pyc new file mode 100644 index 0000000..62400a0 Binary files /dev/null and b/apps/project/migrations/__pycache__/0016_dutmetrics.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/__init__.cpython-313.pyc b/apps/project/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..2df0783 Binary files /dev/null and b/apps/project/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/project/migrations/__pycache__/__init__.cpython-38.pyc b/apps/project/migrations/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..57dcf2b Binary files /dev/null and b/apps/project/migrations/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/project/models.py b/apps/project/models.py new file mode 100644 index 0000000..cadad03 --- /dev/null +++ b/apps/project/models.py @@ -0,0 +1,402 @@ +from django.db import models +from utils.models import CoreModel +from tinymce.models import HTMLField +from shortuuidfield import ShortUUIDField + +def create_list(): + return [] + +def create_list_1(): + return ['1'] + +class Project(CoreModel): + ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="项目标识", + help_text="项目标识", unique=True) # 唯一 + name = models.CharField(max_length=100, blank=True, null=True, verbose_name="项目名称", help_text="项目名称") + beginTime = models.DateField(auto_now_add=True, null=True, blank=True, help_text="开始时间", + verbose_name="开始时间") + endTime = models.DateField(auto_now_add=True, null=True, blank=True, help_text="结束时间", verbose_name="结束时间") + duty_person = models.CharField(max_length=64, verbose_name="负责人", help_text="负责人") + member = models.JSONField(null=True, blank=True, help_text="项目成员", verbose_name="项目成员", default=create_list) + # 8月新增字段 + quality_person = models.CharField(max_length=64, verbose_name="质量保证员", help_text="质量保证员") + vise_person = models.CharField(max_length=64, verbose_name="质量监督员", help_text="质量监督员") + config_person = models.CharField(max_length=64, verbose_name="配置管理员", help_text="配置管理员") + # ~~~~~~~~~~~ + security_level = models.CharField(max_length=8, blank=True, null=True, verbose_name="安全等级", + help_text="安全等级") + test_level = models.JSONField(null=True, blank=True, help_text="测试级别", verbose_name="测试级别", + default=create_list) + plant_type = models.JSONField(null=True, blank=True, help_text="平台类型", verbose_name="平台类型", + default=create_list) + 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="委托方邮箱") + 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="研制方邮箱") + 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="开发环境") + # 9月2日新增字段:密级 + secret = models.CharField(max_length=30, default='1', verbose_name='密级', help_text='密级') + + def __str__(self): + return f'项目{self.ident}-{self.name}' + + class Meta: + db_table = 'project_project' + verbose_name = "项目信息" + verbose_name_plural = verbose_name + ordering = ('-create_datetime',) + +class Round(CoreModel): + objects = models.Manager() + ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="轮次标识", + help_text="轮次标识") # 后面加上unique=True + name = models.CharField(max_length=64, blank=True, null=True, verbose_name="轮次名称", + help_text="轮次名称") + beginTime = models.DateField(auto_now_add=True, null=True, blank=True, help_text="开始时间", + verbose_name="开始时间") + endTime = models.DateField(auto_now_add=True, null=True, blank=True, help_text="结束时间", verbose_name="结束时间") + grade = models.CharField(max_length=64, blank=True, null=True, verbose_name="等级", help_text="等级", default='1') + best_condition_voltage = models.CharField(max_length=64, blank=True, null=True, verbose_name="最优工况电压", + help_text="最优工况电压") + best_condition_tem = models.CharField(max_length=64, blank=True, null=True, verbose_name="最优工况温度", + help_text="最优工况温度") + typical_condition_voltage = models.CharField(max_length=64, blank=True, null=True, verbose_name="典型工况电压", + help_text="典型工况电压") + typical_condition_tem = models.CharField(max_length=64, blank=True, null=True, verbose_name="典型工况温度", + help_text="典型工况温度") + low_condition_voltage = models.CharField(max_length=64, blank=True, null=True, verbose_name="最低工况电压", + help_text="最低工况电压") + low_condition_tem = models.CharField(max_length=64, blank=True, null=True, verbose_name="最低工况温度", + help_text="最低工况温度") + project = models.ForeignKey(to="Project", db_constraint=False, related_name="pField", on_delete=models.CASCADE, + verbose_name='归属项目', help_text='归属项目', related_query_name='pQuery') + level = models.CharField(max_length=15, verbose_name='树状级别第一级', help_text="树状级别第一级", default='0') + key = models.CharField(max_length=15, verbose_name='给前端的树状级别', help_text="给前端的树状级别") + title = models.CharField(max_length=15, verbose_name='给前端的name', help_text="给前端的name") + # 新增执行地点 + location = models.CharField(max_length=30, verbose_name='测评执行地点', help_text='测评执行地点') + + def __str__(self): + return f'第{str(int(self.key) + 1)}轮次' + + class Meta: + db_table = 'project_round' + verbose_name = "轮次信息" + verbose_name_plural = verbose_name + ordering = ('key',) + +class Dut(CoreModel): + objects = models.Manager() + ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="被测件标识", + help_text="被测件标识") # 后面加上unique=True + type = models.CharField(max_length=16, blank=True, null=True, verbose_name="被测件类型", help_text="被测件类型") + name = models.CharField(max_length=64, blank=True, null=True, verbose_name="被测件名称", help_text="被测件名称") + # 2025年4月28日更新,分为总函数、有效代码行数、注释行数 + total_lines = models.CharField(max_length=64, blank=True, null=True, verbose_name='总行数') + effective_lines = models.CharField(max_length=64, blank=True, null=True, verbose_name='有效代码行数') + comment_lines = models.CharField(max_length=64, blank=True, null=True, verbose_name='注释行数') + + # 更新结束 + title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称", help_text="树-名称") + key = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-key", help_text="树-key") + # 被测件添加版本、发布单位、发布时间 + version = models.CharField(max_length=64, blank=True, null=True, verbose_name="发布版本", help_text="发布版本") + release_union = models.CharField(max_length=64, blank=True, null=True, verbose_name="发布版本", + help_text="发布版本") + release_date = models.DateField(auto_now_add=True, null=True, blank=True, help_text="发布时间", + verbose_name="发布时间") + # 新增用户文档的编号 + ref = models.CharField(max_length=32, blank=True, null=True, verbose_name="文档编号", help_text="文档编号") + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + level = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-level", help_text="树-level", + default=1) # 默认为1 + project = models.ForeignKey(to="Project", db_constraint=False, related_name="pdField", on_delete=models.CASCADE, + verbose_name='归属项目', help_text='归属项目', related_query_name='pdQuery') + round = models.ForeignKey(to="Round", db_constraint=False, related_name="rdField", on_delete=models.CASCADE, + verbose_name='归属轮次', help_text='归属轮次', related_query_name='rdQuery') + + def __str__(self): + return f'被测件:{self.name}' + + class Meta: + db_table = 'project_dut' + verbose_name = "被测件信息" + verbose_name_plural = verbose_name + ordering = ('key',) + +class DutMetrics(models.Model): + objects = models.Manager() + id = ShortUUIDField(primary_key=True, help_text="id", verbose_name="id") + # 外键Dut,一个Dut储存一个指标 + dut = models.OneToOneField(Dut, on_delete=models.CASCADE, related_name='metrics', related_query_name='metrics', + db_constraint=False, verbose_name='归属源代码被测件') + avg_function_lines = models.IntegerField(verbose_name='平均模块大小') + avg_cyclomatic = models.IntegerField(verbose_name='平均圈复杂度') + avg_fan_out = models.IntegerField(verbose_name='平均扇出数') + function_count = models.IntegerField(verbose_name='模块数量') + max_cyclomatic = models.IntegerField(verbose_name='最大圈复杂度') + high_cyclomatic_ratio = models.IntegerField(verbose_name='圈复杂度>20模块占比') + total_blanks = models.IntegerField(verbose_name='空行数') + +class Design(CoreModel): + objects = models.Manager() + ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="设计需求标识", + help_text="设计需求标识") + name = models.CharField(max_length=64, blank=True, null=True, verbose_name="设计需求名称", help_text="设计需求名称") + demandType = models.CharField(max_length=8, blank=True, null=True, verbose_name="设计需求类型", + help_text="设计需求类型") + description = HTMLField(blank=True, null=True, verbose_name="设计需求描述", help_text="设计需求描述") + title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称", help_text="树-名称") + key = models.CharField(max_length=64, blank=True, null=True, verbose_name="round-dut-designkey", + help_text="round-dut-designkey") + level = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-level", help_text="树-level", + default=2) # 默认为2 + chapter = models.CharField(max_length=64, blank=True, verbose_name="设计需求章节号", help_text="设计需求章节号") + project = models.ForeignKey(to="Project", db_constraint=False, related_name="psField", on_delete=models.CASCADE, + verbose_name='归属项目', help_text='归属项目', related_query_name='psQuery') + round = models.ForeignKey(to="Round", db_constraint=False, related_name="dsField", on_delete=models.CASCADE, + verbose_name='归属轮次', help_text='归属轮次', related_query_name='rsQuery') + dut = models.ForeignKey(to="Dut", db_constraint=False, related_name="rsField", on_delete=models.CASCADE, + verbose_name='归属轮次', help_text='归属轮次', related_query_name='rsQuery') + # 如果是demandTye='3'则加上如下字段 + source = models.CharField(max_length=64, blank=True, null=True, default='', verbose_name='接口来源', + help_text='接口来源') + to = models.CharField(max_length=64, blank=True, null=True, default='', verbose_name='接口目的地', + help_text='接口目的地') + type = models.CharField(max_length=64, blank=True, null=True, default='', verbose_name='接口类型', + help_text='接口类型') + # 注意:该字段改为接口数据 + protocal = models.CharField(max_length=64, blank=True, null=True, default='', verbose_name='接口数据', + help_text='接口数据') + + def __str__(self): + return f'设计需求:{self.name}' + + class Meta: + db_table = 'project_design' + verbose_name = "测试需求" + verbose_name_plural = verbose_name + 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="树-名称") + key = models.CharField(max_length=64, blank=True, null=True, verbose_name="round-dut-designkey-testdemand", + help_text="round-dut-designkey-testdemand") + level = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-level", help_text="树-level", + default=3) # 默认为3 + project = models.ForeignKey(to="Project", db_constraint=False, related_name="ptField", on_delete=models.CASCADE, + verbose_name='归属项目', help_text='归属项目', related_query_name='ptQuery') + round = models.ForeignKey(to="Round", db_constraint=False, related_name="rtField", on_delete=models.CASCADE, + verbose_name='归属轮次', help_text='归属轮次', related_query_name='dutQuery') + dut = models.ForeignKey(to="Dut", db_constraint=False, related_name="dutField", on_delete=models.CASCADE, + verbose_name='归属被测件', help_text='归属被测件', related_query_name='dtQuery') + design = models.ForeignKey(to="Design", db_constraint=False, related_name="dtField", on_delete=models.CASCADE, + verbose_name='归属设计需求', help_text='归属设计需求', related_query_name='dtQuery') + otherDesign = models.ManyToManyField(to="Design", db_constraint=False, related_name="odField", + related_query_name='odQuery', blank=True) + # 新模版要求:测试项描述对整个测试项进行描述 + testDesciption = models.CharField(max_length=1024, blank=True, null=True, verbose_name='测试项描述', default="", + help_text='测试项描述') + + def __str__(self): + return f'测试项:{self.name}' + +class TestDemandContent(CoreModel): + objects = models.Manager() + """测试方法中的测试子项内容""" + testDemand = models.ForeignKey(to="TestDemand", db_constraint=False, related_name="testQField", + on_delete=models.CASCADE, verbose_name='归属的测试项', help_text='归属的测试项', + related_query_name='testQField') + # 2025年4月16日去掉subDesc、condition、observe + # 4月17日修改:因为新增步骤所以把operation和expect弄到下面Model里面了,新增字段 + subName = models.CharField(max_length=1024, blank=True, null=True, verbose_name='测试子项名称') + + def __str__(self): + return f'测试子项:{self.subName}' + +# 4月17日新增:因为测试项需要测试子项step +class TestDemandContentStep(CoreModel): + objects = models.Manager() + id = ShortUUIDField(primary_key=True, help_text="Id", verbose_name="Id") + operation = models.CharField(max_length=3072, blank=True, null=True, verbose_name='测试子项操作') + expect = models.CharField(max_length=1024, blank=True, null=True, verbose_name='期望') + testDemandContent = models.ForeignKey(to="TestDemandContent", db_constraint=False, related_name="testStepField", + on_delete=models.CASCADE, verbose_name='归属的测试项', + help_text='归属的测试项', + related_query_name='testStepField') + +class Case(CoreModel): + objects = models.Manager() + ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="用例标识", help_text="用例标识") + name = models.CharField(max_length=64, blank=True, null=True, verbose_name="用例名称", help_text="用例名称") + initialization = models.CharField(max_length=128, blank=True, null=True, verbose_name="初始条件", + help_text="初始化条件") + premise = models.CharField(max_length=128, blank=True, null=True, verbose_name="前提和约束", help_text="前提和约束") + summarize = models.CharField(max_length=256, blank=True, null=True, verbose_name="用例综述", help_text="用例综述") + designPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="设计人员", help_text="设计人员") + testPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="测试人员", help_text="测试人员") + monitorPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="审核人员", + help_text="审核人员") + project = models.ForeignKey(to="Project", db_constraint=False, related_name="pcField", on_delete=models.CASCADE, + verbose_name='归属项目', help_text='归属项目', related_query_name='pcQuery') + isLeaf = models.BooleanField(default=True, verbose_name="树状图最后一个节点", help_text="树状图最后一个节点") + round = models.ForeignKey(to="Round", db_constraint=False, related_name="rcField", on_delete=models.CASCADE, + verbose_name='归属轮次', help_text='归属轮次', related_query_name='rcQuery') + dut = models.ForeignKey(to="Dut", db_constraint=False, related_name="ducField", on_delete=models.CASCADE, + verbose_name='归属被测件', help_text='归属被测件', related_query_name='ducQuery') + design = models.ForeignKey(to="Design", db_constraint=False, related_name="dcField", on_delete=models.CASCADE, + verbose_name='归属设计需求', help_text='归属设计需求', related_query_name='dcQuery') + test = models.ForeignKey(to="TestDemand", db_constraint=False, related_name="tcField", on_delete=models.CASCADE, + verbose_name='归属测试需求', help_text='归属测试需求', related_query_name='tcQuery') + title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称", help_text="树-名称") + key = models.CharField(max_length=64, blank=True, null=True, verbose_name="round-dut-designkey-testdemand-case", + help_text="round-dut-designkey-testdemand-case") + level = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-level", help_text="树-level", + default=4) # 默认为4 + # 2024年5月31日新增属性:执行时间 + exe_time = models.DateField(blank=True, null=True, verbose_name='执行时间', help_text='执行时间') + # 2025年4月24日新增属性:FPGA的时序图 + timing_diagram = HTMLField(blank=True, null=True, verbose_name="FPGA时序图", help_text="FPGA时序图") + + def __str__(self): + return f'测试用例:{self.name}' + + class Meta: + db_table = 'project_case' + verbose_name = "测试用例" + verbose_name_plural = verbose_name + ordering = ('key',) + +class CaseStep(CoreModel): + objects = models.Manager() + operation = HTMLField(blank=True, null=True, verbose_name="测试步骤-操作", help_text="测试步骤-操作") + expect = models.CharField(max_length=3072, blank=True, null=True, verbose_name="用例预期", help_text="用例预期") + result = HTMLField(blank=True, null=True, verbose_name="测试步骤-结果", help_text="测试步骤-结果") + passed = models.CharField(max_length=8, null=True, blank=True, help_text="是否通过", verbose_name="是否通过", + default="3") + # status = models.CharField(max_length=8, null=True, blank=True, help_text="执行状态", verbose_name="执行状态", + # default="3") + case = models.ForeignKey(to="Case", db_constraint=False, related_name="step", + on_delete=models.CASCADE, verbose_name='归属的测试用例', help_text='归属的测试用例', + related_query_name='stepQ') + + def __str__(self): + return f'测试用例步骤' + +class Problem(CoreModel): + objects = models.Manager() + # ident为PT_RXXXX_ident,这里需要根据测试项类型进行排序处理 + ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="问题单标识", help_text="问题单标识") + name = models.CharField(max_length=64, blank=True, null=True, verbose_name="问题单名称", help_text="问题单名称") + # 问题状态1-已闭环 2-开放 3-推迟 4-撤销 + status = models.CharField(max_length=8, blank=True, null=True, verbose_name="缺陷状态", help_text="缺陷状态") + # 问题等级1-一般 2-严重 3-建议 4-重大 + grade = models.CharField(max_length=8, blank=True, null=True, verbose_name="缺陷等级", help_text="缺陷等级") + # 问题类型1-其他问题 2-文档问题 3-程序问题 4-设计问题 5-需求问题 6-数据问题 + type = models.CharField(max_length=8, blank=True, null=True, verbose_name="缺陷类型", help_text="缺陷类型") + closeMethod = models.JSONField(null=True, blank=True, help_text="闭环方式", verbose_name="闭环方式", + default=create_list_1) + operation = HTMLField(blank=True, null=True, verbose_name="问题描述", help_text="问题描述") + result = HTMLField(blank=True, null=True, verbose_name="问题结果/影响", help_text="问题结果/影响") + postPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="提出人员", help_text="提出人员") + postDate = models.DateField(auto_now_add=True, null=True, blank=True, help_text="提单日期", verbose_name="提单日期") + designerPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="开发人员", + help_text="开发人员") + designDate = models.DateField(auto_now_add=True, null=True, blank=True, help_text="确认日期", + verbose_name="确认日期") + verifyPerson = models.CharField(max_length=16, blank=True, null=True, verbose_name="验证人员", help_text="验证人员") + verifyDate = models.DateField(auto_now_add=True, null=True, blank=True, help_text="验证日期", + verbose_name="验证日期") + project = models.ForeignKey(to="Project", db_constraint=False, related_name="projField", on_delete=models.CASCADE, + verbose_name='归属项目', help_text='归属项目', related_query_name='projQuery') + case = models.ManyToManyField(to="Case", db_constraint=False, related_name="caseField", verbose_name='归属测试用例', + help_text='归属测试用例-多对多', related_query_name='caseQuery') + solve = models.TextField(verbose_name='开发人员填写-改正措施', + help_text='开发人员填写-改正措施,该字段需要关联“status=1”', blank=True, null=True) + analysis = HTMLField(blank=True, null=True, verbose_name="开发人员填写-原因分析", help_text="开发人员填写-原因分析") + effect_scope = HTMLField(blank=True, null=True, verbose_name="开发人员填写-影响域分析", + help_text="开发人员填写-影响域分析") + verify_result = HTMLField(blank=True, null=True, verbose_name="回归结果", help_text="回归结果") + + def __str__(self): + return f'问题单:{self.ident}-{self.name}' + + class Meta: + db_table = 'project_problem' + verbose_name = "问题单" + verbose_name_plural = verbose_name + ordering = ('id',) + +# 单位信息 +class Contact(CoreModel): + objects = models.Manager() + entrust_person = models.CharField(max_length=16, blank=True, verbose_name="法人", help_text="法人") + name = models.CharField(max_length=64, blank=True, verbose_name="公司名称", help_text="公司名称") + key = models.IntegerField(auto_created=True, verbose_name="公司编号", help_text="公司编号") + # 新增地址 + addr = models.CharField(max_length=64, blank=True, verbose_name="公司地址", help_text="公司地址") + # 新增简称 + refer_name = models.CharField(max_length=32, blank=True, verbose_name='公司简称', help_text='公司简称') + + def __str__(self): + return f'联系方式:{self.name}' + + class Meta: + db_table = 'contact_gongsi' + verbose_name = '委托方、研制方、测试方信息' + verbose_name_plural = verbose_name + ordering = ('create_datetime',) + +# ~~~~~2024年2月27日新增~~~~~ +class Abbreviation(models.Model): + objects = models.Manager() + title = models.CharField(max_length=64, verbose_name="缩略语", help_text="缩略语") + des = models.CharField(max_length=256, verbose_name="描述", help_text="描述") + + def __str__(self): + return f'缩略语:{self.title}' + + class Meta: + db_table = 'project_abbreviation' + verbose_name = '缩略语和行业词汇' + verbose_name_plural = '缩略语和行业词汇' diff --git a/apps/project/schemas/__init__.py b/apps/project/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/project/schemas/__pycache__/__init__.cpython-313.pyc b/apps/project/schemas/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..fee4d10 Binary files /dev/null and b/apps/project/schemas/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/project/schemas/__pycache__/__init__.cpython-38.pyc b/apps/project/schemas/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..c327db1 Binary files /dev/null and b/apps/project/schemas/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/project/schemas/__pycache__/case.cpython-313.pyc b/apps/project/schemas/__pycache__/case.cpython-313.pyc new file mode 100644 index 0000000..45f5136 Binary files /dev/null and b/apps/project/schemas/__pycache__/case.cpython-313.pyc differ diff --git a/apps/project/schemas/__pycache__/case.cpython-38.pyc b/apps/project/schemas/__pycache__/case.cpython-38.pyc new file mode 100644 index 0000000..730ea9d Binary files /dev/null and b/apps/project/schemas/__pycache__/case.cpython-38.pyc differ diff --git a/apps/project/schemas/__pycache__/design.cpython-313.pyc b/apps/project/schemas/__pycache__/design.cpython-313.pyc new file mode 100644 index 0000000..a15d153 Binary files /dev/null and b/apps/project/schemas/__pycache__/design.cpython-313.pyc differ diff --git a/apps/project/schemas/__pycache__/design.cpython-38.pyc b/apps/project/schemas/__pycache__/design.cpython-38.pyc new file mode 100644 index 0000000..7b020f5 Binary files /dev/null and b/apps/project/schemas/__pycache__/design.cpython-38.pyc differ diff --git a/apps/project/schemas/__pycache__/dut.cpython-313.pyc b/apps/project/schemas/__pycache__/dut.cpython-313.pyc new file mode 100644 index 0000000..263dfe9 Binary files /dev/null and b/apps/project/schemas/__pycache__/dut.cpython-313.pyc differ diff --git a/apps/project/schemas/__pycache__/dut.cpython-38.pyc b/apps/project/schemas/__pycache__/dut.cpython-38.pyc new file mode 100644 index 0000000..7aa821b Binary files /dev/null and b/apps/project/schemas/__pycache__/dut.cpython-38.pyc differ diff --git a/apps/project/schemas/__pycache__/problem.cpython-313.pyc b/apps/project/schemas/__pycache__/problem.cpython-313.pyc new file mode 100644 index 0000000..c7b93e3 Binary files /dev/null and b/apps/project/schemas/__pycache__/problem.cpython-313.pyc differ diff --git a/apps/project/schemas/__pycache__/problem.cpython-38.pyc b/apps/project/schemas/__pycache__/problem.cpython-38.pyc new file mode 100644 index 0000000..91615fe Binary files /dev/null and b/apps/project/schemas/__pycache__/problem.cpython-38.pyc differ diff --git a/apps/project/schemas/__pycache__/project.cpython-313.pyc b/apps/project/schemas/__pycache__/project.cpython-313.pyc new file mode 100644 index 0000000..065d87c Binary files /dev/null and b/apps/project/schemas/__pycache__/project.cpython-313.pyc differ diff --git a/apps/project/schemas/__pycache__/project.cpython-38.pyc b/apps/project/schemas/__pycache__/project.cpython-38.pyc new file mode 100644 index 0000000..2e5d0e0 Binary files /dev/null and b/apps/project/schemas/__pycache__/project.cpython-38.pyc differ diff --git a/apps/project/schemas/__pycache__/round.cpython-313.pyc b/apps/project/schemas/__pycache__/round.cpython-313.pyc new file mode 100644 index 0000000..c7e50b7 Binary files /dev/null and b/apps/project/schemas/__pycache__/round.cpython-313.pyc differ diff --git a/apps/project/schemas/__pycache__/round.cpython-38.pyc b/apps/project/schemas/__pycache__/round.cpython-38.pyc new file mode 100644 index 0000000..5592ad5 Binary files /dev/null and b/apps/project/schemas/__pycache__/round.cpython-38.pyc differ diff --git a/apps/project/schemas/__pycache__/testDemand.cpython-313.pyc b/apps/project/schemas/__pycache__/testDemand.cpython-313.pyc new file mode 100644 index 0000000..424745a Binary files /dev/null and b/apps/project/schemas/__pycache__/testDemand.cpython-313.pyc differ diff --git a/apps/project/schemas/__pycache__/testDemand.cpython-38.pyc b/apps/project/schemas/__pycache__/testDemand.cpython-38.pyc new file mode 100644 index 0000000..8e284f2 Binary files /dev/null and b/apps/project/schemas/__pycache__/testDemand.cpython-38.pyc differ diff --git a/apps/project/schemas/__pycache__/treeOperation.cpython-313.pyc b/apps/project/schemas/__pycache__/treeOperation.cpython-313.pyc new file mode 100644 index 0000000..46174c4 Binary files /dev/null and b/apps/project/schemas/__pycache__/treeOperation.cpython-313.pyc differ diff --git a/apps/project/schemas/__pycache__/treeOperation.cpython-38.pyc b/apps/project/schemas/__pycache__/treeOperation.cpython-38.pyc new file mode 100644 index 0000000..a6949bc Binary files /dev/null and b/apps/project/schemas/__pycache__/treeOperation.cpython-38.pyc differ diff --git a/apps/project/schemas/case.py b/apps/project/schemas/case.py new file mode 100644 index 0000000..bab469b --- /dev/null +++ b/apps/project/schemas/case.py @@ -0,0 +1,121 @@ +from pydantic import AliasChoices +from apps.project.models import Case, CaseStep +from ninja import Field, Schema, ModelSchema +from typing import List, Union, Optional +from datetime import date +# 关联问题单 +from apps.project.schemas.problem import ProblemModelOutSchema + +# 删除schema +class DeleteSchema(Schema): + ids: List[int] + +# 测试步骤输出schema +class CaseStepSchema(ModelSchema): + class Config: + model = CaseStep + model_fields = ["operation", 'expect', 'result', 'passed', 'case', 'id'] + +# 测试用例的步骤输出schema,输出isPassed和isExe转换后的 +class CaseStepWithTransitionSchema(ModelSchema): + class Meta: + model = CaseStep + fields = ["operation", 'expect', 'result', 'passed', 'case', 'id'] + +# 输出case:不关联问题单和步骤 +class CaseModelOutSchemaWithoutProblem(ModelSchema): + testStep: List[CaseStepWithTransitionSchema] + testType: str # 用例额外字段,用于测试类型FT的标识给前端 + + class Config: + model = Case + model_exclude = ['project', 'round', 'dut', 'design', 'test', 'remark', 'sort'] + +# 输出case:关联问题单 +class CaseModelOutSchema(ModelSchema): + testStep: List[CaseStepSchema] + testType: str # 用例额外字段,用于测试类型FT的标识给前端 + # 新增:关联的问题单 + problem: Optional[ProblemModelOutSchema] = None + + class Config: + model = Case + model_exclude = ['project', 'round', 'dut', 'design', 'test', 'remark', 'sort'] + +# 查询测试项 +class CaseFilterSchema(Schema): + id: int = Field(None, alias='id') + project_id: int = Field(None, alias='projectId') + round_id: str = Field(None, alias='round') + dut_id: str = Field(None, alias='dut') + design_id: str = Field(None, alias='designDemand') + test_id: str = Field(None, alias='testDemand') + # 其他字段 + ident: str = Field(None, alias='ident') + name: str = Field(None, alias='name') + designPerson: str = Field(None, alias='designPerson') + testPerson: str = Field(None, alias='testPerson') + monitorPerson: str = Field(None, alias='monitorPerson') + summarize: str = Field(None, alias='summarize') + +# 处理树状结构的schema +class CaseTreeReturnSchema(Schema): + title: str = Field(..., alias='title') + key: str = Field(..., alias='key') + level: str = Field(..., alias='level') + # 3月13日新增字段,让case作为树状尾部节点 + isLeaf: bool = Field(True, alias='isLeaf') + # 2024年6月6日新增:用于树图显示 + isRelatedProblem: bool = Field(False, alias='isRelatedProblem') + isNotPassed: bool = Field(False, alias='isNotPassed') + +class CaseTreeInputSchema(Schema): + # 注意这里有alias + project_id: int = Field(None, alias='projectId') + key: str = Field(None, alias='key') + level: str = Field(None, alias='level') + +# 增加测试用例 +class CaseCreateOutSchema(ModelSchema): + level: Union[str, int] + + class Config: + model = Case + model_exclude = ['remark', 'sort', 'project', 'round', 'dut', 'design'] + +# 新增接口schema +class CaseInputSchema(Schema): + operation: str = Field(None, alias="operation") + expect: str = Field(None, alias="expect") + result: str = Field(None, alias="result") + passed: str = Field('3', alias="passed") + +class CaseCreateInputSchema(Schema): + project_id: int = Field(..., validation_alias=AliasChoices('project_id', 'projectId'), + serialization_alias='projectId') + round_key: str = Field(None, alias="round") + dut_key: str = Field(None, alias="dut") + design_key: str = Field(None, alias="designDemand") + test_key: str = Field(None, alias="testDemand") + # 其他字段 + ident: str = Field('', alias='ident') + name: str = Field('', alias='name') + designPerson: str = Field('', alias='designPerson') + testPerson: str = Field('', alias='testPerson') + monitorPerson: str = Field('', alias='monitorPerson') + summarize: str = Field('', alias='summarize') + initialization: str = Field('', alias='initialization') + premise: str = Field('', alias='premise') + testStep: List[CaseInputSchema] + # 新增执行时间字段 + exe_time: date = Field(None, alias='exe_time') + # 新增时序图字段 + timing_diagram: str = Field("", alias="timing_diagram") + +# 由demand创建case的输入Schema +class DemandNodeSchema(Schema): + project_id: int + level: int = Field(3, gt=0) + isLeaf: bool = False + key: str = Field(None, alias='nodekey') + title: str = Field(None) diff --git a/apps/project/schemas/design.py b/apps/project/schemas/design.py new file mode 100644 index 0000000..d8a56da --- /dev/null +++ b/apps/project/schemas/design.py @@ -0,0 +1,74 @@ +from apps.project.models import Design +from ninja import Field, Schema, ModelSchema +from typing import List, Union +from pydantic import AliasChoices + +# 删除schema +class DeleteSchema(Schema): + ids: List[int] + +# 查询设计需求 +class DesignFilterSchema(Schema): + project_id: int = Field(None, alias='projectId') + round_id: str = Field(None, alias='round') + dut_id: str = Field(None, alias='dut') + ident: str = Field(None, alias='ident') + demandType: str = Field(None, alias='demandType') + name: str = Field(None, alias='name') + # 新增字段 - chapter + chapter: str = Field(None, alias='chapter') + +class DesignModelOutSchema(ModelSchema): + class Config: + model = Design + model_exclude = ['project', 'round', 'dut', 'remark', 'sort'] + +# 处理树状结构的schema +class DesignTreeReturnSchema(Schema): + title: str = Field(..., alias='title') + key: str = Field(..., alias='key') + level: str = Field(..., alias='level') + +class DesignTreeInputSchema(Schema): + # 注意这里有alias + project_id: int = Field(None, alias='projectId') + key: str = Field(None, alias='key') + level: str = Field(None, alias='level') + +# 增加设计需求/更新设计需求 +class DesignCreateOutSchema(ModelSchema): + level: Union[str, int] + + class Meta: + model = Design + exclude = ['remark', 'sort', 'project', 'round', 'dut'] + +# 新增接口schema +class DesignCreateInputSchema(Schema): + project_id: int = Field(..., validation_alias=AliasChoices('project_id', 'projectId'), + serialization_alias='projectId') + round_key: str = Field(..., alias="round") + dut_key: str = Field(..., alias="dut") + ident: str = Field("", alias="ident") + name: str = Field(None, alias="name") + demandType: str = Field(None, alias="demandType") + description: str = Field("", alias="description") + chapter: str = Field(None, alias='chapter') + # 接口独有的4个字段 + source: str = Field('', alias='source') + to: str = Field('', alias='to') + type: str = Field('', alias='type') + protocal: str = Field('', alias='protocal') + +class SingleDesignSchema(Schema): + ident: str = Field(None, alias="ident") + name: str = Field(None, alias="title") + demandType: str = Field(None, alias="demandType") + description: str = Field(None, alias="content") + chapter: str = Field(None, alias='chapter') + +# 批量新增design的Schema +class MultiDesignCreateInputSchema(Schema): + project_id: int = Field(..., alias="projectId") + dut_key: str = Field(..., alias="key") + data: List[SingleDesignSchema] diff --git a/apps/project/schemas/dut.py b/apps/project/schemas/dut.py new file mode 100644 index 0000000..b70f3aa --- /dev/null +++ b/apps/project/schemas/dut.py @@ -0,0 +1,78 @@ +from apps.project.models import Dut +from ninja import Field, Schema, ModelSchema +from typing import List, Union, Optional +from datetime import date +from pydantic import AliasChoices + +class DutModelOutSchema(ModelSchema): + class Config: + model = Dut + model_exclude = ['project', 'round', 'remark', 'sort'] + +class DutFilterSchema(Schema): + project_id: int = Field(None, alias='projectId') + round_id: int = Field(None, alias='round') + ident: str = Field(None, alias='ident') + type: str = Field(None, alias='type') + name: str = Field(None, alias='name') + # 新增版本、单位 + version: str = Field(None, alias="version") + release_union: str = Field(None, alias="release_union") + +# 树状目录schema +class DutTreeInputSchema(Schema): + # 注意这里有alias + project_id: int = Field(None, alias='projectId') + key: str = Field(None, alias='key') + level: str = Field(None, alias='level') + +class DutTreeReturnSchema(Schema): + title: str = Field(..., alias='title') + key: str = Field(..., alias='key') + level: str = Field(..., alias='level') + +# 新增接口schema +class DutCreateInputSchema(Schema): + project_id: int = Field(..., validation_alias=AliasChoices('project_id', 'projectId'), + serialization_alias='projectId') + round_key: str = Field(..., alias="round") + ident: str = Field(None, alias="ident") + name: str = Field(None, alias="name") + type: str = Field(None, alias="type") + total_lines: Union[str, int] = Field(None, alias="total_lines") + effective_lines: Union[str, int] = Field(None, alias="effective_lines") + comment_lines: Union[str, int] = Field(None, alias="comment_lines") + # 新增版本、单位、发布日期 + version: str = Field(None, alias="version") + release_union: str = Field(None, alias="release_union") + release_date: str = Field(None, alias="release_date") + # 新增用户标识 + ref: str = Field(None, alias='ref') + +# 不能去掉,这个决定前端动态刷新树状目录 +class DutCreateOutSchema(ModelSchema): + level: Union[str, int] + total_lines: Optional[Union[str, int]] = None + effective_lines: Optional[Union[str, int]] = None + comment_lines: Optional[Union[str, int]] = None + + class Config: + model = Dut + model_exclude = ['remark', 'sort', 'project', 'round'] + +# 删除schema +class DeleteSchema(Schema): + ids: List[int] + +# 第一轮如果没有源代码被测的新增so的schema +class DutCreateR1SoDutSchema(Schema): + project_id: int + version: str + ref: str = Field(..., alias='userRef') + release_union: str = Field(..., alias='unit') + release_date: date = Field(None, alias='date') + total_lines: Union[str, int] = None + effective_lines: Union[str, int] = None + comment_lines: Union[str, int] = None + # 5月17日新增轮次的key + round_key: str diff --git a/apps/project/schemas/problem.py b/apps/project/schemas/problem.py new file mode 100644 index 0000000..559d76c --- /dev/null +++ b/apps/project/schemas/problem.py @@ -0,0 +1,122 @@ +from apps.project.models import Problem +from ninja import Field, Schema, ModelSchema +from typing import List, Optional + +# 删除schema +class DeleteSchema(Schema): + ids: List[int] + +# 问题单-输出schema +class ProblemModelOutSchema(ModelSchema): + related: Optional[bool] = Field(False) # 给前端反应是否为关联的问题单 + hang: bool = Field(False) # 给前端反应是否是悬挂状态(即没有关联case) + + class Config: + model = Problem + model_exclude = ['case', 'remark', 'sort'] + +# 查询问题单 +class ProblemFilterSchema(Schema): + project_id: int = Field(None, alias='projectId') + round_id: str = Field(None, alias='round') + dut_id: str = Field(None, alias='dut') + design_id: str = Field(None, alias='designDemand') + test_id: str = Field(None, alias='testDemand') + case_id: str = Field(None, alias='case') + key: str = Field(None, alias='key') + # 其他字段 + ident: str = Field(None, alias='ident') + name: str = Field(None, alias='name') + status: str = Field(None, alias='status') + type: str = Field(None, alias='type') + grade: str = Field(None, alias='grade') + operation: str = Field(None, alias='operation') + postPerson: str = Field(None, alias='postPerson') + +class ProblemFilterWithHangSchema(ProblemFilterSchema): + # 搜索增加hang字段 + hang: str = Field('3', alias='hang') + +# 处理树状结构的schema +class ProblemTreeReturnSchema(Schema): + title: str = Field(..., alias='title') + key: str = Field(..., alias='key') + level: str = Field(..., alias='level') + isLeaf: bool = Field(..., alias='isLeaf') + +class ProblemTreeInputSchema(Schema): + # 注意这里有alias + project_id: int = Field(None, alias='projectId') + key: str = Field(None, alias='key') + level: str = Field(None, alias='level') + isLeaf: bool = Field(None, alias='isLeaf') + +# 增加问题单 +class ProblemCreateOutSchema(ModelSchema): + class Config: + model = Problem + model_exclude = ['remark', 'sort', 'case'] + +# 更新,新增schema +class ProblemCreateInputSchema(Schema): + project_id: int = Field(..., alias="projectId") + round_key: str = Field(None, alias="round") + dut_key: str = Field(None, alias="dut") + design_key: str = Field(None, alias="designDemand") + test_key: str = Field(None, alias="testDemand") + case_key: str = Field(None, alias="case") + # 其他字段 + ident: str = Field(None, alias='ident') + name: str = Field(None, alias='name') + grade: str = Field(None, alias='grade') + operation: str = Field("", alias='operation') # !重要:由于保存到数据库为null,则查询空字符串无法查询出来 + result: str = Field("", alias='result') # 问题影响 + status: str = Field(None, alias='status') + type: str = Field(None, alias='type') + postPerson: str = Field(None, alias='postPerson') + postDate: str = Field(None, alias='postDate') + designerPerson: str = Field("", alias='designerPerson') + designDate: str = Field(None, alias='designDate') + verifyPerson: str = Field("", alias='verifyPerson') + verifyDate: str = Field(None, alias='verifyDate') + closeMethod: List[str] + # 2024年3月27日新增-处理方式字段 + solve: Optional[str] = "" + # 2024年5月13日新增 + analysis: str = Field("", alias='analysis') + effect_scope: str = Field("", alias='effect_scope') + verify_result: str = Field("", alias='verify_result') + +# 不带round_key、dut_key、design_key、test_key、case_key的更新Schema +class ProblemUpdateInputSchema(Schema): + project_id: int = Field(..., alias="projectId") + # 其他字段 + ident: str = Field(None, alias='ident') + name: str = Field(None, alias='name') + grade: str = Field(None, alias='grade') + operation: str = Field(None, alias='operation') + result: str = Field(None, alias='result') + status: str = Field(None, alias='status') + type: str = Field(None, alias='type') + postPerson: str = Field(None, alias='postPerson') + postDate: str = Field(None, alias='postDate') + designerPerson: str = Field(None, alias='designerPerson') + designDate: str = Field(None, alias='designDate') + verifyPerson: str = Field(None, alias='verifyPerson') + verifyDate: str = Field(None, alias='verifyDate') + closeMethod: List[str] + # 5月13日新增字段 + analysis: str = Field(None, alias='analysis') + effect_scope: str = Field(None, alias='effect_scope') + verify_result: str = Field(None, alias='verify_result') + # 更新字段 + solve: Optional[str] = None + +class ProblemSingleInputSchema(Schema): + project_id: int = Field(..., alias="projectId") + round_id: str = Field(..., alias="round") + dut_id: str = Field(..., alias="dut") + design_id: str = Field(..., alias="designDemand") + test_id: str = Field(..., alias="testDemand") + case_id: str = Field(..., alias="case") + problem_id: str = Field(..., alias="problem") diff --git a/apps/project/schemas/project.py b/apps/project/schemas/project.py new file mode 100644 index 0000000..3af0cd1 --- /dev/null +++ b/apps/project/schemas/project.py @@ -0,0 +1,41 @@ +from ninja.errors import HttpError +from apps.project.models import Project +from ninja import Schema, ModelSchema +from pydantic import field_validator +from typing import List, Optional + +window_file_str = ['\\', '/', ':', '*', '?', '"', '<', '>', "|"] + +class ProjectRetrieveSchema(ModelSchema): + class Config: + model = Project + model_exclude = ['update_datetime', 'create_datetime', 'remark'] + +class ProjectFilterSchema(Schema): + ident: Optional[str] = None + name: Optional[str] = None + duty_person: Optional[str] = None + security_level: Optional[str] = None + report_type: Optional[str] = None + step: Optional[str] = None + # 新增软件类型:新研/改造 + soft_type: Optional[str] = None + # 新增密级 + secret: Optional[str] = None + +class ProjectCreateInput(ModelSchema): + ident: str + + class Config: + model = Project + model_exclude = ['remark', 'update_datetime', 'create_datetime', 'sort', 'id'] + + @field_validator('ident') + @staticmethod + def check_ident_window(val): + if any(window_str in val for window_str in window_file_str): + raise HttpError(400, message='标识包含window文件名不允许的特殊字符') + return val + +class DeleteSchema(Schema): + ids: List[int] diff --git a/apps/project/schemas/round.py b/apps/project/schemas/round.py new file mode 100644 index 0000000..de6437e --- /dev/null +++ b/apps/project/schemas/round.py @@ -0,0 +1,57 @@ +from typing import Optional +from ninja import Schema, ModelSchema +from pydantic import Field +from apps.project.models import Round + +# 输出树状信息的schema +class TreeReturnRound(Schema): + title: str = Field(..., alias='title') + key: str = Field(..., alias='key') + level: str = Field(..., alias='level') + +class RoundInfoOutSchema(ModelSchema): + class Meta: + model = Round + exclude = ('remark',) + +class EditSchemaIn(Schema): + beginTime: str + best_condition_tem: Optional[str] = None + best_condition_voltage: Optional[str] = None + create_datetime: str + endTime: str + grade: str = '3' + id: int + ident: str + key: str + level: str + low_condition_tem: Optional[str] = None + low_condition_voltage: Optional[str] = None + name: str + project: int + title: str + update_datetime: str + typical_condition_tem: Optional[str] = None + typical_condition_voltage: Optional[str] = None + # 新增 + location: str + +class DeleteSchema(Schema): + title: str + key: str + level: str + +class CreateRoundOutSchema(ModelSchema): + class Meta: + model = Round + exclude = ['remark'] + +class CreateRoundInputSchema(ModelSchema): + class Meta: + model = Round + fields = ['beginTime', 'best_condition_tem', 'best_condition_voltage', 'endTime', 'grade', 'ident', + 'low_condition_tem', 'low_condition_voltage', 'name', + 'typical_condition_tem', 'typical_condition_voltage', 'key', 'location'] + fields_optional = ['best_condition_tem', 'best_condition_voltage', + 'low_condition_tem', 'low_condition_voltage', 'typical_condition_tem', + 'typical_condition_voltage' 'grade'] diff --git a/apps/project/schemas/testDemand.py b/apps/project/schemas/testDemand.py new file mode 100644 index 0000000..e9a7fc1 --- /dev/null +++ b/apps/project/schemas/testDemand.py @@ -0,0 +1,104 @@ +from apps.project.models import TestDemand, TestDemandContent, TestDemandContentStep +from ninja import Field, Schema, ModelSchema +from typing import List, Union, Optional +from pydantic import AliasChoices + +# 删除schema +class DeleteSchema(Schema): + ids: List[int] + +# 测试项-输出schema -> 包含两层嵌套 +class TestContentStepSchema(ModelSchema): + class Meta: + model = TestDemandContentStep + fields = ['operation', 'expect'] + +class TestContentSchema(ModelSchema): + subStep: List[TestContentStepSchema] = [] # 可能为空 + + class Meta: + model = TestDemandContent + fields = ["subName"] + +class TestDemandModelOutSchema(ModelSchema): + testContent: List[TestContentSchema] + + class Meta: + model = TestDemand + exclude = ['project', 'round', 'dut', 'design', 'remark', 'sort'] + +# 查询测试项 +class TestDemandFilterSchema(Schema): + project_id: int = Field(None, alias='projectId') + round_id: str = Field(None, alias='round') + dut_id: str = Field(None, alias='dut') + design_id: str = Field(None, alias='designDemand') + # 其他字段 + ident: str = Field(None, alias='ident') + testType: str = Field(None, alias='testType') + name: str = Field(None, alias='name') + priority: str = Field(None, alias="priority") + +# 处理树状结构的schema +class TestDemandTreeReturnSchema(Schema): + title: str = Field(..., alias='title') + key: str = Field(..., alias='key') + level: str = Field(..., alias='level') + +class TestDemandTreeInputSchema(Schema): + # 注意这里有alias + project_id: int = Field(None, alias='projectId') + key: str = Field(None, alias='key') + level: str = Field(None, alias='level') + +# 增加测试项 +class TestDemandCreateOutSchema(ModelSchema): + level: Union[str, int] + + class Config: + model = TestDemand + model_exclude = ['remark', 'sort', 'project', 'round', 'dut', 'design'] + +# 新增测试子项,单个子项的Schema +class TestContentInputSchema(Schema): + subName: str = None + subStep: Optional[List[TestContentStepSchema]] = [] + +# 新增/更新测试项Schema +class TestDemandCreateInputSchema(Schema): + project_id: int = Field(..., validation_alias=AliasChoices("projectId", "project_id"), + serialization_alias="projectId") + round_key: str = Field(..., alias="round") + dut_key: str = Field(..., alias="dut") + design_key: str = Field(..., alias="designDemand") + # 其他字段 + ident: str = Field(None, alias="ident") + name: str = Field(None, alias="name") + adequacy: str = Field(None, alias="adequacy") + priority: str = Field(None, alias="priority") + testContent: List[TestContentInputSchema] = [] + testMethod: List[str] = [] + testType: str = Field(None, alias="testType") + testDesciption: str = Field("", alias='testDesciption') + +# 处理前端请求-设计需求关联测试需求(测试项) +class TestDemandRelatedSchema(Schema): + data: List[int] = None + project_id: int = Field(None, alias="project_id") + round_key: str = Field(None, alias="roundNumber") + dut_key: str = Field(None, alias="dutNumber") + design_key: str = Field(None, alias="designDemandNumber") + +# 处理前端请求-设计需求已关联测试需求(测试项) +class TestDemandExistRelatedSchema(Schema): + project_id: int = Field(None, alias="project_id") + round_key: str = Field(None, alias="roundNumber") + dut_key: str = Field(None, alias="dutNumber") + design_key: str = Field(None, alias="designDemandNumber") + +# 根据design的id,testDemand数据,项目id,depth复制测试项到指定design +class DemandCopyToDesignSchema(Schema): + project_id: int + design_id: int + demand_key: str + depth: bool = False diff --git a/apps/project/schemas/treeOperation.py b/apps/project/schemas/treeOperation.py new file mode 100644 index 0000000..511bdb1 --- /dev/null +++ b/apps/project/schemas/treeOperation.py @@ -0,0 +1,6 @@ +from ninja import Schema, Field +from typing import List + +class CopySchema(Schema): + pid: int = Field(alias='project_id') + data: List[str] = Field(alias='checkedNodes') diff --git a/apps/project/signals.py b/apps/project/signals.py new file mode 100644 index 0000000..0b6ba75 --- /dev/null +++ b/apps/project/signals.py @@ -0,0 +1,91 @@ +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) diff --git a/apps/project/tests.py b/apps/project/tests.py new file mode 100644 index 0000000..de8bdc0 --- /dev/null +++ b/apps/project/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/project/tool/__pycache__/source_counter.cpython-313.pyc b/apps/project/tool/__pycache__/source_counter.cpython-313.pyc new file mode 100644 index 0000000..19ddc46 Binary files /dev/null and b/apps/project/tool/__pycache__/source_counter.cpython-313.pyc differ diff --git a/apps/project/tool/__pycache__/timeList.cpython-313.pyc b/apps/project/tool/__pycache__/timeList.cpython-313.pyc new file mode 100644 index 0000000..a3f1f93 Binary files /dev/null and b/apps/project/tool/__pycache__/timeList.cpython-313.pyc differ diff --git a/apps/project/tool/__pycache__/timeList.cpython-38.pyc b/apps/project/tool/__pycache__/timeList.cpython-38.pyc new file mode 100644 index 0000000..e9f00cf Binary files /dev/null and b/apps/project/tool/__pycache__/timeList.cpython-38.pyc differ diff --git a/apps/project/tool/__pycache__/xq_parse.cpython-313.pyc b/apps/project/tool/__pycache__/xq_parse.cpython-313.pyc new file mode 100644 index 0000000..6293cd0 Binary files /dev/null and b/apps/project/tool/__pycache__/xq_parse.cpython-313.pyc differ diff --git a/apps/project/tool/source_counter.py b/apps/project/tool/source_counter.py new file mode 100644 index 0000000..7a87b70 --- /dev/null +++ b/apps/project/tool/source_counter.py @@ -0,0 +1,100 @@ +import lizard +import os +import zipfile +from pathlib import Path + +def analyze_code_directory(file_path): + results = { + 'comment_rate': 0.0, # 注释率-手动 + 'total_lines': 0, # 总函数 + 'effective_lines': 0, # 有效代码行数 + 'avg_function_lines': 0, # 平均模块行数 + 'avg_cyclomatic': 0, # 平均圈复杂度 + 'avg_fan_out': 0, # 平均扇出 + 'function_count': 0 # 函数个数 + } + total_comments = 0 + total_blanks = 0 + total_lines = 0 + total_effective = 0 + functions = [] + for root, _, files in os.walk(file_path): + for file in files: + if file.endswith(('.c', '.cpp', '.h', '.hpp', '.cc', '.cxx')): + filepath = os.path.join(root, file) + # 使用 lizard 分析代码结构 + analysis = lizard.analyze_file(filepath) + functions.extend(analysis.function_list) + # 使用 lizard 的有效代码行数统计 + total_effective += analysis.nloc + # 手动统计注释行数(新方法) + with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: + content = f.readlines() + total_comments += sum( + 1 for line in content if line.strip().startswith(('//', '/*', '*')) or '*/' in line) + total_blanks += sum(1 for line in content if not line.strip()) + # 计算函数相关指标 + if functions: + cyclomatic_list = [f.cyclomatic_complexity for f in functions] + high_cyclo = sum(1 for c in cyclomatic_list if c >= 20) + # 输出的指标 + results['function_count'] = len(functions) # 模块数量 + results['avg_function_lines'] = sum(f.length for f in functions) / len(functions) # 平均规模 + results['avg_cyclomatic'] = sum(f.cyclomatic_complexity for f in functions) / len(functions) # 平均圈复杂 + results['avg_fan_out'] = sum(f.fan_out for f in functions) / len(functions) # 平均扇出 + results['max_cyclomatic'] = max(cyclomatic_list) # 模块最大圈复杂度 + results['high_cyclomatic_ratio'] = high_cyclo / len(functions) * 100 # 圈复杂度>20比例 + total_lines = sum(f.length for f in functions) + + # 计算全局指标 - 输出 + if total_lines > 0: + results['comment_lines'] = total_comments if total_comments > 0 else 0 + results['comment_rate'] = total_comments / total_lines * 100 if total_comments > 0 else 0 + results['total_lines'] = total_lines + results['effective_lines'] = total_effective + results['total_blanks'] = total_blanks + results['code_ratio'] = total_effective / total_lines if total_lines > 0 else 0 + return results + +# 解压zip文件方法 +def extract_and_get_paths(zip_path: str, + extract_to: str = 'unzipped_files') -> str: + """ + 解压ZIP文件并返回目标扩展名文件的绝对路径列表 + 参数: + zip_path: ZIP文件路径 + extract_to: 解压目录(默认'unzipped_files') + target_extensions: 目标文件扩展名(默认('.c', '.h')) + + 返回: + 匹配文件的绝对路径列表 + """ + # 创建解压目录(如果不存在) + if extract_to is None: + extract_to = os.path.join(os.getcwd(), f"unzip_temp_{os.urandom(4).hex()}") + os.makedirs(extract_to, exist_ok=True) + try: + with zipfile.ZipFile(zip_path, 'r') as zip_ref: + zip_ref.extractall(extract_to) + return os.path.abspath(extract_to) + except zipfile.BadZipFile: + raise ValueError(f"无效的ZIP文件: {zip_path}") + except Exception as e: + raise RuntimeError(f"解压失败: {str(e)}") + +# 使用示例 +if __name__ == "__main__": + path = Path("../Cpro/") + if not path.is_dir(): + print("错误: 路径不存在或不是目录") + else: + stats = analyze_code_directory(path) + + print("\n代码分析结果:") + print(f"1. 注释率: {stats['comment_rate']:.2f}%") + print( + f"2. 有效代码行数/总行数: {stats['total_lines']}/{stats['effective_lines']} (比例: {stats['code_ratio']:.2f})") + print(f"3. 函数数量: {stats['function_count']}") + print(f"4. 函数平均行数: {stats['avg_function_lines']:.1f}") + print(f"5. 函数平均圈复杂度: {stats['avg_cyclomatic']:.1f}") + print(f"6. 函数平均扇出数: {stats['avg_fan_out']:.1f}") diff --git a/apps/project/tool/temp.py b/apps/project/tool/temp.py new file mode 100644 index 0000000..cb69c4f --- /dev/null +++ b/apps/project/tool/temp.py @@ -0,0 +1,104 @@ +temp = [ + ('3', '需求'), + ('3.1', '要求的状态和方式'), + ('3.2', 'CSCI能力需求'), + ('3.3', '系统外部接口需求'), + ('3.4', '系统内部接口需求'), + ('3.5', '内部数据需求'), + ('3.6', '适应性需求'), + ('3.7', '安全性需求'), + ('3.8', '保密性需求'), + ('3.9', 'CSCI环境需求'), + ('3.10', '计算机资源需求'), + ('3.11', '软件质量因素'), + ('3.12', '设计和实现约束'), + ('3.13', '人员需求'), + ('3.14', '培训需求'), + ('3.15', '软件保障需求'), + ('3.16', '其他需求'), + ('3.17', '验收、交付和包装需求'), ('3.18', '需求的优先顺序和关键程度') +] +temp1 = [ + ('3', '需求', '[]'), + ('3.1', '要求的状态和方式', + "['声学探测单元声探测信息交互软件分为两个部分。一是主机控制模块,二是通信管理模块,模块间通过内部网口进行信息交互。']"), + ('3.2', 'CSCI能力需求', + "['功能模块图', '声学探测单元声探测信息交互软件分为主机控制模块和通信管理模块,功能模块框图如图1所示。', '图1 声探测信息交互软件功能模块框图', '功能模块描述', '主机控制模块', '主机控制模块功能模块主要分为探测显示、参数设置、设备自检和信息处理四个部分。', '探测显示主要包括开机画面显示、目标信息显示、环境噪声等级及时间显示等;', '参数设置主要是使用键盘对设备编号、阵列基准、环境温度、屏幕亮度等参数进行设置;', '设备自检主要是检测通道状态并显示故障通道编号,并将自检结果上传;', '信息处理主要是各外部接口的信息传输,主要包括串口输入数据接收解析,键盘输入信息处理,以及与通信管理模块软件网口通信等。', '主机控制模块功能模块描述见表2。', '表2功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t需求描述\\t是否有子功能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t需求描述\\t是否有子功能', '1\\t探测显示\\tTCXSMK\\t探测结果\\t开机界面显示,探测结果显示\\t开机画面\\n显示目标批号、方向、类型(串口触发)\\n显示环境噪声等级(串口触发)\\n显示时间(串口触发)\\n快捷键跳转\\t有', '2\\t参数设置\\tCSSZMK\\t键盘\\t指令打包\\t设备编号(键盘设置)\\n阵列基准(键盘设置)\\n环境温度(键盘设置)\\n屏幕亮度(键盘设置)\\n快捷键跳转\\t有', '3\\t设备自检\\tSBZJMK\\t键盘\\t指令打包\\t通道自检:检测通道状态,显示故障通道编号(串口触发、键盘设置)\\n快捷键跳转\\t有', '4\\t信息处理\\tXICLMK\\t串口输入/网口输入 \\t网口输出/外部IO输出\\t串口输入数据接收解析\\n网口上传信息\\n键盘控制\\t有'], '探测显示功能模块', '探测显示功能模块主要实现以下功能:', '开机显示:显示设备名称及生产厂家;', '探测结果:显示目标批号、方向、类型;', '环境噪声等级:显示环境噪声等级;', '系统时间:显示系统时间。', '探测显示功能模块框图见图2,具体描述见表3。', '图2 探测显示功能模块图', '表3 探测显示功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t满足的各项需求及性能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t满足的各项需求及性能', '1\\t开机显示\\tKJXSZMK\\tDSP握手信息\\t界面显示\\t显示设备名称及生产厂家', '2\\t探测结果\\tTCJGZMK\\t解析后的探测结果信息\\t界面显示\\t目标方位(1,2,3,4,5)\\n目标方位(000.0)\\n目标类型(0000)', '3\\t环境噪声\\tHJZSZMK\\t解析后的环境噪声等级信息\\t界面显示\\t探测环境品质因素\\n(优、良、中、差)', '4\\t系统时间\\tXTSJZMK\\t解析后的时间信息\\t界面显示\\t显示系统时间:\\n(时:分:秒)'], '参数设置功能模块', '参数设置主要是对设备参数进行设置,包括键盘设置设备编号、阵列基准、环境温度、屏幕亮度。参数设置功能模块描述见表4。', '表4参数设置功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t满足的各项需求及性能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t满足的各项需求及性能', '1\\t设备参数设置\\tSBCSZMK\\t键盘\\t参数设置指令\\t探测仪编号:1-9;\\n阵列基线指北角度偏差:\\n000.0°-360.0°;\\n环境温度:-40°- +65°;\\n屏幕亮度:“高”、“中”和“低”;'], '设备自检功能模块', '设备自检主要是检测通道状态并显示故障通道编号,并将自检结果上传。设备自检功能模块描述见表5。', '表5 设备自检功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t满足的各项需求及性能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t满足的各项需求及性能', '1\\t通道自检\\tTDZJZMK\\t键盘\\t通道自检指令\\t若无通道损坏,提示通道正常;\\n若有通道损坏,显示其中损坏通道的编号'], '信息处理功能模块', '信息处理主要是各外部接口的信息传输,主要包括串口接收数据解析、网口数据收发、键盘控制等。', '串口接收中断:串口有数据即进入串口中断服务程序,接收串口数据并解析;', '网口收发:把需要发送的信息和指令按照协议打包发往网口;接收网口数据;', '键盘控制:扫描键盘,获取按键值;', '信息处理功能模块图见图3,具体描述见表6。', '图3 信息处理功能模块图', '表6 信息处理功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t满足的各项需求及性能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t满足的各项需求及性能', '1\\t串口接收中断\\tCKJSZMK\\t串口\\t解析的指令\\t接收声探测信息处理软件指令,并解析指令。', '2\\t网口收发\\tWKSFZMK\\t打包的指令\\t网口\\t按照协议打包信息或指令发送;接收并解析网口数据。', '3\\t键盘控制\\tJPKZZMK\\t硬件\\t按键值\\t返回按下按键的值'], '通信管理模块软件', '通信管理模块软件功能模块主要分为设备状态管理、任务参数规划、目标探测信息、时统信息解析、模拟训练和数据记录六个部分。', '设备状态管理主要是实时向上级指挥控制系统上报装备链路状态、装备状态、工作状态、健康状态等信息;', '任务参数规划主要是设备与指挥控制系统建立链接后,从指挥控制系统获取初始化参数与作战管理权限;', '目标探测信息主要是当设备生成目标探测结果后向上级指挥控制系统上报目标探测信息;', '时统信息解析主要是设备从接收并解析接入通信单元下发的NTP授时信息;', '模拟训练根据指控下发的目标,筛选在声探测设备探测能力范围内的目标,并形成航迹批次上报到指控。', '数据记录主要是对设备的决策信息、控制信息、状态信息等信息进行本地存储。', '通信管理模块软件功能模块描述见表7。', '表7通信管理模块软件功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t需求描述\\t是否有子功能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t需求描述\\t是否有子功能', '1\\t设备状\\n态管理\\tZTGLMK\\t设备状态\\t网口\\t向上级指控系统上报装备链路状态、装备状态、工作状态、健康状态等信息\\t有', '2\\t任务参\\n数规划\\tCSGHMK\\t网口\\t解析的参数\\t从指控系统获取初始化参数与作战管理权限\\t有', '3\\t目标探\\n测信息\\tTCXXMK\\t设备探测结果\\t网口\\t向指控系统上报探测信息\\t无', '4\\t时统信\\n息解析\\tSTXXMK\\t网口\\t解析的参数\\t接收通信单元的授时信息并解析\\t无', '5\\t模拟训练\\tMNXLMK\\t网口\\t网口\\t能够根据指控下发的“模拟训练”目标信息形成航迹批次并上报指控\\t无', '6\\t运行状态监控\\tYXZTJK\\t-\\t-\\t设备运行状态监控系统功能模块主要实现查看设备运行状态、文件上传、状态统计等功能\\t', '7\\t数据记录\\tSJJLMK\\t-\\t-\\t记录设备所有的接收和发送的报文\\t无'], '设备状态管理功能模块', '设备状态管理功能模块主要实现以下设备状态的管理功能:', '链路监测', '通信管理模块软件启动完毕后,以固定周期1s向上级指挥控制系统发送链路监测报文,上级指挥控制系统收到链路监测报文后,立即向声探测信息交互软件回复链路监测报文,声探测信息交互软件收到链路监测报文后,完成建链。任意一方连续3个周期未收到链路监测报文后,链路状态由通转为断。链路监测报文持续发送,收到回复后链路状态由断转为通,重新完成链路建立。本文档所涉及的所有报文都要基于链路通畅时,才能进行发送;', '装备状态', '通信管理模块软件周期性向上级指挥控制系统上报装备状态信息,其中主要包括工作状态、受控状态、时统状态信息。', '工作状态', '通信管理模块软件可以接收并响应上级指挥控制系统下发工作状态设置指令并响应,分为作战状态与模拟训练状态,装备开机后默认进入作战状态。', '健康状态', '主机控制模块周期性地向通信管理模块软件发送设备各通道自检结果,通信管理模块软件形成健康状态报文周期性上报上级指挥控制系统。', '设备状态管理功能模块功能框图见图4,具体描述见表8。', '图4 设备状态管理功能模块图', '表8设备状态管理功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t满足的各项需求及性能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t满足的各项需求及性能', '1\\t链路监测\\tLLJCZMK\\t网口\\t网口\\t与指控的链路的通断控制', '2\\t装备状态\\tZBZTZMK\\t设备状态\\t网口\\t向指控上报装备状态', '3\\t工作状态\\tGZZTZMK\\t网口\\t网口\\t根据指控指令更改工作状态', '4\\t健康状态\\tJKZTZMK\\t设备状态\\t网口\\t向指控上报健康状态'], '任务参数规划功能模块', '任务参数规划功能模块主要包括初始化参数与作战管理权限两部分功能:', '初始化参数', '通信管理模块软件与上级指挥控制系统的链路连通后会定时向上级发送初始化参数请求,上级指挥控制系统收到请求后会立即向声探测信息交互软件下发初始化参数指令,进行初始化设置,其中有站址来源、声学探测单元数量及装备位置等参数信息。', '作战管理权限', '通信管理模块软件会接收到上级指挥控制系统作战权限管理功能指令,同时向上级指控发送初始化参数停止请求,包括上级和下级的编码、个数、权限。当上级系统发现变化时,声探测信息交互软件与之前通信的上级指控的链路连通性立刻断开,并持续向新的上级指挥控制中心发送链路监测报,待与新的上级指挥控制中心链路连通后,声探测信息交互软件的控制权限将交接给新的上级指控系统。', '任务参数规划功能模块功能框图见图5,具体描述见表9。', '图5 任务参数规划功能模块图', '表9任务参数规划功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t满足的各项需求及性能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t满足的各项需求及性能', '1\\t初始化参数\\tCSHCSZMK\\t网口\\t网口\\t向指控请求初始化参数', '2\\t作战权限管理\\tZZGLQXZMK\\t网口\\t网口\\t上级指控的更改'], '目标探测信息功能模块', '通信管理模块软件接收到主机控制软件上报的目标探测结果后,会立即生成目标探测信息报文上报上级指挥控制系统,主要内容包括:', '本地批号;', '上级批号;', '目标数量;0-5;', '目标属性;', '目标类型:0-无效,1-未识别的气动目标,2-隐身飞机,3-巡航导弹,4-空地弹,5-精确制导炸药,150-未识别的无人机,151-固定翼,152-旋翼,200-未识别的弹道目标,201-弹头,202-诱饵;', '目标型号;', '目标识别概率:0-10000;', 'RCS值;保留', '数据周期;', '航迹状态:跟踪连续性、状态编码:0-无效,1-航迹丢失,2-主动跟踪,3-被动跟踪,4-外推跟踪;', '目标位置X(地心X、经度、斜距、北方向);', '目标位置Y(地心Y、纬度、方位、天方向);', '目标位置Z(地心Z、海拔高、俯仰、东方向);', 'X向速度(地心X、经度、斜距、北方向);', 'Y向速度(地心Y、纬度、方位、天方向);', 'Z向速度(地心Z、海拔高、俯仰、东方向);', '合速度;保留', '系统误差;', '随机误差;', '时统信息解析功能模块', '通信管理模块软件会定时向接入通信单元发送NTP授时请求报文,接入通信单元收到后会向授时方发送NTP授时报文,通信管理模块软件通过NTP授时报文校准当前的绝对时统,当通信管理模块软件的绝对时统与相对时统误差过大时,通信管理模块软件切换到相对时统,默认使用绝对时统。', '模拟训练功能模块', '根据指控下发的目标,筛选在声探测设备探测能力范围内的目标,并形成航迹批次上报到指控。', '运行状态监控模块', '设备运行状态监控系统功能模块主要实现查看设备运行状态、文件上传、状态统计等功能。', 'a)设备运行状态', '软件通过UDP协议获取声探测信息交互软件发送的设备状态实时数据,然后对设备部件状态数据(包括通道状态、整机安全保密状态、整机健康状态、传感器健康状态和声学探测主机状态等)和设备运行状态(包括工作状态、受控状态、时统状态和加电状态等)进行解析处理保存到系统数据库,前端通过定时接口请求显示最新的设备状态信息。', 'b)文件上传', '主要实现将信息交互软件升级安装包上传到系统,通过点击“升级”按钮,首先备份历史版本信息交互软件安装包,然后解压上传的最新版本信息交互软件安装包后重启设备或者重启软件完成软件升级功能。', 'c)状态统计', '通过浏览器查看设备在指定时间范围内的设备运行状态,进行图表显示,方便用户进行数据分析和故障信息查看等。', '数据记录功能模块', '具备记录、存储日志功能,可将装备状态,目标探测结果等信息进行本地存储。']"), + ('3.3', '系统外部接口需求', + "['接口标识和接口图', '声探测信息交互软件的外部接口主要包含与声探测信号处理软件的串口通信,与FPGA的SPI通信,与指挥控制系统的以太网通信,通过串口接收授时软件的授时信息,以及SD卡存储和通过数字IO实现与OLED和键盘交互。外部接口示意图见图7,外部接口标识见表8。', '图7 外部接口示意图', '表8外部接口标识', ['序号\\t接口名称\\t接口标识号\\t接口描述\\t来源\\t目的地\\t接口类型\\t优先级别', '1\\t探测结果\\tIN1_1\\t接收声探测信号处理软件的探测结果;环境噪声分级;通道自检结果;\\tDSP uart0\\tMCU串口0\\t串口\\t重要', '2\\t探测参数\\tIN1_2\\t向声探测信号处理软件发送环境温度、自检指令。\\tMCU串口0\\tDSP uart0\\t串口\\t重要', '3\\tOLED屏\\tSOUT1_1\\t更新界面显示\\t主机控制模块\\tOLED屏\\t数字IO\\t重要', '4\\t键盘\\tSOUT1_2\\t输入\\t键盘\\t主机控制模块\\t数字IO\\t重要', '5\\t以太网输出\\tSOUT2\\t以太网输出探测结果、设备状态等信息\\t指挥控制系统\\t通信管理模块软件\\t网口\\t重要', '6\\t以太网接收\\tSOUT3\\t以太网接收指挥控制系统作战命令\\t通信管理模块软件\\t指挥控制系统\\t网口\\t重要'], '外部接口数据结构描述', '外部接口数据结构描述见表9。', '表9外部接口数据结构描述', ['序号\\t接口名称\\t项目唯一标识号\\t有无\\n通信\\n协议\\t数据描述\\t数据\\n类型\\t数据\\n长度\\n(byte)\\t数据项标识\\t接口说明', '1\\t探测结果\\tIN1_1\\t有\\t接收主机目标定向嵌入式软件的探测结果或状态信息\\tchar\\t10\\tsend_data\\t发送参数指令', '2\\t探测参数\\tIN1_2\\t有\\t向主机目标定向嵌入式软件发送的指令或参数信息\\tchar\\t32\\trec_buffer1\\t接收探测结果与状态', '3\\tOLED屏\\tSOUT1_1\\t无\\t数字IO高低控制\\t--\\t--\\tPD0-D7 、PE8-E12\\t屏显', '4\\t键盘\\tSOUT1_2\\t无\\t键盘按键宏定义\\tchar\\t1\\t设置:para\\n自检:selfcheck\\n确认:enter\\n探测:detect\\n上:up\\n下:down\\n左:left\\n右:right\\t键盘输入', '5\\t以太网输出\\tSOUT2\\t有\\t向上级指挥控制系统发送装备状态、探测信息等\\tchar\\t据具体命令而定\\tsendState,\\nsendDetect\\t发送至上级指挥控制系统', '6\\t以太网接收\\tSOUT3\\t有\\t接收上级指挥控制系统作战命令\\tchar\\t据具体命令而定\\trecvControl\\t发送至上级指挥控制系统']]"), + ('3.4', '系统内部接口需求', + "['声探测信息交互软件内部接口主要包括设备自检接口、探测显示接口、参数设置接口和数据接收接口。内部接口示意图见图8,内部接口标识见表10。', '图8 内部接口示意图', '表10内部接口标识', ['序号\\t接口标识号\\t接口描述\\t来源\\t目的地\\t优先级别', '1\\tZIn1_1\\t自检时声探测信号处理软件发送的握手信息;\\n声探测信号处理软件发送的状态信息\\t主机控制模块\\t设备自检\\t重要', '2\\tZIn1_2\\t自检时向声探测信号处理软件发送的握手信息;\\n向声探测信号处理软件发送的自检指令\\t设备自检\\t主机控制模块\\t重要', '3\\tZIn2\\t需要显示的内容\\t主机控制模块\\t设备自检\\t重要', '4\\tZIn3_1\\t保存参数设置到内部存储\\t主机控制模块\\t参数设置\\t一般', '5\\tZIn3_2\\t读取内部存储中的参数设置\\t参数设置\\t信息交互\\t一般', '6\\tZIn4_1\\t向通信管理模块软件上报目标探测结果、自检结果等信息\\t主机控制模块\\t通信管理模块软件\\t重要', '7\\tZIn4_2\\t接收通信管理模块软件发送的指令\\t通信管理模块软件\\t主机控制模块\\t重要']]"), + ('3.5', '内部数据需求', + "['内部数据需求见表11。', '表11内部数据需求', ['序号\\t名称\\t项目唯一标识号\\t描述\\t数据项格式\\t数据项标识\\t数据项长度(BYTE)\\t单位\\t值域', '1\\t设备自检\\tSBZJMK\\t通道状态\\tint\\tChannelState\\t16*4\\t--\\t0或1', '2\\t探测显示\\tTCXSMK\\t目标方位\\tint\\tazimuth\\t1*4\\t密位\\t0 - 6000', '3\\t探测显示\\tTCXSMK\\t目标类型\\tint\\thelitype\\t1*4\\t--\\t0、1、2、10 - 26', '4\\t探测显示\\tTCXSMK\\t环境噪声\\tint\\tpw0_5\\t1*4\\t--\\t0 - 3', '5\\t参数设置\\tCSSZMK\\t设备编号\\tint\\tMID\\t1*4\\t--\\t1 - 9', '6\\t参数设置\\tCSSZMK\\t阵列基准\\tfloat\\tZBJJ\\t1*4\\t度\\t0 - 360', '7\\t参数设置\\tCSSZMK\\t温度\\tchar\\tTempT\\t1\\tºC\\t-40至+65', '8\\t参数设置\\tCSSZMK\\t屏幕亮度\\tuchar\\tScreenLight\\t1\\t--\\t高、中、低\\n三档', '9\\t网口发送\\tWKFSZMK\\t探测结果\\tchar\\tdetectInfo\\t41\\t-\\t-']]"), + ('3.6', '适应性需求', "['需要配置主机控制模块与通信管理模块软件的本地IP和端口、对端IP和端口。']"), + ('3.7', '安全性需求', + "['如果软件失效,不会对设备或系统造成破坏,不会造成关键数据丢失。同时采用看门狗监控程序运行状态,防止程序跑飞或卡死。']"), + ('3.8', '保密性需求', "['本软件编译形成烧写代码,下载固化到处理器中,源码无法被读取和修改。']"), + ('3.9', 'CSCI环境需求', "['声探测信息交互软件运行环境为一片GD32F450ZIT6芯片和一片FT-2000/4芯片。']"), + ('3.10', '计算机资源需求', + "['计算机硬件需求', 'GD32F450ZIT6、FT-2000/4芯片各一片;PHY芯片及网络变压器各一片、串口RS232芯片一片、DC-DC电路一套、晶振一个、电阻电容若干。', '计算机硬件资源使用需求', 'GD32F450ZIT6芯片的硬件资源为:', '内核最大工作频率:200MHz;', '片内存储器:FLASH:2048kB、RAM:512kB;', 'FT-2000/4芯片资源为:', 'CPU主频:2.2GHz~2.6GHz;', 'SPI Flash:128Mb。', '计算机软件需求', 'MCU芯片,对硬件性能需求较小,因此MCU芯片降频运行,以降低系统功耗。FT-2000/4完成主要的信息交互功能,使用银河麒麟v4.0.2操作系统。', '计算机通信需求', '设备与上级指挥控制系统及接入通信单元通过网口通信,与信号处理软件通过串口通信,主机控制模块与通信管理模块软件之间使用内部网口通信。']"), + ('3.11', '软件质量因素', "['本节无内容。']"), + ('3.12', '设计和实现约束', + "['GD32F450ZIT6设计采用IAR for ARM V7.2开发软件,使用C语言开发。FT-2000/4在银河麒麟v4.0.2操作系统下使用cmake和g++开发。']"), + ('3.13', '人员需求', "['维护人员:熟练操作开发软件,掌握烧写程序的方法。']"), + ('3.14', '培训需求', "['维护人员:以演示和讲解的方式培训其烧写操作过程。']"), + ('3.15', '软件保障需求', "['本节无内容。']"), ('3.16', '其他需求', "['本节无内容。']"), + ('3.17', '验收、交付和包装需求', + "['声探测信息交互软件烧写在芯片中,与系统同时验收和交付。使用光盘存储声探测信息交互软件的可烧写文件,并对安装光盘进行必要的标识、封装等处理。']"), + ('3.18', '需求的优先顺序和关键程度', + "['软件各项需求的优先顺序和关键等级表见表 12。', '表 12 需求的优先顺序和关键等级表', ['序号\\t优先顺序\\t需求描述\\t关键等级\\t备注', '1\\t1\\t探测显示需求\\t高\\t--', '2\\t2\\t参数设置需求\\t高\\t--', '3\\t3\\t设备自检需求\\t高\\t--', '4\\t4\\t信息处理需求\\t高\\t--', '5\\t5\\t设备状态管理需求\\t高\\t--', '6\\t6\\t任务参数规划需求\\t高\\t--', '7\\t7\\t目标探测需求\\t高\\t', '8\\t8\\t时统信息需求\\t高\\t', '9\\t9\\t模拟训练需求\\t高\\t', '10\\t10\\t数据记录需求\\t中\\t', '\\t注:优先顺序为从上到下排序,顺序越靠前优先级越高。\\t注:优先顺序为从上到下排序,顺序越靠前优先级越高。\\t注:优先顺序为从上到下排序,顺序越靠前优先级越高。\\t注:优先顺序为从上到下排序,顺序越靠前优先级越高。']]")] +temp2 = { + 'number': '3', + 'title': '需求', + 'ordinal': None, + 'content': '[]', + 'children': [ + {'number': '3.1', + 'title': '要求的状态和方式', + 'ordinal': None, + 'content': "['声学探测单元声探测信息交互软件分为两个部分。一是主机控制模块,二是通信管理模块,模块间通过内部网口进行信息交互。']", + 'children': [] + }, + {'number': '3.2', 'title': 'CSCI能力需求', 'ordinal': None, + 'content': "['功能模块图', '声学探测单元声探测信息交互软件分为主机控制模块和通信管理模块,功能模块框图如图1所示。', '图1 声探测信息交互软件功能模块框图', '功能模块描述', '主机控制模块', '主机控制模块功能模块主要分为探测显示、参数设置、设备自检和信息处理四个部分。', '探测显示主要包括开机画面显示、目标信息显示、环境噪声等级及时间显示等;', '参数设置主要是使用键盘对设备编号、阵列基准、环境温度、屏幕亮度等参数进行设置;', '设备自检主要是检测通道状态并显示故障通道编号,并将自检结果上传;', '信息处理主要是各外部接口的信息传输,主要包括串口输入数据接收解析,键盘输入信息处理,以及与通信管理模块软件网口通信等。', '主机控制模块功能模块描述见表2。', '表2功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t需求描述\\t是否有子功能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t需求描述\\t是否有子功能', '1\\t探测显示\\tTCXSMK\\t探测结果\\t开机界面显示,探测结果显示\\t开机画面\\n显示目标批号、方向、类型(串口触发)\\n显示环境噪声等级(串口触发)\\n显示时间(串口触发)\\n快捷键跳转\\t有', '2\\t参数设置\\tCSSZMK\\t键盘\\t指令打包\\t设备编号(键盘设置)\\n阵列基准(键盘设置)\\n环境温度(键盘设置)\\n屏幕亮度(键盘设置)\\n快捷键跳转\\t有', '3\\t设备自检\\tSBZJMK\\t键盘\\t指令打包\\t通道自检:检测通道状态,显示故障通道编号(串口触发、键盘设置)\\n快捷键跳转\\t有', '4\\t信息处理\\tXICLMK\\t串口输入/网口输入 \\t网口输出/外部IO输出\\t串口输入数据接收解析\\n网口上传信息\\n键盘控制\\t有'], '探测显示功能模块', '探测显示功能模块主要实现以下功能:', '开机显示:显示设备名称及生产厂家;', '探测结果:显示目标批号、方向、类型;', '环境噪声等级:显示环境噪声等级;', '系统时间:显示系统时间。', '探测显示功能模块框图见图2,具体描述见表3。', '图2 探测显示功能模块图', '表3 探测显示功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t满足的各项需求及性能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t满足的各项需求及性能', '1\\t开机显示\\tKJXSZMK\\tDSP握手信息\\t界面显示\\t显示设备名称及生产厂家', '2\\t探测结果\\tTCJGZMK\\t解析后的探测结果信息\\t界面显示\\t目标方位(1,2,3,4,5)\\n目标方位(000.0)\\n目标类型(0000)', '3\\t环境噪声\\tHJZSZMK\\t解析后的环境噪声等级信息\\t界面显示\\t探测环境品质因素\\n(优、良、中、差)', '4\\t系统时间\\tXTSJZMK\\t解析后的时间信息\\t界面显示\\t显示系统时间:\\n(时:分:秒)'], '参数设置功能模块', '参数设置主要是对设备参数进行设置,包括键盘设置设备编号、阵列基准、环境温度、屏幕亮度。参数设置功能模块描述见表4。', '表4参数设置功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t满足的各项需求及性能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t满足的各项需求及性能', '1\\t设备参数设置\\tSBCSZMK\\t键盘\\t参数设置指令\\t探测仪编号:1-9;\\n阵列基线指北角度偏差:\\n000.0°-360.0°;\\n环境温度:-40°- +65°;\\n屏幕亮度:“高”、“中”和“低”;'], '设备自检功能模块', '设备自检主要是检测通道状态并显示故障通道编号,并将自检结果上传。设备自检功能模块描述见表5。', '表5 设备自检功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t满足的各项需求及性能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t满足的各项需求及性能', '1\\t通道自检\\tTDZJZMK\\t键盘\\t通道自检指令\\t若无通道损坏,提示通道正常;\\n若有通道损坏,显示其中损坏通道的编号'], '信息处理功能模块', '信息处理主要是各外部接口的信息传输,主要包括串口接收数据解析、网口数据收发、键盘控制等。', '串口接收中断:串口有数据即进入串口中断服务程序,接收串口数据并解析;', '网口收发:把需要发送的信息和指令按照协议打包发往网口;接收网口数据;', '键盘控制:扫描键盘,获取按键值;', '信息处理功能模块图见图3,具体描述见表6。', '图3 信息处理功能模块图', '表6 信息处理功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t满足的各项需求及性能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t满足的各项需求及性能', '1\\t串口接收中断\\tCKJSZMK\\t串口\\t解析的指令\\t接收声探测信息处理软件指令,并解析指令。', '2\\t网口收发\\tWKSFZMK\\t打包的指令\\t网口\\t按照协议打包信息或指令发送;接收并解析网口数据。', '3\\t键盘控制\\tJPKZZMK\\t硬件\\t按键值\\t返回按下按键的值'], '通信管理模块软件', '通信管理模块软件功能模块主要分为设备状态管理、任务参数规划、目标探测信息、时统信息解析、模拟训练和数据记录六个部分。', '设备状态管理主要是实时向上级指挥控制系统上报装备链路状态、装备状态、工作状态、健康状态等信息;', '任务参数规划主要是设备与指挥控制系统建立链接后,从指挥控制系统获取初始化参数与作战管理权限;', '目标探测信息主要是当设备生成目标探测结果后向上级指挥控制系统上报目标探测信息;', '时统信息解析主要是设备从接收并解析接入通信单元下发的NTP授时信息;', '模拟训练根据指控下发的目标,筛选在声探测设备探测能力范围内的目标,并形成航迹批次上报到指控。', '数据记录主要是对设备的决策信息、控制信息、状态信息等信息进行本地存储。', '通信管理模块软件功能模块描述见表7。', '表7通信管理模块软件功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t需求描述\\t是否有子功能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t需求描述\\t是否有子功能', '1\\t设备状\\n态管理\\tZTGLMK\\t设备状态\\t网口\\t向上级指控系统上报装备链路状态、装备状态、工作状态、健康状态等信息\\t有', '2\\t任务参\\n数规划\\tCSGHMK\\t网口\\t解析的参数\\t从指控系统获取初始化参数与作战管理权限\\t有', '3\\t目标探\\n测信息\\tTCXXMK\\t设备探测结果\\t网口\\t向指控系统上报探测信息\\t无', '4\\t时统信\\n息解析\\tSTXXMK\\t网口\\t解析的参数\\t接收通信单元的授时信息并解析\\t无', '5\\t模拟训练\\tMNXLMK\\t网口\\t网口\\t能够根据指控下发的“模拟训练”目标信息形成航迹批次并上报指控\\t无', '6\\t运行状态监控\\tYXZTJK\\t-\\t-\\t设备运行状态监控系统功能模块主要实现查看设备运行状态、文件上传、状态统计等功能\\t', '7\\t数据记录\\tSJJLMK\\t-\\t-\\t记录设备所有的接收和发送的报文\\t无'], '设备状态管理功能模块', '设备状态管理功能模块主要实现以下设备状态的管理功能:', '链路监测', '通信管理模块软件启动完毕后,以固定周期1s向上级指挥控制系统发送链路监测报文,上级指挥控制系统收到链路监测报文后,立即向声探测信息交互软件回复链路监测报文,声探测信息交互软件收到链路监测报文后,完成建链。任意一方连续3个周期未收到链路监测报文后,链路状态由通转为断。链路监测报文持续发送,收到回复后链路状态由断转为通,重新完成链路建立。本文档所涉及的所有报文都要基于链路通畅时,才能进行发送;', '装备状态', '通信管理模块软件周期性向上级指挥控制系统上报装备状态信息,其中主要包括工作状态、受控状态、时统状态信息。', '工作状态', '通信管理模块软件可以接收并响应上级指挥控制系统下发工作状态设置指令并响应,分为作战状态与模拟训练状态,装备开机后默认进入作战状态。', '健康状态', '主机控制模块周期性地向通信管理模块软件发送设备各通道自检结果,通信管理模块软件形成健康状态报文周期性上报上级指挥控制系统。', '设备状态管理功能模块功能框图见图4,具体描述见表8。', '图4 设备状态管理功能模块图', '表8设备状态管理功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t满足的各项需求及性能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t满足的各项需求及性能', '1\\t链路监测\\tLLJCZMK\\t网口\\t网口\\t与指控的链路的通断控制', '2\\t装备状态\\tZBZTZMK\\t设备状态\\t网口\\t向指控上报装备状态', '3\\t工作状态\\tGZZTZMK\\t网口\\t网口\\t根据指控指令更改工作状态', '4\\t健康状态\\tJKZTZMK\\t设备状态\\t网口\\t向指控上报健康状态'], '任务参数规划功能模块', '任务参数规划功能模块主要包括初始化参数与作战管理权限两部分功能:', '初始化参数', '通信管理模块软件与上级指挥控制系统的链路连通后会定时向上级发送初始化参数请求,上级指挥控制系统收到请求后会立即向声探测信息交互软件下发初始化参数指令,进行初始化设置,其中有站址来源、声学探测单元数量及装备位置等参数信息。', '作战管理权限', '通信管理模块软件会接收到上级指挥控制系统作战权限管理功能指令,同时向上级指控发送初始化参数停止请求,包括上级和下级的编码、个数、权限。当上级系统发现变化时,声探测信息交互软件与之前通信的上级指控的链路连通性立刻断开,并持续向新的上级指挥控制中心发送链路监测报,待与新的上级指挥控制中心链路连通后,声探测信息交互软件的控制权限将交接给新的上级指控系统。', '任务参数规划功能模块功能框图见图5,具体描述见表9。', '图5 任务参数规划功能模块图', '表9任务参数规划功能模块描述', ['序号\\tCSCI的功能\\tCSCI的功能\\t输入\\t输出\\t满足的各项需求及性能', '序号\\t名称\\t项目唯一标识号\\t输入\\t输出\\t满足的各项需求及性能', '1\\t初始化参数\\tCSHCSZMK\\t网口\\t网口\\t向指控请求初始化参数', '2\\t作战权限管理\\tZZGLQXZMK\\t网口\\t网口\\t上级指控的更改'], '目标探测信息功能模块', '通信管理模块软件接收到主机控制软件上报的目标探测结果后,会立即生成目标探测信息报文上报上级指挥控制系统,主要内容包括:', '本地批号;', '上级批号;', '目标数量;0-5;', '目标属性;', '目标类型:0-无效,1-未识别的气动目标,2-隐身飞机,3-巡航导弹,4-空地弹,5-精确制导炸药,150-未识别的无人机,151-固定翼,152-旋翼,200-未识别的弹道目标,201-弹头,202-诱饵;', '目标型号;', '目标识别概率:0-10000;', 'RCS值;保留', '数据周期;', '航迹状态:跟踪连续性、状态编码:0-无效,1-航迹丢失,2-主动跟踪,3-被动跟踪,4-外推跟踪;', '目标位置X(地心X、经度、斜距、北方向);', '目标位置Y(地心Y、纬度、方位、天方向);', '目标位置Z(地心Z、海拔高、俯仰、东方向);', 'X向速度(地心X、经度、斜距、北方向);', 'Y向速度(地心Y、纬度、方位、天方向);', 'Z向速度(地心Z、海拔高、俯仰、东方向);', '合速度;保留', '系统误差;', '随机误差;', '时统信息解析功能模块', '通信管理模块软件会定时向接入通信单元发送NTP授时请求报文,接入通信单元收到后会向授时方发送NTP授时报文,通信管理模块软件通过NTP授时报文校准当前的绝对时统,当通信管理模块软件的绝对时统与相对时统误差过大时,通信管理模块软件切换到相对时统,默认使用绝对时统。', '模拟训练功能模块', '根据指控下发的目标,筛选在声探测设备探测能力范围内的目标,并形成航迹批次上报到指控。', '运行状态监控模块', '设备运行状态监控系统功能模块主要实现查看设备运行状态、文件上传、状态统计等功能。', 'a)设备运行状态', '软件通过UDP协议获取声探测信息交互软件发送的设备状态实时数据,然后对设备部件状态数据(包括通道状态、整机安全保密状态、整机健康状态、传感器健康状态和声学探测主机状态等)和设备运行状态(包括工作状态、受控状态、时统状态和加电状态等)进行解析处理保存到系统数据库,前端通过定时接口请求显示最新的设备状态信息。', 'b)文件上传', '主要实现将信息交互软件升级安装包上传到系统,通过点击“升级”按钮,首先备份历史版本信息交互软件安装包,然后解压上传的最新版本信息交互软件安装包后重启设备或者重启软件完成软件升级功能。', 'c)状态统计', '通过浏览器查看设备在指定时间范围内的设备运行状态,进行图表显示,方便用户进行数据分析和故障信息查看等。', '数据记录功能模块', '具备记录、存储日志功能,可将装备状态,目标探测结果等信息进行本地存储。']", + 'children': []}, + {'number': '3.3', 'title': '系统外部接口需求', 'ordinal': None, + 'content': "['接口标识和接口图', '声探测信息交互软件的外部接口主要包含与声探测信号处理软件的串口通信,与FPGA的SPI通信,与指挥控制系统的以太网通信,通过串口接收授时软件的授时信息,以及SD卡存储和通过数字IO实现与OLED和键盘交互。外部接口示意图见图7,外部接口标识见表8。', '图7 外部接口示意图', '表8外部接口标识', ['序号\\t接口名称\\t接口标识号\\t接口描述\\t来源\\t目的地\\t接口类型\\t优先级别', '1\\t探测结果\\tIN1_1\\t接收声探测信号处理软件的探测结果;环境噪声分级;通道自检结果;\\tDSP uart0\\tMCU串口0\\t串口\\t重要', '2\\t探测参数\\tIN1_2\\t向声探测信号处理软件发送环境温度、自检指令。\\tMCU串口0\\tDSP uart0\\t串口\\t重要', '3\\tOLED屏\\tSOUT1_1\\t更新界面显示\\t主机控制模块\\tOLED屏\\t数字IO\\t重要', '4\\t键盘\\tSOUT1_2\\t输入\\t键盘\\t主机控制模块\\t数字IO\\t重要', '5\\t以太网输出\\tSOUT2\\t以太网输出探测结果、设备状态等信息\\t指挥控制系统\\t通信管理模块软件\\t网口\\t重要', '6\\t以太网接收\\tSOUT3\\t以太网接收指挥控制系统作战命令\\t通信管理模块软件\\t指挥控制系统\\t网口\\t重要'], '外部接口数据结构描述', '外部接口数据结构描述见表9。', '表9外部接口数据结构描述', ['序号\\t接口名称\\t项目唯一标识号\\t有无\\n通信\\n协议\\t数据描述\\t数据\\n类型\\t数据\\n长度\\n(byte)\\t数据项标识\\t接口说明', '1\\t探测结果\\tIN1_1\\t有\\t接收主机目标定向嵌入式软件的探测结果或状态信息\\tchar\\t10\\tsend_data\\t发送参数指令', '2\\t探测参数\\tIN1_2\\t有\\t向主机目标定向嵌入式软件发送的指令或参数信息\\tchar\\t32\\trec_buffer1\\t接收探测结果与状态', '3\\tOLED屏\\tSOUT1_1\\t无\\t数字IO高低控制\\t--\\t--\\tPD0-D7 、PE8-E12\\t屏显', '4\\t键盘\\tSOUT1_2\\t无\\t键盘按键宏定义\\tchar\\t1\\t设置:para\\n自检:selfcheck\\n确认:enter\\n探测:detect\\n上:up\\n下:down\\n左:left\\n右:right\\t键盘输入', '5\\t以太网输出\\tSOUT2\\t有\\t向上级指挥控制系统发送装备状态、探测信息等\\tchar\\t据具体命令而定\\tsendState,\\nsendDetect\\t发送至上级指挥控制系统', '6\\t以太网接收\\tSOUT3\\t有\\t接收上级指挥控制系统作战命令\\tchar\\t据具体命令而定\\trecvControl\\t发送至上级指挥控制系统']]", + 'children': []}, + {'number': '3.4', 'title': '系统内部接口需求', 'ordinal': None, + 'content': "['声探测信息交互软件内部接口主要包括设备自检接口、探测显示接口、参数设置接口和数据接收接口。内部接口示意图见图8,内部接口标识见表10。', '图8 内部接口示意图', '表10内部接口标识', ['序号\\t接口标识号\\t接口描述\\t来源\\t目的地\\t优先级别', '1\\tZIn1_1\\t自检时声探测信号处理软件发送的握手信息;\\n声探测信号处理软件发送的状态信息\\t主机控制模块\\t设备自检\\t重要', '2\\tZIn1_2\\t自检时向声探测信号处理软件发送的握手信息;\\n向声探测信号处理软件发送的自检指令\\t设备自检\\t主机控制模块\\t重要', '3\\tZIn2\\t需要显示的内容\\t主机控制模块\\t设备自检\\t重要', '4\\tZIn3_1\\t保存参数设置到内部存储\\t主机控制模块\\t参数设置\\t一般', '5\\tZIn3_2\\t读取内部存储中的参数设置\\t参数设置\\t信息交互\\t一般', '6\\tZIn4_1\\t向通信管理模块软件上报目标探测结果、自检结果等信息\\t主机控制模块\\t通信管理模块软件\\t重要', '7\\tZIn4_2\\t接收通信管理模块软件发送的指令\\t通信管理模块软件\\t主机控制模块\\t重要']]", + 'children': []}, + {'number': '3.5', 'title': '内部数据需求', 'ordinal': None, + 'content': "['内部数据需求见表11。', '表11内部数据需求', ['序号\\t名称\\t项目唯一标识号\\t描述\\t数据项格式\\t数据项标识\\t数据项长度(BYTE)\\t单位\\t值域', '1\\t设备自检\\tSBZJMK\\t通道状态\\tint\\tChannelState\\t16*4\\t--\\t0或1', '2\\t探测显示\\tTCXSMK\\t目标方位\\tint\\tazimuth\\t1*4\\t密位\\t0 - 6000', '3\\t探测显示\\tTCXSMK\\t目标类型\\tint\\thelitype\\t1*4\\t--\\t0、1、2、10 - 26', '4\\t探测显示\\tTCXSMK\\t环境噪声\\tint\\tpw0_5\\t1*4\\t--\\t0 - 3', '5\\t参数设置\\tCSSZMK\\t设备编号\\tint\\tMID\\t1*4\\t--\\t1 - 9', '6\\t参数设置\\tCSSZMK\\t阵列基准\\tfloat\\tZBJJ\\t1*4\\t度\\t0 - 360', '7\\t参数设置\\tCSSZMK\\t温度\\tchar\\tTempT\\t1\\tºC\\t-40至+65', '8\\t参数设置\\tCSSZMK\\t屏幕亮度\\tuchar\\tScreenLight\\t1\\t--\\t高、中、低\\n三档', '9\\t网口发送\\tWKFSZMK\\t探测结果\\tchar\\tdetectInfo\\t41\\t-\\t-']]", + 'children': []}, + {'number': '3.6', 'title': '适应性需求', 'ordinal': None, + 'content': "['需要配置主机控制模块与通信管理模块软件的本地IP和端口、对端IP和端口。']", + 'children': []}, + {'number': '3.7', 'title': '安全性需求', 'ordinal': None, + 'content': "['如果软件失效,不会对设备或系统造成破坏,不会造成关键数据丢失。同时采用看门狗监控程序运行状态,防止程序跑飞或卡死。']", + 'children': []}, + {'number': '3.8', 'title': '保密性需求', 'ordinal': None, + 'content': "['本软件编译形成烧写代码,下载固化到处理器中,源码无法被读取和修改。']", 'children': []}, + {'number': '3.9', 'title': 'CSCI环境需求', 'ordinal': None, + 'content': "['声探测信息交互软件运行环境为一片GD32F450ZIT6芯片和一片FT-2000/4芯片。']", 'children': []}, + {'number': '3.10', 'title': '计算机资源需求', 'ordinal': None, + 'content': "['计算机硬件需求', 'GD32F450ZIT6、FT-2000/4芯片各一片;PHY芯片及网络变压器各一片、串口RS232芯片一片、DC-DC电路一套、晶振一个、电阻电容若干。', '计算机硬件资源使用需求', 'GD32F450ZIT6芯片的硬件资源为:', '内核最大工作频率:200MHz;', '片内存储器:FLASH:2048kB、RAM:512kB;', 'FT-2000/4芯片资源为:', 'CPU主频:2.2GHz~2.6GHz;', 'SPI Flash:128Mb。', '计算机软件需求', 'MCU芯片,对硬件性能需求较小,因此MCU芯片降频运行,以降低系统功耗。FT-2000/4完成主要的信息交互功能,使用银河麒麟v4.0.2操作系统。', '计算机通信需求', '设备与上级指挥控制系统及接入通信单元通过网口通信,与信号处理软件通过串口通信,主机控制模块与通信管理模块软件之间使用内部网口通信。']", + 'children': []}, + {'number': '3.11', 'title': '软件质量因素', 'ordinal': None, 'content': "['本节无内容。']", 'children': []}, + {'number': '3.12', 'title': '设计和实现约束', 'ordinal': None, + 'content': "['GD32F450ZIT6设计采用IAR for ARM V7.2开发软件,使用C语言开发。FT-2000/4在银河麒麟v4.0.2操作系统下使用cmake和g++开发。']", + 'children': []}, {'number': '3.13', 'title': '人员需求', 'ordinal': None, + 'content': "['维护人员:熟练操作开发软件,掌握烧写程序的方法。']", 'children': []}, + {'number': '3.14', 'title': '培训需求', 'ordinal': None, + 'content': "['维护人员:以演示和讲解的方式培训其烧写操作过程。']", 'children': []}, + {'number': '3.15', 'title': '软件保障需求', 'ordinal': None, 'content': "['本节无内容。']", 'children': []}, + {'number': '3.16', 'title': '其他需求', 'ordinal': None, 'content': "['本节无内容。']", 'children': []}, + {'number': '3.17', 'title': '验收、交付和包装需求', 'ordinal': None, + 'content': "['声探测信息交互软件烧写在芯片中,与系统同时验收和交付。使用光盘存储声探测信息交互软件的可烧写文件,并对安装光盘进行必要的标识、封装等处理。']", + 'children': []}, + {'number': '3.18', 'title': '需求的优先顺序和关键程度', 'ordinal': None, + 'content': "['软件各项需求的优先顺序和关键等级表见表 12。', '表 12 需求的优先顺序和关键等级表', ['序号\\t优先顺序\\t需求描述\\t关键等级\\t备注', '1\\t1\\t探测显示需求\\t高\\t--', '2\\t2\\t参数设置需求\\t高\\t--', '3\\t3\\t设备自检需求\\t高\\t--', '4\\t4\\t信息处理需求\\t高\\t--', '5\\t5\\t设备状态管理需求\\t高\\t--', '6\\t6\\t任务参数规划需求\\t高\\t--', '7\\t7\\t目标探测需求\\t高\\t', '8\\t8\\t时统信息需求\\t高\\t', '9\\t9\\t模拟训练需求\\t高\\t', '10\\t10\\t数据记录需求\\t中\\t', '\\t注:优先顺序为从上到下排序,顺序越靠前优先级越高。\\t注:优先顺序为从上到下排序,顺序越靠前优先级越高。\\t注:优先顺序为从上到下排序,顺序越靠前优先级越高。\\t注:优先顺序为从上到下排序,顺序越靠前优先级越高。']]", + 'children': [] + } + ] +} diff --git a/apps/project/tool/timeList.py b/apps/project/tool/timeList.py new file mode 100644 index 0000000..08ee83b --- /dev/null +++ b/apps/project/tool/timeList.py @@ -0,0 +1,104 @@ +from apps.createDocument.extensions.documentTime import DocTime +from django.shortcuts import get_object_or_404 +from apps.project.models import Project + +def time_return_to(id): + project_obj = get_object_or_404(Project, id=id) + time = [] + time_parser = DocTime(id) + dg_otime = {} + # 1.大纲-测评地点与时间相关的时间 + temp_dict = time_parser.dg_address_time() + dg_otime['title'] = '测评大纲' + dg_otime['被测件接收'] = [temp_dict['beginTime_strf'], temp_dict['beginTime_strf']] + dg_otime['大纲编制时间'] = [temp_dict['dgCompileStart'], temp_dict['dgCompileEnd']] + dg_otime['设计与实现时间'] = [temp_dict['designStart'], temp_dict['designEnd']] + # 2.大纲-文档时间 + temp_dict = time_parser.dg_final_time() + dg_otime['封面时间'] = temp_dict['cover_time'] + dg_otime['拟制时间'] = temp_dict['preparation_time'] + dg_otime['校对时间'] = temp_dict['inspect_time'] + dg_otime['审核时间'] = temp_dict['auditing_time'] + dg_otime['批准时间'] = temp_dict['ratify_time'] + dg_otime['创建时间'] = temp_dict['create_doc_time'] + dg_otime['v1版本时间'] = temp_dict['doc_v1_time'] + time.append(dg_otime) + # 3.说明的时间 + temp_dict = time_parser.sm_final_time() + sm_otime = { + 'title': '测试说明', + '封面时间': temp_dict['cover_time'], + '拟制时间': temp_dict['preparation_time'], + '校对时间': temp_dict['inspect_time'], + '审核时间': temp_dict['auditing_time'], + '批准时间': temp_dict['ratify_time'], + '创建时间': temp_dict['create_doc_time'], + 'v1版本时间': temp_dict['doc_v1_time'] + } + time.append(sm_otime) + # 4.记录时间 + temp_dict = time_parser.jl_final_time() + jl_otime = { + 'title': '测试记录', + '封面时间': temp_dict['cover_time'], + '拟制时间': temp_dict['preparation_time'], + '校对时间': temp_dict['inspect_time'], + '审核时间': temp_dict['auditing_time'], + '批准时间': temp_dict['ratify_time'], + '创建时间': temp_dict['create_doc_time'], + 'v1版本时间': temp_dict['doc_v1_time'] + } + time.append(jl_otime) + # 5.回归说明时间 + # 5.回归记录时间 + rounds = project_obj.pField.all() + for round in rounds: + if round.key == '0': + continue + temp_dict = time_parser.hsm_final_time(round.key) + round_otime = { + 'title': f'第{int(round.key) + 1}轮测试说明', + '封面时间': temp_dict['cover_time'], + '拟制时间': temp_dict['preparation_time'], + '校对时间': temp_dict['inspect_time'], + '审核时间': temp_dict['auditing_time'], + '批准时间': temp_dict['ratify_time'], + '创建时间': temp_dict['create_doc_time'], + 'v1版本时间': temp_dict['doc_v1_time'] + } + time.append(round_otime) + temp_dict = time_parser.hjl_final_time(round.key) + round_otime = { + 'title': f'第{int(round.key) + 1}轮测试记录', + '封面时间': temp_dict['cover_time'], + '拟制时间': temp_dict['preparation_time'], + '校对时间': temp_dict['inspect_time'], + '审核时间': temp_dict['auditing_time'], + '批准时间': temp_dict['ratify_time'], + '创建时间': temp_dict['create_doc_time'], + 'v1版本时间': temp_dict['doc_v1_time'] + } + time.append(round_otime) + # 6.报告时间 + ## 6.1.报告文档片段-测评时间和地点 + temp_dict = time_parser.bg_address_time() + bg_otime = { + 'title': '测评报告', + '被测件接收时间': temp_dict['begin_time'], + '大纲编制时间': [temp_dict['dg_weave_start_date'], temp_dict['dg_weave_end_date']], + '测评设计与实现': [temp_dict['sj_weave_start_date'], temp_dict['sj_weave_end_date']], + '测评总结': [temp_dict['summary_start_date'], temp_dict['summary_end_date']] + } + for r in temp_dict['round_time_list']: + bg_otime[r['name']] = [r['start'], r['end']] + temp_dict = time_parser.bg_final_time() + ### 6.2.报告文档时间 + bg_otime['封面时间'] = temp_dict['cover_time'], + bg_otime['拟制时间'] = temp_dict['preparation_time'] + bg_otime['校对时间'] = temp_dict['inspect_time'], + bg_otime['审核时间'] = temp_dict['auditing_time'], + bg_otime['批准时间'] = temp_dict['ratify_time'], + bg_otime['创建时间'] = temp_dict['create_doc_time'], + bg_otime['v1版本时间'] = temp_dict['doc_v1_time'] + time.append(bg_otime) + return time diff --git a/apps/project/tool/xq_parse.py b/apps/project/tool/xq_parse.py new file mode 100644 index 0000000..aa5dbfd --- /dev/null +++ b/apps/project/tool/xq_parse.py @@ -0,0 +1,238 @@ +import re +import docx +from docx.document import Document +from docx.text.paragraph import Paragraph +from docx.parts.image import ImagePart +from docx.table import _Cell, Table +from docx.oxml.table import CT_Tbl +from docx.oxml.text.paragraph import CT_P +from collections import OrderedDict + +class DocxChapterExtractor(object): + def __init__(self, docx_path): + self.doc = docx.Document(docx_path) # 解析文档 + + def extract_chapter_info(self, text): + """提取章节编号和标题""" + pattern = r'^(\d+(?:\.\d+)*)\s+(.*?)(?:\s*\d+)?\s*$' + match = re.match(pattern, text) + chapter_num = None + content = None + if match: + chapter_num = match.group(1) # '4.1' or '4' + content = match.group(2).strip() # '外部接口需求' + else: + print(f"'{text}' no match") + return chapter_num, content + + def if_valid_match(self, chaptera_name, text): + pattern = r'^(\d+(?:\.\d+)*)\s+' + chaptera_name + r'(?:\s*\d+)?\s*$' + return re.match(pattern, text) is not None + + def get_chapter_number(self, chapter_name): + """获取目录结构""" + directory = [] + chapter_num = '' + flag = False + for paragraph in self.doc.paragraphs: + if self.if_valid_match(chapter_name, paragraph.text) and 'toc' in paragraph.style.name: + chapter_num, content = self.extract_chapter_info(paragraph.text) + directory.append((chapter_num, content)) + flag = True + continue + if flag and paragraph.text.startswith(chapter_num) and 'toc' in paragraph.style.name: + num, content = self.extract_chapter_info(paragraph.text) + directory.append((num, content)) + return directory + + def build_hierarchy(self, chapter_body_list): + """将线性章节列表转换为嵌套结构""" + hierarchy = {} + path = [] # 当前路径栈,存储章节号的字符串部分(如 ["4", "2"]) + for item in chapter_body_list: + # 处理不同格式的输入数据 + if len(item) == 3: + num, content, _ = item # 忽略第三个元素 + elif len(item) == 2: + num, content = item + else: + continue # 跳过无效数据 + # 切割章节号为字符串列表(如 '4.2.1' -> ["4", "2", "1"]) + parts = num.split('.') + # 1. 回溯路径找到当前层级 + while len(path) >= len(parts): + path.pop() + # 2. 逐级构建或定位父节点 + current_level = hierarchy + for i in range(len(path)): + part = path[i] + # 如果父节点不存在,自动创建占位节点 + if part not in current_level: + current_level[part] = { + "number": ".".join(parts[:i + 1]), + "title": "[未命名章节]", # 占位节点标题 + "children": {} + } + current_level = current_level[part]["children"] + # 3. 插入当前节点 + current_part = parts[len(path)] # 当前层级的部分(如 "1") + if current_part not in current_level: + current_level[current_part] = { + "number": num, + "title": content, + "children": {} + } + # 4. 更新路径栈 + path = parts.copy() + return hierarchy + + def extract_title_ordinal(self, s): + # 正则表达式匹配以括号结尾的字符串 + pattern = r'^(.*?)\s*[((](.*?)[))]$' + match = re.match(pattern, s) + if match: + # 提取标题并去除前后空格 + title = match.group(1).strip() + # 提取序号并去除前后空格 + ordinal = match.group(2).strip() + else: + title = s + ordinal = None + return title, ordinal + + def build_json_tree(self, chapter_body_list): + """直接生成树形JSON结构""" + root = {"number": "", "title": "ROOT", "content": "", "children": []} + node_map = OrderedDict() + node_map[""] = root # 初始化根节点映射 + for item in chapter_body_list: + # 处理不同格式的输入数据 + if len(item) == 3: + num, chapter_name, chapter_content = item + title, ordinal = self.extract_title_ordinal(chapter_name) + elif len(item) == 2: + num, chapter_name = item + title, ordinal = self.extract_title_ordinal(chapter_name) + chapter_content = "" + else: + continue # 跳过无效数据 + parts = num.split('.') + parent_node = root # 始终从根节点开始查找父级 + for depth in range(len(parts)): + current_num = ".".join(parts[:depth + 1]) + if current_num not in node_map: + new_node = { + "number": current_num, + "title": title if (depth == len(parts) - 1) else "[未命名章节]", + "ordinal": ordinal if (depth == len(parts) - 1) else "", + "content": chapter_content if (depth == len(parts) - 1) else "", + "children": [] + } + parent_num = ".".join(parts[:depth]) + parent_node = node_map[parent_num] + parent_node["children"].append(new_node) + node_map[current_num] = new_node + parent_node = node_map[current_num] + # 确保最终标题和内容正确 + node_map[num]["title"] = title + node_map[num]["ordinal"] = ordinal + node_map[num]["content"] = chapter_content + return root["children"][0] if root["children"] else {} + + def is_image(self, graph: Paragraph, doc: Document): + """判断段落是否图片""" + images = graph._element.xpath('.//pic:pic') # 获取所有图片 + for image in images: + for img_id in image.xpath('.//a:blip/@r:embed'): # 获取图片id + part = doc.part.related_parts[img_id] # 根据图片id获取对应的图片 + if isinstance(part, ImagePart): + return True + return False + + def get_ImagePart(self, graph: Paragraph, doc: Document): # 一行只能获取一个图片 + """获取图片字节流,类型为bytes""" + images = graph._element.xpath('.//pic:pic') # 获取所有图片 + for image in images: + for img_id in image.xpath('.//a:blip/@r:embed'): # 获取图片id + part = doc.part.related_parts[img_id] # 根据图片id获取对应的图片 + if isinstance(part, ImagePart): + return part.blob + return None + + def iter_block_items(self, parent, directory): + """ + 根据目录匹配章节内容 + parent: docx解析内容, 传入self.doc + directory: 章节目录结构,例如[('4', '工程需求'), ('4.1', '外部接口需求'), + ('4.2', '功能需求'), ('4.2.1', '知识库大模型检索问答功能')] + """ + if isinstance(parent, Document): + parent_elm = parent.element.body + elif isinstance(parent, _Cell): + parent_elm = parent._tc + else: + raise ValueError("something's not right") + i = 0 + body_list = [] + body = [] + flag = False # 判断是否循环到章节标题 + for child in parent_elm.iterchildren(): + if isinstance(child, CT_P): + paragraph = Paragraph(child, parent) + if i < len(directory) - 1: + if paragraph.text == directory[i][1] and 'Heading' in paragraph.style.name: + flag = True + continue + if paragraph.text == directory[i + 1][1] and 'Heading' in paragraph.style.name: + # body_list.append(body) + new_tuple = directory[i] + (repr(body),) + body_list.append(new_tuple) + # print(new_tuple) + body = [] + i += 1 + continue + if flag: + if self.is_image(paragraph, parent): + body.append(self.get_ImagePart(paragraph, parent)) + + elif paragraph.text != '': + body.append(paragraph.text) + elif i == len(directory) - 1: + if 'Heading' in paragraph.style.name: + new_tuple = directory[i] + (repr(body),) + body_list.append(new_tuple) + break + if self.is_image(paragraph, parent): + body.append(self.get_ImagePart(paragraph, parent)) + elif paragraph.text != '': + body.append(paragraph.text) + # print(body_list) + # print(paragraph.text, '--------------->', paragraph.style.name) + else: + flag = False + elif isinstance(child, CT_Tbl): + if flag: + table = [] + for row in Table(child, parent).rows: + # 获取每行的单元格文本 + row_text = [cell.text for cell in row.cells] + # 用制表符或其他分隔符连接单元格内容 + table.append("\t".join(row_text)) + body.append(table) + return body_list + + def main(self, chapter_name): + directory = self.get_chapter_number(chapter_name) + print(directory) + chapter_body_list = self.iter_block_items(self.doc, directory) + print(chapter_body_list) + # 构建层级结构 + # hierarchy = self.build_hierarchy(chapter_body_list) + # print(hierarchy) + json_tree = self.build_json_tree(chapter_body_list) + print(json_tree) + +if __name__ == '__main__': + docx_path = 'test - 副本.docx' + extractor = DocxChapterExtractor(docx_path) + extractor.main('工程需求') diff --git a/apps/project/tools/__init__.py b/apps/project/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/project/tools/__pycache__/__init__.cpython-313.pyc b/apps/project/tools/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..ff7b2e9 Binary files /dev/null and b/apps/project/tools/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/project/tools/__pycache__/__init__.cpython-38.pyc b/apps/project/tools/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..fcddc57 Binary files /dev/null and b/apps/project/tools/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/project/tools/__pycache__/auto_create_data.cpython-313.pyc b/apps/project/tools/__pycache__/auto_create_data.cpython-313.pyc new file mode 100644 index 0000000..3f6705d Binary files /dev/null and b/apps/project/tools/__pycache__/auto_create_data.cpython-313.pyc differ diff --git a/apps/project/tools/__pycache__/auto_create_data.cpython-38.pyc b/apps/project/tools/__pycache__/auto_create_data.cpython-38.pyc new file mode 100644 index 0000000..218278a Binary files /dev/null and b/apps/project/tools/__pycache__/auto_create_data.cpython-38.pyc differ diff --git a/apps/project/tools/__pycache__/copyCase.cpython-313.pyc b/apps/project/tools/__pycache__/copyCase.cpython-313.pyc new file mode 100644 index 0000000..2e5bfa1 Binary files /dev/null and b/apps/project/tools/__pycache__/copyCase.cpython-313.pyc differ diff --git a/apps/project/tools/__pycache__/copyCase.cpython-38.pyc b/apps/project/tools/__pycache__/copyCase.cpython-38.pyc new file mode 100644 index 0000000..16f8a41 Binary files /dev/null and b/apps/project/tools/__pycache__/copyCase.cpython-38.pyc differ diff --git a/apps/project/tools/__pycache__/copyDemand.cpython-313.pyc b/apps/project/tools/__pycache__/copyDemand.cpython-313.pyc new file mode 100644 index 0000000..63e71a0 Binary files /dev/null and b/apps/project/tools/__pycache__/copyDemand.cpython-313.pyc differ diff --git a/apps/project/tools/__pycache__/copyDemand.cpython-38.pyc b/apps/project/tools/__pycache__/copyDemand.cpython-38.pyc new file mode 100644 index 0000000..497f245 Binary files /dev/null and b/apps/project/tools/__pycache__/copyDemand.cpython-38.pyc differ diff --git a/apps/project/tools/__pycache__/delete_change_key.cpython-313.pyc b/apps/project/tools/__pycache__/delete_change_key.cpython-313.pyc new file mode 100644 index 0000000..4499d08 Binary files /dev/null and b/apps/project/tools/__pycache__/delete_change_key.cpython-313.pyc differ diff --git a/apps/project/tools/__pycache__/delete_change_key.cpython-38.pyc b/apps/project/tools/__pycache__/delete_change_key.cpython-38.pyc new file mode 100644 index 0000000..7647f97 Binary files /dev/null and b/apps/project/tools/__pycache__/delete_change_key.cpython-38.pyc differ diff --git a/apps/project/tools/__pycache__/keyTools.cpython-313.pyc b/apps/project/tools/__pycache__/keyTools.cpython-313.pyc new file mode 100644 index 0000000..4e3d277 Binary files /dev/null and b/apps/project/tools/__pycache__/keyTools.cpython-313.pyc differ diff --git a/apps/project/tools/__pycache__/keyTools.cpython-38.pyc b/apps/project/tools/__pycache__/keyTools.cpython-38.pyc new file mode 100644 index 0000000..201a312 Binary files /dev/null and b/apps/project/tools/__pycache__/keyTools.cpython-38.pyc differ diff --git a/apps/project/tools/auto_create_data.py b/apps/project/tools/auto_create_data.py new file mode 100644 index 0000000..9e52a8b --- /dev/null +++ b/apps/project/tools/auto_create_data.py @@ -0,0 +1,249 @@ +"""该模块主要自动生成静态分析、代码审查以及文档审查的设计需求、测试项、用例""" +from apps.project.models import ( + Project, + Dut, + Design, + TestDemand, + TestDemandContent, + TestDemandContentStep, + Case, + CaseStep +) + +def auto_create_jt_and_dm(user_name: str, dut_qs: Dut, project_obj: Project): + """传入源代码dut以及测试人员名称username,自动在dut下面生成静态分析和代码审查设计需求、测试项、用例""" + # 先查询dut_qs下面有多少design,以便写里面的key + design_index = dut_qs.rsField.count() + # 1.1.自动创建design静态分析 + jt_design_create_dict = { + 'ident': 'JTFX', + 'name': '静态分析', + 'demandType': '6', + 'description': "依据相关的要求,利用静态分析工具对被测软件全部源程序进行控制流分析、" + "数据流分析进行分析,并统计软件质量度量信息,给出软件源代码检查结果", + 'title': '静态分析', + 'key': ''.join([dut_qs.key, '-', str(design_index)]), + 'level': '2', + 'chapter': '/', + 'project': project_obj, + 'round': dut_qs.round, + 'dut': dut_qs + } + design_index += 1 + new_design_jt: Design = Design.objects.create(**jt_design_create_dict) + # 1.1.1.自动创建demand静态分析 + jt_demand_create_dict = { + 'ident': 'JTFX', + 'name': '静态分析', + 'adequacy': '1)对软件全部源代码进行静态分析;\a' + '2)对度量指标不满足指标要求的模块,应进行专项代码审查;\a' + '3)按照控制流和数据流分析表单,对软件的控制流和数据流进行分析。', + 'priority': '2', + 'testType': '15', + 'testMethod': ["3"], + 'testDesciption': '对被测软件全部源程序进行静态分析,' + '对控制流、数据流进行分析,验证软件是否满足控制流和数据流要求,' + '并依据质量特性需求统计质量度量信息', + 'title': '静态分析', + 'key': ''.join([new_design_jt.key, '-', '0']), + 'level': '3', + 'project': project_obj, + 'round': new_design_jt.round, + 'dut': new_design_jt.dut, + 'design': new_design_jt, + } + new_demand_jt = TestDemand.objects.create(**jt_demand_create_dict) + new_demand_content_obj = TestDemandContent.objects.create(testDemand=new_demand_jt, subName='静态分析') + TestDemandContentStep.objects.create(testDemandContent=new_demand_content_obj, + operation='根据静态分析的审查项和技术要求以及被测软件质量特性需求,' + '编制检查单。使用静态分析工具Testbed和klocwork进行静态分析,对程序进行检查:\a' + '1)使用静态分析工具统计软件质量度量信息;\a' + '2)使用静态分析工具对软件进行规则检查;\a' + '3)使用静态分析工具结合人工分析对控制流和数据流进行分析。') + new_case_jt = Case.objects.create( + ident='JTFX', + name='静态分析', + initialization='已获取全部被测件源代码程序,静态分析工具准备齐备', + premise='提交的代码出自委托方受控库,是委托方正式签署外发的', + summarize='依据委托方的要求进行静态分析,验证软件质量度量和编码规则是否满足军标要求', + designPerson=user_name, + testPerson=user_name, + monitorPerson=user_name, + project=project_obj, + isLeaf=True, + round=new_demand_jt.round, + dut=new_demand_jt.dut, + design=new_demand_jt.design, + test=new_demand_jt, + title='静态分析', + key=''.join([new_demand_jt.key, '-', '0']), + level='4' + ) + CaseStep.objects.create(case=new_case_jt, + operation='使用LDRA TestBed软件和Klocwork软件工具对被测软件' + '全部源程序进行静态分析,并配合人工以及检查单进行分析', + expect='静态审查单全部通过,且源代码满足编码规则和质量度量要求', + result='静态度量结果符合国军标要求,静态分析审查单全部通过', ) + # 1.2.自动创建代码审查design + dm_design_create_dict = { + 'ident': 'DMSC', + 'name': '代码审查', + 'demandType': '6', + 'description': "依据相关要求及软件文档开展针对软件程序代码的代码审查", + 'title': '代码审查', + 'key': ''.join([dut_qs.key, '-', str(design_index)]), + 'level': '2', + 'chapter': '/', + 'project': dut_qs.project, + 'round': dut_qs.round, + 'dut': dut_qs + } + new_design_dm = Design.objects.create(**dm_design_create_dict) + dm_demand_create_dict = { + 'ident': 'DMSC', + 'name': '代码审查', + 'adequacy': '根据代码审查单的审查项,工具审查完成全部代码的审查,人工审查完成关键模块的审查,' + '审查中发现的问题均得到有效处理。', + 'priority': '2', + 'testType': '2', + 'testMethod': ["3"], + 'title': '代码审查', + 'testDesciption': '通过人工审查及借助klocwork、Testbed工具辅助分析的方式开展代码审查,' + '审查代码和设计的一致性、代码执行标准的情况、代码逻辑表达的正确性、' + '代码结构的合理性以及代码的可读性。人工审查中发现的问题,审查人员应及时记录。', + 'key': ''.join([new_design_dm.key, '-', '0']), + 'level': '3', + 'project': project_obj, + 'round': new_design_dm.round, + 'dut': new_design_dm.dut, + 'design': new_design_dm, + } + new_demand_dm = TestDemand.objects.create(**dm_demand_create_dict) + new_content_obj = TestDemandContent.objects.create( + testDemand=new_demand_dm, + subName='代码审查' + ) + TestDemandContentStep.objects.create( + testDemandContent=new_content_obj, + operation='使用klocwork、testbed工具根据本大纲附录B中的代码审查单对代码审查范围内的源代码开展四个方面的审查,' + '人工对所选模块进行如下四个方面的审查:\a' + '1)编程准则检查:依据编程准则的要求,对程序的编码与编程准则进行符合性检查;\a' + '2)代码流程审查:审查程序代码的条件判别、控制流程、数据处理等满足设计要求;\a' + '3)软件结构审查:依据设计文档,审查程序代码的结构设计的合理性,包括程序结构设计和数据结构设计;\a' + '4)需求实现审查:依据需求文档及其他相关资料,审查程序代码的需求层的功能实现是否正确。', + ) + new_case_dm = Case.objects.create( + ident='DMSC', + name='代码审查', + initialization='代码已提交', + premise='提交的代码出自委托方受控库,是委托方正式签署外发的', + summarize='通过人工审查及借助工具辅助分析的方式开展代码审查,审查代码编程准则的符合性、' + '代码流程实现的正确性、代码结构的合理性以及代码实现需求的正确性;人工审查中发现的问题,审查人员应及时记录', + designPerson=user_name, + testPerson=user_name, + monitorPerson=user_name, + project=project_obj, + isLeaf=True, + round=new_demand_dm.round, + dut=new_demand_dm.dut, + design=new_demand_dm.design, + test=new_demand_dm, + title='代码审查', + key=''.join([new_demand_dm.key, '-', '0']), + level='4' + ) + CaseStep.objects.create(case=new_case_dm, + operation='通过人工审查及借助工具辅助分析的方式开展代码审查,审查代码编程准则的符合性、' + '代码流程实现的正确性、代码结构的合理性以及代码实现需求的正确性;' + '人工审查中发现的问题,审查人员应及时记录', + expect='代码设计正确,满足审查单要求,无不符合项', + result='代码设计正确,满足审查单要求,无不符合项', ) + +def auto_create_wd(user_name: str, dut_qs: Dut, project_obj: Project): + """传入用户名、在dut下创建、项目对象,自动创建文档审查的设计需求、测试项、测试用例""" + # 先查询dut_qs下有多少desgin,然后设置key + design_index = dut_qs.rsField.count() + # 1.1.自动创建文档审查design + wd_design_create_dict = { + 'ident': 'WDSC', + 'name': '文档审查', + 'demandType': '6', + 'description': "依据相关要求,逐项检查被测文档的齐套性、完整性、一致性和准确性是否满足要求", + 'title': '文档审查', + 'key': ''.join([dut_qs.key, '-', str(design_index)]), + 'level': '2', + 'chapter': '/', + 'project': project_obj, + 'round': dut_qs.round, + 'dut': dut_qs + } + new_wd_design_obj: Design = Design.objects.create(**wd_design_create_dict) + # 1.1.1.自动创建demand文档审查 + wd_demand_create_dict = { + 'ident': 'WDSC', + 'name': '文档审查', + 'adequacy': '按照审查单审查文档的齐套性、完整性、一致性、准确性。', + 'priority': '1', + 'testType': '8', + 'testMethod': ["3"], + 'title': '文档审查', + 'testDesciption': '本次文档审查包括的内容如下:\a' + '1)软件研制总结报告\a' + '2)软件开发计划\a' + '3)软件运行方案说明\a' + '4)软件接口需求规格说明\a' + '5)软件系统设计说明\a' + '6)软件接口设计说明\a' + '7)软件需求规格说明\a' + '8)软件配置项设计说明\a' + '9)软件测试说明\a' + '10)软件测试报告\a' + '11)产品规格说明\a' + '12)软件版本说明\a' + '13)软件用户手册\a' + '14)固件保障手册', + 'key': ''.join([new_wd_design_obj.key, '-', '0']), + 'level': '3', + 'project': project_obj, + 'round': new_wd_design_obj.round, + 'dut': new_wd_design_obj.dut, + 'design': new_wd_design_obj, + } + new_wd_demand_obj = TestDemand.objects.create(**wd_demand_create_dict) + new_wd_content_obj = TestDemandContent.objects.create(testDemand=new_wd_demand_obj, subName='文档审查') + TestDemandContentStep.objects.create( + testDemandContent=new_wd_content_obj, + operation='根据文档审查表人工逐项检查,检查此项目文档的齐套性、完整性、规范性:\a' + '1)使用人工审查方法,按照附录A中文档齐套性审查单检查需求类、设计类、用户类、测试类文档是否齐套;\a' + '2)使用人工审查方法,按照附录A中需求规格说明审查单对软件需求规格说明逐项检查;\a' + '3)使用人工审查方法,按照附录A中软件设计文档审查单逐项检查。' + ) + new_wd_case_obj = Case.objects.create( + ident='WDSC', + name='文档审查', + initialization='开发方已提交被测文档', + premise='提交的文档出自委托方受控库,是委托方正式签署外发的', + summarize='测试人员阅读文档,依据文档检查单对软件文档进行审查,审查文档内容是否完整、' + '文档描述是否准确、文档格式是否规范、文档是否文文一致', + designPerson=user_name, + testPerson=user_name, + monitorPerson=user_name, + project=project_obj, + isLeaf=True, + round=new_wd_demand_obj.round, + dut=new_wd_demand_obj.dut, + design=new_wd_demand_obj.design, + test=new_wd_demand_obj, + title='文档审查', + key=''.join([new_wd_demand_obj.key, '-', '0']), + level='4' + ) + CaseStep.objects.create(case=new_wd_case_obj, + operation='按照测试需求中文档齐套性检查单检查需求类、设计类、用户类、测试类文档是否齐套', + expect='文档齐套性检查单全部通过,软件文档齐套', + result='文档齐套性检查单全部通过,软件文档齐套', ) + CaseStep.objects.create(case=new_wd_case_obj, + operation='按照测试需求中文档需求规格说明、设计文档等审查单,对相关文档进行审查', + expect='文档满足完整性、准确性、规范性和一致性的要求', + result='文档检查单全部审查通过,文档内容完整、文档描述准确、' + '文档格式规范、文档文文一致', ) diff --git a/apps/project/tools/copyCase.py b/apps/project/tools/copyCase.py new file mode 100644 index 0000000..2d6b878 --- /dev/null +++ b/apps/project/tools/copyCase.py @@ -0,0 +1,163 @@ +from typing import Tuple, Any +from copy import deepcopy +from django.shortcuts import get_object_or_404 +from apps.project.models import Project +from ninja.errors import HttpError + +# 1. case被移动到某测试项下面 +def case_move_to_test(project_id: int, case_key: str, demand_key: str) -> Tuple[str, Any]: + """移动case到某个测试项下面,传入project_id,case的key,测试项的key,renturn -> 元组(旧case的key,新case的key)""" + same_root_flag = False + if '-'.join(case_key.split('-')[:-1]) == demand_key: + same_root_flag = True + # 判断是否移动到自己所属的demand的,直接返回不操作 + if same_root_flag: + raise HttpError(500, message='无法移动到自己所属测试项里面') + project_qs = get_object_or_404(Project, id=project_id) + case = project_qs.pcField.filter(key=case_key).first() + demand_origin = case.test # 未变化之前case对应的测试项 + demand = project_qs.ptField.filter(key=demand_key).first() # 新的demand对象 + case.ident = demand.ident + case.test = demand + case.round = demand.round + case.dut = demand.dut + case.design = demand.design + # 查询被拖拽到的demand有多少用例,用于设置key + case.key = "".join([demand.key, '-', str(demand.tcField.count())]) + case.save() + # 因为移动要删除之前demand的用例,所以需要重新设置key + index = 0 + for c in demand_origin.tcField.all(): + c.key = "".join([demand_origin.key, '-', str(index)]) + c.save() + index += 1 + return case_key, case.key + +# 2.case复制到测试项中 +def case_copy_to_test(project_id: int, case_key: str, demand_key: str) -> Tuple[str, Any]: + # 初始化内容 + project_qs = get_object_or_404(Project, id=project_id) + case = project_qs.pcField.filter(key=case_key).first() + demand = project_qs.ptField.filter(key=demand_key).first() + origin_case = deepcopy(case) + case.id = None + case.ident = demand.ident + case.test = demand + case.round = demand.round + case.dut = demand.dut + case.design = demand.design + case.key = "".join([demand.key, '-', str(demand.tcField.count())]) + case.save() + # 用例步骤也要复制一份过去 + for case_step in origin_case.step.all(): + case_step.id = None + case_step.case = case + case_step.save() + return case_key, case.key + +# 3.(需要拆分)拖拽用例到用例的函数,TODO:必须拆分 +def case_to_case_copy_or_move(project_id: int, drag_key: str, drop_key: str, move: bool, position: int): + """待优化和上面一样:TODO:封装多个函数理清思路""" + project_qs = get_object_or_404(Project, id=project_id) + drag_case = project_qs.pcField.filter(key=drag_key).first() + drag_demand = drag_case.test + drop_case = project_qs.pcField.filter(key=drop_key).first() + drop_demand = drop_case.test + # 判断是否移动到同级 + same_root_flag = False + if drag_key.split('-')[:-2] == drop_key.split('-')[:-2]: + same_root_flag = True + if move: + # 1.移动到同级demand - 只需要改变2个key + if same_root_flag: + case_list = list(drag_demand.tcField.all()) # 这是已经排序的 + case_list.pop(case_list.index(drag_case)) + # 移动出去后,查看现在drop_case索引 + drop_case_index = case_list.index(drop_case) + if position == 0 or position == 1: # 1和0就是往下放,首先drop的位置是不变的 + case_list.insert(drop_case_index + 1, drag_case) + elif position == -1: + case_list.insert(drop_case_index, drag_case) + # 已经排序好了就开始修改key + ca_index = 0 + for ca in case_list: + ca.key = "".join([drag_demand.key, '-', str(ca_index)]) + ca.save() + ca_index += 1 + # 2.移动到非同级demand + else: + drag_case.ident = drop_demand.ident + drag_case.test = drop_demand + drag_case.round = drop_demand.round + drag_case.dut = drop_demand.dut + drag_case.design = drop_demand.design + drag_case.save() # 到这里drag_case已经放入了drop_demand + # 查询所有的drop_demand的所有用例 + drop_case_list = list(drop_demand.tcField.all()) + drop_case_list.pop(drop_case_list.index(drag_case)) + drop_case_index = drop_case_list.index(drop_case) + if position == 0 or position == 1: # 1和0就是往下放,首先drop的位置是不变的 + drop_case_list.insert(drop_case_index + 1, drag_case) + elif position == -1: + drop_case_list.insert(drop_case_index, drag_case) + ca_ind = 0 + for ca in drop_case_list: + ca.key = "".join([drop_demand.key, '-', str(ca_ind)]) + ca.save() + ca_ind += 1 + # 因为移动要删除之前demand的用例,所以需要重新设置key + index = 0 + for c in drag_demand.tcField.all(): + c.key = "".join([drag_demand.key, '-', str(index)]) + c.save() + index += 1 + # 如果是复制 + else: + origin_drag_case = deepcopy(drag_case) # 深度复制被拖拽的case + # 同一个测试项内用例的复制,还要看顺序 + if same_root_flag: + drag_case.id = None + drag_case.save() + # 开始插入到被拖拽case上下 + case_sx_list = list(drag_demand.tcField.all()) + case_sx_list.pop(case_sx_list.index(drag_case)) + drop_case_in = case_sx_list.index(drop_case) + if position == 0 or position == 1: # 1和0就是往下放,首先drop的位置是不变的 + case_sx_list.insert(drop_case_in + 1, drag_case) + elif position == -1: + case_sx_list.insert(drop_case_in, drag_case) + ind = 0 + for cas in case_sx_list: + cas.key = "".join([drag_demand.key, '-', str(ind)]) + cas.save() + ind += 1 + # 根据origin复制步骤 + for c_step in origin_drag_case.step.all(): + c_step.id = None + c_step.case = drag_case # 外键设置为新用例 + c_step.save() + else: + drag_case.id = None + drag_case.ident = drop_demand.ident + drag_case.test = drop_demand + drag_case.round = drop_demand.round + drag_case.dut = drop_demand.dut + drag_case.design = drop_demand.design + drag_case.save() + cases_list = list(drop_demand.tcField.all()) + cases_list.pop(cases_list.index(drag_case)) + drop_case_idx = cases_list.index(drop_case) + if position == 0 or position == 1: # 1和0就是往下放,首先drop的位置是不变的 + cases_list.insert(drop_case_idx + 1, drag_case) + elif position == -1: + cases_list.insert(drop_case_idx, drag_case) + cas_idx = 0 + for cas in cases_list: + cas.key = "".join([drop_demand.key, '-', str(cas_idx)]) + cas.save() + cas_idx += 1 + # 复制用例步骤 + for cs_step in origin_drag_case.step.all(): + cs_step.id = None + cs_step.case = drag_case # 外键设置为新用例 + cs_step.save() diff --git a/apps/project/tools/copyDemand.py b/apps/project/tools/copyDemand.py new file mode 100644 index 0000000..05fa7a9 --- /dev/null +++ b/apps/project/tools/copyDemand.py @@ -0,0 +1,48 @@ +from copy import deepcopy +from django.shortcuts import get_object_or_404 +from apps.project.models import Project + +def demand_copy_to_design(project_id: int, demand_key: str, design_id: int, depth: bool = False): + """注意传入项目id,测试项是key,设计需求是id""" + project_qs = get_object_or_404(Project, id=project_id) + design = project_qs.psField.filter(id=design_id).first() + demand = project_qs.ptField.filter(key=demand_key).first() + # 1.这里先要根据老的demand取出其步骤,并深度复制 + origin_demand = deepcopy(demand) + demand.id = None + demand.title = '(复制)' + demand.title + demand.name = '(复制)' + demand.name + demand.dut = design.dut + demand.round = design.round + demand.design = design + demand.ident += '(复制)' + # 先查询目标design下面有多少个demand,然后key+1 + demand.key = "".join([design.key, '-', str(design.dtField.count())]) + demand.save() + for content_obj in origin_demand.testQField.all(): + content_origin = deepcopy(content_obj) + content_obj.id = None + content_obj.testDemand = demand + content_obj.save() + for step_obj in content_origin.testStepField.all(): + step_obj.testDemandContent = content_obj + step_obj.id = None + step_obj.save() + # 2.如果depth=True,则复制用例过去 + if depth: + for case in origin_demand.tcField.all(): + origin_case = deepcopy(case) + case.id = None + case.round = demand.round + case.dut = demand.dut + case.design = demand.design + case.test = demand + # 处理新case的key + case_key_lastkey = case.key.split("-")[-1] + case.key = "".join([demand.key, '-', case_key_lastkey]) + case.save() + for case_step in origin_case.step.all(): + case_step.id = None + case_step.case = case + case_step.save() + return demand.key diff --git a/apps/project/tools/delete_change_key.py b/apps/project/tools/delete_change_key.py new file mode 100644 index 0000000..e91a9f9 --- /dev/null +++ b/apps/project/tools/delete_change_key.py @@ -0,0 +1,82 @@ +""" +本模块主要解决删除父级节点后,其他同级节点的key会重排,重排后子节点没有重排问题 +""" +from apps.project.models import Round, Dut, Design, TestDemand + +# 0.round删除后,重新排子节点 +def round_delete_sub_node_key(round: Round): + for dut in round.rdField.all(): + remain_dut_key: str = dut.key.split('-')[-1] + dut_key_list = [round.key, remain_dut_key] + dut.key = '-'.join(dut_key_list) + dut.save() + for design in dut.rsField.all(): + # 取出design的key最后一位,该位是正确的 + remain_key: str = design.key.split('-')[-1] + key_list = dut.key.split('-') + key_list.append(remain_key) + design.key = "-".join(key_list) + design.save() + for demand in design.dtField.all(): + remain_demand_key = demand.key.split('-')[-1] + demand_key_list = design.key.split('-') + demand_key_list.append(remain_demand_key) + demand.key = '-'.join(demand_key_list) + demand.save() + for case in demand.tcField.all(): + remain_case_key = case.key.split('-')[-1] + case_key_list = demand.key.split('-') + case_key_list.append(remain_case_key) + case.key = '-'.join(case_key_list) + case.save() + +# 1.dut删除后,重排同级子节点 +def dut_delete_sub_node_key(dut: Dut): + """ + 传入一个删除同级dut后,遍历的dut对象,这里dut的key已经正确,依据该key遍历子节点修改key + :param dut: dut对象 + :return: None + """ + for design in dut.rsField.all(): + # 取出design的key最后一位,该位是正确的 + remain_key: str = design.key.split('-')[-1] + key_list = dut.key.split('-') + key_list.append(remain_key) + design.key = "-".join(key_list) + design.save() + for demand in design.dtField.all(): + remain_demand_key = demand.key.split('-')[-1] + demand_key_list = design.key.split('-') + demand_key_list.append(remain_demand_key) + demand.key = '-'.join(demand_key_list) + demand.save() + for case in demand.tcField.all(): + remain_case_key = case.key.split('-')[-1] + case_key_list = demand.key.split('-') + case_key_list.append(remain_case_key) + case.key = '-'.join(case_key_list) + case.save() + +# 2.design删除后,重排同级子节点 +def design_delete_sub_node_key(design: Design): + for demand in design.dtField.all(): + remain_demand_key = demand.key.split('-')[-1] + demand_key_list = design.key.split('-') + demand_key_list.append(remain_demand_key) + demand.key = '-'.join(demand_key_list) + demand.save() + for case in demand.tcField.all(): + remain_case_key = case.key.split('-')[-1] + case_key_list = demand.key.split('-') + case_key_list.append(remain_case_key) + case.key = '-'.join(case_key_list) + case.save() + +# 3.demand删除后,重排case的key顺序 +def demand_delete_sub_node_key(demand: TestDemand): + for case in demand.tcField.all(): + remain_case_key = case.key.split('-')[-1] + case_key_list = demand.key.split('-') + case_key_list.append(remain_case_key) + case.key = '-'.join(case_key_list) + case.save() diff --git a/apps/project/tools/keyTools.py b/apps/project/tools/keyTools.py new file mode 100644 index 0000000..615b605 --- /dev/null +++ b/apps/project/tools/keyTools.py @@ -0,0 +1,342 @@ +"""该模块:选择节点新创建一个轮次,数据为选中节点""" +import re +from copy import deepcopy +from apps.project.models import Project, Round, Dut, Design, TestDemand, Case + +class TreeKey(object): + """生成一个dict,展示复制的节点tree_dict是解析后的属性名称""" + """递归写法: + def slide(key_list): + tree_dict = {} + for key in key_list: + split_list = key.split('-', 1) + if split_list[0] not in tree_dict: + tree_dict[split_list[0]] = [] + if len(split_list) > 1: + tree_dict[split_list[0]].append(split_list[1]) + else: + tree_dict[split_list[0]] = 'all' + else: + if isinstance(tree_dict[split_list[0]], list): + if len(split_list) > 1: + tree_dict[split_list[0]].append(split_list[1]) + else: + tree_dict[split_list[0]] = 'all' + + for k, v in tree_dict.items(): + if isinstance(v, list): + tree_dict[k] = slide(v) + return tree_dict + + key_l = ['0-1-1-0', '0-1-2-1', '0-2-0-1'] + print(slide(key_l)) + """ + + def __init__(self, key_list): + self.tree_dict = {} + if key_list is None: + self.key_list = [] + return + self.key_list = deepcopy(key_list) + res_dict = self.list_dict(self.key_list) + for k, v in res_dict.items(): + if isinstance(v, list): + res_dict[k] = self.list_dict(v) + for k1, v1 in res_dict[k].items(): + if isinstance(v1, list): + res_dict[k][k1] = self.list_dict(v1) + for k2, v2 in res_dict[k][k1].items(): + if isinstance(v2, list): + res_dict[k][k1][k2] = self.list_dict(v2) + for k3, v3 in res_dict[k][k1][k2].items(): + if isinstance(v3, list): + res_dict[k][k1][k2][k3] = self.list_dict(v3) + self.tree_dict = res_dict + + def list_dict(self, key_list) -> dict: + tree_dict = {} + for key in key_list: + split_list = key.split('-', 1) + if len(split_list) == 1: + tree_dict[split_list[0]] = 'all' + else: + if split_list[0] not in tree_dict: + tree_dict[split_list[0]] = [] + if isinstance(tree_dict[split_list[0]], list): + tree_dict[split_list[0]].append(split_list[1]) + return tree_dict + + def copy_tree(self, round_count: int, project_obj: Project): + for round_k, round_v in self.tree_dict.items(): + # 1.这是轮次层级,如果为'all',则复制整个轮次到第二轮 + if round_v == 'all': + round_obj: Round = Round.objects.filter(key=round_k, project=project_obj).first() + round_obj.remark = f"第{round_count + 1}轮测试" + round_obj.ident = round_obj.ident.replace(round_obj.ident[-1], f'{round_count + 1}') + round_obj.name = f"第{round_count + 1}轮测试" + round_obj.title = f"第{round_count + 1}轮测试" + round_obj.key = f"{round_count}" + round_obj.id = None + round_obj.save() + round_origin: Round = Round.objects.filter(key=round_k, project=project_obj).first() + dut_qs = round_origin.rdField.all() + for dut in dut_qs: + dut_origin = deepcopy(dut) + dut.ident = re.sub(r"-R\d+-", f'-R{round_count + 1}-', dut.ident) + dut.key = f"{round_count}-{dut.key.split('-')[-1]}" + dut.round = round_obj + dut.id = None + dut.save() + design_qs = dut_origin.rsField.all() + for i, design in enumerate(design_qs): + design_origin = deepcopy(design) + design.key = dut.key + f"-{i}" + design.dut = dut + design.round = round_obj + design.id = None + design.save() + demand_qs = design_origin.dtField.all() + for j, demand in enumerate(demand_qs): + demand_origin = deepcopy(demand) + # 需要将demand_item也复制一份 + demand.key = design.key + f"-{j}" + demand.design = design + demand.dut = dut + demand.round = round_obj + demand.id = None + demand.save() + for content_obj in demand_origin.testQField.all(): + content_origin = deepcopy(content_obj) + content_obj.testDemand = demand + content_obj.id = None + content_obj.save() + # 需要将DemandContentStep也要复制一份 + for step_obj in content_origin.testStepField.all(): + step_obj.testDemandContent = content_obj + step_obj.id = None + step_obj.save() + case_qs = demand_origin.tcField.all() + for k, case in enumerate(case_qs): + case_origin = deepcopy(case) + case.key = demand.key + f"-{k}" + case.test = demand + case.design = design + case.dut = dut + case.round = round_obj + case.id = None + case.save() + for step_obj in case_origin.step.all(): + step_obj.case = case + step_obj.id = None + step_obj.save() + # 2.如果不是all,下面有值 + if isinstance(round_v, dict): + # 2.1 如果字典,则先要创建轮次 + round_obj: Round = Round.objects.filter(key=round_k, project=project_obj).first() + round_obj.remark = f"第{round_count + 1}轮测试" + round_obj.ident = round_obj.ident.replace(round_obj.ident[-1], f'{round_count + 1}') + round_obj.name = f"第{round_count + 1}轮测试" + round_obj.title = f"第{round_count + 1}轮测试" + round_obj.key = f"{round_count}" + round_obj.id = None + round_obj.save() + dut_index = 1 + for dut_k, dut_v in round_v.items(): + # 2.1 如果dut_v是all,那么从该dut整体复制 + dut_key_origin = round_k + "-" + dut_k + if dut_v == 'all': + # 查询原始dut + dut_obj = Dut.objects.filter(key=dut_key_origin, project=project_obj).first() + dut_obj.ident = dut_obj.ident[:11] + str(dut_index) + dut_obj.ident = re.sub(r"-R\d+-", f'-R{round_count + 1}-', dut_obj.ident) + dut_obj.key = f"{round_count}-{dut_index - 1}" + dut_obj.round = round_obj + dut_obj.id = None + dut_obj.save() + dut_origin = Dut.objects.filter(key=dut_key_origin, project=project_obj).first() + design_qs = dut_origin.rsField.all() + for i, design in enumerate(design_qs): + design_origin = deepcopy(design) + design.key = dut_obj.key + f"-{i}" + design.dut = dut_obj + design.round = round_obj + design.id = None + design.save() + demand_qs = design_origin.dtField.all() + for j, demand in enumerate(demand_qs): + demand_origin = deepcopy(demand) + # 需要将demand_item也复制一份 + demand.key = design.key + f"-{j}" + demand.design = design + demand.dut = dut_obj + demand.round = round_obj + demand.id = None + demand.save() + for content_obj in demand_origin.testQField.all(): + content_origin = deepcopy(content_obj) + content_obj.testDemand = demand + content_obj.id = None + content_obj.save() + # 需要将DemandContentStep也要复制一份 + for step_obj in content_origin.testStepField.all(): + step_obj.testDemandContent = content_obj + step_obj.id = None + step_obj.save() + case_qs = demand_origin.tcField.all() + for k, case in enumerate(case_qs): + case_origin = deepcopy(case) + case.key = demand.key + f"-{k}" + case.test = demand + case.design = design + case.dut = dut_obj + case.round = round_obj + case.id = None + case.save() + for step_obj in case_origin.step.all(): + step_obj.case = case + step_obj.id = None + step_obj.save() + + if isinstance(dut_v, dict): + dut_obj = Dut.objects.filter(key=dut_key_origin, project=project_obj).first() + dut_obj.ident = dut_obj.ident[:11] + str(dut_index) + dut_obj.ident = re.sub(r"-R\d+-", f'-R{round_count + 1}-', dut_obj.ident) + dut_obj.key = f"{round_count}-{dut_index - 1}" + dut_obj.round = round_obj + dut_obj.id = None + dut_obj.save() + design_index = 1 + for design_k, design_v in dut_v.items(): + design_key_origin = round_k + "-" + dut_k + "-" + design_k + if design_v == 'all': + design_obj = Design.objects.filter(key=design_key_origin, project=project_obj).first() + design_obj.key = f"{dut_obj.key}-{design_index - 1}" + design_obj.dut = dut_obj + design_obj.round = round_obj + design_obj.id = None + design_obj.save() + design_origin = Design.objects.filter(key=design_key_origin, + project=project_obj).first() + demand_qs = design_origin.dtField.all() + for j, demand in enumerate(demand_qs): + demand_origin = deepcopy(demand) + # 需要将demand_item也复制一份 + demand.key = design_obj.key + f"-{j}" + demand.design = design_obj + demand.dut = dut_obj + demand.round = round_obj + demand.id = None + demand.save() + for content_obj in demand_origin.testQField.all(): + content_origin = deepcopy(content_obj) + content_obj.testDemand = demand + content_obj.id = None + content_obj.save() + for step_obj in content_origin.testStepField.all(): + step_obj.testDemandContent = content_obj + step_obj.id = None + step_obj.save() + case_qs = demand_origin.tcField.all() + for k, case in enumerate(case_qs): + case_origin = deepcopy(case) + case.key = demand.key + f"-{k}" + case.test = demand + case.design = design_obj + case.dut = dut_obj + case.round = round_obj + case.id = None + case.save() + for step_obj in case_origin.step.all(): + step_obj.case = case + step_obj.id = None + step_obj.save() + + if isinstance(design_v, dict): + design_obj = Design.objects.filter(key=design_key_origin, project=project_obj).first() + design_obj.key = f"{round_count}-{dut_index - 1}-{design_index - 1}" + design_obj.dut = dut_obj + design_obj.round = round_obj + design_obj.id = None + design_obj.save() + demand_index = 1 + for demand_k, demand_v in design_v.items(): + demand_key_origin = round_k + "-" + dut_k + "-" + design_k + "-" + demand_k + if demand_v == 'all': + demand_obj = TestDemand.objects.filter(key=demand_key_origin, + project=project_obj).first() + demand_obj.key = f"{design_obj.key}-{demand_index - 1}" + demand_obj.design = design_obj + demand_obj.dut = dut_obj + demand_obj.round = round_obj + demand_obj.id = None + demand_obj.save() + demand_origin = TestDemand.objects.filter(key=demand_key_origin, + project=project_obj).first() + for content_obj in demand_origin.testQField.all(): + content_origin = deepcopy(content_obj) + content_obj.testDemand = demand_obj + content_obj.id = None + content_obj.save() + for step_obj in content_origin.testStepField.all(): + step_obj.testDemandContent = content_obj + step_obj.id = None + step_obj.save() + case_qs = demand_origin.tcField.all() + for k, case in enumerate(case_qs): + case_origin = deepcopy(case) + case.key = demand_obj.key + f"-{k}" + case.test = demand_obj + case.design = design_obj + case.dut = dut_obj + case.round = round_obj + case.id = None + case.save() + for step_obj in case_origin.step.all(): + step_obj.case = case + step_obj.id = None + step_obj.save() + + if isinstance(demand_v, dict): + demand_obj = TestDemand.objects.filter(key=demand_key_origin, + project=project_obj).first() + demand_obj.key = f"{round_count}-{dut_index - 1}-{design_index - 1}-{demand_index - 1}" + demand_obj.design = design_obj + demand_obj.dut = dut_obj + demand_obj.round = round_obj + demand_obj.id = None + demand_obj.save() + demand_origin = TestDemand.objects.filter(key=demand_key_origin, + project=project_obj).first() + for content_obj in demand_origin.testQField.all(): + content_origin = deepcopy(content_obj) + content_obj.testDemand = demand_obj + content_obj.id = None + content_obj.save() + for step_obj in content_origin.testStepField.all(): + step_obj.testDemandContent = content_obj + step_obj.id = None + step_obj.save() + case_index = 1 + for case_k, case_v in demand_v.items(): + case_key_origin = round_k + "-" + dut_k + "-" + design_k + "-" + demand_k + "-" + case_k + if case_v == 'all': + case_obj = Case.objects.filter(key=case_key_origin, + project=project_obj).first() + case_obj.key = f"{demand_obj.key}-{case_index}" + case_obj.test = demand_obj + case_obj.dut = dut_obj + case_obj.design = design_obj + case_obj.round = round_obj + case_obj.id = None + case_obj.save() + case_origin = Case.objects.filter(key=case_key_origin, + project=project_obj).first() + for step_obj in case_origin.step.all(): + step_obj.case = case_obj + step_obj.id = None + step_obj.save() + + case_index += 1 + demand_index += 1 + design_index += 1 + dut_index += 1 diff --git a/apps/project/views.py b/apps/project/views.py new file mode 100644 index 0000000..c60c790 --- /dev/null +++ b/apps/project/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/apps/system/__init__.py b/apps/system/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/system/__pycache__/__init__.cpython-313.pyc b/apps/system/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..215a5b3 Binary files /dev/null and b/apps/system/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/system/__pycache__/__init__.cpython-38.pyc b/apps/system/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..05cd7dd Binary files /dev/null and b/apps/system/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/system/__pycache__/admin.cpython-313.pyc b/apps/system/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000..8baf304 Binary files /dev/null and b/apps/system/__pycache__/admin.cpython-313.pyc differ diff --git a/apps/system/__pycache__/admin.cpython-38.pyc b/apps/system/__pycache__/admin.cpython-38.pyc new file mode 100644 index 0000000..d413baf Binary files /dev/null and b/apps/system/__pycache__/admin.cpython-38.pyc differ diff --git a/apps/system/__pycache__/apps.cpython-313.pyc b/apps/system/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000..2098e7a Binary files /dev/null and b/apps/system/__pycache__/apps.cpython-313.pyc differ diff --git a/apps/system/__pycache__/apps.cpython-38.pyc b/apps/system/__pycache__/apps.cpython-38.pyc new file mode 100644 index 0000000..8270c63 Binary files /dev/null and b/apps/system/__pycache__/apps.cpython-38.pyc differ diff --git a/apps/system/__pycache__/models.cpython-313.pyc b/apps/system/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..9cafc4a Binary files /dev/null and b/apps/system/__pycache__/models.cpython-313.pyc differ diff --git a/apps/system/__pycache__/models.cpython-38.pyc b/apps/system/__pycache__/models.cpython-38.pyc new file mode 100644 index 0000000..fb64634 Binary files /dev/null and b/apps/system/__pycache__/models.cpython-38.pyc differ diff --git a/apps/system/admin.py b/apps/system/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/system/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/system/apps.py b/apps/system/apps.py new file mode 100644 index 0000000..c835446 --- /dev/null +++ b/apps/system/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class SystemConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.system' diff --git a/apps/system/controllers/__init__.py b/apps/system/controllers/__init__.py new file mode 100644 index 0000000..eae7f56 --- /dev/null +++ b/apps/system/controllers/__init__.py @@ -0,0 +1,4 @@ +# 导入下面的控制器 +from apps.system.controllers.log import LogController +# 暴露控制器变量 +__all__ = ['LogController'] diff --git a/apps/system/controllers/__pycache__/__init__.cpython-313.pyc b/apps/system/controllers/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..81c35d7 Binary files /dev/null and b/apps/system/controllers/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/system/controllers/__pycache__/__init__.cpython-38.pyc b/apps/system/controllers/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..aec1011 Binary files /dev/null and b/apps/system/controllers/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/system/controllers/__pycache__/log.cpython-313.pyc b/apps/system/controllers/__pycache__/log.cpython-313.pyc new file mode 100644 index 0000000..26b937e Binary files /dev/null and b/apps/system/controllers/__pycache__/log.cpython-313.pyc differ diff --git a/apps/system/controllers/__pycache__/log.cpython-38.pyc b/apps/system/controllers/__pycache__/log.cpython-38.pyc new file mode 100644 index 0000000..da6ee01 Binary files /dev/null and b/apps/system/controllers/__pycache__/log.cpython-38.pyc differ diff --git a/apps/system/controllers/log.py b/apps/system/controllers/log.py new file mode 100644 index 0000000..3aaf87a --- /dev/null +++ b/apps/system/controllers/log.py @@ -0,0 +1,78 @@ +from typing import List +from datetime import datetime, timedelta +from ninja_extra import api_controller, ControllerBase, route +from ninja_jwt.authentication import JWTAuth +from ninja_extra.permissions import IsAuthenticated +from django.db.transaction import atomic +from apps.system.models import LoginLog, OperationLog +from utils.chen_pagination import MyPagination +from ninja.pagination import paginate +from ninja import Query +from utils.chen_response import ChenResponse +# 导入schemas +from apps.system.schemas.log import LogOutSchema, OperationLogOutSchema, LoginLogOutSchema, DeleteInputSchema + +@api_controller("/system/log", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['日志相关']) +class LogController(ControllerBase): + @route.get('/list', response=List[LogOutSchema]) + @atomic + def get_login_log(self): + """获取当前用户的登录日志""" + user = self.context.request.user + log_qs = LoginLog.objects.filter(creator=user) + logs = log_qs[:5] + return logs + + @route.get('/operations', response=List[OperationLogOutSchema]) + @atomic + def get_operations(self): + """获取当前用户操作日志""" + user = self.context.request.user + log_qs = OperationLog.objects.filter(creator=user) + return log_qs[:5] + + # 操作日志:查询 + @route.get('/operationsPagination', response=List[OperationLogOutSchema]) + @atomic + @paginate(MyPagination) + def get_operations_pagination(self, request_username: str = None): + logs = OperationLog.objects.all() + if request_username is None: + request_username = '' + qs = logs.filter(request_username__icontains=request_username) + return qs + + # 操作日志:根据请求参数天数删除 + @route.get('/operationsDel') + @atomic + def operation_delete_log(self, payload: DeleteInputSchema = Query(...)): + time = datetime.now() - timedelta(days=payload.day) + log_qs = OperationLog.objects.filter(create_datetime__lt=time) + log_qs.delete() + if payload.day > 0: + return ChenResponse(message=f'删除{payload.day}天前数据成功') + else: + return ChenResponse(message='全部日志删除成功') + + # 登录日志:查询 + @route.get('/loginLogsList', response=List[LoginLogOutSchema]) + @atomic + @paginate(MyPagination) + def get_login_logs(self, username: str = None): + logs = LoginLog.objects.all() + if username is None: + username = '' + qs = logs.filter(username__icontains=username) + return qs + + # 登录日志:根据请求参数天数删除 + @route.get('/loginLogsDel') + @atomic + def login_logs_delete(self, payload: DeleteInputSchema = Query(...)): + time = datetime.now() - timedelta(days=payload.day) + log_qs = LoginLog.objects.filter(create_datetime__lt=time) + log_qs.delete() + if payload.day > 0: + return ChenResponse(message=f'删除{payload.day}天前数据成功') + else: + return ChenResponse(message='全部日志删除成功') diff --git a/apps/system/migrations/0001_initial.py b/apps/system/migrations/0001_initial.py new file mode 100644 index 0000000..642c4b5 --- /dev/null +++ b/apps/system/migrations/0001_initial.py @@ -0,0 +1,72 @@ +# Generated by Django 4.2.13 on 2024-07-03 10:38 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='OperationLog', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('request_username', models.CharField(blank=True, help_text='请求用户', max_length=50, null=True, verbose_name='请求用户')), + ('request_modular', models.CharField(blank=True, help_text='请求模块', max_length=64, null=True, verbose_name='请求模块')), + ('request_path', models.CharField(blank=True, help_text='请求地址', max_length=400, null=True, verbose_name='请求地址')), + ('request_body', models.TextField(blank=True, help_text='请求参数', null=True, verbose_name='请求参数')), + ('request_method', models.CharField(blank=True, help_text='请求方式', max_length=8, null=True, verbose_name='请求方式')), + ('request_msg', models.TextField(blank=True, help_text='操作说明', null=True, verbose_name='操作说明')), + ('request_ip', models.CharField(blank=True, help_text='请求ip地址', max_length=32, null=True, verbose_name='请求ip地址')), + ('request_browser', models.CharField(blank=True, help_text='请求浏览器', max_length=64, null=True, verbose_name='请求浏览器')), + ('response_code', models.CharField(blank=True, help_text='响应状态码', max_length=32, null=True, verbose_name='响应状态码')), + ('request_os', models.CharField(blank=True, help_text='操作系统', max_length=64, null=True, verbose_name='操作系统')), + ('json_result', models.TextField(blank=True, help_text='返回信息', null=True, verbose_name='返回信息')), + ('status', models.BooleanField(default=False, help_text='响应状态', verbose_name='响应状态')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '操作日志', + 'verbose_name_plural': '操作日志', + 'db_table': 'system_operation_log', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='LoginLog', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('username', models.CharField(blank=True, help_text='登录用户名', max_length=32, null=True, verbose_name='登录用户名')), + ('ip', models.CharField(blank=True, help_text='登录ip', max_length=32, null=True, verbose_name='登录ip')), + ('agent', models.TextField(blank=True, help_text='agent信息', null=True, verbose_name='agent信息')), + ('browser', models.CharField(blank=True, help_text='浏览器名', max_length=200, null=True, verbose_name='浏览器名')), + ('os', models.CharField(blank=True, help_text='操作系统', max_length=200, null=True, verbose_name='操作系统')), + ('country', models.CharField(blank=True, help_text='国家', max_length=50, null=True, verbose_name='国家')), + ('login_type', models.IntegerField(choices=[(1, '普通登录')], default=1, help_text='登录类型', verbose_name='登录类型')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '登录日志', + 'verbose_name_plural': '登录日志', + 'db_table': 'system_login_log', + 'ordering': ('-create_datetime',), + }, + ), + ] diff --git a/apps/system/migrations/__init__.py b/apps/system/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/system/migrations/__pycache__/0001_initial.cpython-313.pyc b/apps/system/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000..10b0336 Binary files /dev/null and b/apps/system/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/apps/system/migrations/__pycache__/0001_initial.cpython-38.pyc b/apps/system/migrations/__pycache__/0001_initial.cpython-38.pyc new file mode 100644 index 0000000..8f15b77 Binary files /dev/null and b/apps/system/migrations/__pycache__/0001_initial.cpython-38.pyc differ diff --git a/apps/system/migrations/__pycache__/__init__.cpython-313.pyc b/apps/system/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..a844745 Binary files /dev/null and b/apps/system/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/system/migrations/__pycache__/__init__.cpython-38.pyc b/apps/system/migrations/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..30a6824 Binary files /dev/null and b/apps/system/migrations/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/system/models.py b/apps/system/models.py new file mode 100644 index 0000000..a621b2a --- /dev/null +++ b/apps/system/models.py @@ -0,0 +1,66 @@ +from django.db import models +from cdtestplant_v1 import settings + +class LoginLog(models.Model): + id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id") + remark = models.CharField(max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述") + creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True, + verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL, db_constraint=False) + modifier = models.CharField(max_length=255, null=True, blank=True, help_text="修改人", verbose_name="修改人") + LOGIN_TYPE_CHOICES = ( + (1, '普通登录'), + ) + username = models.CharField(max_length=32, verbose_name="登录用户名", null=True, blank=True, help_text="登录用户名") + ip = models.CharField(max_length=32, verbose_name="登录ip", null=True, blank=True, help_text="登录ip") + agent = models.TextField(verbose_name="agent信息", null=True, blank=True, help_text="agent信息") + browser = models.CharField(max_length=200, verbose_name="浏览器名", null=True, blank=True, help_text="浏览器名") + os = models.CharField(max_length=200, verbose_name="操作系统", null=True, blank=True, help_text="操作系统") + country = models.CharField(max_length=50, verbose_name="国家", null=True, blank=True, help_text="国家") + login_type = models.IntegerField(default=1, choices=LOGIN_TYPE_CHOICES, verbose_name="登录类型", + help_text="登录类型") + update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间") + create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间", + verbose_name="创建时间") + sort = models.IntegerField(default=1, null=True, blank=True, verbose_name="显示排序", help_text="显示排序") + + class Meta: + db_table = 'system_login_log' + verbose_name = '登录日志' + verbose_name_plural = verbose_name + ordering = ('-create_datetime',) + +class OperationLog(models.Model): + id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id") + remark = models.CharField(max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述") + creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True, + verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL, db_constraint=False) + modifier = models.CharField(max_length=255, null=True, blank=True, help_text="修改人", verbose_name="修改人") + request_username = models.CharField(max_length=50, blank=True, null=True, verbose_name="请求用户", + help_text="请求用户") + request_modular = models.CharField(max_length=64, verbose_name="请求模块", null=True, blank=True, + help_text="请求模块") + request_path = models.CharField(max_length=400, verbose_name="请求地址", null=True, blank=True, + help_text="请求地址") + request_body = models.TextField(verbose_name="请求参数", null=True, blank=True, help_text="请求参数") + request_method = models.CharField(max_length=8, verbose_name="请求方式", null=True, blank=True, + help_text="请求方式") + request_msg = models.TextField(verbose_name="操作说明", null=True, blank=True, help_text="操作说明") + request_ip = models.CharField(max_length=32, verbose_name="请求ip地址", null=True, blank=True, + help_text="请求ip地址") + request_browser = models.CharField(max_length=64, verbose_name="请求浏览器", null=True, blank=True, + help_text="请求浏览器") + response_code = models.CharField(max_length=32, verbose_name="响应状态码", null=True, blank=True, + help_text="响应状态码") + request_os = models.CharField(max_length=64, verbose_name="操作系统", null=True, blank=True, help_text="操作系统") + json_result = models.TextField(verbose_name="返回信息", null=True, blank=True, help_text="返回信息") + status = models.BooleanField(default=False, verbose_name="响应状态", help_text="响应状态") + update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间") + create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间", + verbose_name="创建时间") + sort = models.IntegerField(default=1, null=True, blank=True, verbose_name="显示排序", help_text="显示排序") + + class Meta: + db_table = 'system_operation_log' + verbose_name = '操作日志' + verbose_name_plural = verbose_name + ordering = ('-create_datetime',) diff --git a/apps/system/schemas/__init__.py b/apps/system/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/system/schemas/__pycache__/__init__.cpython-313.pyc b/apps/system/schemas/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..5e7256c Binary files /dev/null and b/apps/system/schemas/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/system/schemas/__pycache__/__init__.cpython-38.pyc b/apps/system/schemas/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..b7d66e2 Binary files /dev/null and b/apps/system/schemas/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/system/schemas/__pycache__/log.cpython-313.pyc b/apps/system/schemas/__pycache__/log.cpython-313.pyc new file mode 100644 index 0000000..8b99e74 Binary files /dev/null and b/apps/system/schemas/__pycache__/log.cpython-313.pyc differ diff --git a/apps/system/schemas/__pycache__/log.cpython-38.pyc b/apps/system/schemas/__pycache__/log.cpython-38.pyc new file mode 100644 index 0000000..e8de100 Binary files /dev/null and b/apps/system/schemas/__pycache__/log.cpython-38.pyc differ diff --git a/apps/system/schemas/log.py b/apps/system/schemas/log.py new file mode 100644 index 0000000..674da7b --- /dev/null +++ b/apps/system/schemas/log.py @@ -0,0 +1,24 @@ +from ninja import ModelSchema, Field, Schema +from apps.system.models import LoginLog, OperationLog + +# 1.登录日志输出schema - 前五条 +class LogOutSchema(ModelSchema): + class Meta: + model = LoginLog + fields = ['id', 'username', 'agent', 'ip', 'browser', 'os', 'create_datetime'] + +# 2.操作日志输出shcema - 前五条 +class OperationLogOutSchema(ModelSchema): + class Meta: + model = OperationLog + exclude = ['remark', 'modifier', 'request_modular', 'request_msg', 'sort', 'creator'] + +# 3.登录日志输出schema +class LoginLogOutSchema(ModelSchema): + class Meta: + model = LoginLog + exclude = ['remark', 'modifier', 'country', 'sort', 'creator'] + +# 4.删除日志的Schema +class DeleteInputSchema(Schema): + day: int = Field(7, ge=0) diff --git a/apps/system/tests.py b/apps/system/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/system/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/system/views.py b/apps/system/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/apps/system/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/apps/user/__init__.py b/apps/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/user/__pycache__/__init__.cpython-313.pyc b/apps/user/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..f8e836e Binary files /dev/null and b/apps/user/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/user/__pycache__/__init__.cpython-38.pyc b/apps/user/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..e36c9b5 Binary files /dev/null and b/apps/user/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/user/__pycache__/admin.cpython-313.pyc b/apps/user/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000..0346865 Binary files /dev/null and b/apps/user/__pycache__/admin.cpython-313.pyc differ diff --git a/apps/user/__pycache__/admin.cpython-38.pyc b/apps/user/__pycache__/admin.cpython-38.pyc new file mode 100644 index 0000000..a1478cb Binary files /dev/null and b/apps/user/__pycache__/admin.cpython-38.pyc differ diff --git a/apps/user/__pycache__/apps.cpython-313.pyc b/apps/user/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000..0997786 Binary files /dev/null and b/apps/user/__pycache__/apps.cpython-313.pyc differ diff --git a/apps/user/__pycache__/apps.cpython-38.pyc b/apps/user/__pycache__/apps.cpython-38.pyc new file mode 100644 index 0000000..5c4e46d Binary files /dev/null and b/apps/user/__pycache__/apps.cpython-38.pyc differ diff --git a/apps/user/__pycache__/controllers.cpython-313.pyc b/apps/user/__pycache__/controllers.cpython-313.pyc new file mode 100644 index 0000000..c87d5a8 Binary files /dev/null and b/apps/user/__pycache__/controllers.cpython-313.pyc differ diff --git a/apps/user/__pycache__/controllers.cpython-38.pyc b/apps/user/__pycache__/controllers.cpython-38.pyc new file mode 100644 index 0000000..c37bf0e Binary files /dev/null and b/apps/user/__pycache__/controllers.cpython-38.pyc differ diff --git a/apps/user/__pycache__/models.cpython-313.pyc b/apps/user/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..81756eb Binary files /dev/null and b/apps/user/__pycache__/models.cpython-313.pyc differ diff --git a/apps/user/__pycache__/models.cpython-38.pyc b/apps/user/__pycache__/models.cpython-38.pyc new file mode 100644 index 0000000..0308602 Binary files /dev/null and b/apps/user/__pycache__/models.cpython-38.pyc differ diff --git a/apps/user/__pycache__/schema.cpython-313.pyc b/apps/user/__pycache__/schema.cpython-313.pyc new file mode 100644 index 0000000..b941ab8 Binary files /dev/null and b/apps/user/__pycache__/schema.cpython-313.pyc differ diff --git a/apps/user/__pycache__/schema.cpython-38.pyc b/apps/user/__pycache__/schema.cpython-38.pyc new file mode 100644 index 0000000..0febf70 Binary files /dev/null and b/apps/user/__pycache__/schema.cpython-38.pyc differ diff --git a/apps/user/admin.py b/apps/user/admin.py new file mode 100644 index 0000000..2422f9c --- /dev/null +++ b/apps/user/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from apps.user.models import Users + +# 在这里将数据库表注册到admin中 +admin.site.register([Users]) + diff --git a/apps/user/apps.py b/apps/user/apps.py new file mode 100644 index 0000000..8fb1779 --- /dev/null +++ b/apps/user/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class UserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.user' diff --git a/apps/user/controllers.py b/apps/user/controllers.py new file mode 100644 index 0000000..b6b5b3c --- /dev/null +++ b/apps/user/controllers.py @@ -0,0 +1,182 @@ +from django.contrib.auth import get_user_model +from datetime import datetime, timedelta, timezone +from ninja_extra import api_controller, ControllerBase, route +from ninja.pagination import paginate +from utils.chen_pagination import MyPagination +from ninja_extra.permissions import IsAuthenticated, IsAdminUser +from ninja import Query +from django.db import transaction +from django.contrib.auth import authenticate +from django.shortcuts import get_object_or_404 +from ninja_jwt.tokens import RefreshToken +from ninja_jwt.authentication import JWTAuth +from ninja_jwt.controller import TokenObtainPairController +from ninja_jwt import schema +from typing import List +from utils.chen_response import ChenResponse +from apps.user.schema import UserInfoOutSchema, CreateUserSchema, CreateUserOutSchema, UserRetrieveInputSchema, \ + UserRetrieveOutSchema, UpdateDeleteUserSchema, UpdateDeleteUserOutSchema, DeleteUserSchema, LogOutSchema, \ + LogInputSchema, LogDeleteInSchema, AdminModifyPasswordSchema +from apps.user.models import TableOperationLog, Users as UserClass +from apps.project.models import Project +# 工具函数 +from utils.chen_crud import update, multi_delete +from apps.user.tools.ldap_tools import load_ldap_users +# 导入登录日志函数 +from utils.log_util.request_util import save_login_log + +Users: UserClass = get_user_model() # type:ignore + +# 定义用户登录接口,包含token刷新和生成 +@api_controller("/system", tags=['用户token控制和登录接口']) +class UserTokenController(TokenObtainPairController): + auto_import = True + + @route.post("/login", url_name='login') + def obtain_token(self, user_token: schema.TokenObtainPairSerializer): + """新版本有特性,后期修改""" + # 注意TokenObtainPairSerializer是老版本,所以兼容,本质是TokenObtainPairInputSchema + user: UserClass = user_token._user + if user: + # 判断是否为启用状态 + if user.status == '2': + return ChenResponse(status=500, code=500, message='账号已被禁用,请联系管理员...') + save_login_log(request=self.context.request, user=user) # 保存登录日志 + refresh = RefreshToken.for_user(user) + token = refresh.access_token # type:ignore + return ChenResponse(code=200, + data={'token': str(token), 'refresh': str(refresh), + 'token_exp_data': datetime.fromtimestamp(token["exp"], tz=timezone.utc)}) + + @route.get("/getInfo", response=UserInfoOutSchema, url_name="get_info", auth=JWTAuth()) + def get_user_info(self): + # 直接按照Schema返回 + return self.context.request.auth + + @route.post("/logout", url_name="logout", auth=JWTAuth()) + def logout(self): + return ChenResponse(code=200, message='退出登录成功') + +# 定义system/user用户管理接口 +@api_controller("/system/user", tags=['用户管理'], auth=JWTAuth()) +class UserManageController(ControllerBase): + # 用户创建接口 + @route.post("/save", response=CreateUserOutSchema, url_name="user_create", auth=JWTAuth(), + permissions=[IsAuthenticated, IsAdminUser]) + def create_user(self, user_schema: CreateUserSchema): + user = user_schema.create() + return user + + # 给前端传所有用户当做字典 + @route.get('/list', response=List[UserRetrieveOutSchema], url_name="user_list", auth=None) + @transaction.atomic + def list_user(self, project_id: int = None): + """如果传了project_id则返回项目中的成员而非全部用户""" + qs = Users.objects.all() + if project_id is not None: + project_obj = get_object_or_404(Project, id=project_id) + all_member: list = project_obj.member + # 将member和duty_person联合 + if project_obj.duty_person not in project_obj.member: + all_member.append(project_obj.duty_person) + qs = qs.filter(name__in=all_member) + return qs + + # 用户检索接口 + @route.get("/index", response=List[UserRetrieveOutSchema]) + @paginate(MyPagination) + def index_user(self, filters: UserRetrieveInputSchema = Query(...)): + # 重要,处理前端不传值为None的情况 + for attr, value in filters.__dict__.items(): + if getattr(filters, attr) is None: + setattr(filters, attr, '') + start_time = self.context.request.GET.get('create_datetime[0]') + if start_time is None: + start_time = "2000-01-01" + end_time = self.context.request.GET.get('create_datetime[1]') + if end_time is None: + end_time = '8000-01-01' + date_list = [start_time, end_time] + qs = Users.objects.filter(name__icontains=filters.name, username__icontains=filters.username, + phone__icontains=filters.phone, status__contains=filters.status, + create_datetime__range=date_list).order_by('-create_datetime') + return qs + + @route.put("/update/{user_id}", response=UpdateDeleteUserOutSchema, permissions=[IsAuthenticated, IsAdminUser], + url_name="user-update") + def update_user(self, user_id: int, payload: UpdateDeleteUserSchema): + if payload.username == "superAdmin": + return ChenResponse(code=400, status=400, message="无法编辑,唯一管理员账号") + payload.validate_unique_username(user_id) + update_user = update(self.context.request, user_id, payload, Users) + return {"message": "用户更新成功"} + + @route.delete("/delete", permissions=[IsAuthenticated, IsAdminUser], url_name="user-delete") + def delete_user(self, data: DeleteUserSchema): + ids = data.ids + # 去掉删除创始人 + for item in ids: + if item == 1: + ids.pop(item) + multi_delete(ids, Users) + return ChenResponse(code=200, status=200, message="删除成功") + + # 管理员改变用户状态是否停用/启用 + @route.get('/change_status', auth=JWTAuth(), permissions=[IsAuthenticated, IsAdminUser], url_name='user-change') + def change_user_status(self, user_status: str, userId: int): + user = Users.objects.filter(id=userId).first() + if not user: + return ChenResponse(status=400, code=400, message='用户未找到') + if user.id == 1: + return ChenResponse(status=400, code=400, message='管理员不能被禁用,此操作无效') + user.status = user_status + user.save() + return user.status + + @route.post("/modifyPassword", auth=JWTAuth(), permissions=[IsAuthenticated, IsAdminUser]) + def modify_password(self, payload: AdminModifyPasswordSchema): + user: UserClass = self.context.request.user # type:ignore + if user: + # 判断就密码是否正确 + user_old = authenticate(username=user.username, password=payload.oldPassword) + if not user_old: + return ChenResponse(status=500, code=500, message='旧密码错误,请检查') + user.set_password(payload.newPassword) + user.save() + return ChenResponse(status=200, code=200, message='管理员修改密码成功') + + # 用户登录后动态读取LDAP用户录入数据 + @route.get("/ldap", url_name='user-ldap') + def load_ldap(self): + try: + load_ldap_users() + return ChenResponse(status=200, code=200, message='连接LDAP服务器成功,同步用户数据') + except Exception as exc: + print(exc) + return ChenResponse(status=200, code=200, message='欢迎您,正在外网访问') + +# 操作日志接口 +@api_controller("/system/log", tags=['日志记录'], auth=JWTAuth()) +class LogController(ControllerBase): + @route.get("/operation_list", url_name="log_list", response=List[LogOutSchema], auth=None) + @paginate(MyPagination) + def log_list(self, data: Query[LogInputSchema]): + for attr, value in data.model_dump().items(): + if getattr(data, attr) is None: + setattr(data, attr, '') + logs = TableOperationLog.objects.values('id', 'user__username', 'operate_obj', 'create_datetime', + 'operate_des').order_by( + '-create_datetime') + # 根据条件搜索 + logs = logs.filter(user__username__icontains=data.user, create_datetime__range=data.create_datetime) + return logs + + @route.get('/operation_delete', url_name='log_delete', permissions=[IsAuthenticated, IsAdminUser], auth=JWTAuth()) + def log_delete(self, data: LogDeleteInSchema = Query(...)): + time = datetime.now() - timedelta(days=data.day) + log_qs = TableOperationLog.objects.filter(create_datetime__lt=time) + log_qs.delete() + if data.day > 0: + return ChenResponse(message=f'删除{data.day}天前数据成功') + else: + return ChenResponse(message='全部日志删除成功') diff --git a/apps/user/migrations/0001_initial.py b/apps/user/migrations/0001_initial.py new file mode 100644 index 0000000..91670e4 --- /dev/null +++ b/apps/user/migrations/0001_initial.py @@ -0,0 +1,79 @@ +# Generated by Django 4.2.13 on 2024-07-03 10:30 + +from django.conf import settings +import django.contrib.auth.models +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='Users', + fields=[ + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('remark', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('update_datetime', models.DateField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('username', models.CharField(db_index=True, help_text='用户账号', max_length=150, unique=True, verbose_name='用户账号')), + ('name', models.CharField(help_text='姓名', max_length=40, verbose_name='姓名')), + ('avatar', models.TextField(blank=True, help_text='头像', null=True, verbose_name='头像')), + ('email', models.EmailField(blank=True, help_text='邮箱', max_length=255, null=True, verbose_name='邮箱')), + ('status', models.CharField(default='1', help_text='status', max_length=15, verbose_name='启用状态')), + ('job', models.CharField(blank=True, help_text='工作', max_length=255, null=True, verbose_name='工作')), + ('jobName', models.CharField(blank=True, help_text='工作名称', max_length=255, null=True, verbose_name='工作名称')), + ('organization', models.CharField(blank=True, help_text='工作组织', max_length=255, null=True, verbose_name='工作组织')), + ('location', models.CharField(blank=True, help_text='住地', max_length=255, null=True, verbose_name='住地')), + ('locationName', models.CharField(blank=True, help_text='住地名称', max_length=255, null=True, verbose_name='住地名称')), + ('introduction', models.CharField(blank=True, help_text='自我介绍', max_length=255, null=True, verbose_name='自我介绍')), + ('personalWebsite', models.CharField(blank=True, help_text='个人网站', max_length=255, null=True, verbose_name='个人网站')), + ('phone', models.CharField(blank=True, default='18888888888', help_text='电话', max_length=255, null=True, verbose_name='电话')), + ('accountId', models.CharField(blank=True, default='1', help_text='用户标识', max_length=255, null=True, verbose_name='用户标识')), + ('role', models.CharField(blank=True, default='user', help_text='角色', max_length=64, null=True, verbose_name='角色')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': '用户表', + 'verbose_name_plural': '用户表', + 'db_table': 'user_user', + 'ordering': ('-create_datetime',), + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='TableOperationLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('operate_obj', models.CharField(default='未关联对象', help_text='操作对象', max_length=256, verbose_name='操作对象')), + ('operate_des', models.CharField(default='未有操作详情', help_text='操作详情', max_length=1024, verbose_name='操作详情')), + ('user', models.ForeignKey(blank=True, db_constraint=False, help_text='操作人员', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ruser', related_query_name='quser', to=settings.AUTH_USER_MODEL, verbose_name='操作人员')), + ], + options={ + 'verbose_name': '用户操作日志表', + 'verbose_name_plural': '用户操作日志表', + 'db_table': 'operation_log', + 'ordering': ('-create_datetime',), + }, + ), + ] diff --git a/apps/user/migrations/0002_alter_users_email.py b/apps/user/migrations/0002_alter_users_email.py new file mode 100644 index 0000000..5f040c1 --- /dev/null +++ b/apps/user/migrations/0002_alter_users_email.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.13 on 2024-07-15 13:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='users', + name='email', + field=models.EmailField(blank=True, default=1, max_length=254, verbose_name='email address'), + preserve_default=False, + ), + ] diff --git a/apps/user/migrations/0003_remove_users_email.py b/apps/user/migrations/0003_remove_users_email.py new file mode 100644 index 0000000..afcb1f0 --- /dev/null +++ b/apps/user/migrations/0003_remove_users_email.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.13 on 2024-07-15 13:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0002_alter_users_email'), + ] + + operations = [ + migrations.RemoveField( + model_name='users', + name='email', + ), + ] diff --git a/apps/user/migrations/0004_users_email.py b/apps/user/migrations/0004_users_email.py new file mode 100644 index 0000000..3e7afa9 --- /dev/null +++ b/apps/user/migrations/0004_users_email.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.13 on 2024-07-15 13:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0003_remove_users_email'), + ] + + operations = [ + migrations.AddField( + model_name='users', + name='email', + field=models.EmailField(blank=True, max_length=254, verbose_name='email address'), + ), + ] diff --git a/apps/user/migrations/__init__.py b/apps/user/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/user/migrations/__pycache__/0001_initial.cpython-313.pyc b/apps/user/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000..eb58617 Binary files /dev/null and b/apps/user/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/apps/user/migrations/__pycache__/0001_initial.cpython-38.pyc b/apps/user/migrations/__pycache__/0001_initial.cpython-38.pyc new file mode 100644 index 0000000..425eb1f Binary files /dev/null and b/apps/user/migrations/__pycache__/0001_initial.cpython-38.pyc differ diff --git a/apps/user/migrations/__pycache__/0002_alter_users_email.cpython-313.pyc b/apps/user/migrations/__pycache__/0002_alter_users_email.cpython-313.pyc new file mode 100644 index 0000000..05c7e3e Binary files /dev/null and b/apps/user/migrations/__pycache__/0002_alter_users_email.cpython-313.pyc differ diff --git a/apps/user/migrations/__pycache__/0002_alter_users_email.cpython-38.pyc b/apps/user/migrations/__pycache__/0002_alter_users_email.cpython-38.pyc new file mode 100644 index 0000000..da492ad Binary files /dev/null and b/apps/user/migrations/__pycache__/0002_alter_users_email.cpython-38.pyc differ diff --git a/apps/user/migrations/__pycache__/0003_remove_users_email.cpython-313.pyc b/apps/user/migrations/__pycache__/0003_remove_users_email.cpython-313.pyc new file mode 100644 index 0000000..520e4fa Binary files /dev/null and b/apps/user/migrations/__pycache__/0003_remove_users_email.cpython-313.pyc differ diff --git a/apps/user/migrations/__pycache__/0003_remove_users_email.cpython-38.pyc b/apps/user/migrations/__pycache__/0003_remove_users_email.cpython-38.pyc new file mode 100644 index 0000000..1d24edd Binary files /dev/null and b/apps/user/migrations/__pycache__/0003_remove_users_email.cpython-38.pyc differ diff --git a/apps/user/migrations/__pycache__/0004_users_email.cpython-313.pyc b/apps/user/migrations/__pycache__/0004_users_email.cpython-313.pyc new file mode 100644 index 0000000..1143968 Binary files /dev/null and b/apps/user/migrations/__pycache__/0004_users_email.cpython-313.pyc differ diff --git a/apps/user/migrations/__pycache__/0004_users_email.cpython-38.pyc b/apps/user/migrations/__pycache__/0004_users_email.cpython-38.pyc new file mode 100644 index 0000000..a879766 Binary files /dev/null and b/apps/user/migrations/__pycache__/0004_users_email.cpython-38.pyc differ diff --git a/apps/user/migrations/__pycache__/__init__.cpython-313.pyc b/apps/user/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..e1877ad Binary files /dev/null and b/apps/user/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/apps/user/migrations/__pycache__/__init__.cpython-38.pyc b/apps/user/migrations/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..aed42ed Binary files /dev/null and b/apps/user/migrations/__pycache__/__init__.cpython-38.pyc differ diff --git a/apps/user/models.py b/apps/user/models.py new file mode 100644 index 0000000..fe16410 --- /dev/null +++ b/apps/user/models.py @@ -0,0 +1,56 @@ +from django.contrib.auth.models import AbstractUser, Group +from django.db import models +from utils.models import CoreModel + +STATUS_CHOICES = ( + (0, '禁用'), + (1, '启用') +) + +class Users(AbstractUser, CoreModel): + username = models.CharField(max_length=150, unique=True, db_index=True, verbose_name='用户账号', + help_text="用户账号") + name = models.CharField(max_length=40, verbose_name="姓名", help_text="姓名") + avatar = models.TextField(verbose_name="头像", null=True, blank=True, help_text="头像") + status = models.CharField(max_length=15, verbose_name='启用状态', help_text="status", default='1') + job = models.CharField(max_length=255, verbose_name='工作', null=True, blank=True, help_text='工作') + jobName = models.CharField(max_length=255, verbose_name='工作名称', null=True, blank=True, help_text='工作名称') + organization = models.CharField(max_length=255, verbose_name='工作组织', null=True, blank=True, + help_text='工作组织') + location = models.CharField(max_length=255, verbose_name='住地', null=True, blank=True, help_text='住地') + locationName = models.CharField(max_length=255, verbose_name='住地名称', null=True, blank=True, + help_text='住地名称') + introduction = models.CharField(max_length=255, verbose_name='自我介绍', null=True, blank=True, + help_text='自我介绍') + personalWebsite = models.CharField(max_length=255, verbose_name='个人网站', null=True, blank=True, + help_text='个人网站') + phone = models.CharField(max_length=255, verbose_name="电话", null=True, blank=True, help_text="电话", default='18888888888') + accountId = models.CharField(max_length=255, verbose_name="用户标识", null=True, blank=True, help_text="用户标识", default='1') + role = models.CharField(max_length=64, verbose_name="角色", null=True, blank=True, help_text="角色", default='user') + + def __str__(self): + return f'用户账号:{self.username}-用户名:{self.name}' + + class Meta: + db_table = 'user_user' + verbose_name = '用户表' + verbose_name_plural = verbose_name + ordering = ('-create_datetime',) + +class TableOperationLog(models.Model): + create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间", + verbose_name="创建时间") + user = models.ForeignKey(to="Users", db_constraint=False, related_name="ruser", on_delete=models.CASCADE, + verbose_name='操作人员', help_text='操作人员', related_query_name='quser', null=True, + blank=True) + # 2.操作的对象 + operate_obj = models.CharField(max_length=256, verbose_name='操作对象', default='未关联对象', help_text='操作对象') + # 3.操作详情 + operate_des = models.CharField(max_length=1024, verbose_name='操作详情', default='未有操作详情', + help_text='操作详情') + + class Meta: + db_table = 'operation_log' + verbose_name = '用户操作日志表' + verbose_name_plural = verbose_name + ordering = ('-create_datetime',) diff --git a/apps/user/schema.py b/apps/user/schema.py new file mode 100644 index 0000000..0870f96 --- /dev/null +++ b/apps/user/schema.py @@ -0,0 +1,115 @@ +from apps.user.models import Users +from django.contrib.auth.models import Group +from ninja_schema import ModelSchema, model_validator, Schema +from ninja_extra.exceptions import APIException +from ninja_extra import status +from datetime import datetime +from typing import List +from ninja import Field + +UserModel = Users + +# 定义用户名异常 +class UsernameException(APIException): + status_code = status.HTTP_400_BAD_REQUEST + default_detail = "用户名已存在,注册失败" + default_code = 400 + +class GroupSchema(ModelSchema): + class Config: + # 因为保证唯一性,所以ninja_schema使用set集合 + model = Group + # 注意ninja_schema(include)和ninja(model_fields) + include = ("name",) + +# schema:作用于创建用户请求 +class CreateUserSchema(ModelSchema): + class Config: + model = UserModel + include = ('username', 'name', 'password', 'phone', 'status',) + + # username判重 + @model_validator("username") + @classmethod + def unique_username(cls, value): + if UserModel.objects.filter(username__icontains=value).exists(): + raise UsernameException() + return value + + def create(self): + # 注意这里使用exclude_none,dict()方式属于pydantic + return UserModel.objects.create_user(**self.dict(exclude_none=True), email='xxx@qq.com') + +# schema:作用于创建用户后response +class CreateUserOutSchema(ModelSchema): + class Config: + model = UserModel + exclude = ('password',) + +# schema:定义前端查询用户信息的输出 +class UserInfoOutSchema(ModelSchema): + class Config: + model = UserModel + exclude = ("password",) + +# schema:作用于用户检索以及其他 +class UserRetrieveInputSchema(ModelSchema): + class Config: + model = UserModel + include = ("name", "username", "phone", "status",) + # ninja_schema的可选字段 + optional = ("name", "username", "phone", "status",) + +# schema:作用于检索后的输出定义 +class UserRetrieveOutSchema(ModelSchema): + class Config: + model = UserModel + exclude = ("password",) + +# 删除和更新用户 +class UpdateDeleteUserSchema(ModelSchema): + class Config: + model = UserModel + include = ("name", "username", "phone", "status") + + def validate_unique_username(self, id: int): + user_filters = UserModel.objects.filter(username=self.username) + if len(user_filters) > 1: + raise UsernameException() + elif len(user_filters) == 1: + if user_filters[0].id == id: + return + else: + raise UsernameException() + else: + return + +class UpdateDeleteUserOutSchema(Schema): + message: str + +class DeleteUserSchema(Schema): + ids: List[int] + +# ~~~~~~~~~~~~~~~~~~~~日志schema~~~~~~~~~~~~~~~~~~~~ +# 操作日志的schema +class LogOutSchema(Schema): + id: int + user: str = Field(..., alias='user__username') + operate_obj: str + create_datetime: datetime + operate_des: str + +# 操作日志的查询 +class LogInputSchema(Schema): + user: str = Field("", alias='user') + create_datetime: List = ['2000-01-01', '9999-01-01'] + +# 操作日志的删除输入 +class LogDeleteInSchema(Schema): + day: int = Field(7, ge=0, description='删除多少天前的数据') + +# 管理员修改密码 +class AdminModifyPasswordSchema(Schema): + newPassword: str + newPassword_confirmation: str + oldPassword: str diff --git a/apps/user/tests.py b/apps/user/tests.py new file mode 100644 index 0000000..de8bdc0 --- /dev/null +++ b/apps/user/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/user/tools/__pycache__/ldap_tools.cpython-313.pyc b/apps/user/tools/__pycache__/ldap_tools.cpython-313.pyc new file mode 100644 index 0000000..1d9e08f Binary files /dev/null and b/apps/user/tools/__pycache__/ldap_tools.cpython-313.pyc differ diff --git a/apps/user/tools/__pycache__/ldap_tools.cpython-38.pyc b/apps/user/tools/__pycache__/ldap_tools.cpython-38.pyc new file mode 100644 index 0000000..adfec6a Binary files /dev/null and b/apps/user/tools/__pycache__/ldap_tools.cpython-38.pyc differ diff --git a/apps/user/tools/ldap_tools.py b/apps/user/tools/ldap_tools.py new file mode 100644 index 0000000..7da1bb1 --- /dev/null +++ b/apps/user/tools/ldap_tools.py @@ -0,0 +1,56 @@ +import ldap +from django.contrib.auth import get_user_model + +def load_ldap_users(url='ldap://dns.paisat.cn:389', + dn="CN=Administrator,CN=Users,DC=sstc,DC=ctu", + pwd="WXWX2019!!!!!!", + search_dn="OU=ALL,DC=sstc,DC=ctu", + search_filter='(&(sAMAccountName=*))'): + Users = get_user_model() + + ldap_server = ldap.initialize(url) + ldap_server.simple_bind_s(dn, pwd) + ldap_users = ldap_server.search_ext_s(search_dn, + ldap.SCOPE_SUBTREE, + search_filter) + + temp_users = [] + for user in ldap_users: + username_field = user[-1]['sAMAccountName'][0] + email_field = user[-1].get('mail', username_field + b'@sstc.ctu')[0] + if isinstance(email_field, int): + email_field = username_field + b'@sstc.ctu' + user_dict = { + 'username': username_field.decode(), + 'name': user[-1]['name'][0].decode(), + 'email': email_field.decode(), + } + temp_users.append(user_dict) + db_user = Users.objects.filter(username=user_dict['username']) + exsits = db_user.exists() + if exsits: + # 如果存在则更新 + update_flag = False + c_user = db_user.first() + if c_user != user_dict['username']: + c_user.username = user_dict['username'] + update_flag = True + if c_user.name != user_dict['name']: + c_user.name = user_dict['name'] + update_flag = True + if c_user.email != user_dict['email']: + c_user.email = user_dict['email'] + update_flag = True + if update_flag: + c_user.save() + else: + user_dict['remark'] = '自动同步LDAP数据用户' + user_dict['status'] = '1' + user_dict['phone'] = '18888888888' + user_dict['role'] = 'user' + user_dict['accountId'] = 'user' + user_single = Users.objects.create(**user_dict) + user_single.set_password('wxwx2018!!!') + user_single.save() + # 6月3日新增组别 + diff --git a/apps/user/views.py b/apps/user/views.py new file mode 100644 index 0000000..c60c790 --- /dev/null +++ b/apps/user/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/cdtestplant_v1/__init__.py b/cdtestplant_v1/__init__.py new file mode 100644 index 0000000..d6929e4 --- /dev/null +++ b/cdtestplant_v1/__init__.py @@ -0,0 +1,3 @@ +# from .celery import app as celery_app +# +# __all__ = ["celery_app"] \ No newline at end of file diff --git a/cdtestplant_v1/__pycache__/__init__.cpython-313.pyc b/cdtestplant_v1/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..2979fdf Binary files /dev/null and b/cdtestplant_v1/__pycache__/__init__.cpython-313.pyc differ diff --git a/cdtestplant_v1/__pycache__/__init__.cpython-38.pyc b/cdtestplant_v1/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..3ad62e9 Binary files /dev/null and b/cdtestplant_v1/__pycache__/__init__.cpython-38.pyc differ diff --git a/cdtestplant_v1/__pycache__/api.cpython-313.pyc b/cdtestplant_v1/__pycache__/api.cpython-313.pyc new file mode 100644 index 0000000..d378947 Binary files /dev/null and b/cdtestplant_v1/__pycache__/api.cpython-313.pyc differ diff --git a/cdtestplant_v1/__pycache__/api.cpython-38.pyc b/cdtestplant_v1/__pycache__/api.cpython-38.pyc new file mode 100644 index 0000000..d711ae8 Binary files /dev/null and b/cdtestplant_v1/__pycache__/api.cpython-38.pyc differ diff --git a/cdtestplant_v1/__pycache__/asgi.cpython-38.pyc b/cdtestplant_v1/__pycache__/asgi.cpython-38.pyc new file mode 100644 index 0000000..0599897 Binary files /dev/null and b/cdtestplant_v1/__pycache__/asgi.cpython-38.pyc differ diff --git a/cdtestplant_v1/__pycache__/dev_settings.cpython-313.pyc b/cdtestplant_v1/__pycache__/dev_settings.cpython-313.pyc new file mode 100644 index 0000000..9c78143 Binary files /dev/null and b/cdtestplant_v1/__pycache__/dev_settings.cpython-313.pyc differ diff --git a/cdtestplant_v1/__pycache__/dev_settings.cpython-38.pyc b/cdtestplant_v1/__pycache__/dev_settings.cpython-38.pyc new file mode 100644 index 0000000..263070c Binary files /dev/null and b/cdtestplant_v1/__pycache__/dev_settings.cpython-38.pyc differ diff --git a/cdtestplant_v1/__pycache__/parser.cpython-313.pyc b/cdtestplant_v1/__pycache__/parser.cpython-313.pyc new file mode 100644 index 0000000..cd1b651 Binary files /dev/null and b/cdtestplant_v1/__pycache__/parser.cpython-313.pyc differ diff --git a/cdtestplant_v1/__pycache__/parser.cpython-38.pyc b/cdtestplant_v1/__pycache__/parser.cpython-38.pyc new file mode 100644 index 0000000..2a84949 Binary files /dev/null and b/cdtestplant_v1/__pycache__/parser.cpython-38.pyc differ diff --git a/cdtestplant_v1/__pycache__/renderer.cpython-313.pyc b/cdtestplant_v1/__pycache__/renderer.cpython-313.pyc new file mode 100644 index 0000000..aeefae4 Binary files /dev/null and b/cdtestplant_v1/__pycache__/renderer.cpython-313.pyc differ diff --git a/cdtestplant_v1/__pycache__/renderer.cpython-38.pyc b/cdtestplant_v1/__pycache__/renderer.cpython-38.pyc new file mode 100644 index 0000000..ff5db5a Binary files /dev/null and b/cdtestplant_v1/__pycache__/renderer.cpython-38.pyc differ diff --git a/cdtestplant_v1/__pycache__/settings.cpython-313.pyc b/cdtestplant_v1/__pycache__/settings.cpython-313.pyc new file mode 100644 index 0000000..fe87751 Binary files /dev/null and b/cdtestplant_v1/__pycache__/settings.cpython-313.pyc differ diff --git a/cdtestplant_v1/__pycache__/settings.cpython-38.pyc b/cdtestplant_v1/__pycache__/settings.cpython-38.pyc new file mode 100644 index 0000000..70f2407 Binary files /dev/null and b/cdtestplant_v1/__pycache__/settings.cpython-38.pyc differ diff --git a/cdtestplant_v1/__pycache__/urls.cpython-313.pyc b/cdtestplant_v1/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000..7f28077 Binary files /dev/null and b/cdtestplant_v1/__pycache__/urls.cpython-313.pyc differ diff --git a/cdtestplant_v1/__pycache__/urls.cpython-38.pyc b/cdtestplant_v1/__pycache__/urls.cpython-38.pyc new file mode 100644 index 0000000..1b74b9f Binary files /dev/null and b/cdtestplant_v1/__pycache__/urls.cpython-38.pyc differ diff --git a/cdtestplant_v1/__pycache__/wsgi.cpython-313.pyc b/cdtestplant_v1/__pycache__/wsgi.cpython-313.pyc new file mode 100644 index 0000000..88dc0f7 Binary files /dev/null and b/cdtestplant_v1/__pycache__/wsgi.cpython-313.pyc differ diff --git a/cdtestplant_v1/__pycache__/wsgi.cpython-38.pyc b/cdtestplant_v1/__pycache__/wsgi.cpython-38.pyc new file mode 100644 index 0000000..2368452 Binary files /dev/null and b/cdtestplant_v1/__pycache__/wsgi.cpython-38.pyc differ diff --git a/cdtestplant_v1/api.py b/cdtestplant_v1/api.py new file mode 100644 index 0000000..303b7c7 --- /dev/null +++ b/cdtestplant_v1/api.py @@ -0,0 +1,18 @@ +from utils.chen_ninja import ChenNinjaAPI +# 导入orjson解析器,渲染器,提升性能 +from cdtestplant_v1.parser import MyParser +from cdtestplant_v1.renderer import MyRenderer +# swagger-ui配置 +from ninja import Swagger + +api = ChenNinjaAPI( + title="测试管理平台API", + description="测试管理平台的接口一系列接口函数", + urls_namespace="cdtestplant_v1", + parser=MyParser(), + renderer=MyRenderer(), + docs=Swagger({"persistAuthorization": True}) +) + +# 自动寻找每个app下面controllers.py中被@api_controller修饰的类 +api.auto_discover_controllers() diff --git a/cdtestplant_v1/asgi.py b/cdtestplant_v1/asgi.py new file mode 100644 index 0000000..aaac5a7 --- /dev/null +++ b/cdtestplant_v1/asgi.py @@ -0,0 +1,7 @@ +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cdtestplant_v1.settings') + +application = get_asgi_application() diff --git a/cdtestplant_v1/celery.py.bak b/cdtestplant_v1/celery.py.bak new file mode 100644 index 0000000..5ed6145 --- /dev/null +++ b/cdtestplant_v1/celery.py.bak @@ -0,0 +1,15 @@ +import os + +from celery import Celery, platforms +from django.conf import settings + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', "cdtestplant_v1.settings") + +# app = Celery(f"application") +app = Celery(f"system") + +# app.config_from_object('django.conf:settings', namespace='CELERY') +app.config_from_object('django.conf:settings') +app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) +# app.autodiscover_tasks() +platforms.C_FORCE_ROOT = True diff --git a/cdtestplant_v1/dev_settings.py b/cdtestplant_v1/dev_settings.py new file mode 100644 index 0000000..167dbc3 --- /dev/null +++ b/cdtestplant_v1/dev_settings.py @@ -0,0 +1,47 @@ +from .settings import * + +# ~~~~这部分是延用settings.py,注意在settings统一修改~~~~ +BASE_DIR = BASE_DIR +SECRET_KEY = SECRET_KEY +INSTALLED_APPS = INSTALLED_APPS +MIDDLEWARE = MIDDLEWARE +SECURE_CROSS_ORIGIN_OPENER_POLICY = SECURE_CROSS_ORIGIN_OPENER_POLICY +ROOT_URLCONF = ROOT_URLCONF +TEMPLATES = TEMPLATES +WSGI_APPLICATION = WSGI_APPLICATION +CACHES = CACHES +LANGUAGE_CODE = LANGUAGE_CODE +TIME_ZONE = TIME_ZONE +USE_I18N = USE_I18N +USE_TZ = USE_TZ +DEFAULT_AUTO_FIELD = DEFAULT_AUTO_FIELD +TOKEN_LIFETIME = TOKEN_LIFETIME +AUTH_USER_MODEL = AUTH_USER_MODEL +USERNAME_FIELD = USERNAME_FIELD +NINJA_JWT = NINJA_JWT +ALLOWED_HOSTS = ALLOWED_HOSTS +STATIC_URL = STATIC_URL +STATIC_ROOT = STATIC_ROOT +MEDIA_URL = MEDIA_URL +MEDIA_ROOT = MEDIA_ROOT +API_LOG_ENABLE = API_LOG_ENABLE +API_LOG_METHODS = API_LOG_METHODS +API_MODEL_MAP = API_MODEL_MAP +API_OPERATION_EXCLUDE_START = API_OPERATION_EXCLUDE_START +DATA_UPLOAD_MAX_MEMORY_SIZE = DATA_UPLOAD_MAX_MEMORY_SIZE + +# ~~~~下面都是开发环境的配置~~~~ +# 调试模式【开发环境有变动】 +DEBUG = True + +# Mysql数据库【开发环境有变动】 +DATABASES = { + "default": { + "ENGINE": "django.db.backends.mysql", + "HOST": DATABASE_HOST, + "PORT": 3306, + "USER": DATABASE_USER, + "PASSWORD": DATABASE_PASSWORD, + "NAME": DATABASE_NAME, + } +} diff --git a/cdtestplant_v1/parser.py b/cdtestplant_v1/parser.py new file mode 100644 index 0000000..9becc4d --- /dev/null +++ b/cdtestplant_v1/parser.py @@ -0,0 +1,8 @@ +import orjson +from django.http import HttpRequest +from ninja.parser import Parser +from ninja.types import DictStrAny + +class MyParser(Parser): + def parse_body(self, request: HttpRequest) -> DictStrAny: + return orjson.loads(request.body) diff --git a/cdtestplant_v1/renderer.py b/cdtestplant_v1/renderer.py new file mode 100644 index 0000000..0ebdd8d --- /dev/null +++ b/cdtestplant_v1/renderer.py @@ -0,0 +1,11 @@ +from typing import Any +import orjson +from django.http import HttpRequest +from ninja.renderers import BaseRenderer + +class MyRenderer(BaseRenderer): + media_type = 'application/json' + charset = 'utf-8' + + def render(self, request: HttpRequest, data: Any, *, response_status: int) -> Any: + return orjson.dumps(data) diff --git a/cdtestplant_v1/settings.py b/cdtestplant_v1/settings.py new file mode 100644 index 0000000..189ca8d --- /dev/null +++ b/cdtestplant_v1/settings.py @@ -0,0 +1,161 @@ +from pathlib import Path +import os +import datetime +# 导入其他配置:env、ninja_extra、ldap、log +from conf.env import * +from conf.ninja_extra_settings import * +from django_auth_ldap.config import LDAPSearch +from conf.logConfig import LOGGING + +BASE_DIR = Path(__file__).resolve().parent.parent + +SECRET_KEY = 'django-insecure-gng$@ebwaxy7bsc86r5pc&$(h8a8+to0v1rbzc9+vkopuv6j-g' + +INSTALLED_APPS = [ + # 'simpleui', + # 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + # 'django.contrib.sessions', + # 'django.contrib.messages', + 'django.contrib.staticfiles', # TODO:生成环境记得删除,这是为了swagger文档调试用的 + # 第三方包 + 'ninja', + 'ninja_extra', + 'ninja_jwt', + 'tinymce', + # apps + 'apps.user', + 'apps.dict', + 'apps.system', + 'apps.project', + 'apps.createDocument', + 'apps.createSeiTaiDocument', +] + +# auth中间件未打开 +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + # 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + # 'django.contrib.auth.middleware.AuthenticationMiddleware', + # 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + # 新加入日志记录的中间件 + 'utils.log_util.middleware.ApiLoggingMiddleware' +] + +# 设置跨域 +SECURE_CROSS_ORIGIN_OPENER_POLICY = 'None' + +ROOT_URLCONF = 'cdtestplant_v1.urls' + +# 模版不需要,生成环境删除 TODO:生成环境记得删除,这是为了swagger文档调试用的 +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [BASE_DIR / 'templates'], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + # 'django.template.context_processors.debug', + # 'django.template.context_processors.request', + # 'django.contrib.auth.context_processors.auth', + # 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'cdtestplant_v1.wsgi.application' + +# Mysql数据库 +DATABASES = { + "default": { + "ENGINE": "django.db.backends.mysql", + "HOST": DATABASE_HOST, + "PORT": DATABASE_PORT, + "USER": DATABASE_USER, + "PASSWORD": DATABASE_PASSWORD, + "NAME": DATABASE_NAME, + } +} + +# 调试模式 +DEBUG = False + +# 配置缓存 +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://127.0.0.1:6379/1", # 这里直接使用redis别名作为host ip地址 + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + # "PASSWORD": "yourpassword", # 换成你自己密码 + }, + } +} + +# 密码验证Django处理,rest不需要 +# AUTH_PASSWORD_VALIDATORS = [ +# { +# 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', +# }, +# { +# 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', +# }, +# { +# 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', +# }, +# { +# 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', +# }, +# ] + +LANGUAGE_CODE = 'zh-hans' +TIME_ZONE = 'Asia/Shanghai' +USE_I18N = True +USE_TZ = False # False时数据库保存本地时间 +# 默认ORM主键pk +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +# token 有效时间 时 分 秒(暂未使用) +TOKEN_LIFETIME = 12 * 60 * 60 +# User表去找我们自定义的 +AUTH_USER_MODEL = 'user.Users' +USERNAME_FIELD = 'username' + +# JWT配置 +NINJA_JWT = { + # token类型,其他方案:SlidingToken + "AUTH_TOKEN_CLASSES": ("ninja_jwt.tokens.AccessToken",), + # token失效时间,14小时失效 + "ACCESS_TOKEN_LIFETIME": datetime.timedelta(hours=14), + "REFRESH_TOKEN_LIFETIME": datetime.timedelta(days=1), +} +ALLOWED_HOSTS = ["*"] # 线上环境设置 + +# 静态文件目录 - manage.py collectstatic +# -> 会将所有app静态文件移动到STATIC_ROOT目录下面 +STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(BASE_DIR, '../../static') + +# 配置MEDIA_ROOT和MEDIA_URL +MEDIA_URL = "/uploads/" +MEDIA_ROOT = os.path.join(BASE_DIR, '../../uploads') + +# 接口日志记录 +API_LOG_ENABLE = True +API_LOG_METHODS = ['POST', 'GET', 'DELETE', 'PUT'] +API_MODEL_MAP = {} # 暂时不使用,使用信号记录模型操作 +# 接口日志黑名单:字典的操作日志、所有联系人的操作、所有缩略语的操作、判断轮次是否有源代码被测件、不记录自己 +API_OPERATION_EXCLUDE_START = [ + '/api/system/dataDict', + '/api/system/contact/index', + '/api/system/abbreviation/index', + '/api/project/dut/soExist', + '/api/system/log/', +] + +# 配置单次请求最大字节数(base64图片和上传需求文档适用) +DATA_UPLOAD_MAX_MEMORY_SIZE = 5242880 * 10 diff --git a/cdtestplant_v1/urls.py b/cdtestplant_v1/urls.py new file mode 100644 index 0000000..f5c2dd2 --- /dev/null +++ b/cdtestplant_v1/urls.py @@ -0,0 +1,10 @@ +from django.urls import path,re_path +from django.views.static import serve +from cdtestplant_v1 import settings +from .api import api + +urlpatterns = [ + path("api/",api.urls), + # 访问静态资源 + re_path(r'uploads/(?P.*)$',serve,{'document_root':settings.MEDIA_ROOT}), +] diff --git a/cdtestplant_v1/wsgi.py b/cdtestplant_v1/wsgi.py new file mode 100644 index 0000000..07a98a9 --- /dev/null +++ b/cdtestplant_v1/wsgi.py @@ -0,0 +1,7 @@ +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cdtestplant_v1.settings') + +application = get_wsgi_application() diff --git a/conf/__init__.py b/conf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/conf/__pycache__/__init__.cpython-313.pyc b/conf/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..82ccf2b Binary files /dev/null and b/conf/__pycache__/__init__.cpython-313.pyc differ diff --git a/conf/__pycache__/__init__.cpython-38.pyc b/conf/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..f084391 Binary files /dev/null and b/conf/__pycache__/__init__.cpython-38.pyc differ diff --git a/conf/__pycache__/env.cpython-313.pyc b/conf/__pycache__/env.cpython-313.pyc new file mode 100644 index 0000000..06bc910 Binary files /dev/null and b/conf/__pycache__/env.cpython-313.pyc differ diff --git a/conf/__pycache__/env.cpython-38.pyc b/conf/__pycache__/env.cpython-38.pyc new file mode 100644 index 0000000..d25f141 Binary files /dev/null and b/conf/__pycache__/env.cpython-38.pyc differ diff --git a/conf/__pycache__/logConfig.cpython-313.pyc b/conf/__pycache__/logConfig.cpython-313.pyc new file mode 100644 index 0000000..35a0d0a Binary files /dev/null and b/conf/__pycache__/logConfig.cpython-313.pyc differ diff --git a/conf/__pycache__/logConfig.cpython-38.pyc b/conf/__pycache__/logConfig.cpython-38.pyc new file mode 100644 index 0000000..ef222e4 Binary files /dev/null and b/conf/__pycache__/logConfig.cpython-38.pyc differ diff --git a/conf/__pycache__/ninja_extra_settings.cpython-313.pyc b/conf/__pycache__/ninja_extra_settings.cpython-313.pyc new file mode 100644 index 0000000..6d2c0c0 Binary files /dev/null and b/conf/__pycache__/ninja_extra_settings.cpython-313.pyc differ diff --git a/conf/__pycache__/ninja_extra_settings.cpython-38.pyc b/conf/__pycache__/ninja_extra_settings.cpython-38.pyc new file mode 100644 index 0000000..a038722 Binary files /dev/null and b/conf/__pycache__/ninja_extra_settings.cpython-38.pyc differ diff --git a/conf/bak_document/dg/主要战技指标.docx b/conf/bak_document/dg/主要战技指标.docx new file mode 100644 index 0000000..26d6665 Binary files /dev/null and b/conf/bak_document/dg/主要战技指标.docx differ diff --git a/conf/bak_document/dg/测试总体要求.docx b/conf/bak_document/dg/测试总体要求.docx new file mode 100644 index 0000000..e1e5248 Binary files /dev/null and b/conf/bak_document/dg/测试总体要求.docx differ diff --git a/conf/bak_document/dg/被测软件功能.docx b/conf/bak_document/dg/被测软件功能.docx new file mode 100644 index 0000000..578aeec Binary files /dev/null and b/conf/bak_document/dg/被测软件功能.docx differ diff --git a/conf/bak_document/dg/被测软件性能.docx b/conf/bak_document/dg/被测软件性能.docx new file mode 100644 index 0000000..d8c18aa Binary files /dev/null and b/conf/bak_document/dg/被测软件性能.docx differ diff --git a/conf/bak_document/dg/记录现在模版存在但无用的文档片段.txt b/conf/bak_document/dg/记录现在模版存在但无用的文档片段.txt new file mode 100644 index 0000000..470821b --- /dev/null +++ b/conf/bak_document/dg/记录现在模版存在但无用的文档片段.txt @@ -0,0 +1,2 @@ +一)测评大纲模版 +1.缩略语.docx \ No newline at end of file diff --git a/conf/base_document/form_template/bg/temporary/研总需归追踪_temp.docx b/conf/base_document/form_template/bg/temporary/研总需归追踪_temp.docx new file mode 100644 index 0000000..2149856 Binary files /dev/null and b/conf/base_document/form_template/bg/temporary/研总需归追踪_temp.docx differ diff --git a/conf/base_document/form_template/bg/总体结论.docx b/conf/base_document/form_template/bg/总体结论.docx new file mode 100644 index 0000000..0e8e52a Binary files /dev/null and b/conf/base_document/form_template/bg/总体结论.docx differ diff --git a/conf/base_document/form_template/bg/技术依据文件.docx b/conf/base_document/form_template/bg/技术依据文件.docx new file mode 100644 index 0000000..a04e4ba Binary files /dev/null and b/conf/base_document/form_template/bg/技术依据文件.docx differ diff --git a/conf/base_document/form_template/bg/摸底清单.docx b/conf/base_document/form_template/bg/摸底清单.docx new file mode 100644 index 0000000..910f097 Binary files /dev/null and b/conf/base_document/form_template/bg/摸底清单.docx differ diff --git a/conf/base_document/form_template/bg/测评完成情况.docx b/conf/base_document/form_template/bg/测评完成情况.docx new file mode 100644 index 0000000..c996bf7 Binary files /dev/null and b/conf/base_document/form_template/bg/测评完成情况.docx differ diff --git a/conf/base_document/form_template/bg/测评时间和地点.docx b/conf/base_document/form_template/bg/测评时间和地点.docx new file mode 100644 index 0000000..18ab5bb Binary files /dev/null and b/conf/base_document/form_template/bg/测评时间和地点.docx differ diff --git a/conf/base_document/form_template/bg/测试内容和结果_第一轮次.docx b/conf/base_document/form_template/bg/测试内容和结果_第一轮次.docx new file mode 100644 index 0000000..18e9f80 Binary files /dev/null and b/conf/base_document/form_template/bg/测试内容和结果_第一轮次.docx differ diff --git a/conf/base_document/form_template/bg/测试内容和结果_第二轮次.docx b/conf/base_document/form_template/bg/测试内容和结果_第二轮次.docx new file mode 100644 index 0000000..951a26e Binary files /dev/null and b/conf/base_document/form_template/bg/测试内容和结果_第二轮次.docx differ diff --git a/conf/base_document/form_template/bg/测试有效性充分性说明.docx b/conf/base_document/form_template/bg/测试有效性充分性说明.docx new file mode 100644 index 0000000..020a48d Binary files /dev/null and b/conf/base_document/form_template/bg/测试有效性充分性说明.docx differ diff --git a/conf/base_document/form_template/bg/研总需归追踪.docx b/conf/base_document/form_template/bg/研总需归追踪.docx new file mode 100644 index 0000000..051f263 Binary files /dev/null and b/conf/base_document/form_template/bg/研总需归追踪.docx differ diff --git a/conf/base_document/form_template/bg/综述.docx b/conf/base_document/form_template/bg/综述.docx new file mode 100644 index 0000000..afdb36a Binary files /dev/null and b/conf/base_document/form_template/bg/综述.docx differ diff --git a/conf/base_document/form_template/bg/被测软件基本信息.docx b/conf/base_document/form_template/bg/被测软件基本信息.docx new file mode 100644 index 0000000..7dc9a6a Binary files /dev/null and b/conf/base_document/form_template/bg/被测软件基本信息.docx differ diff --git a/conf/base_document/form_template/bg/软件质量评价.docx b/conf/base_document/form_template/bg/软件质量评价.docx new file mode 100644 index 0000000..605e42b Binary files /dev/null and b/conf/base_document/form_template/bg/软件质量评价.docx differ diff --git a/conf/base_document/form_template/bg/软件问题统计.docx b/conf/base_document/form_template/bg/软件问题统计.docx new file mode 100644 index 0000000..cb0a4bb Binary files /dev/null and b/conf/base_document/form_template/bg/软件问题统计.docx differ diff --git a/conf/base_document/form_template/bg/问题汇总表.docx b/conf/base_document/form_template/bg/问题汇总表.docx new file mode 100644 index 0000000..3af92f6 Binary files /dev/null and b/conf/base_document/form_template/bg/问题汇总表.docx differ diff --git a/conf/base_document/form_template/bg/需求指标符合性情况.docx b/conf/base_document/form_template/bg/需求指标符合性情况.docx new file mode 100644 index 0000000..91ec078 Binary files /dev/null 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 new file mode 100644 index 0000000..b6df75e Binary files /dev/null and b/conf/base_document/form_template/dg/主要功能和性能指标.docx differ diff --git a/conf/base_document/form_template/dg/主要战技指标.docx b/conf/base_document/form_template/dg/主要战技指标.docx new file mode 100644 index 0000000..26d6665 Binary files /dev/null and b/conf/base_document/form_template/dg/主要战技指标.docx differ diff --git a/conf/base_document/form_template/dg/代码质量度量分析表.docx b/conf/base_document/form_template/dg/代码质量度量分析表.docx new file mode 100644 index 0000000..cd23618 Binary files /dev/null and b/conf/base_document/form_template/dg/代码质量度量分析表.docx differ diff --git a/conf/base_document/form_template/dg/动态测试环境说明.docx b/conf/base_document/form_template/dg/动态测试环境说明.docx new file mode 100644 index 0000000..a3d5a4a Binary files /dev/null and b/conf/base_document/form_template/dg/动态测试环境说明.docx differ diff --git a/conf/base_document/form_template/dg/动态硬件和固件项.docx b/conf/base_document/form_template/dg/动态硬件和固件项.docx new file mode 100644 index 0000000..268c1f7 Binary files /dev/null and b/conf/base_document/form_template/dg/动态硬件和固件项.docx differ diff --git a/conf/base_document/form_template/dg/动态软件项.docx b/conf/base_document/form_template/dg/动态软件项.docx new file mode 100644 index 0000000..213d189 Binary files /dev/null and b/conf/base_document/form_template/dg/动态软件项.docx differ diff --git a/conf/base_document/form_template/dg/反向需求规格追踪表.docx b/conf/base_document/form_template/dg/反向需求规格追踪表.docx new file mode 100644 index 0000000..acfa8c3 Binary files /dev/null and b/conf/base_document/form_template/dg/反向需求规格追踪表.docx differ diff --git a/conf/base_document/form_template/dg/技术依据文件.docx b/conf/base_document/form_template/dg/技术依据文件.docx new file mode 100644 index 0000000..f2cab7b Binary files /dev/null and b/conf/base_document/form_template/dg/技术依据文件.docx differ diff --git a/conf/base_document/form_template/dg/标准依据文件.docx b/conf/base_document/form_template/dg/标准依据文件.docx new file mode 100644 index 0000000..0405408 Binary files /dev/null and b/conf/base_document/form_template/dg/标准依据文件.docx differ diff --git a/conf/base_document/form_template/dg/测评对象.docx b/conf/base_document/form_template/dg/测评对象.docx new file mode 100644 index 0000000..56f9764 Binary files /dev/null and b/conf/base_document/form_template/dg/测评对象.docx differ diff --git a/conf/base_document/form_template/dg/测评数据.docx b/conf/base_document/form_template/dg/测评数据.docx new file mode 100644 index 0000000..bf7f29b Binary files /dev/null and b/conf/base_document/form_template/dg/测评数据.docx differ diff --git a/conf/base_document/form_template/dg/测评时间和地点.docx b/conf/base_document/form_template/dg/测评时间和地点.docx new file mode 100644 index 0000000..8f797ce Binary files /dev/null and b/conf/base_document/form_template/dg/测评时间和地点.docx differ diff --git a/conf/base_document/form_template/dg/测评条件保障.docx b/conf/base_document/form_template/dg/测评条件保障.docx new file mode 100644 index 0000000..4a9ed25 Binary files /dev/null and b/conf/base_document/form_template/dg/测评条件保障.docx differ diff --git a/conf/base_document/form_template/dg/测评组织及任务分工.docx b/conf/base_document/form_template/dg/测评组织及任务分工.docx new file mode 100644 index 0000000..4340f53 Binary files /dev/null and b/conf/base_document/form_template/dg/测评组织及任务分工.docx differ diff --git a/conf/base_document/form_template/dg/测试内容充分性及测试方法有效性分析.docx b/conf/base_document/form_template/dg/测试内容充分性及测试方法有效性分析.docx new file mode 100644 index 0000000..7342627 Binary files /dev/null and b/conf/base_document/form_template/dg/测试内容充分性及测试方法有效性分析.docx differ diff --git a/conf/base_document/form_template/dg/测试策略.docx b/conf/base_document/form_template/dg/测试策略.docx new file mode 100644 index 0000000..e3acea7 Binary files /dev/null and b/conf/base_document/form_template/dg/测试策略.docx differ diff --git a/conf/base_document/form_template/dg/测试级别和测试类型.docx b/conf/base_document/form_template/dg/测试级别和测试类型.docx new file mode 100644 index 0000000..e83ecd5 Binary files /dev/null and b/conf/base_document/form_template/dg/测试级别和测试类型.docx differ diff --git a/conf/base_document/form_template/dg/测试项及方法.docx b/conf/base_document/form_template/dg/测试项及方法.docx new file mode 100644 index 0000000..e1aa9da Binary files /dev/null and b/conf/base_document/form_template/dg/测试项及方法.docx differ diff --git a/conf/base_document/form_template/dg/环境差异性分析.docx b/conf/base_document/form_template/dg/环境差异性分析.docx new file mode 100644 index 0000000..02b21e6 Binary files /dev/null and b/conf/base_document/form_template/dg/环境差异性分析.docx differ diff --git a/conf/base_document/form_template/dg/研制总要求追踪表.docx b/conf/base_document/form_template/dg/研制总要求追踪表.docx new file mode 100644 index 0000000..7611f78 Binary files /dev/null and b/conf/base_document/form_template/dg/研制总要求追踪表.docx differ diff --git a/conf/base_document/form_template/dg/缩略语.docx b/conf/base_document/form_template/dg/缩略语.docx new file mode 100644 index 0000000..09b549f Binary files /dev/null and b/conf/base_document/form_template/dg/缩略语.docx differ diff --git a/conf/base_document/form_template/dg/联系人和方式.docx b/conf/base_document/form_template/dg/联系人和方式.docx new file mode 100644 index 0000000..9ab57cd Binary files /dev/null and b/conf/base_document/form_template/dg/联系人和方式.docx differ diff --git a/conf/base_document/form_template/dg/被测软件功能.docx b/conf/base_document/form_template/dg/被测软件功能.docx new file mode 100644 index 0000000..578aeec Binary files /dev/null and b/conf/base_document/form_template/dg/被测软件功能.docx differ diff --git a/conf/base_document/form_template/dg/被测软件基本信息.docx b/conf/base_document/form_template/dg/被测软件基本信息.docx new file mode 100644 index 0000000..6c7a36b Binary files /dev/null and b/conf/base_document/form_template/dg/被测软件基本信息.docx differ diff --git a/conf/base_document/form_template/dg/被测软件性能.docx b/conf/base_document/form_template/dg/被测软件性能.docx new file mode 100644 index 0000000..d8c18aa Binary files /dev/null and b/conf/base_document/form_template/dg/被测软件性能.docx differ diff --git a/conf/base_document/form_template/dg/被测软件接口.docx b/conf/base_document/form_template/dg/被测软件接口.docx new file mode 100644 index 0000000..9529c67 Binary files /dev/null and b/conf/base_document/form_template/dg/被测软件接口.docx differ diff --git a/conf/base_document/form_template/dg/需求规格说明追踪表.docx b/conf/base_document/form_template/dg/需求规格说明追踪表.docx new file mode 100644 index 0000000..3c4dd8a Binary files /dev/null and b/conf/base_document/form_template/dg/需求规格说明追踪表.docx differ diff --git a/conf/base_document/form_template/dg/静态测试环境说明.docx b/conf/base_document/form_template/dg/静态测试环境说明.docx new file mode 100644 index 0000000..8074344 Binary files /dev/null and b/conf/base_document/form_template/dg/静态测试环境说明.docx differ diff --git a/conf/base_document/form_template/dg/静态硬件和固件项.docx b/conf/base_document/form_template/dg/静态硬件和固件项.docx new file mode 100644 index 0000000..85fdffe Binary files /dev/null and b/conf/base_document/form_template/dg/静态硬件和固件项.docx differ diff --git a/conf/base_document/form_template/dg/静态软件项.docx b/conf/base_document/form_template/dg/静态软件项.docx new file mode 100644 index 0000000..dc64c0e Binary files /dev/null and b/conf/base_document/form_template/dg/静态软件项.docx differ diff --git a/conf/base_document/form_template/dg/顶层技术文件.docx b/conf/base_document/form_template/dg/顶层技术文件.docx new file mode 100644 index 0000000..27b7e96 Binary files /dev/null and b/conf/base_document/form_template/dg/顶层技术文件.docx differ diff --git a/conf/base_document/form_template/hjl/测试用例记录.docx b/conf/base_document/form_template/hjl/测试用例记录.docx new file mode 100644 index 0000000..0e1009b Binary files /dev/null and b/conf/base_document/form_template/hjl/测试用例记录.docx differ diff --git a/conf/base_document/form_template/hjl/被测软件基本信息.docx b/conf/base_document/form_template/hjl/被测软件基本信息.docx new file mode 100644 index 0000000..6ddc8d8 Binary files /dev/null and b/conf/base_document/form_template/hjl/被测软件基本信息.docx differ diff --git a/conf/base_document/form_template/hsm/temporary/第三轮用例追踪_temp.docx b/conf/base_document/form_template/hsm/temporary/第三轮用例追踪_temp.docx new file mode 100644 index 0000000..2f25e93 Binary files /dev/null and b/conf/base_document/form_template/hsm/temporary/第三轮用例追踪_temp.docx differ diff --git a/conf/base_document/form_template/hsm/temporary/第二轮用例追踪_temp.docx b/conf/base_document/form_template/hsm/temporary/第二轮用例追踪_temp.docx new file mode 100644 index 0000000..bc1d787 Binary files /dev/null and b/conf/base_document/form_template/hsm/temporary/第二轮用例追踪_temp.docx differ diff --git a/conf/base_document/form_template/hsm/回归测试用例概述.docx b/conf/base_document/form_template/hsm/回归测试用例概述.docx new file mode 100644 index 0000000..4439f7a Binary files /dev/null and b/conf/base_document/form_template/hsm/回归测试用例概述.docx differ diff --git a/conf/base_document/form_template/hsm/回归测试需求.docx b/conf/base_document/form_template/hsm/回归测试需求.docx new file mode 100644 index 0000000..b99716b Binary files /dev/null and b/conf/base_document/form_template/hsm/回归测试需求.docx differ diff --git a/conf/base_document/form_template/hsm/技术依据文件.docx b/conf/base_document/form_template/hsm/技术依据文件.docx new file mode 100644 index 0000000..19e32c5 Binary files /dev/null and b/conf/base_document/form_template/hsm/技术依据文件.docx differ diff --git a/conf/base_document/form_template/hsm/文档概述.docx b/conf/base_document/form_template/hsm/文档概述.docx new file mode 100644 index 0000000..3c2ca5d Binary files /dev/null and b/conf/base_document/form_template/hsm/文档概述.docx differ diff --git a/conf/base_document/form_template/hsm/测试用例.docx b/conf/base_document/form_template/hsm/测试用例.docx new file mode 100644 index 0000000..6988be8 Binary files /dev/null and b/conf/base_document/form_template/hsm/测试用例.docx differ diff --git a/conf/base_document/form_template/hsm/用例追踪.docx b/conf/base_document/form_template/hsm/用例追踪.docx new file mode 100644 index 0000000..293e7be Binary files /dev/null and b/conf/base_document/form_template/hsm/用例追踪.docx differ diff --git a/conf/base_document/form_template/hsm/被测软件基本信息.docx b/conf/base_document/form_template/hsm/被测软件基本信息.docx new file mode 100644 index 0000000..08bb234 Binary files /dev/null and b/conf/base_document/form_template/hsm/被测软件基本信息.docx differ diff --git a/conf/base_document/form_template/hsm/软件更改部分.docx b/conf/base_document/form_template/hsm/软件更改部分.docx new file mode 100644 index 0000000..dad9c64 Binary files /dev/null and b/conf/base_document/form_template/hsm/软件更改部分.docx differ diff --git a/conf/base_document/form_template/jl/测试用例记录.docx b/conf/base_document/form_template/jl/测试用例记录.docx new file mode 100644 index 0000000..ffd0632 Binary files /dev/null and b/conf/base_document/form_template/jl/测试用例记录.docx differ diff --git a/conf/base_document/form_template/products/回归测试记录.docx b/conf/base_document/form_template/products/回归测试记录.docx new file mode 100644 index 0000000..913866c Binary files /dev/null and b/conf/base_document/form_template/products/回归测试记录.docx differ diff --git a/conf/base_document/form_template/products/回归测试说明.docx b/conf/base_document/form_template/products/回归测试说明.docx new file mode 100644 index 0000000..301b0b8 Binary files /dev/null and b/conf/base_document/form_template/products/回归测试说明.docx differ diff --git a/conf/base_document/form_template/products/测评大纲.docx b/conf/base_document/form_template/products/测评大纲.docx new file mode 100644 index 0000000..067c379 Binary files /dev/null and b/conf/base_document/form_template/products/测评大纲.docx differ diff --git a/conf/base_document/form_template/products/测评报告.docx b/conf/base_document/form_template/products/测评报告.docx new file mode 100644 index 0000000..05290b5 Binary files /dev/null and b/conf/base_document/form_template/products/测评报告.docx differ diff --git a/conf/base_document/form_template/products/测试记录.docx b/conf/base_document/form_template/products/测试记录.docx new file mode 100644 index 0000000..1b0fdcc Binary files /dev/null and b/conf/base_document/form_template/products/测试记录.docx differ diff --git a/conf/base_document/form_template/products/测试说明.docx b/conf/base_document/form_template/products/测试说明.docx new file mode 100644 index 0000000..81325ba Binary files /dev/null and b/conf/base_document/form_template/products/测试说明.docx differ diff --git a/conf/base_document/form_template/products/问题单.docx b/conf/base_document/form_template/products/问题单.docx new file mode 100644 index 0000000..6d7002e Binary files /dev/null and b/conf/base_document/form_template/products/问题单.docx differ diff --git a/conf/base_document/form_template/sm/temporary/说明追踪_temp.docx b/conf/base_document/form_template/sm/temporary/说明追踪_temp.docx new file mode 100644 index 0000000..5d52c31 Binary files /dev/null and b/conf/base_document/form_template/sm/temporary/说明追踪_temp.docx differ diff --git a/conf/base_document/form_template/sm/技术依据文件.docx b/conf/base_document/form_template/sm/技术依据文件.docx new file mode 100644 index 0000000..1bc11ce Binary files /dev/null and b/conf/base_document/form_template/sm/技术依据文件.docx differ diff --git a/conf/base_document/form_template/sm/测试用例.docx b/conf/base_document/form_template/sm/测试用例.docx new file mode 100644 index 0000000..20c5115 Binary files /dev/null and b/conf/base_document/form_template/sm/测试用例.docx differ diff --git a/conf/base_document/form_template/sm/用例说明.docx b/conf/base_document/form_template/sm/用例说明.docx new file mode 100644 index 0000000..abfbdd5 Binary files /dev/null and b/conf/base_document/form_template/sm/用例说明.docx differ diff --git a/conf/base_document/form_template/sm/说明追踪.docx b/conf/base_document/form_template/sm/说明追踪.docx new file mode 100644 index 0000000..f902712 Binary files /dev/null and b/conf/base_document/form_template/sm/说明追踪.docx differ diff --git a/conf/base_document/form_template/wtd/问题详情表.docx b/conf/base_document/form_template/wtd/问题详情表.docx new file mode 100644 index 0000000..d9d3369 Binary files /dev/null and b/conf/base_document/form_template/wtd/问题详情表.docx differ diff --git a/conf/base_document/reuse/basic_doc.docx b/conf/base_document/reuse/basic_doc.docx new file mode 100644 index 0000000..1e42259 Binary files /dev/null and b/conf/base_document/reuse/basic_doc.docx differ diff --git a/conf/env.py b/conf/env.py new file mode 100644 index 0000000..48209fa --- /dev/null +++ b/conf/env.py @@ -0,0 +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", +} +# ================================================= # +# *************** ...........配置 *************** # +# ================================================= # diff --git a/conf/logConfig.py b/conf/logConfig.py new file mode 100644 index 0000000..796164a --- /dev/null +++ b/conf/logConfig.py @@ -0,0 +1,66 @@ +from pathlib import Path + +# 目录根目录 +LOG_DIR = Path.cwd() / 'logs' +if not LOG_DIR.exists(): + LOG_DIR.mkdir(parents=True) + +# 生成文档文件 +LOG_GENERATE_FILE = LOG_DIR / 'generates_logs' + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}}, + "formatters": { + # 详细 + "verbose": { + "format": "%(levelname)s %(asctime)s %(module)s " + "%(process)d %(thread)d %(message)s" + }, + # 简单 + 'simple': { + 'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' + }, + }, + # 两种分发器 + "handlers": { # 定义了三种分发器 + 'root_log_file': { + 'level': "WARNING", + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': LOG_DIR / 'root_log', + 'maxBytes': 1024 * 1024 * 10, + 'backupCount': 10, + 'formatter': 'simple', + 'encoding': 'utf-8', + }, + 'generate_log_file': { + 'level': "INFO", + # 滚动生成日志,切割 + 'class': 'logging.handlers.RotatingFileHandler', + # 日志文件名 + 'filename': LOG_GENERATE_FILE, + # 单个日志文件最大为20M + 'maxBytes': 1024 * 1024 * 20, + # 日志备份文件最大数量30个 + 'backupCount': 30, + # 简单格式 + 'formatter': 'simple', + # 放置中文乱码 + 'encoding': 'utf-8', + }, + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", + }, + }, + "root": {"level": "WARNING", "handlers": ["console", 'root_log_file']}, # Django-root日志默认级别为WARNING + "loggers": { + "generate_document_logger": { + "level": "DEBUG", + "handlers": ["console", "generate_log_file"], + "propagate": True, + }, + }, +} diff --git a/conf/ninja_extra_settings.py b/conf/ninja_extra_settings.py new file mode 100644 index 0000000..f9fb432 --- /dev/null +++ b/conf/ninja_extra_settings.py @@ -0,0 +1,8 @@ +"""该模块主要完成ninja_extra的设置""" +NINJA_EXTRA = { + 'THROTTLE_RATES': { + 'user': '100/s', + 'anon': '10/s' + }, + 'NUM_PROXIES': None, +} diff --git a/db.json b/db.json new file mode 100644 index 0000000..29a7837 --- /dev/null +++ b/db.json @@ -0,0 +1 @@ +[{"model": "auth.permission", "pk": 1, "fields": {"name": "Can add log entry", "content_type": 1, "codename": "add_logentry"}}, {"model": "auth.permission", "pk": 2, "fields": {"name": "Can change log entry", "content_type": 1, "codename": "change_logentry"}}, {"model": "auth.permission", "pk": 3, "fields": {"name": "Can delete log entry", "content_type": 1, "codename": "delete_logentry"}}, {"model": "auth.permission", "pk": 4, "fields": {"name": "Can view log entry", "content_type": 1, "codename": "view_logentry"}}, {"model": "auth.permission", "pk": 5, "fields": {"name": "Can add permission", "content_type": 2, "codename": "add_permission"}}, {"model": "auth.permission", "pk": 6, "fields": {"name": "Can change permission", "content_type": 2, "codename": "change_permission"}}, {"model": "auth.permission", "pk": 7, "fields": {"name": "Can delete permission", "content_type": 2, "codename": "delete_permission"}}, {"model": "auth.permission", "pk": 8, "fields": {"name": "Can view permission", "content_type": 2, "codename": "view_permission"}}, {"model": "auth.permission", "pk": 9, "fields": {"name": "Can add group", "content_type": 3, "codename": "add_group"}}, {"model": "auth.permission", "pk": 10, "fields": {"name": "Can change group", "content_type": 3, "codename": "change_group"}}, {"model": "auth.permission", "pk": 11, "fields": {"name": "Can delete group", "content_type": 3, "codename": "delete_group"}}, {"model": "auth.permission", "pk": 12, "fields": {"name": "Can view group", "content_type": 3, "codename": "view_group"}}, {"model": "auth.permission", "pk": 13, "fields": {"name": "Can add content type", "content_type": 4, "codename": "add_contenttype"}}, {"model": "auth.permission", "pk": 14, "fields": {"name": "Can change content type", "content_type": 4, "codename": "change_contenttype"}}, {"model": "auth.permission", "pk": 15, "fields": {"name": "Can delete content type", "content_type": 4, "codename": "delete_contenttype"}}, {"model": "auth.permission", "pk": 16, "fields": {"name": "Can view content type", "content_type": 4, "codename": "view_contenttype"}}, {"model": "auth.permission", "pk": 17, "fields": {"name": "Can add session", "content_type": 5, "codename": "add_session"}}, {"model": "auth.permission", "pk": 18, "fields": {"name": "Can change session", "content_type": 5, "codename": "change_session"}}, {"model": "auth.permission", "pk": 19, "fields": {"name": "Can delete session", "content_type": 5, "codename": "delete_session"}}, {"model": "auth.permission", "pk": 20, "fields": {"name": "Can view session", "content_type": 5, "codename": "view_session"}}, {"model": "auth.permission", "pk": 21, "fields": {"name": "Can add 用户表", "content_type": 6, "codename": "add_users"}}, {"model": "auth.permission", "pk": 22, "fields": {"name": "Can change 用户表", "content_type": 6, "codename": "change_users"}}, {"model": "auth.permission", "pk": 23, "fields": {"name": "Can delete 用户表", "content_type": 6, "codename": "delete_users"}}, {"model": "auth.permission", "pk": 24, "fields": {"name": "Can view 用户表", "content_type": 6, "codename": "view_users"}}, {"model": "auth.permission", "pk": 25, "fields": {"name": "Can add 字典表", "content_type": 7, "codename": "add_dict"}}, {"model": "auth.permission", "pk": 26, "fields": {"name": "Can change 字典表", "content_type": 7, "codename": "change_dict"}}, {"model": "auth.permission", "pk": 27, "fields": {"name": "Can delete 字典表", "content_type": 7, "codename": "delete_dict"}}, {"model": "auth.permission", "pk": 28, "fields": {"name": "Can view 字典表", "content_type": 7, "codename": "view_dict"}}, {"model": "auth.permission", "pk": 29, "fields": {"name": "Can add 字典表item表", "content_type": 8, "codename": "add_dictitem"}}, {"model": "auth.permission", "pk": 30, "fields": {"name": "Can change 字典表item表", "content_type": 8, "codename": "change_dictitem"}}, {"model": "auth.permission", "pk": 31, "fields": {"name": "Can delete 字典表item表", "content_type": 8, "codename": "delete_dictitem"}}, {"model": "auth.permission", "pk": 32, "fields": {"name": "Can view 字典表item表", "content_type": 8, "codename": "view_dictitem"}}, {"model": "auth.permission", "pk": 33, "fields": {"name": "Can add 项目信息", "content_type": 9, "codename": "add_project"}}, {"model": "auth.permission", "pk": 34, "fields": {"name": "Can change 项目信息", "content_type": 9, "codename": "change_project"}}, {"model": "auth.permission", "pk": 35, "fields": {"name": "Can delete 项目信息", "content_type": 9, "codename": "delete_project"}}, {"model": "auth.permission", "pk": 36, "fields": {"name": "Can view 项目信息", "content_type": 9, "codename": "view_project"}}, {"model": "auth.permission", "pk": 37, "fields": {"name": "Can add 轮次信息", "content_type": 10, "codename": "add_round"}}, {"model": "auth.permission", "pk": 38, "fields": {"name": "Can change 轮次信息", "content_type": 10, "codename": "change_round"}}, {"model": "auth.permission", "pk": 39, "fields": {"name": "Can delete 轮次信息", "content_type": 10, "codename": "delete_round"}}, {"model": "auth.permission", "pk": 40, "fields": {"name": "Can view 轮次信息", "content_type": 10, "codename": "view_round"}}, {"model": "auth.permission", "pk": 41, "fields": {"name": "Can add 被测件信息", "content_type": 11, "codename": "add_dut"}}, {"model": "auth.permission", "pk": 42, "fields": {"name": "Can change 被测件信息", "content_type": 11, "codename": "change_dut"}}, {"model": "auth.permission", "pk": 43, "fields": {"name": "Can delete 被测件信息", "content_type": 11, "codename": "delete_dut"}}, {"model": "auth.permission", "pk": 44, "fields": {"name": "Can view 被测件信息", "content_type": 11, "codename": "view_dut"}}, {"model": "auth.permission", "pk": 45, "fields": {"name": "Can add 测试需求", "content_type": 12, "codename": "add_design"}}, {"model": "auth.permission", "pk": 46, "fields": {"name": "Can change 测试需求", "content_type": 12, "codename": "change_design"}}, {"model": "auth.permission", "pk": 47, "fields": {"name": "Can delete 测试需求", "content_type": 12, "codename": "delete_design"}}, {"model": "auth.permission", "pk": 48, "fields": {"name": "Can view 测试需求", "content_type": 12, "codename": "view_design"}}, {"model": "auth.permission", "pk": 49, "fields": {"name": "Can add 核心模型", "content_type": 13, "codename": "add_testdemand"}}, {"model": "auth.permission", "pk": 50, "fields": {"name": "Can change 核心模型", "content_type": 13, "codename": "change_testdemand"}}, {"model": "auth.permission", "pk": 51, "fields": {"name": "Can delete 核心模型", "content_type": 13, "codename": "delete_testdemand"}}, {"model": "auth.permission", "pk": 52, "fields": {"name": "Can view 核心模型", "content_type": 13, "codename": "view_testdemand"}}, {"model": "auth.permission", "pk": 53, "fields": {"name": "Can add 核心模型", "content_type": 14, "codename": "add_testdemandcontent"}}, {"model": "auth.permission", "pk": 54, "fields": {"name": "Can change 核心模型", "content_type": 14, "codename": "change_testdemandcontent"}}, {"model": "auth.permission", "pk": 55, "fields": {"name": "Can delete 核心模型", "content_type": 14, "codename": "delete_testdemandcontent"}}, {"model": "auth.permission", "pk": 56, "fields": {"name": "Can view 核心模型", "content_type": 14, "codename": "view_testdemandcontent"}}, {"model": "auth.permission", "pk": 57, "fields": {"name": "Can add 核心模型", "content_type": 15, "codename": "add_casestep"}}, {"model": "auth.permission", "pk": 58, "fields": {"name": "Can change 核心模型", "content_type": 15, "codename": "change_casestep"}}, {"model": "auth.permission", "pk": 59, "fields": {"name": "Can delete 核心模型", "content_type": 15, "codename": "delete_casestep"}}, {"model": "auth.permission", "pk": 60, "fields": {"name": "Can view 核心模型", "content_type": 15, "codename": "view_casestep"}}, {"model": "auth.permission", "pk": 61, "fields": {"name": "Can add 测试用例", "content_type": 16, "codename": "add_case"}}, {"model": "auth.permission", "pk": 62, "fields": {"name": "Can change 测试用例", "content_type": 16, "codename": "change_case"}}, {"model": "auth.permission", "pk": 63, "fields": {"name": "Can delete 测试用例", "content_type": 16, "codename": "delete_case"}}, {"model": "auth.permission", "pk": 64, "fields": {"name": "Can view 测试用例", "content_type": 16, "codename": "view_case"}}, {"model": "auth.permission", "pk": 65, "fields": {"name": "Can add 问题单", "content_type": 17, "codename": "add_problem"}}, {"model": "auth.permission", "pk": 66, "fields": {"name": "Can change 问题单", "content_type": 17, "codename": "change_problem"}}, {"model": "auth.permission", "pk": 67, "fields": {"name": "Can delete 问题单", "content_type": 17, "codename": "delete_problem"}}, {"model": "auth.permission", "pk": 68, "fields": {"name": "Can view 问题单", "content_type": 17, "codename": "view_problem"}}, {"model": "auth.permission", "pk": 69, "fields": {"name": "Can add 委托方、研制方、测试方信息", "content_type": 18, "codename": "add_contact"}}, {"model": "auth.permission", "pk": 70, "fields": {"name": "Can change 委托方、研制方、测试方信息", "content_type": 18, "codename": "change_contact"}}, {"model": "auth.permission", "pk": 71, "fields": {"name": "Can delete 委托方、研制方、测试方信息", "content_type": 18, "codename": "delete_contact"}}, {"model": "auth.permission", "pk": 72, "fields": {"name": "Can view 委托方、研制方、测试方信息", "content_type": 18, "codename": "view_contact"}}, {"model": "auth.permission", "pk": 73, "fields": {"name": "Can add 缩略语和行业词汇", "content_type": 19, "codename": "add_abbreviation"}}, {"model": "auth.permission", "pk": 74, "fields": {"name": "Can change 缩略语和行业词汇", "content_type": 19, "codename": "change_abbreviation"}}, {"model": "auth.permission", "pk": 75, "fields": {"name": "Can delete 缩略语和行业词汇", "content_type": 19, "codename": "delete_abbreviation"}}, {"model": "auth.permission", "pk": 76, "fields": {"name": "Can view 缩略语和行业词汇", "content_type": 19, "codename": "view_abbreviation"}}, {"model": "auth.permission", "pk": 77, "fields": {"name": "Can add 用户操作日志表", "content_type": 20, "codename": "add_operationlog"}}, {"model": "auth.permission", "pk": 78, "fields": {"name": "Can change 用户操作日志表", "content_type": 20, "codename": "change_operationlog"}}, {"model": "auth.permission", "pk": 79, "fields": {"name": "Can delete 用户操作日志表", "content_type": 20, "codename": "delete_operationlog"}}, {"model": "auth.permission", "pk": 80, "fields": {"name": "Can view 用户操作日志表", "content_type": 20, "codename": "view_operationlog"}}, {"model": "auth.permission", "pk": 81, "fields": {"name": "Can add 组别", "content_type": 21, "codename": "add_ogroup"}}, {"model": "auth.permission", "pk": 82, "fields": {"name": "Can change 组别", "content_type": 21, "codename": "change_ogroup"}}, {"model": "auth.permission", "pk": 83, "fields": {"name": "Can delete 组别", "content_type": 21, "codename": "delete_ogroup"}}, {"model": "auth.permission", "pk": 84, "fields": {"name": "Can view 组别", "content_type": 21, "codename": "view_ogroup"}}, {"model": "auth.permission", "pk": 85, "fields": {"name": "Can add 组别", "content_type": 21, "codename": "add_department"}}, {"model": "auth.permission", "pk": 86, "fields": {"name": "Can change 组别", "content_type": 21, "codename": "change_department"}}, {"model": "auth.permission", "pk": 87, "fields": {"name": "Can delete 组别", "content_type": 21, "codename": "delete_department"}}, {"model": "auth.permission", "pk": 88, "fields": {"name": "Can view 组别", "content_type": 21, "codename": "view_department"}}, {"model": "auth.permission", "pk": 89, "fields": {"name": "Can add 登录日志", "content_type": 22, "codename": "add_loginlog"}}, {"model": "auth.permission", "pk": 90, "fields": {"name": "Can change 登录日志", "content_type": 22, "codename": "change_loginlog"}}, {"model": "auth.permission", "pk": 91, "fields": {"name": "Can delete 登录日志", "content_type": 22, "codename": "delete_loginlog"}}, {"model": "auth.permission", "pk": 92, "fields": {"name": "Can view 登录日志", "content_type": 22, "codename": "view_loginlog"}}, {"model": "auth.permission", "pk": 93, "fields": {"name": "Can add 操作日志", "content_type": 23, "codename": "add_operationlog"}}, {"model": "auth.permission", "pk": 94, "fields": {"name": "Can change 操作日志", "content_type": 23, "codename": "change_operationlog"}}, {"model": "auth.permission", "pk": 95, "fields": {"name": "Can delete 操作日志", "content_type": 23, "codename": "delete_operationlog"}}, {"model": "auth.permission", "pk": 96, "fields": {"name": "Can view 操作日志", "content_type": 23, "codename": "view_operationlog"}}, {"model": "auth.permission", "pk": 97, "fields": {"name": "Can add 地区表", "content_type": 24, "codename": "add_area"}}, {"model": "auth.permission", "pk": 98, "fields": {"name": "Can change 地区表", "content_type": 24, "codename": "change_area"}}, {"model": "auth.permission", "pk": 99, "fields": {"name": "Can delete 地区表", "content_type": 24, "codename": "delete_area"}}, {"model": "auth.permission", "pk": 100, "fields": {"name": "Can view 地区表", "content_type": 24, "codename": "view_area"}}, {"model": "auth.permission", "pk": 101, "fields": {"name": "Can add 用户操作日志表", "content_type": 25, "codename": "add_tableoperationlog"}}, {"model": "auth.permission", "pk": 102, "fields": {"name": "Can change 用户操作日志表", "content_type": 25, "codename": "change_tableoperationlog"}}, {"model": "auth.permission", "pk": 103, "fields": {"name": "Can delete 用户操作日志表", "content_type": 25, "codename": "delete_tableoperationlog"}}, {"model": "auth.permission", "pk": 104, "fields": {"name": "Can view 用户操作日志表", "content_type": 25, "codename": "view_tableoperationlog"}}, {"model": "auth.permission", "pk": 105, "fields": {"name": "Can add 用户字段表", "content_type": 26, "codename": "add_userdictfield"}}, {"model": "auth.permission", "pk": 106, "fields": {"name": "Can change 用户字段表", "content_type": 26, "codename": "change_userdictfield"}}, {"model": "auth.permission", "pk": 107, "fields": {"name": "Can delete 用户字段表", "content_type": 26, "codename": "delete_userdictfield"}}, {"model": "auth.permission", "pk": 108, "fields": {"name": "Can view 用户字段表", "content_type": 26, "codename": "view_userdictfield"}}, {"model": "auth.permission", "pk": 109, "fields": {"name": "Can add 文档片段", "content_type": 27, "codename": "add_fragment"}}, {"model": "auth.permission", "pk": 110, "fields": {"name": "Can change 文档片段", "content_type": 27, "codename": "change_fragment"}}, {"model": "auth.permission", "pk": 111, "fields": {"name": "Can delete 文档片段", "content_type": 27, "codename": "delete_fragment"}}, {"model": "auth.permission", "pk": 112, "fields": {"name": "Can view 文档片段", "content_type": 27, "codename": "view_fragment"}}, {"model": "auth.permission", "pk": 113, "fields": {"name": "Can add 图片", "content_type": 28, "codename": "add_picturefield"}}, {"model": "auth.permission", "pk": 114, "fields": {"name": "Can change 图片", "content_type": 28, "codename": "change_picturefield"}}, {"model": "auth.permission", "pk": 115, "fields": {"name": "Can delete 图片", "content_type": 28, "codename": "delete_picturefield"}}, {"model": "auth.permission", "pk": 116, "fields": {"name": "Can view 图片", "content_type": 28, "codename": "view_picturefield"}}, {"model": "auth.permission", "pk": 117, "fields": {"name": "Can add 图片", "content_type": 29, "codename": "add_tablefield"}}, {"model": "auth.permission", "pk": 118, "fields": {"name": "Can change 图片", "content_type": 29, "codename": "change_tablefield"}}, {"model": "auth.permission", "pk": 119, "fields": {"name": "Can delete 图片", "content_type": 29, "codename": "delete_tablefield"}}, {"model": "auth.permission", "pk": 120, "fields": {"name": "Can view 图片", "content_type": 29, "codename": "view_tablefield"}}, {"model": "auth.permission", "pk": 121, "fields": {"name": "Can add 储存当行文本", "content_type": 30, "codename": "add_textfield"}}, {"model": "auth.permission", "pk": 122, "fields": {"name": "Can change 储存当行文本", "content_type": 30, "codename": "change_textfield"}}, {"model": "auth.permission", "pk": 123, "fields": {"name": "Can delete 储存当行文本", "content_type": 30, "codename": "delete_textfield"}}, {"model": "auth.permission", "pk": 124, "fields": {"name": "Can view 储存当行文本", "content_type": 30, "codename": "view_textfield"}}, {"model": "auth.permission", "pk": 125, "fields": {"name": "Can add 储存当行文本", "content_type": 31, "codename": "add_wordfield"}}, {"model": "auth.permission", "pk": 126, "fields": {"name": "Can change 储存当行文本", "content_type": 31, "codename": "change_wordfield"}}, {"model": "auth.permission", "pk": 127, "fields": {"name": "Can delete 储存当行文本", "content_type": 31, "codename": "delete_wordfield"}}, {"model": "auth.permission", "pk": 128, "fields": {"name": "Can view 储存当行文本", "content_type": 31, "codename": "view_wordfield"}}, {"model": "auth.group", "pk": 1, "fields": {"name": "信息化", "permissions": []}}, {"model": "auth.group", "pk": 2, "fields": {"name": "成都-CPU组", "permissions": []}}, {"model": "auth.group", "pk": 3, "fields": {"name": "上海-CPU组", "permissions": []}}, {"model": "auth.group", "pk": 4, "fields": {"name": "北京-CPU组", "permissions": []}}, {"model": "auth.group", "pk": 5, "fields": {"name": "成都-FPGA组", "permissions": []}}, {"model": "auth.group", "pk": 6, "fields": {"name": "上海-FPGA组", "permissions": []}}, {"model": "auth.group", "pk": 7, "fields": {"name": "北京-FPGA组", "permissions": []}}, {"model": "auth.group", "pk": 8, "fields": {"name": "成都-开发组", "permissions": []}}, {"model": "auth.group", "pk": 9, "fields": {"name": "上海-开发组", "permissions": []}}, {"model": "auth.group", "pk": 10, "fields": {"name": "北京-开发组", "permissions": []}}, {"model": "auth.group", "pk": 11, "fields": {"name": "成都-商务组", "permissions": []}}, {"model": "auth.group", "pk": 12, "fields": {"name": "上海-商务组", "permissions": []}}, {"model": "auth.group", "pk": 13, "fields": {"name": "北京-商务组", "permissions": []}}, {"model": "auth.group", "pk": 14, "fields": {"name": "成都-综合组", "permissions": []}}, {"model": "auth.group", "pk": 15, "fields": {"name": "上海-综合组", "permissions": []}}, {"model": "auth.group", "pk": 16, "fields": {"name": "北京-综合组", "permissions": []}}, {"model": "auth.group", "pk": 17, "fields": {"name": "成都-质量组", "permissions": []}}, {"model": "auth.group", "pk": 18, "fields": {"name": "上海-质量组", "permissions": []}}, {"model": "auth.group", "pk": 19, "fields": {"name": "北京-质量组", "permissions": []}}, {"model": "auth.group", "pk": 20, "fields": {"name": "其他", "permissions": []}}, {"model": "contenttypes.contenttype", "pk": 1, "fields": {"app_label": "admin", "model": "logentry"}}, {"model": "contenttypes.contenttype", "pk": 2, "fields": {"app_label": "auth", "model": "permission"}}, {"model": "contenttypes.contenttype", "pk": 3, "fields": {"app_label": "auth", "model": "group"}}, {"model": "contenttypes.contenttype", "pk": 4, "fields": {"app_label": "contenttypes", "model": "contenttype"}}, {"model": "contenttypes.contenttype", "pk": 5, "fields": {"app_label": "sessions", "model": "session"}}, {"model": "contenttypes.contenttype", "pk": 6, "fields": {"app_label": "user", "model": "users"}}, {"model": "contenttypes.contenttype", "pk": 7, "fields": {"app_label": "dict", "model": "dict"}}, {"model": "contenttypes.contenttype", "pk": 8, "fields": {"app_label": "dict", "model": "dictitem"}}, {"model": "contenttypes.contenttype", "pk": 9, "fields": {"app_label": "project", "model": "project"}}, {"model": "contenttypes.contenttype", "pk": 10, "fields": {"app_label": "project", "model": "round"}}, {"model": "contenttypes.contenttype", "pk": 11, "fields": {"app_label": "project", "model": "dut"}}, {"model": "contenttypes.contenttype", "pk": 12, "fields": {"app_label": "project", "model": "design"}}, {"model": "contenttypes.contenttype", "pk": 13, "fields": {"app_label": "project", "model": "testdemand"}}, {"model": "contenttypes.contenttype", "pk": 14, "fields": {"app_label": "project", "model": "testdemandcontent"}}, {"model": "contenttypes.contenttype", "pk": 15, "fields": {"app_label": "project", "model": "casestep"}}, {"model": "contenttypes.contenttype", "pk": 16, "fields": {"app_label": "project", "model": "case"}}, {"model": "contenttypes.contenttype", "pk": 17, "fields": {"app_label": "project", "model": "problem"}}, {"model": "contenttypes.contenttype", "pk": 18, "fields": {"app_label": "project", "model": "contact"}}, {"model": "contenttypes.contenttype", "pk": 19, "fields": {"app_label": "project", "model": "abbreviation"}}, {"model": "contenttypes.contenttype", "pk": 20, "fields": {"app_label": "user", "model": "operationlog"}}, {"model": "contenttypes.contenttype", "pk": 21, "fields": {"app_label": "user", "model": "department"}}, {"model": "contenttypes.contenttype", "pk": 22, "fields": {"app_label": "system", "model": "loginlog"}}, {"model": "contenttypes.contenttype", "pk": 23, "fields": {"app_label": "system", "model": "operationlog"}}, {"model": "contenttypes.contenttype", "pk": 24, "fields": {"app_label": "system", "model": "area"}}, {"model": "contenttypes.contenttype", "pk": 25, "fields": {"app_label": "user", "model": "tableoperationlog"}}, {"model": "contenttypes.contenttype", "pk": 26, "fields": {"app_label": "dict", "model": "userdictfield"}}, {"model": "contenttypes.contenttype", "pk": 27, "fields": {"app_label": "dict", "model": "fragment"}}, {"model": "contenttypes.contenttype", "pk": 28, "fields": {"app_label": "dict", "model": "picturefield"}}, {"model": "contenttypes.contenttype", "pk": 29, "fields": {"app_label": "dict", "model": "tablefield"}}, {"model": "contenttypes.contenttype", "pk": 30, "fields": {"app_label": "dict", "model": "textfield"}}, {"model": "contenttypes.contenttype", "pk": 31, "fields": {"app_label": "dict", "model": "wordfield"}}, {"model": "user.users", "pk": 1, "fields": {"password": "pbkdf2_sha256$600000$gu81jenwlVuq4CwKQf7WmK$Om4BUJyYRJ5BupFSi6y/EzVoDzBeRRNHkmWloofdXGs=", "last_login": "2024-02-19T09:55:45.199", "is_superuser": true, "first_name": "chen", "last_name": "junyi", "email": "314298729@qq.com", "is_staff": true, "is_active": true, "date_joined": "2023-07-21T11:53:02.181", "remark": "这是超级用户", "update_datetime": "2024-07-15", "create_datetime": "2023-07-21", "sort": 1, "username": "superAdmin", "name": "陈俊亦", "avatar": "//lf1-xgcdn-tos.pstatp.com/obj/vcloud/vadmin/start.8e0e4855ee346a46ccff8ff3e24db27b.png", "status": "1", "job": "前端艺术家", "jobName": "Frontend", "organization": "chengdu", "location": "成都", "locationName": "四川省", "introduction": "这是我的自我介绍", "personalWebsite": "https://www.arco.design", "phone": "15012312300", "accountId": "1", "role": "admin", "groups": [], "user_permissions": []}}, {"model": "user.users", "pk": 34, "fields": {"password": "pbkdf2_sha256$600000$0pulQALmYmvXJhA2LbXvcy$zkm0DO+U2uXJTUXKTdYMg1LKCgfx4Me3lNyeMC3acGs=", "last_login": null, "is_superuser": false, "first_name": "", "last_name": "", "email": "xxx@qq.com", "is_staff": false, "is_active": true, "date_joined": "2024-07-15T13:54:27.734", "remark": null, "update_datetime": "2024-10-16", "create_datetime": "2024-07-15", "sort": 1, "username": "newUser", "name": "某质量人员", "avatar": null, "status": "1", "job": null, "jobName": null, "organization": null, "location": null, "locationName": null, "introduction": null, "personalWebsite": null, "phone": "18888888888", "accountId": "1", "role": "user", "groups": [], "user_permissions": []}}, {"model": "user.users", "pk": 40, "fields": {"password": "pbkdf2_sha256$600000$vZqiqPTuSjcBXjPg9MRAvI$wUc20+LqFvHiTyv8wdmmHk1MM9ODxMPQyyFxF/UZ44U=", "last_login": null, "is_superuser": true, "first_name": "", "last_name": "", "email": "xxx@qq.com", "is_staff": false, "is_active": true, "date_joined": "2024-08-12T19:28:18.270", "remark": null, "update_datetime": "2024-10-16", "create_datetime": "2024-08-12", "sort": 1, "username": "test1", "name": "某测试人员1", "avatar": null, "status": "1", "job": null, "jobName": null, "organization": null, "location": null, "locationName": null, "introduction": null, "personalWebsite": null, "phone": "18888888888", "accountId": "1", "role": "user", "groups": [], "user_permissions": []}}, {"model": "user.users", "pk": 41, "fields": {"password": "pbkdf2_sha256$600000$S47LRVjwwqR284yCiTab2X$nw3w+pa0KMp2bk4+bHwtAKupJd45y5BwAh+A3fGLG88=", "last_login": null, "is_superuser": false, "first_name": "", "last_name": "", "email": "xxx@qq.com", "is_staff": false, "is_active": true, "date_joined": "2024-09-05T10:56:29.147", "remark": null, "update_datetime": "2024-10-16", "create_datetime": "2024-09-05", "sort": 1, "username": "test2", "name": "某测试人员2", "avatar": null, "status": "1", "job": null, "jobName": null, "organization": null, "location": null, "locationName": null, "introduction": null, "personalWebsite": null, "phone": "18782947123", "accountId": "1", "role": "user", "groups": [], "user_permissions": []}}, {"model": "user.users", "pk": 42, "fields": {"password": "pbkdf2_sha256$600000$fZ4dxfyTgXNOgpEwvQ9Fzx$RC0LPSWmV9SK3jOaiHond61JvZNQeQYTuQ3qalmIyi0=", "last_login": null, "is_superuser": false, "first_name": "", "last_name": "", "email": "xxx@qq.com", "is_staff": false, "is_active": true, "date_joined": "2024-10-16T09:21:37.846", "remark": null, "update_datetime": "2024-10-16", "create_datetime": "2024-10-16", "sort": 1, "username": "test3", "name": "某测试人员3", "avatar": null, "status": "1", "job": null, "jobName": null, "organization": null, "location": null, "locationName": null, "introduction": null, "personalWebsite": null, "phone": "18888888888", "accountId": "1", "role": "user", "groups": [], "user_permissions": []}}, {"model": "user.users", "pk": 43, "fields": {"password": "pbkdf2_sha256$600000$DjXkQeJxHK2KlC5jw2j0OL$NJG9WfUArSoIF3jgYAjoRfUJq2CUx/ngkP3gHZQOcNg=", "last_login": null, "is_superuser": false, "first_name": "", "last_name": "", "email": "xxx@qq.com", "is_staff": false, "is_active": true, "date_joined": "2024-10-21T15:07:35.849", "remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "username": "test4", "name": "某测试人员4", "avatar": null, "status": "1", "job": null, "jobName": null, "organization": null, "location": null, "locationName": null, "introduction": null, "personalWebsite": null, "phone": "18223451321", "accountId": "1", "role": "user", "groups": [], "user_permissions": []}}, {"model": "user.tableoperationlog", "pk": 7591, "fields": {"create_datetime": "2024-10-16T09:16:15.587", "user": 1, "operate_obj": "项目R2319-XXX测试鉴定", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7592, "fields": {"create_datetime": "2024-10-16T09:16:15.598", "user": 1, "operate_obj": "第1轮次", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7593, "fields": {"create_datetime": "2024-10-16T09:20:39.271", "user": 1, "operate_obj": "第1轮次", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7594, "fields": {"create_datetime": "2024-10-16T09:20:39.273", "user": 1, "operate_obj": "项目R2319-XXX测试鉴定", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7595, "fields": {"create_datetime": "2024-10-16T09:21:53.896", "user": 1, "operate_obj": "问题单:1-问题单1号", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7596, "fields": {"create_datetime": "2024-10-16T09:21:53.897", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7597, "fields": {"create_datetime": "2024-10-16T09:21:53.898", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7598, "fields": {"create_datetime": "2024-10-16T09:21:53.899", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7599, "fields": {"create_datetime": "2024-10-16T09:21:53.899", "user": 1, "operate_obj": "测试用例:新test111", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7600, "fields": {"create_datetime": "2024-10-16T09:21:53.900", "user": 1, "operate_obj": "测试用例:测试子项11", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7601, "fields": {"create_datetime": "2024-10-16T09:21:53.900", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7602, "fields": {"create_datetime": "2024-10-16T09:21:53.901", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7603, "fields": {"create_datetime": "2024-10-16T09:21:53.901", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7604, "fields": {"create_datetime": "2024-10-16T09:21:53.902", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7605, "fields": {"create_datetime": "2024-10-16T09:21:53.903", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7606, "fields": {"create_datetime": "2024-10-16T09:21:53.904", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7607, "fields": {"create_datetime": "2024-10-16T09:21:53.904", "user": 1, "operate_obj": "测试项:2号测试项", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7608, "fields": {"create_datetime": "2024-10-16T09:21:53.905", "user": 1, "operate_obj": "测试项:1号测试项", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7609, "fields": {"create_datetime": "2024-10-16T09:21:53.906", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7610, "fields": {"create_datetime": "2024-10-16T09:21:53.906", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7611, "fields": {"create_datetime": "2024-10-16T09:21:53.906", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7612, "fields": {"create_datetime": "2024-10-16T09:21:53.908", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7613, "fields": {"create_datetime": "2024-10-16T09:21:53.908", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7614, "fields": {"create_datetime": "2024-10-16T09:21:53.909", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7615, "fields": {"create_datetime": "2024-10-16T09:21:53.909", "user": 1, "operate_obj": "设计需求:test设计", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7616, "fields": {"create_datetime": "2024-10-16T09:21:53.910", "user": 1, "operate_obj": "设计需求:设计需求测试1号A", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7617, "fields": {"create_datetime": "2024-10-16T09:21:53.910", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7618, "fields": {"create_datetime": "2024-10-16T09:21:53.911", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7619, "fields": {"create_datetime": "2024-10-16T09:21:53.911", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7620, "fields": {"create_datetime": "2024-10-16T09:21:53.912", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7621, "fields": {"create_datetime": "2024-10-16T09:21:53.913", "user": 1, "operate_obj": "被测件:软件需求分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7622, "fields": {"create_datetime": "2024-10-16T09:21:53.913", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7623, "fields": {"create_datetime": "2024-10-16T09:21:53.914", "user": 1, "operate_obj": "第2轮次", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7624, "fields": {"create_datetime": "2024-10-16T09:21:53.915", "user": 1, "operate_obj": "第1轮次", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7625, "fields": {"create_datetime": "2024-10-16T09:21:53.916", "user": 1, "operate_obj": "项目R2323-陈之项目", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7626, "fields": {"create_datetime": "2024-10-16T09:21:56.399", "user": 1, "operate_obj": "问题单:1-111", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7627, "fields": {"create_datetime": "2024-10-16T09:21:56.401", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7628, "fields": {"create_datetime": "2024-10-16T09:21:56.401", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7629, "fields": {"create_datetime": "2024-10-16T09:21:56.402", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7630, "fields": {"create_datetime": "2024-10-16T09:21:56.402", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7631, "fields": {"create_datetime": "2024-10-16T09:21:56.403", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7632, "fields": {"create_datetime": "2024-10-16T09:21:56.403", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7633, "fields": {"create_datetime": "2024-10-16T09:21:56.404", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7634, "fields": {"create_datetime": "2024-10-16T09:21:56.404", "user": 1, "operate_obj": "测试用例:代码审查111", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7635, "fields": {"create_datetime": "2024-10-16T09:21:56.405", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7636, "fields": {"create_datetime": "2024-10-16T09:21:56.407", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7637, "fields": {"create_datetime": "2024-10-16T09:21:56.408", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7638, "fields": {"create_datetime": "2024-10-16T09:21:56.408", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7639, "fields": {"create_datetime": "2024-10-16T09:21:56.409", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7640, "fields": {"create_datetime": "2024-10-16T09:21:56.409", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7641, "fields": {"create_datetime": "2024-10-16T09:21:56.409", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7642, "fields": {"create_datetime": "2024-10-16T09:21:56.410", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7643, "fields": {"create_datetime": "2024-10-16T09:21:56.410", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7644, "fields": {"create_datetime": "2024-10-16T09:21:56.411", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7645, "fields": {"create_datetime": "2024-10-16T09:21:56.412", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7646, "fields": {"create_datetime": "2024-10-16T09:21:56.413", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7647, "fields": {"create_datetime": "2024-10-16T09:21:56.413", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7648, "fields": {"create_datetime": "2024-10-16T09:21:56.413", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7649, "fields": {"create_datetime": "2024-10-16T09:21:56.414", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7650, "fields": {"create_datetime": "2024-10-16T09:21:56.414", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7651, "fields": {"create_datetime": "2024-10-16T09:21:56.415", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7652, "fields": {"create_datetime": "2024-10-16T09:21:56.415", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7653, "fields": {"create_datetime": "2024-10-16T09:21:56.416", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7654, "fields": {"create_datetime": "2024-10-16T09:21:56.417", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7655, "fields": {"create_datetime": "2024-10-16T09:21:56.417", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7656, "fields": {"create_datetime": "2024-10-16T09:21:56.418", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7657, "fields": {"create_datetime": "2024-10-16T09:21:56.419", "user": 1, "operate_obj": "第3轮次", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7658, "fields": {"create_datetime": "2024-10-16T09:21:56.419", "user": 1, "operate_obj": "第2轮次", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7659, "fields": {"create_datetime": "2024-10-16T09:21:56.420", "user": 1, "operate_obj": "第1轮次", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7660, "fields": {"create_datetime": "2024-10-16T09:21:56.421", "user": 1, "operate_obj": "项目R9527-123", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7661, "fields": {"create_datetime": "2024-10-16T09:23:07.749", "user": 1, "operate_obj": "联系方式:某委托方单位", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7662, "fields": {"create_datetime": "2024-10-16T09:23:07.751", "user": 1, "operate_obj": "联系方式:某委托方单位", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7663, "fields": {"create_datetime": "2024-10-16T09:23:31.351", "user": 1, "operate_obj": "联系方式:某测试方单位", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7664, "fields": {"create_datetime": "2024-10-16T09:23:31.352", "user": 1, "operate_obj": "联系方式:某测试方单位", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7665, "fields": {"create_datetime": "2024-10-16T09:25:35.481", "user": 1, "operate_obj": "项目R2424-某型号项目鉴定", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7666, "fields": {"create_datetime": "2024-10-16T09:25:35.484", "user": 1, "operate_obj": "第1轮次", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7667, "fields": {"create_datetime": "2024-10-16T09:29:28.400", "user": 1, "operate_obj": "项目R2424-某型号项目鉴定", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7668, "fields": {"create_datetime": "2024-10-16T09:29:36.215", "user": 1, "operate_obj": "项目R2424-某型号项目鉴定", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7669, "fields": {"create_datetime": "2024-10-16T09:29:40.180", "user": 1, "operate_obj": "项目R2424-某型号项目鉴定", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7670, "fields": {"create_datetime": "2024-10-16T09:30:25.272", "user": 1, "operate_obj": "项目R2424-某型号项目鉴定", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7671, "fields": {"create_datetime": "2024-10-16T09:30:34.386", "user": 1, "operate_obj": "项目R2424-某型号项目鉴定", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7672, "fields": {"create_datetime": "2024-10-16T09:33:52.283", "user": 1, "operate_obj": "项目R2425-某型号项目", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7673, "fields": {"create_datetime": "2024-10-16T09:33:52.285", "user": 1, "operate_obj": "第1轮次", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7674, "fields": {"create_datetime": "2024-10-16T09:43:30.768", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7675, "fields": {"create_datetime": "2024-10-16T09:43:30.774", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7676, "fields": {"create_datetime": "2024-10-16T09:43:30.787", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7677, "fields": {"create_datetime": "2024-10-16T09:43:30.792", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7678, "fields": {"create_datetime": "2024-10-16T09:43:30.796", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7679, "fields": {"create_datetime": "2024-10-16T09:43:30.797", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7680, "fields": {"create_datetime": "2024-10-16T09:43:30.800", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7681, "fields": {"create_datetime": "2024-10-16T09:43:30.805", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7682, "fields": {"create_datetime": "2024-10-16T09:43:30.808", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7683, "fields": {"create_datetime": "2024-10-16T09:43:30.812", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7684, "fields": {"create_datetime": "2024-10-16T09:50:56.143", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7685, "fields": {"create_datetime": "2024-10-16T09:50:56.144", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7686, "fields": {"create_datetime": "2024-10-16T09:50:56.145", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7687, "fields": {"create_datetime": "2024-10-16T09:50:56.146", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7688, "fields": {"create_datetime": "2024-10-16T09:50:56.146", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7689, "fields": {"create_datetime": "2024-10-16T09:50:56.147", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7690, "fields": {"create_datetime": "2024-10-16T09:50:56.148", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7691, "fields": {"create_datetime": "2024-10-16T09:50:56.149", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7692, "fields": {"create_datetime": "2024-10-16T09:50:56.149", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7693, "fields": {"create_datetime": "2024-10-16T09:50:56.150", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7694, "fields": {"create_datetime": "2024-10-16T09:50:56.151", "user": 1, "operate_obj": "第1轮次", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7695, "fields": {"create_datetime": "2024-10-16T09:50:56.152", "user": 1, "operate_obj": "项目R2424-某型号项目鉴定", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7696, "fields": {"create_datetime": "2024-10-16T09:50:58.583", "user": 1, "operate_obj": "第1轮次", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7697, "fields": {"create_datetime": "2024-10-16T09:50:58.584", "user": 1, "operate_obj": "项目R2425-某型号项目", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7698, "fields": {"create_datetime": "2024-10-16T09:54:56.676", "user": 1, "operate_obj": "项目R2428-某型号鉴定项目", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7699, "fields": {"create_datetime": "2024-10-16T09:54:56.678", "user": 1, "operate_obj": "第1轮次", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7700, "fields": {"create_datetime": "2024-10-16T09:59:02.433", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7701, "fields": {"create_datetime": "2024-10-16T09:59:02.443", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7702, "fields": {"create_datetime": "2024-10-16T09:59:02.445", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7703, "fields": {"create_datetime": "2024-10-16T09:59:02.452", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7704, "fields": {"create_datetime": "2024-10-16T09:59:02.457", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7705, "fields": {"create_datetime": "2024-10-16T09:59:02.461", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7706, "fields": {"create_datetime": "2024-10-16T09:59:02.466", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7707, "fields": {"create_datetime": "2024-10-16T09:59:02.471", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7708, "fields": {"create_datetime": "2024-10-16T09:59:02.474", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7709, "fields": {"create_datetime": "2024-10-16T09:59:02.477", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7710, "fields": {"create_datetime": "2024-10-16T10:04:06.498", "user": 1, "operate_obj": "被测件:某型号项目需求说明书", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7711, "fields": {"create_datetime": "2024-10-16T10:05:51.217", "user": 1, "operate_obj": "第2轮次", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7712, "fields": {"create_datetime": "2024-10-16T10:06:17.499", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7713, "fields": {"create_datetime": "2024-10-16T10:06:17.504", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7714, "fields": {"create_datetime": "2024-10-16T10:06:17.508", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7715, "fields": {"create_datetime": "2024-10-16T10:06:17.511", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7716, "fields": {"create_datetime": "2024-10-16T10:06:17.516", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7717, "fields": {"create_datetime": "2024-10-16T10:06:17.518", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7718, "fields": {"create_datetime": "2024-10-16T10:06:17.520", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7719, "fields": {"create_datetime": "2024-10-16T10:06:17.525", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7720, "fields": {"create_datetime": "2024-10-16T10:06:17.527", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7721, "fields": {"create_datetime": "2024-10-16T10:06:17.529", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7722, "fields": {"create_datetime": "2024-10-16T10:10:00.491", "user": 1, "operate_obj": "第3轮次", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7723, "fields": {"create_datetime": "2024-10-16T10:10:00.506", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7724, "fields": {"create_datetime": "2024-10-16T10:10:00.510", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7725, "fields": {"create_datetime": "2024-10-16T10:10:00.519", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7726, "fields": {"create_datetime": "2024-10-16T10:10:00.529", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7727, "fields": {"create_datetime": "2024-10-16T10:10:00.533", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7728, "fields": {"create_datetime": "2024-10-16T10:10:00.538", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7729, "fields": {"create_datetime": "2024-10-16T10:10:00.543", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7730, "fields": {"create_datetime": "2024-10-16T10:10:00.546", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7731, "fields": {"create_datetime": "2024-10-16T10:10:00.549", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7732, "fields": {"create_datetime": "2024-10-16T10:10:00.557", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7733, "fields": {"create_datetime": "2024-10-16T10:10:00.561", "user": 1, "operate_obj": "被测件:某型号项目需求说明书", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7734, "fields": {"create_datetime": "2024-10-16T10:13:05.660", "user": 1, "operate_obj": "被测件:某设计说明", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7735, "fields": {"create_datetime": "2024-10-16T10:16:20.003", "user": 1, "operate_obj": "设计需求:初始化功能", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7736, "fields": {"create_datetime": "2024-10-16T10:25:44.793", "user": 1, "operate_obj": "设计需求:UART总线通讯管理(RQGN01-UART)", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7737, "fields": {"create_datetime": "2024-10-16T10:25:44.805", "user": 1, "operate_obj": "设计需求:UART总线通讯管理(RQGN01-UART)", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7738, "fields": {"create_datetime": "2024-10-16T10:25:44.816", "user": 1, "operate_obj": "设计需求:解析并执行注入指令(RQGN01-UART-ZRZL)", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7739, "fields": {"create_datetime": "2024-10-16T10:25:44.828", "user": 1, "operate_obj": "设计需求:解析并执行注入指令(RQGN01-UART-ZRZL)", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7740, "fields": {"create_datetime": "2024-10-16T10:25:44.839", "user": 1, "operate_obj": "设计需求:解析并执行注入指令(RQGN01-UART-ZRZL)", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7741, "fields": {"create_datetime": "2024-10-16T10:25:44.849", "user": 1, "operate_obj": "设计需求:解析并执行注入指令(RQGN01-UART-ZRZL)", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7742, "fields": {"create_datetime": "2024-10-16T10:25:44.861", "user": 1, "operate_obj": "设计需求:解析并执行注入指令(RQGN01-UART-ZRZL)", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7743, "fields": {"create_datetime": "2024-10-16T10:35:24.282", "user": 1, "operate_obj": "设计需求:初始化功能", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7744, "fields": {"create_datetime": "2024-10-16T10:38:57.338", "user": 1, "operate_obj": "测试项:初始化功能测试", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7745, "fields": {"create_datetime": "2024-10-16T10:43:14.285", "user": 1, "operate_obj": "测试用例:初始化功能正常测试", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7746, "fields": {"create_datetime": "2024-10-16T10:43:14.294", "user": 1, "operate_obj": "测试用例:初始化功能异常测试", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7747, "fields": {"create_datetime": "2024-10-16T10:48:37.852", "user": 1, "operate_obj": "测试用例:初始化正常功能X变量测试", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7748, "fields": {"create_datetime": "2024-10-16T10:52:12.808", "user": 1, "operate_obj": "测试用例:初始化功能正常测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7749, "fields": {"create_datetime": "2024-10-16T10:52:56.418", "user": 1, "operate_obj": "问题单:1-初始化时X变量错误", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7750, "fields": {"create_datetime": "2024-10-16T10:52:56.423", "user": 1, "operate_obj": "问题单:1-初始化时X变量错误", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7751, "fields": {"create_datetime": "2024-10-16T10:52:56.429", "user": 1, "operate_obj": "问题单:1-初始化时X变量错误", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7752, "fields": {"create_datetime": "2024-10-16T10:53:10.287", "user": 1, "operate_obj": "问题单:1-初始化时X变量错误", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7753, "fields": {"create_datetime": "2024-10-16T10:58:06.117", "user": 1, "operate_obj": "问题单:2-初始化Y变量错误", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7754, "fields": {"create_datetime": "2024-10-16T10:58:06.121", "user": 1, "operate_obj": "问题单:2-初始化Y变量错误", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7755, "fields": {"create_datetime": "2024-10-16T11:06:21.114", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7756, "fields": {"create_datetime": "2024-10-16T11:06:21.116", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7757, "fields": {"create_datetime": "2024-10-16T11:06:21.116", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7758, "fields": {"create_datetime": "2024-10-16T11:06:21.118", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7759, "fields": {"create_datetime": "2024-10-16T11:06:21.118", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7760, "fields": {"create_datetime": "2024-10-16T11:06:21.119", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7761, "fields": {"create_datetime": "2024-10-16T11:06:21.121", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7762, "fields": {"create_datetime": "2024-10-16T11:06:21.121", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7763, "fields": {"create_datetime": "2024-10-16T11:06:21.122", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7764, "fields": {"create_datetime": "2024-10-16T11:06:21.125", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7765, "fields": {"create_datetime": "2024-10-16T11:06:21.127", "user": 1, "operate_obj": "第2轮次", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7766, "fields": {"create_datetime": "2024-10-16T11:06:21.130", "user": 1, "operate_obj": "第1轮次", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7767, "fields": {"create_datetime": "2024-10-16T11:06:21.133", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7768, "fields": {"create_datetime": "2024-10-16T11:06:21.136", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7769, "fields": {"create_datetime": "2024-10-16T11:06:21.138", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7770, "fields": {"create_datetime": "2024-10-16T11:06:21.140", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7771, "fields": {"create_datetime": "2024-10-16T11:06:21.142", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7772, "fields": {"create_datetime": "2024-10-16T11:06:21.144", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7773, "fields": {"create_datetime": "2024-10-16T11:06:21.147", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7774, "fields": {"create_datetime": "2024-10-16T11:06:21.148", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7775, "fields": {"create_datetime": "2024-10-16T11:06:21.150", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7776, "fields": {"create_datetime": "2024-10-16T11:06:21.153", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7777, "fields": {"create_datetime": "2024-10-16T11:06:21.154", "user": 1, "operate_obj": "被测件:某型号项目需求说明书", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7778, "fields": {"create_datetime": "2024-10-16T11:06:21.156", "user": 1, "operate_obj": "设计需求:初始化功能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7779, "fields": {"create_datetime": "2024-10-16T11:06:21.159", "user": 1, "operate_obj": "测试项:初始化功能测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7780, "fields": {"create_datetime": "2024-10-16T11:06:21.161", "user": 1, "operate_obj": "测试用例:初始化功能正常测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7781, "fields": {"create_datetime": "2024-10-16T11:06:21.163", "user": 1, "operate_obj": "测试用例:初始化功能异常测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7782, "fields": {"create_datetime": "2024-10-16T11:06:21.164", "user": 1, "operate_obj": "测试用例:初始化正常功能X变量测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7783, "fields": {"create_datetime": "2024-10-16T11:06:21.165", "user": 1, "operate_obj": "被测件:某设计说明", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7784, "fields": {"create_datetime": "2024-10-16T11:06:21.168", "user": 1, "operate_obj": "设计需求:初始化功能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7785, "fields": {"create_datetime": "2024-10-16T11:06:21.170", "user": 1, "operate_obj": "第2轮次", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7786, "fields": {"create_datetime": "2024-10-16T11:06:21.173", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7787, "fields": {"create_datetime": "2024-10-16T11:06:21.175", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7788, "fields": {"create_datetime": "2024-10-16T11:06:21.181", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7789, "fields": {"create_datetime": "2024-10-16T11:06:21.184", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7790, "fields": {"create_datetime": "2024-10-16T11:06:21.185", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7791, "fields": {"create_datetime": "2024-10-16T11:06:21.187", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7792, "fields": {"create_datetime": "2024-10-16T11:06:21.190", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7793, "fields": {"create_datetime": "2024-10-16T11:06:21.191", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7794, "fields": {"create_datetime": "2024-10-16T11:06:21.193", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7795, "fields": {"create_datetime": "2024-10-16T11:06:21.196", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7796, "fields": {"create_datetime": "2024-10-16T11:06:21.198", "user": 1, "operate_obj": "被测件:某型号项目需求说明书", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7797, "fields": {"create_datetime": "2024-10-16T11:06:28.986", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7798, "fields": {"create_datetime": "2024-10-16T11:06:28.987", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7799, "fields": {"create_datetime": "2024-10-16T11:06:28.987", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7800, "fields": {"create_datetime": "2024-10-16T11:06:28.988", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7801, "fields": {"create_datetime": "2024-10-16T11:06:28.988", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7802, "fields": {"create_datetime": "2024-10-16T11:06:28.989", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7803, "fields": {"create_datetime": "2024-10-16T11:06:28.990", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7804, "fields": {"create_datetime": "2024-10-16T11:06:28.990", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7805, "fields": {"create_datetime": "2024-10-16T11:06:28.991", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7806, "fields": {"create_datetime": "2024-10-16T11:06:28.992", "user": 1, "operate_obj": "被测件:某型号项目需求说明书", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7807, "fields": {"create_datetime": "2024-10-16T11:06:28.992", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7808, "fields": {"create_datetime": "2024-10-16T11:06:28.993", "user": 1, "operate_obj": "第2轮次", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7809, "fields": {"create_datetime": "2024-10-16T11:06:28.996", "user": 1, "operate_obj": "第1轮次", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7810, "fields": {"create_datetime": "2024-10-16T11:06:28.999", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7811, "fields": {"create_datetime": "2024-10-16T11:06:29.001", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7812, "fields": {"create_datetime": "2024-10-16T11:06:29.003", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7813, "fields": {"create_datetime": "2024-10-16T11:06:29.006", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7814, "fields": {"create_datetime": "2024-10-16T11:06:29.007", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7815, "fields": {"create_datetime": "2024-10-16T11:06:29.009", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7816, "fields": {"create_datetime": "2024-10-16T11:06:29.013", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7817, "fields": {"create_datetime": "2024-10-16T11:06:29.014", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7818, "fields": {"create_datetime": "2024-10-16T11:06:29.016", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7819, "fields": {"create_datetime": "2024-10-16T11:06:29.019", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7820, "fields": {"create_datetime": "2024-10-16T11:06:29.020", "user": 1, "operate_obj": "被测件:某型号项目需求说明书", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7821, "fields": {"create_datetime": "2024-10-16T11:06:29.023", "user": 1, "operate_obj": "设计需求:初始化功能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7822, "fields": {"create_datetime": "2024-10-16T11:06:29.025", "user": 1, "operate_obj": "测试项:初始化功能测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7823, "fields": {"create_datetime": "2024-10-16T11:06:29.027", "user": 1, "operate_obj": "测试用例:初始化功能正常测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7824, "fields": {"create_datetime": "2024-10-16T11:06:29.029", "user": 1, "operate_obj": "测试用例:初始化功能异常测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7825, "fields": {"create_datetime": "2024-10-16T11:06:29.031", "user": 1, "operate_obj": "测试用例:初始化正常功能X变量测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7826, "fields": {"create_datetime": "2024-10-16T11:06:29.032", "user": 1, "operate_obj": "被测件:某设计说明", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7827, "fields": {"create_datetime": "2024-10-16T11:06:29.034", "user": 1, "operate_obj": "设计需求:初始化功能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7828, "fields": {"create_datetime": "2024-10-16T11:07:10.615", "user": 1, "operate_obj": "测试用例:初始化功能正常测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7829, "fields": {"create_datetime": "2024-10-16T11:07:23.356", "user": 1, "operate_obj": "测试用例:初始化功能异常测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7830, "fields": {"create_datetime": "2024-10-16T11:07:27.125", "user": 1, "operate_obj": "测试用例:初始化功能正常测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7831, "fields": {"create_datetime": "2024-10-16T11:07:35.267", "user": 1, "operate_obj": "测试用例:初始化正常功能X变量测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7832, "fields": {"create_datetime": "2024-10-16T11:07:55.551", "user": 1, "operate_obj": "问题单:1-初始化时X变量错误", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7833, "fields": {"create_datetime": "2024-10-16T11:23:21.621", "user": 1, "operate_obj": "字典项名称:因果法-字典项显示名称:yg", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7834, "fields": {"create_datetime": "2024-10-16T11:23:52.173", "user": 1, "operate_obj": "测试项:初始化功能测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7835, "fields": {"create_datetime": "2024-10-16T13:40:09.057", "user": 1, "operate_obj": "项目R2428-某型号鉴定项目", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7836, "fields": {"create_datetime": "2024-10-16T14:13:32.134", "user": null, "operate_obj": "Migration 0005_remove_fragment_unique_name_belong_doc_and_more for dict", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7837, "fields": {"create_datetime": "2024-10-16T14:13:42.569", "user": 1, "operate_obj": "Fragment object (14)", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7838, "fields": {"create_datetime": "2024-10-16T14:13:56.849", "user": 1, "operate_obj": "Fragment object (14)", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7839, "fields": {"create_datetime": "2024-10-16T14:14:33.704", "user": 1, "operate_obj": "Fragment object (15)", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7840, "fields": {"create_datetime": "2024-10-16T14:16:00.970", "user": 1, "operate_obj": "Fragment object (15)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7841, "fields": {"create_datetime": "2024-10-16T14:17:48.593", "user": 1, "operate_obj": "Fragment object (6)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7842, "fields": {"create_datetime": "2024-10-16T14:17:51.606", "user": 1, "operate_obj": "Fragment object (6)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7843, "fields": {"create_datetime": "2024-10-16T14:19:58.345", "user": 1, "operate_obj": "Fragment object (6)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7844, "fields": {"create_datetime": "2024-10-16T14:19:59.851", "user": 1, "operate_obj": "Fragment object (6)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7845, "fields": {"create_datetime": "2024-10-16T14:20:00.383", "user": 1, "operate_obj": "Fragment object (6)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7846, "fields": {"create_datetime": "2024-10-16T14:20:02.435", "user": 1, "operate_obj": "Fragment object (8)", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7847, "fields": {"create_datetime": "2024-10-17T10:09:21.195", "user": 1, "operate_obj": "设计需求:初始化功能", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7848, "fields": {"create_datetime": "2024-10-17T10:09:21.200", "user": 1, "operate_obj": "被测件:某设计说明", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7849, "fields": {"create_datetime": "2024-10-17T10:09:21.204", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7850, "fields": {"create_datetime": "2024-10-17T10:09:21.207", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7851, "fields": {"create_datetime": "2024-10-17T10:09:21.210", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7852, "fields": {"create_datetime": "2024-10-17T10:09:21.216", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7853, "fields": {"create_datetime": "2024-10-17T10:09:21.219", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7854, "fields": {"create_datetime": "2024-10-17T10:09:21.222", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7855, "fields": {"create_datetime": "2024-10-17T10:09:21.224", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7856, "fields": {"create_datetime": "2024-10-17T10:09:21.226", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7857, "fields": {"create_datetime": "2024-10-17T10:09:21.229", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7858, "fields": {"create_datetime": "2024-10-17T10:09:21.232", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7859, "fields": {"create_datetime": "2024-10-17T10:09:21.235", "user": 1, "operate_obj": "被测件:某型号项目需求说明书", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7860, "fields": {"create_datetime": "2024-10-17T10:09:21.238", "user": 1, "operate_obj": "设计需求:初始化功能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7861, "fields": {"create_datetime": "2024-10-17T10:09:21.241", "user": 1, "operate_obj": "测试项:初始化功能测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7862, "fields": {"create_datetime": "2024-10-17T10:09:21.244", "user": 1, "operate_obj": "测试用例:初始化功能正常测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7863, "fields": {"create_datetime": "2024-10-17T10:09:21.246", "user": 1, "operate_obj": "测试用例:初始化功能异常测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7864, "fields": {"create_datetime": "2024-10-17T10:09:21.247", "user": 1, "operate_obj": "测试用例:初始化正常功能X变量测试", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7865, "fields": {"create_datetime": "2024-10-17T10:10:43.196", "user": 1, "operate_obj": "设计需求:与星务软件RS422通信波特率性能", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7866, "fields": {"create_datetime": "2024-10-17T10:11:24.651", "user": 1, "operate_obj": "设计需求:与数传RS422通信波特率性能", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7867, "fields": {"create_datetime": "2024-10-17T10:11:33.574", "user": 1, "operate_obj": "设计需求:与星务软件RS422通信波特率性能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7868, "fields": {"create_datetime": "2024-10-17T10:18:32.997", "user": 1, "operate_obj": "设计需求:与星务软件RS422通信波特率性能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7869, "fields": {"create_datetime": "2024-10-17T10:18:37.048", "user": 1, "operate_obj": "设计需求:与数传RS422通信波特率性能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7870, "fields": {"create_datetime": "2024-10-17T10:25:00.583", "user": 1, "operate_obj": "设计需求:与数传RS422通信波特率性能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7871, "fields": {"create_datetime": "2024-10-17T10:26:41.526", "user": 1, "operate_obj": "设计需求:与数传RS422通信波特率性能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7872, "fields": {"create_datetime": "2024-10-17T10:36:21.931", "user": 1, "operate_obj": "设计需求:与数传RS422通信波特率性能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7873, "fields": {"create_datetime": "2024-10-17T10:37:54.804", "user": 1, "operate_obj": "设计需求:与数传RS422通信波特率性能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7874, "fields": {"create_datetime": "2024-10-17T10:41:59.159", "user": 1, "operate_obj": "设计需求:与数传RS422通信波特率性能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7875, "fields": {"create_datetime": "2024-10-17T10:42:14.909", "user": 1, "operate_obj": "设计需求:与数传RS422通信波特率性能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7876, "fields": {"create_datetime": "2024-10-17T10:42:37.813", "user": 1, "operate_obj": "设计需求:与数传RS422通信波特率性能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7877, "fields": {"create_datetime": "2024-10-17T10:43:02.223", "user": 1, "operate_obj": "设计需求:与数传RS422通信波特率性能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7878, "fields": {"create_datetime": "2024-10-17T10:43:15.567", "user": 1, "operate_obj": "设计需求:与数传RS422通信波特率性能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7879, "fields": {"create_datetime": "2024-10-17T10:47:31.120", "user": 1, "operate_obj": "设计需求:与数传RS422通信波特率性能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7880, "fields": {"create_datetime": "2024-10-17T10:51:09.694", "user": 1, "operate_obj": "设计需求:与数传RS422通信波特率性能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7881, "fields": {"create_datetime": "2024-10-21T15:10:56.495", "user": 1, "operate_obj": "Fragment object (15)", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7882, "fields": {"create_datetime": "2024-10-21T15:10:56.502", "user": 1, "operate_obj": "问题单:2-初始化Y变量错误", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7883, "fields": {"create_datetime": "2024-10-21T15:10:56.503", "user": 1, "operate_obj": "问题单:1-初始化时X变量错误", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7884, "fields": {"create_datetime": "2024-10-21T15:10:56.505", "user": 1, "operate_obj": "测试用例:初始化正常功能X变量测试", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7885, "fields": {"create_datetime": "2024-10-21T15:10:56.505", "user": 1, "operate_obj": "测试用例:初始化功能异常测试", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7886, "fields": {"create_datetime": "2024-10-21T15:10:56.506", "user": 1, "operate_obj": "测试用例:初始化功能正常测试", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7887, "fields": {"create_datetime": "2024-10-21T15:10:56.507", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7888, "fields": {"create_datetime": "2024-10-21T15:10:56.508", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7889, "fields": {"create_datetime": "2024-10-21T15:10:56.509", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7890, "fields": {"create_datetime": "2024-10-21T15:10:56.511", "user": 1, "operate_obj": "测试项:初始化功能测试", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7891, "fields": {"create_datetime": "2024-10-21T15:10:56.511", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7892, "fields": {"create_datetime": "2024-10-21T15:10:56.512", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7893, "fields": {"create_datetime": "2024-10-21T15:10:56.513", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7894, "fields": {"create_datetime": "2024-10-21T15:10:56.514", "user": 1, "operate_obj": "设计需求:与数传RS422通信波特率性能", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7895, "fields": {"create_datetime": "2024-10-21T15:10:56.515", "user": 1, "operate_obj": "设计需求:与星务软件RS422通信波特率性能", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7896, "fields": {"create_datetime": "2024-10-21T15:10:56.516", "user": 1, "operate_obj": "设计需求:初始化功能", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7897, "fields": {"create_datetime": "2024-10-21T15:10:56.516", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7898, "fields": {"create_datetime": "2024-10-21T15:10:56.517", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7899, "fields": {"create_datetime": "2024-10-21T15:10:56.517", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7900, "fields": {"create_datetime": "2024-10-21T15:10:56.519", "user": 1, "operate_obj": "被测件:某型号项目需求说明书", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7901, "fields": {"create_datetime": "2024-10-21T15:10:56.519", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7902, "fields": {"create_datetime": "2024-10-21T15:10:56.521", "user": 1, "operate_obj": "第1轮次", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7903, "fields": {"create_datetime": "2024-10-21T15:10:56.523", "user": 1, "operate_obj": "项目R2428-某型号鉴定项目", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7904, "fields": {"create_datetime": "2024-10-21T15:13:14.412", "user": 1, "operate_obj": "项目R2486-某型号项目", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7905, "fields": {"create_datetime": "2024-10-21T15:13:14.416", "user": 1, "operate_obj": "第1轮次", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7906, "fields": {"create_datetime": "2024-10-21T15:14:02.074", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7907, "fields": {"create_datetime": "2024-10-21T15:14:02.087", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7908, "fields": {"create_datetime": "2024-10-21T15:14:02.090", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7909, "fields": {"create_datetime": "2024-10-21T15:14:02.095", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7910, "fields": {"create_datetime": "2024-10-21T15:14:02.100", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7911, "fields": {"create_datetime": "2024-10-21T15:14:02.104", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7912, "fields": {"create_datetime": "2024-10-21T15:14:02.109", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7913, "fields": {"create_datetime": "2024-10-21T15:14:02.118", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7914, "fields": {"create_datetime": "2024-10-21T15:14:02.121", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7915, "fields": {"create_datetime": "2024-10-21T15:14:02.125", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7916, "fields": {"create_datetime": "2024-10-21T15:29:38.876", "user": 1, "operate_obj": "被测件:需求规格说明", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7917, "fields": {"create_datetime": "2024-10-21T15:40:17.553", "user": 1, "operate_obj": "被测件:需求规格说明", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7918, "fields": {"create_datetime": "2024-10-21T15:40:17.568", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7919, "fields": {"create_datetime": "2024-10-21T15:40:17.572", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7920, "fields": {"create_datetime": "2024-10-21T15:40:17.575", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7921, "fields": {"create_datetime": "2024-10-21T15:40:17.579", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7922, "fields": {"create_datetime": "2024-10-21T15:40:17.581", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7923, "fields": {"create_datetime": "2024-10-21T15:40:17.584", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7924, "fields": {"create_datetime": "2024-10-21T15:40:17.587", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7925, "fields": {"create_datetime": "2024-10-21T15:40:17.588", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7926, "fields": {"create_datetime": "2024-10-21T15:40:17.591", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7927, "fields": {"create_datetime": "2024-10-21T15:40:17.595", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7928, "fields": {"create_datetime": "2024-10-21T15:40:57.696", "user": 1, "operate_obj": "第2轮次", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7929, "fields": {"create_datetime": "2024-10-21T15:41:14.889", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7930, "fields": {"create_datetime": "2024-10-21T15:41:14.899", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7931, "fields": {"create_datetime": "2024-10-21T15:41:14.903", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7932, "fields": {"create_datetime": "2024-10-21T15:41:14.908", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7933, "fields": {"create_datetime": "2024-10-21T15:41:14.921", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7934, "fields": {"create_datetime": "2024-10-21T15:41:14.926", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7935, "fields": {"create_datetime": "2024-10-21T15:41:14.931", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7936, "fields": {"create_datetime": "2024-10-21T15:41:14.936", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7937, "fields": {"create_datetime": "2024-10-21T15:41:14.939", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7938, "fields": {"create_datetime": "2024-10-21T15:41:14.945", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7939, "fields": {"create_datetime": "2024-10-21T15:41:34.995", "user": 1, "operate_obj": "第3轮次", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7940, "fields": {"create_datetime": "2024-10-21T15:41:34.999", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7941, "fields": {"create_datetime": "2024-10-21T15:41:35.004", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7942, "fields": {"create_datetime": "2024-10-21T15:41:35.017", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7943, "fields": {"create_datetime": "2024-10-21T15:41:35.031", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7944, "fields": {"create_datetime": "2024-10-21T15:41:35.041", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7945, "fields": {"create_datetime": "2024-10-21T15:41:35.051", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7946, "fields": {"create_datetime": "2024-10-21T15:41:35.061", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7947, "fields": {"create_datetime": "2024-10-21T15:41:47.638", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7948, "fields": {"create_datetime": "2024-10-21T15:41:47.640", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7949, "fields": {"create_datetime": "2024-10-21T15:41:47.641", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7950, "fields": {"create_datetime": "2024-10-21T15:41:47.644", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7951, "fields": {"create_datetime": "2024-10-21T15:41:47.645", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7952, "fields": {"create_datetime": "2024-10-21T15:41:47.646", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7953, "fields": {"create_datetime": "2024-10-21T15:41:47.649", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7954, "fields": {"create_datetime": "2024-10-21T15:41:47.651", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7955, "fields": {"create_datetime": "2024-10-21T15:41:47.653", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7956, "fields": {"create_datetime": "2024-10-21T15:41:47.656", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7957, "fields": {"create_datetime": "2024-10-21T15:41:47.657", "user": 1, "operate_obj": "第2轮次", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7958, "fields": {"create_datetime": "2024-10-21T15:41:47.665", "user": 1, "operate_obj": "第1轮次", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7959, "fields": {"create_datetime": "2024-10-21T15:41:47.671", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7960, "fields": {"create_datetime": "2024-10-21T15:41:47.676", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7961, "fields": {"create_datetime": "2024-10-21T15:41:47.682", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7962, "fields": {"create_datetime": "2024-10-21T15:41:47.688", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7963, "fields": {"create_datetime": "2024-10-21T15:41:47.690", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7964, "fields": {"create_datetime": "2024-10-21T15:41:47.697", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7965, "fields": {"create_datetime": "2024-10-21T15:41:47.701", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7966, "fields": {"create_datetime": "2024-10-21T15:41:47.703", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7967, "fields": {"create_datetime": "2024-10-21T15:41:47.706", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7968, "fields": {"create_datetime": "2024-10-21T15:41:47.712", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7969, "fields": {"create_datetime": "2024-10-21T15:41:47.714", "user": 1, "operate_obj": "第2轮次", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7970, "fields": {"create_datetime": "2024-10-21T15:41:47.717", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7971, "fields": {"create_datetime": "2024-10-21T15:41:47.722", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7972, "fields": {"create_datetime": "2024-10-21T15:41:47.728", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7973, "fields": {"create_datetime": "2024-10-21T15:41:47.741", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7974, "fields": {"create_datetime": "2024-10-21T15:41:47.744", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7975, "fields": {"create_datetime": "2024-10-21T15:41:47.747", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7976, "fields": {"create_datetime": "2024-10-21T15:41:47.751", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7977, "fields": {"create_datetime": "2024-10-21T15:41:49.403", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7978, "fields": {"create_datetime": "2024-10-21T15:41:49.404", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7979, "fields": {"create_datetime": "2024-10-21T15:41:49.407", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7980, "fields": {"create_datetime": "2024-10-21T15:41:49.408", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7981, "fields": {"create_datetime": "2024-10-21T15:41:49.411", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7982, "fields": {"create_datetime": "2024-10-21T15:41:49.414", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7983, "fields": {"create_datetime": "2024-10-21T15:41:49.417", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7984, "fields": {"create_datetime": "2024-10-21T15:41:49.420", "user": 1, "operate_obj": "第2轮次", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7985, "fields": {"create_datetime": "2024-10-21T15:41:49.428", "user": 1, "operate_obj": "第1轮次", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7986, "fields": {"create_datetime": "2024-10-21T15:41:49.434", "user": 1, "operate_obj": "被测件:软件源代码", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7987, "fields": {"create_datetime": "2024-10-21T15:41:49.440", "user": 1, "operate_obj": "设计需求:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7988, "fields": {"create_datetime": "2024-10-21T15:41:49.448", "user": 1, "operate_obj": "测试项:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7989, "fields": {"create_datetime": "2024-10-21T15:41:49.454", "user": 1, "operate_obj": "测试用例:静态分析", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7990, "fields": {"create_datetime": "2024-10-21T15:41:49.456", "user": 1, "operate_obj": "设计需求:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7991, "fields": {"create_datetime": "2024-10-21T15:41:49.465", "user": 1, "operate_obj": "测试项:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7992, "fields": {"create_datetime": "2024-10-21T15:41:49.470", "user": 1, "operate_obj": "测试用例:代码审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7993, "fields": {"create_datetime": "2024-10-21T15:41:49.474", "user": 1, "operate_obj": "设计需求:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7994, "fields": {"create_datetime": "2024-10-21T15:41:49.483", "user": 1, "operate_obj": "测试项:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7995, "fields": {"create_datetime": "2024-10-21T15:41:49.494", "user": 1, "operate_obj": "测试用例:文档审查", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7996, "fields": {"create_datetime": "2024-10-21T15:42:21.564", "user": 1, "operate_obj": "被测件:需求规格说明", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 7997, "fields": {"create_datetime": "2024-10-21T15:42:53.132", "user": 1, "operate_obj": "设计需求:解析并执行注入指令(RQGN01-UART-ZRZL)", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 7998, "fields": {"create_datetime": "2024-10-21T15:42:53.136", "user": 1, "operate_obj": "设计需求:总线通讯管理(RQGN01-UART)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 7999, "fields": {"create_datetime": "2024-10-21T15:42:53.139", "user": 1, "operate_obj": "设计需求:总线通讯管理(RQGN01-UART)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8000, "fields": {"create_datetime": "2024-10-21T15:42:53.146", "user": 1, "operate_obj": "设计需求:解析并执行注入指令(RQGN01-UART-ZRZL)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8001, "fields": {"create_datetime": "2024-10-21T15:42:53.151", "user": 1, "operate_obj": "设计需求:解析并执行注入指令(RQGN01-UART-ZRZL)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8002, "fields": {"create_datetime": "2024-10-21T15:42:53.156", "user": 1, "operate_obj": "设计需求:解析并执行注入指令(RQGN01-UART-ZRZL)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8003, "fields": {"create_datetime": "2024-10-21T15:42:53.164", "user": 1, "operate_obj": "设计需求:解析并执行注入指令(RQGN01-UART-ZRZL)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8004, "fields": {"create_datetime": "2024-10-21T15:42:54.811", "user": 1, "operate_obj": "设计需求:解析并执行注入指令(RQGN01-UART-ZRZL)", "operate_des": "删除"}}, {"model": "user.tableoperationlog", "pk": 8005, "fields": {"create_datetime": "2024-10-21T15:42:54.815", "user": 1, "operate_obj": "设计需求:总线通讯管理(RQGN01-UART)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8006, "fields": {"create_datetime": "2024-10-21T15:42:54.818", "user": 1, "operate_obj": "设计需求:总线通讯管理(RQGN01-UART)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8007, "fields": {"create_datetime": "2024-10-21T15:42:54.823", "user": 1, "operate_obj": "设计需求:解析并执行注入指令(RQGN01-UART-ZRZL)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8008, "fields": {"create_datetime": "2024-10-21T15:42:54.830", "user": 1, "operate_obj": "设计需求:解析并执行注入指令(RQGN01-UART-ZRZL)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8009, "fields": {"create_datetime": "2024-10-21T15:42:54.833", "user": 1, "operate_obj": "设计需求:解析并执行注入指令(RQGN01-UART-ZRZL)", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8010, "fields": {"create_datetime": "2024-10-21T15:43:40.849", "user": 1, "operate_obj": "设计需求:初始化功能", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 8011, "fields": {"create_datetime": "2024-10-21T15:45:26.527", "user": 1, "operate_obj": "测试项:初始化正常功能测试", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 8012, "fields": {"create_datetime": "2024-10-21T15:45:40.502", "user": 1, "operate_obj": "测试用例:初始化x变量", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 8013, "fields": {"create_datetime": "2024-10-21T15:45:40.511", "user": 1, "operate_obj": "测试用例:初始化y变量", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 8014, "fields": {"create_datetime": "2024-10-21T15:46:48.040", "user": 1, "operate_obj": "测试用例:初始化x变量异常功能", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 8015, "fields": {"create_datetime": "2024-10-21T15:52:47.039", "user": 1, "operate_obj": "测试用例:初始化x变量异常功能", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8016, "fields": {"create_datetime": "2024-10-21T15:53:46.613", "user": 1, "operate_obj": "问题单:1-初始化x变量异常未报错", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 8017, "fields": {"create_datetime": "2024-10-21T15:53:46.615", "user": 1, "operate_obj": "问题单:1-初始化x变量异常未报错", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8018, "fields": {"create_datetime": "2024-10-21T15:53:46.622", "user": 1, "operate_obj": "问题单:1-初始化x变量异常未报错", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8019, "fields": {"create_datetime": "2024-10-21T15:53:54.614", "user": 1, "operate_obj": "问题单:1-初始化x变量异常未报错", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8020, "fields": {"create_datetime": "2024-10-21T15:54:47.182", "user": 1, "operate_obj": "问题单:2-无关联问题单x变量", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 8021, "fields": {"create_datetime": "2024-10-21T15:54:47.187", "user": 1, "operate_obj": "问题单:2-无关联问题单x变量", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8022, "fields": {"create_datetime": "2024-10-21T15:58:06.855", "user": 1, "operate_obj": "字典项名称:window10-字典项显示名称:win10", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 8023, "fields": {"create_datetime": "2024-10-21T15:58:19.753", "user": 1, "operate_obj": "项目R2486-某型号项目", "operate_des": "修改"}}, {"model": "user.tableoperationlog", "pk": 8024, "fields": {"create_datetime": "2024-10-21T16:00:13.495", "user": 1, "operate_obj": "Fragment object (16)", "operate_des": "新增"}}, {"model": "user.tableoperationlog", "pk": 8025, "fields": {"create_datetime": "2024-10-21T16:00:16.103", "user": 1, "operate_obj": "Fragment object (16)", "operate_des": "修改"}}, {"model": "dict.dict", "pk": 1, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-06-26", "sort": 1, "name": "data_status", "code": "data_status", "status": "1", "remark": "用户状态字典"}}, {"model": "dict.dict", "pk": 2, "fields": {"update_datetime": "2024-06-20", "create_datetime": "2023-06-26", "sort": 1, "name": "report_type", "code": "report_type", "status": "1", "remark": "报告类型字典"}}, {"model": "dict.dict", "pk": 3, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "step", "code": "step", "status": "1", "remark": "测评阶段"}}, {"model": "dict.dict", "pk": 4, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "security_level", "code": "security_level", "status": "1", "remark": "安全等级"}}, {"model": "dict.dict", "pk": 5, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "test_level", "code": "test_level", "status": "1", "remark": "测试级别"}}, {"model": "dict.dict", "pk": 6, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "plant_type", "code": "plant_type", "status": "1", "remark": "平台类型"}}, {"model": "dict.dict", "pk": 7, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "language", "code": "language", "status": "1", "remark": "编程语言"}}, {"model": "dict.dict", "pk": 8, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "standard", "code": "standard", "status": "1", "remark": "测试标准"}}, {"model": "dict.dict", "pk": 9, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "demandType", "code": "demandType", "status": "1", "remark": "需求类型"}}, {"model": "dict.dict", "pk": 10, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "priority", "code": "priority", "status": "1", "remark": "优先级"}}, {"model": "dict.dict", "pk": 11, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "testType", "code": "testType", "status": "1", "remark": "测试类型"}}, {"model": "dict.dict", "pk": 12, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "passType", "code": "passType", "status": "1", "remark": "通过类型"}}, {"model": "dict.dict", "pk": 13, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "execType", "code": "execType", "status": "1", "remark": "用例执行情况"}}, {"model": "dict.dict", "pk": 14, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "problemStatu", "code": "problemStatu", "status": "1", "remark": "问题状态"}}, {"model": "dict.dict", "pk": 15, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "problemType", "code": "problemType", "status": "1", "remark": "问题类型"}}, {"model": "dict.dict", "pk": 16, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "problemGrade", "code": "problemGrade", "status": "1", "remark": "问题级别"}}, {"model": "dict.dict", "pk": 17, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "name": "closeMethod", "code": "closeMethod", "status": "1", "remark": "问题闭环方式"}}, {"model": "dict.dict", "pk": 18, "fields": {"update_datetime": "2024-06-20", "create_datetime": "2023-08-24", "sort": 1, "name": "testMethod", "code": "testMethod", "status": "1", "remark": "测试方法"}}, {"model": "dict.dict", "pk": 19, "fields": {"update_datetime": "2024-08-13", "create_datetime": "2024-03-21", "sort": 1, "name": "runtime", "code": "runtime", "status": "1", "remark": "运行环境"}}, {"model": "dict.dict", "pk": 20, "fields": {"update_datetime": "2024-06-20", "create_datetime": "2024-03-21", "sort": 1, "name": "devplant", "code": "devplant", "status": "1", "remark": "开发环境"}}, {"model": "dict.dict", "pk": 21, "fields": {"update_datetime": "2024-09-02", "create_datetime": "2024-09-02", "sort": 1, "name": "secret", "code": "secret", "status": "1", "remark": "密级"}}, {"model": "dict.dictitem", "pk": 1, "fields": {"update_datetime": "2024-06-20", "create_datetime": "2023-06-26", "sort": 1, "title": "正常", "key": "1", "show_title": "", "status": "1", "dict": 1, "remark": "用户正常状态", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 2, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "停用", "key": "2", "show_title": "", "status": "1", "dict": 1, "remark": "用户停用状态", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 3, "fields": {"update_datetime": "2024-06-11", "create_datetime": "2023-06-26", "sort": 1, "title": "FPGA软件仿真验证报告(二方)", "key": "1", "show_title": "ST", "status": "1", "dict": 2, "remark": "FPGA仿真验证报告", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 4, "fields": {"update_datetime": "2024-06-11", "create_datetime": "2023-06-26", "sort": 1, "title": "FPGA软件配置项三方测评报告", "key": "2", "show_title": "TR", "status": "1", "dict": 2, "remark": "FPGA三方测评报告", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 7, "fields": {"update_datetime": "2024-06-11", "create_datetime": "2023-06-26", "sort": 1, "title": "FPGA软件配置项确认测试报告(二方)", "key": "3", "show_title": "VT", "status": "1", "dict": 2, "remark": "FPGA二方确认", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 8, "fields": {"update_datetime": "2024-06-11", "create_datetime": "2023-06-26", "sort": 1, "title": "CPU软件单元测试报告(二方)", "key": "4", "show_title": "UT", "status": "1", "dict": 2, "remark": "CPU单元二方", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 9, "fields": {"update_datetime": "2024-06-11", "create_datetime": "2023-06-26", "sort": 1, "title": "CPU软件部件测试报告(二方)", "key": "5", "show_title": "PT", "status": "1", "dict": 2, "remark": "CPU部件二方", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 11, "fields": {"update_datetime": "2024-06-11", "create_datetime": "2023-06-26", "sort": 1, "title": "CPU软件配置项三方测评报告", "key": "6", "show_title": "R", "status": "1", "dict": 2, "remark": "CPU配置项三方", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 12, "fields": {"update_datetime": "2024-06-11", "create_datetime": "2023-06-26", "sort": 1, "title": "CPU软件配置项确认测试报告(二方)", "key": "7", "show_title": "CT", "status": "1", "dict": 2, "remark": "CPU确认测试二方", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 13, "fields": {"update_datetime": "2024-06-11", "create_datetime": "2023-06-26", "sort": 1, "title": "系统测试报告(二方)", "key": "8", "show_title": "ST", "status": "1", "dict": 2, "remark": "系统测试二方", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 14, "fields": {"update_datetime": "2024-02-28", "create_datetime": "2023-06-26", "sort": 1, "title": "鉴定测评报告", "key": "9", "show_title": "JD", "status": "1", "dict": 2, "remark": "鉴定测评报告", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 15, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-06-26", "sort": 1, "title": "刚开始", "key": "1", "show_title": "", "status": "1", "dict": 3, "remark": "刚开始", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 16, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "进行中", "key": "2", "show_title": "", "status": "1", "dict": 3, "remark": "进行中", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 17, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "已完成", "key": "3", "show_title": "", "status": "1", "dict": 3, "remark": "已完成", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 18, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "已废除", "key": "4", "show_title": "", "status": "1", "dict": 3, "remark": "已废除", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 19, "fields": {"update_datetime": "2024-02-28", "create_datetime": "2023-06-26", "sort": 1, "title": "A", "key": "1", "show_title": "重要", "status": "1", "dict": 4, "remark": "A", "doc_name": "", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 20, "fields": {"update_datetime": "2024-02-28", "create_datetime": "2023-06-26", "sort": 1, "title": "B", "key": "2", "show_title": "关键", "status": "1", "dict": 4, "remark": "B", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 21, "fields": {"update_datetime": "2024-02-28", "create_datetime": "2023-06-26", "sort": 1, "title": "C", "key": "3", "show_title": "一般", "status": "1", "dict": 4, "remark": "C", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 22, "fields": {"update_datetime": "2024-02-28", "create_datetime": "2023-06-26", "sort": 1, "title": "D", "key": "4", "show_title": "不重要", "status": "1", "dict": 4, "remark": "D", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 23, "fields": {"update_datetime": "2024-06-11", "create_datetime": "2023-06-26", "sort": 1, "title": "系统测试", "key": "1", "show_title": "system", "status": "1", "dict": 5, "remark": "分系统测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 24, "fields": {"update_datetime": "2024-06-11", "create_datetime": "2023-06-26", "sort": 1, "title": "单元测试", "key": "2", "show_title": "unit", "status": "1", "dict": 5, "remark": "单元测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 25, "fields": {"update_datetime": "2024-06-11", "create_datetime": "2023-06-26", "sort": 1, "title": "单元集成测试", "key": "3", "show_title": "unit-Integration", "status": "1", "dict": 5, "remark": "子系统测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 26, "fields": {"update_datetime": "2024-06-11", "create_datetime": "2023-06-26", "sort": 1, "title": "配置项测试", "key": "4", "show_title": "CSCI", "status": "1", "dict": 5, "remark": "系统测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 27, "fields": {"update_datetime": "2024-06-11", "create_datetime": "2023-06-26", "sort": 1, "title": "配置项集成测试", "key": "5", "show_title": "CSCI-integration", "status": "1", "dict": 5, "remark": "部件测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 28, "fields": {"update_datetime": "2024-06-11", "create_datetime": "2023-06-26", "sort": 1, "title": "分系统测试", "key": "6", "show_title": "sub-system", "status": "1", "dict": 5, "remark": "配置项测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 29, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-06-26", "sort": 5, "title": "可编程逻辑器件", "key": "1", "show_title": "", "status": "1", "dict": 6, "remark": "可编程逻辑器件", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 30, "fields": {"update_datetime": "2024-02-19", "create_datetime": "2023-06-26", "sort": 1, "title": "嵌入式处理器", "key": "2", "show_title": "", "status": "1", "dict": 6, "remark": "嵌入式处理器", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 31, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "嵌入式操作系统", "key": "3", "show_title": "", "status": "1", "dict": 6, "remark": "嵌入式操作系统", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 32, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "桌面操作系统", "key": "4", "show_title": "", "status": "1", "dict": 6, "remark": "桌面操作系统", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 33, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-06-26", "sort": 0, "title": "C", "key": "1", "show_title": "", "status": "1", "dict": 7, "remark": "C", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 34, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-06-26", "sort": 1, "title": "C#", "key": "2", "show_title": "", "status": "1", "dict": 7, "remark": "C#", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 35, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-06-26", "sort": 0, "title": "C++", "key": "3", "show_title": "", "status": "1", "dict": 7, "remark": "C++", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 36, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-06-26", "sort": 0, "title": "Java", "key": "4", "show_title": "", "status": "1", "dict": 7, "remark": "Java", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 37, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-06-26", "sort": 1, "title": "Python", "key": "5", "show_title": "", "status": "1", "dict": 7, "remark": "Python", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 38, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-06-26", "sort": 0, "title": "Verilog", "key": "6", "show_title": "", "status": "1", "dict": 7, "remark": "Verilog", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 39, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-06-26", "sort": 0, "title": "VHDL", "key": "7", "show_title": "", "status": "1", "dict": 7, "remark": "VHDL", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 40, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-06-26", "sort": 1, "title": "Rust", "key": "8", "show_title": "", "status": "1", "dict": 7, "remark": "Rust", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 41, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-06-26", "sort": 1, "title": "JavaScript", "key": "9", "show_title": "", "status": "1", "dict": 7, "remark": "JavaScript", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 42, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-06-26", "sort": 2, "title": "Golang", "key": "10", "show_title": "", "status": "1", "dict": 7, "remark": "Golang", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 43, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-06-26", "sort": 5, "title": "其他请在字典里加", "key": "11", "show_title": "", "status": "1", "dict": 7, "remark": "其他请在字典里加", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 44, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-06-26", "sort": 1, "title": "GJB 10157-2021", "key": "5", "show_title": "国军标10157", "status": "1", "dict": 8, "remark": "GJB 10157", "doc_name": "军用可编程逻辑器件软件语言编程安全子集", "publish_date": "2021-12-30", "source": "中央军委装备发展部"}}, {"model": "dict.dictitem", "pk": 45, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-06-26", "sort": 1, "title": "GJB 438B-2009 ", "key": "6", "show_title": "", "status": "1", "dict": 8, "remark": "GJB 438b", "doc_name": "军用软件开发文档通用要求", "publish_date": " 2009-05-25", "source": " 中国人民解放军总装备部"}}, {"model": "dict.dictitem", "pk": 46, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-06-26", "sort": 1, "title": "GJB 9433-2018", "key": "7", "show_title": "", "status": "1", "dict": 8, "remark": "GJB 9433-2018", "doc_name": "军用可编程逻辑器件软件测试要求", "publish_date": " 2018-03-27", "source": " 中央军委装备发展部"}}, {"model": "dict.dictitem", "pk": 47, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-06-26", "sort": 1, "title": "GJB Z 141-2004 ", "key": "8", "show_title": "", "status": "1", "dict": 8, "remark": "GJB/Z 141", "doc_name": "军用软件测试指南", "publish_date": " 2004-09-20", "source": "中国人民解放军总装备部"}}, {"model": "dict.dictitem", "pk": 55, "fields": {"update_datetime": "2024-09-09", "create_datetime": "2023-06-26", "sort": 1, "title": "功能", "key": "1", "show_title": "FT", "status": "1", "dict": 9, "remark": "功能", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 56, "fields": {"update_datetime": "2023-08-23", "create_datetime": "2023-06-26", "sort": 1, "title": "性能", "key": "2", "show_title": "PT", "status": "1", "dict": 9, "remark": "性能", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 57, "fields": {"update_datetime": "2023-08-23", "create_datetime": "2023-06-26", "sort": 1, "title": "接口", "key": "3", "show_title": "IT", "status": "1", "dict": 9, "remark": "接口", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 58, "fields": {"update_datetime": "2023-08-23", "create_datetime": "2023-06-26", "sort": 1, "title": "可靠性", "key": "4", "show_title": "RT", "status": "1", "dict": 9, "remark": "可靠性", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 59, "fields": {"update_datetime": "2023-08-23", "create_datetime": "2023-06-26", "sort": 1, "title": "安全性", "key": "5", "show_title": "AT", "status": "1", "dict": 9, "remark": "安全性", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 60, "fields": {"update_datetime": "2024-09-09", "create_datetime": "2023-06-26", "sort": 1, "title": "静态类型|其他", "key": "6", "show_title": "OT", "status": "1", "dict": 9, "remark": "其他", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 61, "fields": {"update_datetime": "2023-08-24", "create_datetime": "2023-06-26", "sort": 1, "title": "高", "key": "1", "show_title": "high", "status": "1", "dict": 10, "remark": "高", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 62, "fields": {"update_datetime": "2023-08-24", "create_datetime": "2023-06-26", "sort": 1, "title": "中", "key": "2", "show_title": "medium", "status": "1", "dict": 10, "remark": "中", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 63, "fields": {"update_datetime": "2023-08-24", "create_datetime": "2023-06-26", "sort": 1, "title": "低", "key": "3", "show_title": "low", "status": "1", "dict": 10, "remark": "低", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 64, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 3, "title": "代码审查", "key": "2", "show_title": "CR", "status": "1", "dict": 11, "remark": "代码审查", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 65, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 4, "title": "代码走查", "key": "3", "show_title": "CW", "status": "1", "dict": 11, "remark": "代码走查", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 66, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 5, "title": "功能测试", "key": "4", "show_title": "FT", "status": "1", "dict": 11, "remark": "功能测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 67, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 6, "title": "接口测试", "key": "5", "show_title": "IT", "status": "1", "dict": 11, "remark": "接口测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 68, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 7, "title": "性能测试", "key": "6", "show_title": "PT", "status": "1", "dict": 11, "remark": "性能测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 69, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 8, "title": "安全性测试", "key": "7", "show_title": "SC", "status": "1", "dict": 11, "remark": "安全性测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 70, "fields": {"update_datetime": "2023-08-23", "create_datetime": "2023-06-26", "sort": 1, "title": "文档审查", "key": "8", "show_title": "DC", "status": "1", "dict": 11, "remark": "文档审查", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 71, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 9, "title": "边界测试", "key": "9", "show_title": "BT", "status": "1", "dict": 11, "remark": "边界测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 72, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 10, "title": "余量测试", "key": "10", "show_title": "SP", "status": "1", "dict": 11, "remark": "余量测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 73, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 11, "title": "强度测试", "key": "11", "show_title": "ST", "status": "1", "dict": 11, "remark": "强度测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 74, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 12, "title": "人机交互界面测试", "key": "12", "show_title": "GT", "status": "1", "dict": 11, "remark": "人机交互界面测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 75, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 13, "title": "逻辑测试", "key": "13", "show_title": "LT", "status": "1", "dict": 11, "remark": "逻辑测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 76, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 14, "title": "恢复性测试", "key": "14", "show_title": "RT", "status": "1", "dict": 11, "remark": "恢复性测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 77, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 2, "title": "静态分析", "key": "15", "show_title": "SA", "status": "1", "dict": 11, "remark": "静态分析", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 78, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 15, "title": "时序测试", "key": "1", "show_title": "TT", "status": "1", "dict": 11, "remark": "时序测试", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 79, "fields": {"update_datetime": "2023-09-01", "create_datetime": "2023-06-26", "sort": 16, "title": "功耗分析", "key": "16", "show_title": "PA", "status": "1", "dict": 11, "remark": "功耗分析", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 80, "fields": {"update_datetime": "2024-03-13", "create_datetime": "2023-06-26", "sort": 1, "title": "通过", "key": "1", "show_title": "Passed", "status": "1", "dict": 12, "remark": "普通人员不能修改标签", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 81, "fields": {"update_datetime": "2024-03-13", "create_datetime": "2023-06-26", "sort": 1, "title": "未通过", "key": "2", "show_title": "Fail", "status": "1", "dict": 12, "remark": "普通人员不能修改标签", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 82, "fields": {"update_datetime": "2024-03-13", "create_datetime": "2023-06-26", "sort": 1, "title": "未执行", "key": "3", "show_title": "Unexecute", "status": "1", "dict": 12, "remark": "普通人员不能修改标签", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 83, "fields": {"update_datetime": "2024-03-13", "create_datetime": "2023-06-26", "sort": 1, "title": "完整执行", "key": "1", "show_title": "seitai_exe", "status": "1", "dict": 13, "remark": "普通人员不允许修改", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 84, "fields": {"update_datetime": "2024-03-13", "create_datetime": "2023-06-26", "sort": 1, "title": "部分执行", "key": "2", "show_title": "partial_exe", "status": "1", "dict": 13, "remark": "普通人员不允许修改", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 85, "fields": {"update_datetime": "2024-03-13", "create_datetime": "2023-06-26", "sort": 1, "title": "未执行", "key": "3", "show_title": "nonexecute", "status": "1", "dict": 13, "remark": "普通人员不允许修改", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 86, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "关闭", "key": "1", "show_title": "", "status": "1", "dict": 14, "remark": "关闭", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 87, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "开放", "key": "2", "show_title": "", "status": "1", "dict": 14, "remark": "开放", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 88, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "推迟", "key": "3", "show_title": "", "status": "1", "dict": 14, "remark": "推迟", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 89, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "撤销", "key": "4", "show_title": "", "status": "1", "dict": 14, "remark": "撤销", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 90, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "其他问题", "key": "1", "show_title": "", "status": "1", "dict": 15, "remark": "其他问题", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 91, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "文档问题", "key": "2", "show_title": "", "status": "1", "dict": 15, "remark": "文档问题", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 92, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "程序问题", "key": "3", "show_title": "", "status": "1", "dict": 15, "remark": "程序问题", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 93, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "设计问题", "key": "4", "show_title": "", "status": "1", "dict": 15, "remark": "设计问题", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 94, "fields": {"update_datetime": "2024-05-14", "create_datetime": "2023-06-26", "sort": 1, "title": "一般", "key": "1", "show_title": "normal", "status": "1", "dict": 16, "remark": "一般", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 95, "fields": {"update_datetime": "2024-05-14", "create_datetime": "2023-06-26", "sort": 1, "title": "严重", "key": "2", "show_title": "serious", "status": "1", "dict": 16, "remark": "严重", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 96, "fields": {"update_datetime": "2024-05-14", "create_datetime": "2023-06-26", "sort": 1, "title": "建议", "key": "3", "show_title": "propose", "status": "1", "dict": 16, "remark": "建议", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 97, "fields": {"update_datetime": "2024-05-14", "create_datetime": "2023-06-26", "sort": 1, "title": "重大", "key": "4", "show_title": "critical", "status": "1", "dict": 16, "remark": "致命", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 98, "fields": {"update_datetime": "2024-04-28", "create_datetime": "2023-06-26", "sort": 1, "title": "修改文档", "key": "1", "show_title": "", "status": "1", "dict": 17, "remark": "修改文档", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 99, "fields": {"update_datetime": "2023-06-26", "create_datetime": "2023-06-26", "sort": 1, "title": "修改程序", "key": "2", "show_title": "", "status": "1", "dict": 17, "remark": "修改程序", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 102, "fields": {"update_datetime": "2023-08-17", "create_datetime": "2023-08-17", "sort": 1, "title": "dark", "key": "12", "show_title": "", "status": "1", "dict": 7, "remark": "dark", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 103, "fields": {"update_datetime": "2024-09-09", "create_datetime": "2023-08-17", "sort": 1, "title": "swift", "key": "13", "show_title": "", "status": "1", "dict": 7, "remark": "swift", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 104, "fields": {"update_datetime": "2024-03-13", "create_datetime": "2023-08-24", "sort": 1, "title": "时序仿真", "key": "1", "show_title": "SXFZ", "status": "1", "dict": 18, "remark": "时序仿真", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 105, "fields": {"update_datetime": "2023-08-24", "create_datetime": "2023-08-24", "sort": 1, "title": "正交分解法", "key": "2", "show_title": "ZJFJ", "status": "1", "dict": 18, "remark": "正交分解法", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 106, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-10-07", "sort": 1, "title": "GJB-2725A", "key": "1", "show_title": "", "status": "1", "dict": 8, "remark": "GJB-2725A", "doc_name": "测试实验室和校准实验室通用要求", "publish_date": "2001-05-31", "source": "中国人民解放军总装备部"}}, {"model": "dict.dictitem", "pk": 107, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-10-07", "sort": 1, "title": "〔2005〕装电字第324号", "key": "2", "show_title": "", "status": "1", "dict": 8, "remark": "〔2005〕装电字第324号", "doc_name": "军用软件测评实验室测评过程和技术能力要求", "publish_date": "2005-12", "source": "总装备部电子信息基础部"}}, {"model": "dict.dictitem", "pk": 108, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-10-07", "sort": 1, "title": "TE-BTCG-003-2021", "key": "3", "show_title": "", "status": "1", "dict": 8, "remark": null, "doc_name": "军用软件测试指南", "publish_date": "2021-09", "source": "中央军委装备发展部"}}, {"model": "dict.dictitem", "pk": 109, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-10-07", "sort": 1, "title": "TE-BTCG-007-2021", "key": "4", "show_title": "", "status": "1", "dict": 8, "remark": null, "doc_name": "军用软件鉴定测评大纲和报告", "publish_date": "2021-09", "source": "中央军委装备发展部"}}, {"model": "dict.dictitem", "pk": 110, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-10-07", "sort": 1, "title": "GJB 438C-2021", "key": "9", "show_title": "", "status": "1", "dict": 8, "remark": "GJB 438C-2021", "doc_name": "军用软件开发文档通用要求", "publish_date": "2022-03-01", "source": "中央军委装备发展部"}}, {"model": "dict.dictitem", "pk": 111, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-10-07", "sort": 1, "title": "GJB 8114-2013", "key": "10", "show_title": "", "status": "1", "dict": 8, "remark": "GJB 8114-2013", "doc_name": "C/C++语言编程安全子集", "publish_date": "2013-04-11", "source": "国防科学技术工业委员会"}}, {"model": "dict.dictitem", "pk": 112, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-10-07", "sort": 1, "title": "TE-BTCG-004-2021", "key": "11", "show_title": "", "status": "1", "dict": 8, "remark": "TE-BTCG-004-2021", "doc_name": "军用软件鉴定测评指南", "publish_date": "2021-09", "source": "中央军委装备发展部"}}, {"model": "dict.dictitem", "pk": 113, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-10-07", "sort": 1, "title": "TE-BTCG-005-2021", "key": "12", "show_title": "", "status": "1", "dict": 8, "remark": "TE-BTCG-005-2021", "doc_name": "军用软件能力评估指南", "publish_date": "2021-09", "source": "中央军委装备发展部"}}, {"model": "dict.dictitem", "pk": 114, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-10-07", "sort": 1, "title": "TE-BTCG-006-2021", "key": "13", "show_title": "", "status": "1", "dict": 8, "remark": "TE-BTCG-006-2021", "doc_name": "军用软件测评机构能力评价", "publish_date": "2021-09", "source": "中央军委装备发展部"}}, {"model": "dict.dictitem", "pk": 115, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-10-07", "sort": 1, "title": "载人航天工程软件工程化技术标准", "key": "14", "show_title": "", "status": "1", "dict": 8, "remark": null, "doc_name": "载人航天工程软件工程化技术标准", "publish_date": "2014-12", "source": "总装备部载人工程办公室"}}, {"model": "dict.dictitem", "pk": 116, "fields": {"update_datetime": "2023-10-07", "create_datetime": "2023-10-07", "sort": 1, "title": "QJ 3027A-2016", "key": "15", "show_title": "", "status": "1", "dict": 8, "remark": null, "doc_name": "航天型号软件测试规范", "publish_date": "2016-01-19", "source": "国家国防科技工业局"}}, {"model": "dict.dictitem", "pk": 117, "fields": {"update_datetime": "2024-09-09", "create_datetime": "2024-03-21", "sort": 4, "title": "gcc", "key": "2", "show_title": "gcc", "status": "1", "dict": 19, "remark": null, "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 118, "fields": {"update_datetime": "2024-03-21", "create_datetime": "2024-03-21", "sort": 1, "title": "Linux", "key": "1", "show_title": "Linux", "status": "1", "dict": 20, "remark": "开发环境", "doc_name": " ", "publish_date": " ", "source": " "}}, {"model": "dict.dictitem", "pk": 119, "fields": {"update_datetime": "2024-03-22", "create_datetime": "2024-03-22", "sort": 1, "title": "需求问题", "key": "5", "show_title": "", "status": "1", "dict": 15, "remark": null, "doc_name": null, "publish_date": null, "source": null}}, {"model": "dict.dictitem", "pk": 120, "fields": {"update_datetime": "2024-03-22", "create_datetime": "2024-03-22", "sort": 1, "title": "数据问题", "key": "6", "show_title": "", "status": "1", "dict": 15, "remark": null, "doc_name": null, "publish_date": null, "source": null}}, {"model": "dict.dictitem", "pk": 121, "fields": {"update_datetime": "2024-04-28", "create_datetime": "2024-04-28", "sort": 1, "title": "其他闭环方式", "key": "3", "show_title": "", "status": "1", "dict": 17, "remark": null, "doc_name": null, "publish_date": null, "source": null}}, {"model": "dict.dictitem", "pk": 122, "fields": {"update_datetime": "2024-05-08", "create_datetime": "2024-05-08", "sort": 1, "title": "静态测试", "key": "3", "show_title": "STATIC", "status": "1", "dict": 18, "remark": null, "doc_name": null, "publish_date": null, "source": null}}, {"model": "dict.dictitem", "pk": 123, "fields": {"update_datetime": "2024-05-08", "create_datetime": "2024-05-08", "sort": 1, "title": "动态测试", "key": "4", "show_title": "Dynamic", "status": "1", "dict": 18, "remark": null, "doc_name": null, "publish_date": null, "source": null}}, {"model": "dict.dictitem", "pk": 124, "fields": {"update_datetime": "2024-09-09", "create_datetime": "2024-06-20", "sort": 5, "title": "树莓派", "key": "1", "show_title": "Pi", "status": "1", "dict": 19, "remark": null, "doc_name": null, "publish_date": null, "source": null}}, {"model": "dict.dictitem", "pk": 125, "fields": {"update_datetime": "2024-09-02", "create_datetime": "2024-09-02", "sort": 1, "title": "公开", "key": "1", "show_title": "gk(键值为1则为默认选项)", "status": "1", "dict": 21, "remark": null, "doc_name": null, "publish_date": null, "source": null}}, {"model": "dict.dictitem", "pk": 126, "fields": {"update_datetime": "2024-09-02", "create_datetime": "2024-09-02", "sort": 2, "title": "秘密★5年", "key": "3", "show_title": "mm", "status": "1", "dict": 21, "remark": null, "doc_name": null, "publish_date": null, "source": null}}, {"model": "dict.dictitem", "pk": 127, "fields": {"update_datetime": "2024-09-02", "create_datetime": "2024-09-02", "sort": 2, "title": "机密★10年", "key": "4", "show_title": "jm", "status": "1", "dict": 21, "remark": null, "doc_name": null, "publish_date": null, "source": null}}, {"model": "dict.dictitem", "pk": 128, "fields": {"update_datetime": "2024-09-02", "create_datetime": "2024-09-02", "sort": 2, "title": "绝密★20年", "key": "5", "show_title": "jmm", "status": "1", "dict": 21, "remark": null, "doc_name": null, "publish_date": null, "source": null}}, {"model": "dict.dictitem", "pk": 129, "fields": {"update_datetime": "2024-09-02", "create_datetime": "2024-09-02", "sort": 1, "title": "内部", "key": "2", "show_title": "nb", "status": "1", "dict": 21, "remark": null, "doc_name": null, "publish_date": null, "source": null}}, {"model": "dict.dictitem", "pk": 131, "fields": {"update_datetime": "2024-10-16", "create_datetime": "2024-10-16", "sort": 1, "title": "因果法", "key": "5", "show_title": "yg", "status": "1", "dict": 18, "remark": null, "doc_name": null, "publish_date": null, "source": null}}, {"model": "dict.dictitem", "pk": 132, "fields": {"update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "title": "window10", "key": "2", "show_title": "win10", "status": "1", "dict": 20, "remark": null, "doc_name": null, "publish_date": null, "source": null}}, {"model": "dict.fragment", "pk": 6, "fields": {"remark": "这是测试frag", "update_datetime": "2024-10-16", "create_datetime": "2024-07-25", "sort": 1, "name": "测评对象", "belong_doc": 1, "is_main": true, "content": "

XXX系统软件由XX模块、XX模块等组成,软件部署在XXX上,XX软件主要功能为XXX1功能、XXX2功能。

\n

\n

图1 XXX系统组成图

", "project": 17}}, {"model": "dict.fragment", "pk": 16, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "name": "测评对象", "belong_doc": 1, "is_main": true, "content": "

这是一个测评对象概述

\n

\n

图1-5 某数据展示表

", "project": 30}}, {"model": "system.loginlog", "pk": 189, "fields": {"remark": null, "creator": 1, "modifier": null, "username": "superAdmin", "ip": "127.0.0.1", "agent": "PC / Windows 10 / Edge 129.0.0", "browser": "Edge 129.0.0", "os": "Windows 10", "country": null, "login_type": 1, "update_datetime": "2024-09-30T09:31:09.278", "create_datetime": "2024-09-30T09:31:09.278", "sort": 1}}, {"model": "system.loginlog", "pk": 190, "fields": {"remark": null, "creator": 1, "modifier": null, "username": "superAdmin", "ip": "127.0.0.1", "agent": "PC / Windows 8 / Chrome 100.0.4896", "browser": "Chrome 100.0.4896", "os": "Windows 8", "country": null, "login_type": 1, "update_datetime": "2024-09-30T09:53:45.336", "create_datetime": "2024-09-30T09:53:45.336", "sort": 1}}, {"model": "system.loginlog", "pk": 191, "fields": {"remark": null, "creator": 1, "modifier": null, "username": "superAdmin", "ip": "127.0.0.1", "agent": "PC / Windows 8 / Chrome 100.0.4896", "browser": "Chrome 100.0.4896", "os": "Windows 8", "country": null, "login_type": 1, "update_datetime": "2024-09-30T09:54:25.752", "create_datetime": "2024-09-30T09:54:25.752", "sort": 1}}, {"model": "system.loginlog", "pk": 192, "fields": {"remark": null, "creator": 1, "modifier": null, "username": "superAdmin", "ip": "127.0.0.1", "agent": "PC / Windows 10 / Edge 129.0.0", "browser": "Edge 129.0.0", "os": "Windows 10", "country": null, "login_type": 1, "update_datetime": "2024-09-30T10:28:13.888", "create_datetime": "2024-09-30T10:28:13.888", "sort": 1}}, {"model": "system.loginlog", "pk": 193, "fields": {"remark": null, "creator": 1, "modifier": null, "username": "superAdmin", "ip": "127.0.0.1", "agent": "PC / Windows 10 / Edge 129.0.0", "browser": "Edge 129.0.0", "os": "Windows 10", "country": null, "login_type": 1, "update_datetime": "2024-10-08T15:53:22.565", "create_datetime": "2024-10-08T15:53:22.565", "sort": 1}}, {"model": "system.loginlog", "pk": 194, "fields": {"remark": null, "creator": 1, "modifier": null, "username": "superAdmin", "ip": "127.0.0.1", "agent": "PC / Windows 10 / Edge 129.0.0", "browser": "Edge 129.0.0", "os": "Windows 10", "country": null, "login_type": 1, "update_datetime": "2024-10-10T10:11:38.335", "create_datetime": "2024-10-10T10:11:38.335", "sort": 1}}, {"model": "system.loginlog", "pk": 195, "fields": {"remark": null, "creator": 1, "modifier": null, "username": "superAdmin", "ip": "127.0.0.1", "agent": "PC / Windows 10 / Edge 129.0.0", "browser": "Edge 129.0.0", "os": "Windows 10", "country": null, "login_type": 1, "update_datetime": "2024-10-11T17:30:11.008", "create_datetime": "2024-10-11T17:30:11.008", "sort": 1}}, {"model": "system.loginlog", "pk": 196, "fields": {"remark": null, "creator": 1, "modifier": null, "username": "superAdmin", "ip": "127.0.0.1", "agent": "PC / Windows 10 / Edge 129.0.0", "browser": "Edge 129.0.0", "os": "Windows 10", "country": null, "login_type": 1, "update_datetime": "2024-10-12T09:02:50.927", "create_datetime": "2024-10-12T09:02:50.927", "sort": 1}}, {"model": "system.loginlog", "pk": 197, "fields": {"remark": null, "creator": 1, "modifier": null, "username": "superAdmin", "ip": "127.0.0.1", "agent": "PC / Windows 10 / Edge 129.0.0", "browser": "Edge 129.0.0", "os": "Windows 10", "country": null, "login_type": 1, "update_datetime": "2024-10-16T09:11:26.645", "create_datetime": "2024-10-16T09:11:26.645", "sort": 1}}, {"model": "system.loginlog", "pk": 198, "fields": {"remark": null, "creator": 1, "modifier": null, "username": "superAdmin", "ip": "127.0.0.1", "agent": "PC / Windows 10 / Edge 129.0.0", "browser": "Edge 129.0.0", "os": "Windows 10", "country": null, "login_type": 1, "update_datetime": "2024-10-17T10:09:00.224", "create_datetime": "2024-10-17T10:09:00.224", "sort": 1}}, {"model": "system.loginlog", "pk": 199, "fields": {"remark": null, "creator": 1, "modifier": null, "username": "superAdmin", "ip": "127.0.0.1", "agent": "PC / Windows 10 / Edge 130.0.0", "browser": "Edge 130.0.0", "os": "Windows 10", "country": null, "login_type": 1, "update_datetime": "2024-10-21T14:49:17.629", "create_datetime": "2024-10-21T14:49:17.629", "sort": 1}}, {"model": "system.loginlog", "pk": 200, "fields": {"remark": null, "creator": 1, "modifier": null, "username": "superAdmin", "ip": "127.0.0.1", "agent": "PC / Windows 10 / Edge 130.0.0", "browser": "Edge 130.0.0", "os": "Windows 10", "country": null, "login_type": 1, "update_datetime": "2024-10-21T15:11:24.977", "create_datetime": "2024-10-21T15:11:24.978", "sort": 1}}, {"model": "system.loginlog", "pk": 201, "fields": {"remark": null, "creator": 1, "modifier": null, "username": "superAdmin", "ip": "127.0.0.1", "agent": "PC / Windows 10 / Edge 130.0.0", "browser": "Edge 130.0.0", "os": "Windows 10", "country": null, "login_type": 1, "update_datetime": "2024-11-04T09:06:05.106", "create_datetime": "2024-11-04T09:06:05.106", "sort": 1}}, {"model": "system.loginlog", "pk": 202, "fields": {"remark": null, "creator": 1, "modifier": null, "username": "superAdmin", "ip": "127.0.0.1", "agent": "PC / Windows 10 / Edge 130.0.0", "browser": "Edge 130.0.0", "os": "Windows 10", "country": null, "login_type": 1, "update_datetime": "2024-11-07T10:33:55.299", "create_datetime": "2024-11-07T10:33:55.299", "sort": 1}}, {"model": "project.project", "pk": 17, "fields": {"remark": null, "update_datetime": "2024-09-05", "create_datetime": "2024-05-16", "sort": 1, "ident": "R4444", "name": "测试项目", "engin_model": "123", "section_system": "123", "sub_system": "12213", "device": "3123", "beginTime": "2024-06-12", "endTime": "2024-08-23", "duty_person": "陈俊亦", "member": ["修改用户", "陈俊亦"], "quality_person": "陈俊亦", "vise_person": "陈俊亦", "config_person": "陈俊亦", "security_level": "3", "test_level": ["6"], "plant_type": ["4"], "report_type": "9", "language": ["1"], "standard": ["1", "2", "3", "4", "9", "10", "11"], "entrust_unit": "上海微小卫星工程中心", "entrust_contact": "111", "entrust_contact_phone": "13666666666", "entrust_email": "18782947123@163.com", "dev_unit": "上海微小卫星工程中心", "dev_contact": "12312", "dev_contact_phone": "18888828888", "dev_email": "314298729@qq.com", "test_unit": "中国科学院卫星软件测评中心", "test_contact": "123123", "test_contact_phone": "18777818412", "test_email": "314298729@qq.com", "step": "2", "abbreviation": ["UDP", "TCP", "HTML", "HTTP"], "soft_type": 1, "runtime": "2", "devplant": "1", "secret": "3"}}, {"model": "project.project", "pk": 30, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "R2486", "name": "某型号项目", "engin_model": null, "section_system": null, "sub_system": null, "device": null, "beginTime": "2024-10-21", "endTime": "2024-10-21", "duty_person": "陈俊亦", "member": ["某测试人员1", "某测试人员2", "某测试人员3", "某测试人员4"], "quality_person": "某质量人员", "vise_person": "某质量人员", "config_person": "某质量人员", "security_level": "2", "test_level": ["4"], "plant_type": ["3"], "report_type": "9", "language": ["1"], "standard": ["2", "3", "4", "9", "10", "8"], "entrust_unit": "某研制方单位", "entrust_contact": "某研制方人员", "entrust_contact_phone": "18223451321", "entrust_email": null, "dev_unit": "某研制方单位", "dev_contact": "某研制人员", "dev_contact_phone": "18888828888", "dev_email": null, "test_unit": "中国科学院卫星软件测评中心", "test_contact": "高才栋", "test_contact_phone": "13564753024", "test_email": null, "step": "1", "abbreviation": ["UDP", "TCP"], "soft_type": 1, "runtime": "2", "devplant": "2", "secret": "1"}}, {"model": "project.round", "pk": 128, "fields": {"remark": "第一轮测试", "update_datetime": "2024-09-04", "create_datetime": "2024-05-16", "sort": 1, "ident": "R4444-R1", "name": "第1轮测试", "beginTime": "2024-07-05", "endTime": "2024-07-09", "speedGrade": null, "package": null, "grade": "2", "best_condition_voltage": null, "best_condition_tem": null, "typical_condition_voltage": null, "typical_condition_tem": null, "low_condition_voltage": null, "low_condition_tem": null, "project": 17, "level": "0", "key": "0", "title": "第1轮测试", "location": "武汉体育中心123"}}, {"model": "project.round", "pk": 152, "fields": {"remark": "第2轮测试", "update_datetime": "2024-08-26", "create_datetime": "2024-07-29", "sort": 1, "ident": "R4444-R2", "name": "第2轮测试", "beginTime": "2024-07-29", "endTime": "2024-07-29", "speedGrade": null, "package": null, "grade": "2", "best_condition_voltage": null, "best_condition_tem": null, "typical_condition_voltage": null, "typical_condition_tem": null, "low_condition_voltage": null, "low_condition_tem": null, "project": 17, "level": "0", "key": "1", "title": "第2轮测试", "location": "武汉体育中心123"}}, {"model": "project.round", "pk": 174, "fields": {"remark": "第一轮测试", "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "R2486-R1", "name": "第1轮测试", "beginTime": "2024-10-21", "endTime": "2024-10-21", "speedGrade": null, "package": null, "grade": null, "best_condition_voltage": null, "best_condition_tem": null, "typical_condition_voltage": null, "typical_condition_tem": null, "low_condition_voltage": null, "low_condition_tem": null, "project": 30, "level": "0", "key": "0", "title": "第1轮测试", "location": ""}}, {"model": "project.dut", "pk": 187, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-05-16", "sort": 1, "ident": "R4444-R1-UT1", "type": "SO", "name": "软件源代码1L", "black_line": "1111", "code_line": "2222", "mix_line": "3333", "comment_line": "2222", "title": "软件源代码1L", "key": "0-0", "version": "1.1", "release_union": "上海微小卫星工程中心", "release_date": null, "ref": "这是用户标识", "level": "1", "project": 17, "round": 128}}, {"model": "project.dut", "pk": 189, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-05-16", "sort": 1, "ident": "R4444-R1-UT2", "type": "XQ", "name": "需求规格说明1L", "black_line": "", "code_line": "", "mix_line": "", "comment_line": "", "title": "需求规格说明1L", "key": "0-1", "version": "1.20", "release_union": "上海微小卫星工程中心", "release_date": "2024-05-16", "ref": "SRS01", "level": "1", "project": 17, "round": 128}}, {"model": "project.dut", "pk": 191, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-05-30", "sort": 1, "ident": "R4444-R1-UT3", "type": "XY", "name": "PDPU和软件的协议1L", "black_line": "", "code_line": "", "mix_line": "", "comment_line": "", "title": "PDPU和软件的协议1L", "key": "0-2", "version": "1.00", "release_union": "上海微小卫星工程中心", "release_date": "2024-05-30", "ref": "TXXY", "level": "1", "project": 17, "round": 128}}, {"model": "project.dut", "pk": 223, "fields": {"remark": null, "update_datetime": "2024-08-26", "create_datetime": "2024-07-29", "sort": 1, "ident": "R4444-R2-UT1", "type": "XQ", "name": "需求规格说明1.1", "black_line": "", "code_line": "", "mix_line": "", "comment_line": "", "title": "需求规格说明1.1", "key": "1-0", "version": "1.20", "release_union": "上海微小卫星工程中心", "release_date": "2024-07-29", "ref": "SRS01", "level": "1", "project": 17, "round": 152}}, {"model": "project.dut", "pk": 224, "fields": {"remark": null, "update_datetime": "2024-08-26", "create_datetime": "2024-07-29", "sort": 1, "ident": "R4444-R2-UT2", "type": "SO", "name": "软件源代码", "black_line": "1111", "code_line": "11111", "mix_line": "3333", "comment_line": "2222", "title": "软件源代码", "key": "1-1", "version": "1.23", "release_union": "上海微小卫星工程中心", "release_date": "2024-07-29", "ref": "SRS", "level": "1", "project": 17, "round": 152}}, {"model": "project.dut", "pk": 240, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-02", "sort": 1, "ident": "R4444-R1-UT4", "type": "YZ", "name": "官方研制总要求1L", "black_line": "", "code_line": "", "mix_line": "", "comment_line": "", "title": "官方研制总要求1L", "key": "0-3", "version": "1.2", "release_union": "上海微小卫星工程中心", "release_date": "2024-09-02", "ref": "YZYZ", "level": "1", "project": 17, "round": 128}}, {"model": "project.dut", "pk": 253, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "R2486-R1-UT1", "type": "SO", "name": "软件源代码", "black_line": "1222", "code_line": "7298", "mix_line": "5320", "comment_line": "867", "title": "软件源代码", "key": "0-0", "version": "1.0", "release_union": "某研制方单位", "release_date": "2024-10-21", "ref": "DM1", "level": "1", "project": 30, "round": 174}}, {"model": "project.dut", "pk": 257, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "R2486-R1-UT2", "type": "XQ", "name": "需求规格说明", "black_line": null, "code_line": null, "mix_line": null, "comment_line": null, "title": "需求规格说明", "key": "0-1", "version": "1.1", "release_union": "某研制方单位", "release_date": "2024-10-21", "ref": "SRS2", "level": "1", "project": 30, "round": 174}}, {"model": "project.design", "pk": 205, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-05-16", "sort": 1, "ident": "JTFX", "name": "静态分析1L", "demandType": "6", "description": "

根据相关要求,利用静态分析工具对被测软件全部源程序进行控制流分析、数据流分析进行分析,并统计软件质量度量信息,给出软件源代码检查结果

", "title": "静态分析1L", "key": "0-0-0", "level": "2", "chapter": "/", "project": 17, "round": 128, "dut": 187, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 206, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-05-16", "sort": 1, "ident": "DMSC", "name": "代码审查1L", "demandType": "6", "description": "根据相关要求及软件文档开展针对软件程序代码的代码审查", "title": "代码审查1L", "key": "0-0-1", "level": "2", "chapter": "/", "project": 17, "round": 128, "dut": 187, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 207, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-05-16", "sort": 1, "ident": "WDSC", "name": "文档审查1L", "demandType": "6", "description": "依据相关要求,逐项检查被测文档的完整性、一致性和准确性是否满足要求", "title": "文档审查1L", "key": "0-0-2", "level": "2", "chapter": "/", "project": 17, "round": 128, "dut": 187, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3104, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-05-31", "sort": 1, "ident": "CCC2", "name": "测试设计需求首轮哟", "demandType": "1", "description": "

", "title": "测试设计需求首轮哟", "key": "0-1-0", "level": "2", "chapter": "3.3.3", "project": 17, "round": 128, "dut": 189, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3603, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-07-29", "sort": 1, "ident": "CCCC", "name": "测试设计需求", "demandType": "1", "description": "

", "title": "测试设计需求", "key": "1-0-0", "level": "2", "chapter": "3.3.3", "project": 17, "round": 152, "dut": 223, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3604, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-07-29", "sort": 1, "ident": "JTFX", "name": "静态分析", "demandType": "6", "description": "根据相关要求,利用静态分析工具对被测软件全部源程序进行控制流分析、数据流分析进行分析,并统计软件质量度量信息,给出软件源代码检查结果", "title": "静态分析", "key": "1-1-0", "level": "2", "chapter": "/", "project": 17, "round": 152, "dut": 224, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3605, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-07-29", "sort": 1, "ident": "DMSC", "name": "代码审查", "demandType": "6", "description": "根据相关要求及软件文档开展针对软件程序代码的代码审查", "title": "代码审查", "key": "1-1-1", "level": "2", "chapter": "/", "project": 17, "round": 152, "dut": 224, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3606, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-07-29", "sort": 1, "ident": "WDSC", "name": "文档审查", "demandType": "6", "description": "依据相关要求,逐项检查被测文档的完整性、一致性和准确性是否满足要求", "title": "文档审查", "key": "1-1-2", "level": "2", "chapter": "/", "project": 17, "round": 152, "dut": 224, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3663, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-08-14", "sort": 1, "ident": "WWWW", "name": "一个设计需求内容", "demandType": "1", "description": "

\n

设计需求就是这样的

", "title": "一个设计需求内容", "key": "0-2-0", "level": "2", "chapter": "1.1.1", "project": 17, "round": 128, "dut": 191, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3702, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-02", "sort": 1, "ident": "SQBB", "name": "神奇宝贝", "demandType": "1", "description": "", "title": "神奇宝贝", "key": "0-3-0", "level": "2", "chapter": "1.1.1", "project": 17, "round": 128, "dut": 240, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3711, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "ident": "ABCD", "name": "看看描述为空什么情况", "demandType": "1", "description": "", "title": "看看描述为空什么情况", "key": "0-1-1", "level": "2", "chapter": "2.2.2", "project": 17, "round": 128, "dut": 189, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3712, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "ident": "SQBB2", "name": "神奇宝贝2号", "demandType": "1", "description": "", "title": "神奇宝贝2号", "key": "0-3-1", "level": "2", "chapter": "1.5.6", "project": 17, "round": 128, "dut": 240, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3736, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "JTFX", "name": "静态分析", "demandType": "6", "description": "根据相关要求,利用静态分析工具对被测软件全部源程序进行控制流分析、数据流分析进行分析,并统计软件质量度量信息,给出软件源代码检查结果", "title": "静态分析", "key": "0-0-0", "level": "2", "chapter": "/", "project": 30, "round": 174, "dut": 253, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3737, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "DMSC", "name": "代码审查", "demandType": "6", "description": "根据相关要求及软件文档开展针对软件程序代码的代码审查", "title": "代码审查", "key": "0-0-1", "level": "2", "chapter": "/", "project": 30, "round": 174, "dut": 253, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3738, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "WDSC", "name": "文档审查", "demandType": "6", "description": "依据相关要求,逐项检查被测文档的完整性、一致性和准确性是否满足要求", "title": "文档审查", "key": "0-0-2", "level": "2", "chapter": "/", "project": 30, "round": 174, "dut": 253, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3744, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "RQGN01-UART-p1", "name": "总线通讯管理(RQGN01-UART)", "demandType": "1", "description": "WXT星载触发处理单元主控软件通过UART总线与PDPU之间通讯,完成数据注入和广播注入的接收,其中包括注入指令、星表、主控软件目标文件、FPGA目标文件、算法参数、广播时间码、姿态等数据接收。软件上电后,先完成星载触发处理FPGA的UART模块初始化。当注入接收中断有效后,软件从注入接收FIFOcnt寄存器中读取当前FIFO中存放的数据总字数,然后从注入接收FIFO中读取对应总字数的数据,按照格式,对该数据帧头、命令域、长度以及CRC校验和的有效性和合法性进行判断,执行合法有效的注入指令,拷贝合法有效的触发算法参数、主控软件目标文件、FPGA目标文件数据到SDRAM的注入缓存基地址空间", "title": "总线通讯管理(RQGN01-UART)", "key": "0-1-0", "level": "2", "chapter": "1.1.1", "project": 30, "round": 174, "dut": 257, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3745, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "RQGN01-UART-t2", "name": "总线通讯管理(RQGN01-UART)", "demandType": "1", "description": "

序号

参数名称

注入来源

Flash基地址

注入缓存基地址

算法调用地址

实际大小

预留空间大小(字节)

图像处理参数

注入指令

0xbc2xxxx0

0xbc2xxxx0

400字节

32kB

坐标转换参数

注入指令

0xbc20xxx0

0x8220xxx0

176字节*48

32kB

事例等级参数

注入指令

0xbc21xxxx

0x8221xxxx

TRIGGER_CLASS_FIFO

256字节

32kB

PSF参数

软件代码重构

0xbc22xxxx

0x8222xxxx

0x8422xxxx

33kB*48

1.8MB

星表参数

已知星表

星表注入

0xbc1xxxx0

0x821xxxx0

0x841xxxx0

16字节*1xxxx

384kB

临时星表

星表注入

0xbc16xxxx

0x8216xxxx

0x8416xxxx

16字节*1xxxx

384kB

超亮点源

星表注入

0xbc1cxxxx

0x821cxxxx

0x841cxxxx

16字节*5000

128kB

主控软件目标文件

软件代码重构

0xbcxxxx00

0x83xxxx00

\\

110KB左右

1MB

FPGA代码目标文件

FPGA重构

存储在FPGA的NORFLASH芯片上

0x838xxxx0

\\

1.3MB左右

3MB

", "title": "总线通讯管理(RQGN01-UART)", "key": "0-1-1", "level": "2", "chapter": "1.1.1", "project": 30, "round": 174, "dut": 257, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3746, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "RQGN01-UART-ZRZL-p1", "name": "解析并执行注入指令(RQGN01-UART-ZRZL)", "demandType": "1", "description": "接收PDPU发出的注入指令,并对帧头、命令域、长度以及校验和的有效性和合法性进行判断,如果有效则执行合法有效的注入指令,如果无效,则更新“指令错误类型”和“指令错误计数”等软件状态参数,并丢弃当前注入数据;如果格式有效,则再依据注入指令类型进行后续处理。注入指令数据帧格式见格式见表 4‑1所示,注入指令包含状态控制指令、图像处理参数指令、坐标转换参数指令、事例等级参数指令等,具体指令格式详见《XXX通信协议》。", "title": "解析并执行注入指令(RQGN01-UART-ZRZL)", "key": "0-1-2", "level": "2", "chapter": "1.1.1.1", "project": 30, "round": 174, "dut": 257, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3749, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "RQGN01-UART-ZRZL-u4", "name": "解析并执行注入指令(RQGN01-UART-ZRZL)", "demandType": "1", "description": "
  • 接收配置参数指令,如果配置参数指令开关为开状态(0xxxxxxxxx),则将指令数据域的配置参数更新到算法调用缓存区的配置参数中,如果配置参数指令开关为关状态(0xxxxxxxxx),则将算法调用缓存区的配置参数恢复到之前的默认值,配置参数的更新仅在自维护模式下完成。同时在配置参数指令为开状态下,配置参数指令还可以强制转换当前的工作模式,模式强制切换在收到下一包的广播消息后有效,模式强制切换域值为0xxxxxxxxx为自维护,模式强制切换域值为0xcxxxxxxx强制进入触发模式,在这两种情况下,工作模式强制转换使能,工作模式切换将不受控于广播消息中的卫星机动状态,仅当模式强制切换域值为其他值时,工作模式强制转换禁止,工作模式切换受控于广播消息中的卫星机动状态;
  • 接收到图像处理参数指令,判断图像参数注入更新标志是否为无效(xx),如果无效则判断图像注入参数的算法轮询周期、积分时间、触发源打包数目值、xy轴的binning值、临时星表最大阈值进行有效域判断,如果判断正确则将图像处理参数指令内包含的图像处理参数搬移到图像处理参数注入地址空间,并置位图像处理参数注入更新标志有效(值为0xaa),如果图像参数注入更新标志为有效或者图像注入参数的上述值不在有效范围内,则置位触发板软件错误状态,触发板软件错误计数加一,并丢弃当前图像注入参数包;
  • 接收到坐标转换参数指令,判断该探测器号的坐标注入更新标志是否无效(xx),如果无效则将坐标转换参数指令内包含的某个探测器坐标转换参数搬移到对应探测器的坐标转换参数注入地址空间,并置位“坐标转换参数注入总更新标志”和对应探测器的“探测器坐标转换参数注入更新标识”有效(值为xx),如果无效,则置位触发板软件错误状态,触发板软件错误计数加一,并丢弃当前当前探测器的坐标注入参数包;
  • 接收到事例等级参数指令,判断事例等级参数注入更新标志是否无效(xx),如果无效则将事例等级参数指令内包含的事例等级参数搬移到事例等级参数注入地址空间,并置位“事例等级参数注入更新标志”有效(值为xx) ,如果无效,则置位触发板软件错误状态,触发板软件错误计数加一,并丢弃当前事例等级注入参数包。
  • ", "title": "解析并执行注入指令(RQGN01-UART-ZRZL)", "key": "0-1-3", "level": "2", "chapter": "1.1.1.1", "project": 30, "round": 174, "dut": 257, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3750, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "RQGN01-UART-ZRZL-p5", "name": "解析并执行注入指令(RQGN01-UART-ZRZL)", "demandType": "1", "description": "图像处理参数、坐标转换参数、事例等级参数均在该触发算法参数的注入更新标志无效(xx)的时候才能正常接收,接收后均暂缓存在对应的注入缓存空间中,等待进入自维护模式后,才会将接收到新更新的算法参数更新到算法调用空间。如果再注入更新标志有效的情况下收到某算法参数,则会置位触发板软件错误状态,触发板软件错误计数加1,注入错误计数加1,并丢弃当前接收的算法注入参数。算法参数数据的有效性由地面保证。", "title": "解析并执行注入指令(RQGN01-UART-ZRZL)", "key": "0-1-4", "level": "2", "chapter": "1.1.1.1", "project": 30, "round": 174, "dut": 257, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.design", "pk": 3751, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "RQGS03", "name": "初始化功能", "demandType": "1", "description": "

    初始化后x、y变量值为0x0011和0x002

    ", "title": "初始化功能", "key": "0-1-5", "level": "2", "chapter": "3.1.1", "project": 30, "round": 174, "dut": 257, "source": "", "to": "", "type": "", "protocal": ""}}, {"model": "project.testdemand", "pk": 348, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-05-16", "sort": 1, "ident": "JTFX", "name": "静态分析", "adequacy": "对软件全部源程序进行进行质量度量、控制流分析、数据流分析的静态统计信息分析", "priority": "2", "testType": "15", "testMethod": ["3"], "title": "静态分析", "key": "0-0-0-0", "level": "3", "project": 17, "round": 128, "dut": 187, "design": 205, "otherDesign": []}}, {"model": "project.testdemand", "pk": 349, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-05-16", "sort": 1, "ident": "DMSC", "name": "代码审查111", "adequacy": "对软件全部源代码/重点模块进行代码审查", "priority": "2", "testType": "2", "testMethod": ["3"], "title": "代码审查111", "key": "0-0-1-0", "level": "3", "project": 17, "round": 128, "dut": 187, "design": 206, "otherDesign": []}}, {"model": "project.testdemand", "pk": 350, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-05-16", "sort": 1, "ident": "WDSC", "name": "文档审查111", "adequacy": "对所有被测软件文档按照文档检查单逐项进行审查", "priority": "1", "testType": "8", "testMethod": ["3"], "title": "文档审查111", "key": "0-0-2-0", "level": "3", "project": 17, "round": 128, "dut": 187, "design": 207, "otherDesign": []}}, {"model": "project.testdemand", "pk": 407, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-07-29", "sort": 1, "ident": "JTFX", "name": "静态分析", "adequacy": "对软件全部源程序进行进行质量度量、控制流分析、数据流分析的静态统计信息分析", "priority": "2", "testType": "15", "testMethod": ["3"], "title": "静态分析", "key": "1-1-0-0", "level": "3", "project": 17, "round": 152, "dut": 224, "design": 3604, "otherDesign": []}}, {"model": "project.testdemand", "pk": 408, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-07-29", "sort": 1, "ident": "DMSC", "name": "代码审查", "adequacy": "对软件全部源代码/重点模块进行代码审查", "priority": "2", "testType": "2", "testMethod": ["3"], "title": "代码审查", "key": "1-1-1-0", "level": "3", "project": 17, "round": 152, "dut": 224, "design": 3605, "otherDesign": []}}, {"model": "project.testdemand", "pk": 409, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-07-29", "sort": 1, "ident": "WDSC", "name": "文档审查", "adequacy": "对所有被测软件文档按照文档检查单逐项进行审查", "priority": "1", "testType": "8", "testMethod": ["3"], "title": "文档审查", "key": "1-1-2-0", "level": "3", "project": 17, "round": 152, "dut": 224, "design": 3606, "otherDesign": []}}, {"model": "project.testdemand", "pk": 422, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-08-14", "sort": 1, "ident": "CSHG", "name": "wtd1", "adequacy": "测试用例覆盖111、子项2子项要求的全部内容。\n所有用例执行完毕,对于未执行的用例说明未执行原因。", "priority": "1", "testType": "4", "testMethod": ["4"], "title": "wtd1", "key": "0-2-0-0", "level": "3", "project": 17, "round": 128, "dut": 191, "design": 3663, "otherDesign": []}}, {"model": "project.testdemand", "pk": 459, "fields": {"remark": null, "update_datetime": "2024-09-02", "create_datetime": "2024-09-02", "sort": 1, "ident": "SQBB", "name": "神奇宝贝测试项1号", "adequacy": "测试用例覆盖子项1号子项要求的全部内容。\n所有用例执行完毕,对于未执行的用例说明未执行原因。", "priority": "2", "testType": "4", "testMethod": ["4"], "title": "神奇宝贝测试项1号", "key": "0-3-0-0", "level": "3", "project": 17, "round": 128, "dut": 240, "design": 3702, "otherDesign": []}}, {"model": "project.testdemand", "pk": 468, "fields": {"remark": null, "update_datetime": "2024-09-25", "create_datetime": "2024-09-09", "sort": 1, "ident": "CCCC", "name": "首轮测试项1号", "adequacy": "测试用例覆盖1111、子项2号子项要求的全部内容。\n所有用例执行完毕,对于未执行的用例说明未执行原因。", "priority": "1", "testType": "4", "testMethod": ["4"], "title": "首轮测试项1号", "key": "0-1-0-0", "level": "3", "project": 17, "round": 128, "dut": 189, "design": 3104, "otherDesign": []}}, {"model": "project.testdemand", "pk": 469, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "ident": "PPPP", "name": "某测试项", "adequacy": "测试用例覆盖阿萨德子项要求的全部内容。\n所有用例执行完毕,对于未执行的用例说明未执行原因。", "priority": "1", "testType": "4", "testMethod": ["4"], "title": "某测试项", "key": "0-1-0-1", "level": "3", "project": 17, "round": 128, "dut": 189, "design": 3104, "otherDesign": []}}, {"model": "project.testdemand", "pk": 470, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "ident": "DDDD", "name": "站内信测试项", "adequacy": "测试用例覆盖DDD子项要求的全部内容。\n所有用例执行完毕,对于未执行的用例说明未执行原因。", "priority": "1", "testType": "4", "testMethod": ["4"], "title": "站内信测试项", "key": "0-1-0-2", "level": "3", "project": 17, "round": 128, "dut": 189, "design": 3104, "otherDesign": []}}, {"model": "project.testdemand", "pk": 484, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "JTFX", "name": "静态分析", "adequacy": "对软件全部源程序进行进行质量度量、控制流分析、数据流分析的静态统计信息分析", "priority": "2", "testType": "15", "testMethod": ["3"], "title": "静态分析", "key": "0-0-0-0", "level": "3", "project": 30, "round": 174, "dut": 253, "design": 3736, "otherDesign": []}}, {"model": "project.testdemand", "pk": 485, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "DMSC", "name": "代码审查", "adequacy": "对软件全部源代码/重点模块进行代码审查", "priority": "2", "testType": "2", "testMethod": ["3"], "title": "代码审查", "key": "0-0-1-0", "level": "3", "project": 30, "round": 174, "dut": 253, "design": 3737, "otherDesign": []}}, {"model": "project.testdemand", "pk": 486, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "WDSC", "name": "文档审查", "adequacy": "对所有被测软件文档按照文档检查单逐项进行审查", "priority": "1", "testType": "8", "testMethod": ["3"], "title": "文档审查", "key": "0-0-2-0", "level": "3", "project": 30, "round": 174, "dut": 253, "design": 3738, "otherDesign": []}}, {"model": "project.testdemand", "pk": 492, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "CSHX", "name": "初始化正常功能测试", "adequacy": "测试用例覆盖初始化x变量、初始化y变量子项要求的全部内容。\n所有用例执行完毕,对于未执行的用例说明未执行原因。", "priority": "2", "testType": "4", "testMethod": ["4"], "title": "初始化正常功能测试", "key": "0-1-5-0", "level": "3", "project": 30, "round": 174, "dut": 257, "design": 3751, "otherDesign": []}}, {"model": "project.testdemandcontent", "pk": 577, "fields": {"remark": null, "update_datetime": "2024-05-16", "create_datetime": "2024-05-16", "sort": 1, "testDemand": 348, "subName": "静态分析", "subDesc": "对被测软件全部源程序进行静态分析,对控制流、数据流进行分析,验证软件是否满足控制流和数据流要求,并依据质量特性需求统计质量度量信息", "condition": "", "operation": "使用LDRA TestBed软件和Klocwork软件工具对被测软件全部源程序进行静态分析,依据附录的审查单对源程序进行检查。\u00071)使用静态分析工具统计软件质量度量信息,包含:\u0007(1)软件总注释率不小于20%(注释行数/软件规模*100%);\u0007(2)模块的平均规模不大于200行(模块代码行数之和/模块数);\u0007(3)模块的平均圈复杂度不大于10(模块圈复杂度之和/模块总数);\u0007(4)\t模块的平均扇出数不大于7(模块扇出数之和/模块总数)。\u00072)使用静态分析工具结合人工分析对控制流和数据流进行分析,验证软件是否满足控制流和数据流要求。", "observe": null, "expect": null}}, {"model": "project.testdemandcontent", "pk": 587, "fields": {"remark": null, "update_datetime": "2024-05-27", "create_datetime": "2024-05-27", "sort": 1, "testDemand": 355, "subName": "123", "subDesc": "12", "condition": "312", "operation": "2312", "observe": "3", "expect": "123"}}, {"model": "project.testdemandcontent", "pk": 645, "fields": {"remark": null, "update_datetime": "2024-07-29", "create_datetime": "2024-07-29", "sort": 1, "testDemand": 407, "subName": "静态分析", "subDesc": "对被测软件全部源程序进行静态分析,对控制流、数据流进行分析,验证软件是否满足控制流和数据流要求,并依据质量特性需求统计质量度量信息", "condition": "", "operation": "使用LDRA TestBed软件和Klocwork软件工具对被测软件全部源程序进行静态分析,依据附录的审查单对源程序进行检查。\u00071)使用静态分析工具统计软件质量度量信息,包含:\u0007(1)软件总注释率不小于20%(注释行数/软件规模*100%);\u0007(2)模块的平均规模不大于200行(模块代码行数之和/模块数);\u0007(3)模块的平均圈复杂度不大于10(模块圈复杂度之和/模块总数);\u0007(4)\t模块的平均扇出数不大于7(模块扇出数之和/模块总数)。\u00072)使用静态分析工具结合人工分析对控制流和数据流进行分析,验证软件是否满足控制流和数据流要求。", "observe": null, "expect": null}}, {"model": "project.testdemandcontent", "pk": 646, "fields": {"remark": null, "update_datetime": "2024-07-29", "create_datetime": "2024-07-29", "sort": 1, "testDemand": 408, "subName": "代码审查", "subDesc": "通过人工审查及借助工具辅助分析的方式开展代码审查,审查代码编程准则的符合性、代码流程实现的正确性、代码结构的合理性以及代码实现需求的正确性;人工审查中发现的问题,审查人员应及时记录", "condition": "", "operation": "人工审查及借助工具辅助分析的方式", "observe": "和依据附录代码审查单范围内的源代码开展四个方面的审查:\u00071)编程准则检查:依据编程准则的要求,对程序的编码与编程准则进行符合性检查;\u00072)代码流程审查:审查程序代码的条件判别、控制流程、数据处理等满足设计要求;\u00073)软件结构审查:依据设计文档,审查程序代码的结构设计的合理性,包括程序结构设计和数据结构设计;\u00074)需求实现审查:依据需求文档及其他相关资料,审查程序代码的需求层的功能实现是否正确", "expect": ""}}, {"model": "project.testdemandcontent", "pk": 647, "fields": {"remark": null, "update_datetime": "2024-07-29", "create_datetime": "2024-07-29", "sort": 1, "testDemand": 409, "subName": "文档审查", "subDesc": "本软件文档审查包括内容如下:\u00071)软件需求规格说明\u00072)软件设计文档\u00073)软件接口需求规格说明\u00074)软件接口设计说明\u00075)软件用户手册", "condition": "", "operation": "测试人员人工阅读文档,依据文档检查单对软件文档进行审查,文档审查工作内容包括:\u00071)审查软件文档内容是否完整;\u00072)审查软件文档描述是否正确;\u00073)审查软件文档格式是否规范;\u00074)审查软件文档是否文文一致\u0007按照附录的需求规格说明审查单,对被测软件的需求规格说明进行审查;\u0007按照附录的软件设计文档审查单,对被测软件的设计说明文档进行审查;\u0007按照附录的用户手册审查单,对被测软件的用户手册进行审查", "observe": null, "expect": null}}, {"model": "project.testdemandcontent", "pk": 707, "fields": {"remark": null, "update_datetime": "2024-09-02", "create_datetime": "2024-09-02", "sort": 1, "testDemand": 459, "subName": "子项1号", "subDesc": "子项1号的描述", "condition": "在艰苦环境下", "operation": "进行艰苦操作", "observe": "观察艰苦", "expect": "期望正确"}}, {"model": "project.testdemandcontent", "pk": 711, "fields": {"remark": null, "update_datetime": "2024-09-04", "create_datetime": "2024-09-04", "sort": 1, "testDemand": 422, "subName": "111", "subDesc": "2123", "condition": null, "operation": null, "observe": null, "expect": null}}, {"model": "project.testdemandcontent", "pk": 712, "fields": {"remark": null, "update_datetime": "2024-09-04", "create_datetime": "2024-09-04", "sort": 1, "testDemand": 422, "subName": "子项2", "subDesc": "子项描述2", "condition": null, "operation": null, "observe": null, "expect": null}}, {"model": "project.testdemandcontent", "pk": 728, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "testDemand": 349, "subName": "代码审查", "subDesc": "通过人工审查及借助工具辅助分析的方式开展代码审查,审查代码编程准则的符合性、代码流程实现的正确性、代码结构的合理性以及代码实现需求的正确性;人工审查中发现的问题,审查人员应及时记录", "condition": "", "operation": "人工审查及借助工具辅助分析的方式", "observe": "和依据附录代码审查单范围内的源代码开展四个方面的审查:\u00071)编程准则检查:依据编程准则的要求,对程序的编码与编程准则进行符合性检查;\u00072)代码流程审查:审查程序代码的条件判别、控制流程、数据处理等满足设计要求;\u00073)软件结构审查:依据设计文档,审查程序代码的结构设计的合理性,包括程序结构设计和数据结构设计;\u00074)需求实现审查:依据需求文档及其他相关资料,审查程序代码的需求层的功能实现是否正确", "expect": ""}}, {"model": "project.testdemandcontent", "pk": 729, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "testDemand": 350, "subName": "文档审查", "subDesc": "本软件文档审查包括内容如下:\u00071)软件需求规格说明\u00072)软件设计文档\u00073)软件接口需求规格说明\u00074)软件接口设计说明\u00075)软件用户手册", "condition": "", "operation": "测试人员人工阅读文档,依据文档检查单对软件文档进行审查,文档审查工作内容包括:\u00071)审查软件文档内容是否完整;\u00072)审查软件文档描述是否正确;\u00073)审查软件文档格式是否规范;\u00074)审查软件文档是否文文一致\u0007按照附录的需求规格说明审查单,对被测软件的需求规格说明进行审查;\u0007按照附录的软件设计文档审查单,对被测软件的设计说明文档进行审查;\u0007按照附录的用户手册审查单,对被测软件的用户手册进行审查", "observe": null, "expect": null}}, {"model": "project.testdemandcontent", "pk": 731, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "testDemand": 469, "subName": "阿萨德", "subDesc": "大1111", "condition": null, "operation": null, "observe": null, "expect": null}}, {"model": "project.testdemandcontent", "pk": 732, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "testDemand": 470, "subName": "DDD", "subDesc": "AAA", "condition": "123", "operation": "321", "observe": "123", "expect": "123"}}, {"model": "project.testdemandcontent", "pk": 747, "fields": {"remark": null, "update_datetime": "2024-09-25", "create_datetime": "2024-09-25", "sort": 1, "testDemand": 468, "subName": "1111", "subDesc": "22222a\n我看看子项怎么样\n我可以正常换行不", "condition": "我是条件\n我换行1", "operation": "我是操作\n我换行2", "observe": "我是观察\n我换行3", "expect": "我是期望\n我换行2123"}}, {"model": "project.testdemandcontent", "pk": 748, "fields": {"remark": null, "update_datetime": "2024-09-25", "create_datetime": "2024-09-25", "sort": 1, "testDemand": 468, "subName": "子项2号", "subDesc": "22222a\n我看看子项怎么样\n我可以正常换行不", "condition": "我是条件\n我换行1", "operation": "我是操作\n我换行2", "observe": "我是观察\n我换行3", "expect": "我是期望\n我换行2123"}}, {"model": "project.testdemandcontent", "pk": 765, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "testDemand": 484, "subName": "静态分析", "subDesc": "对被测软件全部源程序进行静态分析,对控制流、数据流进行分析,验证软件是否满足控制流和数据流要求,并依据质量特性需求统计质量度量信息", "condition": "", "operation": "使用LDRA TestBed软件和Klocwork软件工具对被测软件全部源程序进行静态分析,依据附录的审查单对源程序进行检查。\u00071)使用静态分析工具统计软件质量度量信息,包含:\u0007(1)软件总注释率不小于20%(注释行数/软件规模*100%);\u0007(2)模块的平均规模不大于200行(模块代码行数之和/模块数);\u0007(3)模块的平均圈复杂度不大于10(模块圈复杂度之和/模块总数);\u0007(4)\t模块的平均扇出数不大于7(模块扇出数之和/模块总数)。\u00072)使用静态分析工具结合人工分析对控制流和数据流进行分析,验证软件是否满足控制流和数据流要求。", "observe": null, "expect": null}}, {"model": "project.testdemandcontent", "pk": 766, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "testDemand": 485, "subName": "代码审查", "subDesc": "通过人工审查及借助工具辅助分析的方式开展代码审查,审查代码编程准则的符合性、代码流程实现的正确性、代码结构的合理性以及代码实现需求的正确性;人工审查中发现的问题,审查人员应及时记录", "condition": "", "operation": "人工审查及借助工具辅助分析的方式", "observe": "和依据附录代码审查单范围内的源代码开展四个方面的审查:\u00071)编程准则检查:依据编程准则的要求,对程序的编码与编程准则进行符合性检查;\u00072)代码流程审查:审查程序代码的条件判别、控制流程、数据处理等满足设计要求;\u00073)软件结构审查:依据设计文档,审查程序代码的结构设计的合理性,包括程序结构设计和数据结构设计;\u00074)需求实现审查:依据需求文档及其他相关资料,审查程序代码的需求层的功能实现是否正确", "expect": ""}}, {"model": "project.testdemandcontent", "pk": 767, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "testDemand": 486, "subName": "文档审查", "subDesc": "本软件文档审查包括内容如下:\u00071)软件需求规格说明\u00072)软件设计文档\u00073)软件接口需求规格说明\u00074)软件接口设计说明\u00075)软件用户手册", "condition": "", "operation": "测试人员人工阅读文档,依据文档检查单对软件文档进行审查,文档审查工作内容包括:\u00071)审查软件文档内容是否完整;\u00072)审查软件文档描述是否正确;\u00073)审查软件文档格式是否规范;\u00074)审查软件文档是否文文一致\u0007按照附录的需求规格说明审查单,对被测软件的需求规格说明进行审查;\u0007按照附录的软件设计文档审查单,对被测软件的设计说明文档进行审查;\u0007按照附录的用户手册审查单,对被测软件的用户手册进行审查", "observe": null, "expect": null}}, {"model": "project.testdemandcontent", "pk": 773, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "testDemand": 492, "subName": "初始化x变量", "subDesc": "设备上电,初始化x变量", "condition": "设备正常启动,功能正常", "operation": "设备上电", "observe": "查看x变量", "expect": "x变量值正确"}}, {"model": "project.testdemandcontent", "pk": 774, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "testDemand": 492, "subName": "初始化y变量", "subDesc": "设备上电,初始化x变量", "condition": "设备正常启动,功能正常", "operation": "设备上电", "observe": "查看y变量", "expect": "y变量值正确"}}, {"model": "project.case", "pk": 435, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-05-16", "sort": 1, "ident": "JTFX", "name": "静态分析111", "initialization": "已获取全部被测件源代码程序,静态分析工具准备齐备", "premise": "提交的代码出自委托方受控库,是委托方正式签署外发的", "summarize": "依据委托方的要求进行静态分析,验证软件质量度量和编码规则是否满足军标要求", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 187, "design": 205, "test": 348, "title": "静态分析111", "key": "0-0-0-0-0", "level": "4", "exe_time": "2024-05-23"}}, {"model": "project.case", "pk": 436, "fields": {"remark": null, "update_datetime": "2024-09-02", "create_datetime": "2024-05-16", "sort": 1, "ident": "DMSC", "name": "代码审查", "initialization": "代码已提交", "premise": "提交的代码出自委托方受控库,是委托方正式签署外发的", "summarize": "通过人工审查及借助工具辅助分析的方式开展代码审查,审查代码编程准则的符合性、代码流程实现的正确性、代码结构的合理性以及代码实现需求的正确性;人工审查中发现的问题,审查人员应及时记录", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 187, "design": 206, "test": 349, "title": "代码审查", "key": "0-0-1-0-0", "level": "4", "exe_time": "2024-09-04"}}, {"model": "project.case", "pk": 437, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-05-16", "sort": 1, "ident": "WDSC", "name": "文档审查", "initialization": "开发方已提交被测文档", "premise": "提交的文档出自委托方受控库,是委托方正式签署外发的", "summarize": "测试人员阅读文档,依据文档检查单对软件文档进行审查,审查文档内容是否完整、文档描述是否准确、文档格式是否规范、文档是否文文一致", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 187, "design": 207, "test": 350, "title": "文档审查", "key": "0-0-2-0-0", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 538, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-07-29", "sort": 1, "ident": "JTFX", "name": "静态分析", "initialization": "已获取全部被测件源代码程序,静态分析工具准备齐备", "premise": "提交的代码出自委托方受控库,是委托方正式签署外发的", "summarize": "依据委托方的要求进行静态分析,验证软件质量度量和编码规则是否满足军标要求", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 152, "dut": 224, "design": 3604, "test": 407, "title": "静态分析", "key": "1-1-0-0-0", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 539, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-07-29", "sort": 1, "ident": "DMSC", "name": "代码审查", "initialization": "代码已提交", "premise": "提交的代码出自委托方受控库,是委托方正式签署外发的", "summarize": "通过人工审查及借助工具辅助分析的方式开展代码审查,审查代码编程准则的符合性、代码流程实现的正确性、代码结构的合理性以及代码实现需求的正确性;人工审查中发现的问题,审查人员应及时记录", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 152, "dut": 224, "design": 3605, "test": 408, "title": "代码审查", "key": "1-1-1-0-0", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 540, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-07-29", "sort": 1, "ident": "WDSC", "name": "文档审查", "initialization": "开发方已提交被测文档", "premise": "提交的文档出自委托方受控库,是委托方正式签署外发的", "summarize": "测试人员阅读文档,依据文档检查单对软件文档进行审查,审查文档内容是否完整、文档描述是否准确、文档格式是否规范、文档是否文文一致", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 152, "dut": 224, "design": 3606, "test": 409, "title": "文档审查", "key": "1-1-2-0-0", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 562, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "ident": "WDSC", "name": "文档审查", "initialization": "开发方已提交被测文档", "premise": "提交的文档出自委托方受控库,是委托方正式签署外发的", "summarize": "测试人员阅读文档,依据文档检查单对软件文档进行审查,审查文档内容是否完整、文档描述是否准确、文档格式是否规范、文档是否文文一致", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 187, "design": 207, "test": 350, "title": "文档审查", "key": "0-0-2-0-1", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 563, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "ident": "WDSC", "name": "文档审查", "initialization": "开发方已提交被测文档", "premise": "提交的文档出自委托方受控库,是委托方正式签署外发的", "summarize": "测试人员阅读文档,依据文档检查单对软件文档进行审查,审查文档内容是否完整、文档描述是否准确、文档格式是否规范、文档是否文文一致", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 187, "design": 207, "test": 350, "title": "文档审查", "key": "0-0-2-0-6", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 575, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "ident": "WDSC", "name": "文档审查", "initialization": "开发方已提交被测文档", "premise": "提交的文档出自委托方受控库,是委托方正式签署外发的", "summarize": "测试人员阅读文档,依据文档检查单对软件文档进行审查,审查文档内容是否完整、文档描述是否准确、文档格式是否规范、文档是否文文一致", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 187, "design": 207, "test": 350, "title": "文档审查", "key": "0-0-2-0-3", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 576, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "ident": "WDSC", "name": "文档审查", "initialization": "开发方已提交被测文档", "premise": "提交的文档出自委托方受控库,是委托方正式签署外发的", "summarize": "测试人员阅读文档,依据文档检查单对软件文档进行审查,审查文档内容是否完整、文档描述是否准确、文档格式是否规范、文档是否文文一致", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 187, "design": 207, "test": 350, "title": "文档审查", "key": "0-0-2-0-4", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 577, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "ident": "WDSC", "name": "文档审查", "initialization": "开发方已提交被测文档", "premise": "提交的文档出自委托方受控库,是委托方正式签署外发的", "summarize": "测试人员阅读文档,依据文档检查单对软件文档进行审查,审查文档内容是否完整、文档描述是否准确、文档格式是否规范、文档是否文文一致", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 187, "design": 207, "test": 350, "title": "文档审查", "key": "0-0-2-0-2", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 581, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "ident": "WDSC", "name": "文档审查", "initialization": "开发方已提交被测文档", "premise": "提交的文档出自委托方受控库,是委托方正式签署外发的", "summarize": "测试人员阅读文档,依据文档检查单对软件文档进行审查,审查文档内容是否完整、文档描述是否准确、文档格式是否规范、文档是否文文一致", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 187, "design": 207, "test": 350, "title": "文档审查", "key": "0-0-2-0-5", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 585, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-08-14", "sort": 1, "ident": "CSHG", "name": "yy用例2号", "initialization": "软件正常启动,正常登录进软件", "premise": "软件正常启动,各界面显示工作正常", "summarize": "我改动了用例综述", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 191, "design": 3663, "test": 422, "title": "yy用例2号", "key": "0-2-0-0-0", "level": "4", "exe_time": "2024-08-31"}}, {"model": "project.case", "pk": 646, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "ident": "PPPP", "name": "阿萨德", "initialization": "软件正常启动,正常运行", "premise": "软件正常启动,外部接口运行正常", "summarize": "大1111", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 189, "design": 3104, "test": 469, "title": "阿萨德", "key": "0-1-0-1-0", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 647, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "ident": "DDDD", "name": "DDD", "initialization": "软件正常启动,正常运行", "premise": "软件正常启动,外部接口运行正常", "summarize": "AAA", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 189, "design": 3104, "test": 470, "title": "DDD", "key": "0-1-0-2-0", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 648, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "ident": "CCCC", "name": "ouman", "initialization": "软件正常启动,正常运行", "premise": "软件正常启动,外部接口运行正常", "summarize": "22222", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 189, "design": 3104, "test": 468, "title": "ouman", "key": "0-1-0-0-0", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 649, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "ident": "CCCC", "name": "1111", "initialization": "软件正常启动,正常运行", "premise": "软件正常启动,外部接口运行正常", "summarize": "22222", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 189, "design": 3104, "test": 468, "title": "1111", "key": "0-1-0-0-1", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 650, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "ident": "CCCC", "name": "1111", "initialization": "软件正常启动,正常运行", "premise": "软件正常启动,外部接口运行正常", "summarize": "22222", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 17, "isLeaf": true, "round": 128, "dut": 189, "design": 3104, "test": 468, "title": "1111", "key": "0-1-0-0-2", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 666, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "JTFX", "name": "静态分析", "initialization": "已获取全部被测件源代码程序,静态分析工具准备齐备", "premise": "提交的代码出自委托方受控库,是委托方正式签署外发的", "summarize": "依据委托方的要求进行静态分析,验证软件质量度量和编码规则是否满足军标要求", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 30, "isLeaf": true, "round": 174, "dut": 253, "design": 3736, "test": 484, "title": "静态分析", "key": "0-0-0-0-0", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 667, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "DMSC", "name": "代码审查", "initialization": "代码已提交", "premise": "提交的代码出自委托方受控库,是委托方正式签署外发的", "summarize": "通过人工审查及借助工具辅助分析的方式开展代码审查,审查代码编程准则的符合性、代码流程实现的正确性、代码结构的合理性以及代码实现需求的正确性;人工审查中发现的问题,审查人员应及时记录", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 30, "isLeaf": true, "round": 174, "dut": 253, "design": 3737, "test": 485, "title": "代码审查", "key": "0-0-1-0-0", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 668, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "WDSC", "name": "文档审查", "initialization": "开发方已提交被测文档", "premise": "提交的文档出自委托方受控库,是委托方正式签署外发的", "summarize": "测试人员阅读文档,依据文档检查单对软件文档进行审查,审查文档内容是否完整、文档描述是否准确、文档格式是否规范、文档是否文文一致", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 30, "isLeaf": true, "round": 174, "dut": 253, "design": 3738, "test": 486, "title": "文档审查", "key": "0-0-2-0-0", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 674, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "CSHX", "name": "初始化x变量", "initialization": "软件正常启动,正常运行", "premise": "软件正常启动,外部接口运行正常", "summarize": "设备上电,初始化x变量", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 30, "isLeaf": true, "round": 174, "dut": 257, "design": 3751, "test": 492, "title": "初始化x变量", "key": "0-1-5-0-0", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 675, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "CSHX", "name": "初始化y变量", "initialization": "软件正常启动,正常运行", "premise": "软件正常启动,外部接口运行正常", "summarize": "设备上电,初始化x变量", "designPerson": "陈俊亦", "testPerson": "陈俊亦", "monitorPerson": "陈俊亦", "project": 30, "isLeaf": true, "round": 174, "dut": 257, "design": 3751, "test": 492, "title": "初始化y变量", "key": "0-1-5-0-1", "level": "4", "exe_time": null}}, {"model": "project.case", "pk": 676, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "CSHX", "name": "初始化x变量异常功能", "initialization": "软件正常启动,正常登录进软件", "premise": "软件正常启动,各界面显示工作正常", "summarize": "初始化x变量异常功能测试", "designPerson": "陈俊亦", "testPerson": "某测试人员1", "monitorPerson": "某测试人员3", "project": 30, "isLeaf": true, "round": 174, "dut": 257, "design": 3751, "test": 492, "title": "初始化x变量异常功能", "key": "0-1-5-0-2", "level": "4", "exe_time": null}}, {"model": "project.casestep", "pk": 496, "fields": {"remark": null, "update_datetime": "2024-05-16", "create_datetime": "2024-05-16", "sort": 1, "operation": "按照测试需求中文档齐套性检查单检查需求类、设计类、用户类、测试类文档是否齐套", "expect": "文档齐套性检查单全部通过,软件文档齐套", "result": "文档齐套性检查单全部通过,软件文档齐套", "passed": "3", "case": 437}}, {"model": "project.casestep", "pk": 497, "fields": {"remark": null, "update_datetime": "2024-05-16", "create_datetime": "2024-05-16", "sort": 1, "operation": "按照测试需求中文档需求规格说明、设计文档等审查单,对相关文档进行审查", "expect": "文档满足完整性、准确性、规范性和一致性的要求", "result": "文档检查单全部审查通过,文档内容完整、文档描述准确、文档格式规范、文档文文一致", "passed": "3", "case": 437}}, {"model": "project.casestep", "pk": 504, "fields": {"remark": null, "update_datetime": "2024-05-27", "create_datetime": "2024-05-27", "sort": 1, "operation": "

    123

    ", "expect": "123", "result": "

    123

    ", "passed": "3", "case": 443}}, {"model": "project.casestep", "pk": 505, "fields": {"remark": null, "update_datetime": "2024-05-27", "create_datetime": "2024-05-27", "sort": 1, "operation": "

    123

    ", "expect": "123", "result": "

    123

    ", "passed": "3", "case": 444}}, {"model": "project.casestep", "pk": 506, "fields": {"remark": null, "update_datetime": "2024-05-27", "create_datetime": "2024-05-27", "sort": 1, "operation": "

    123

    ", "expect": "123", "result": "

    123

    ", "passed": "3", "case": 445}}, {"model": "project.casestep", "pk": 507, "fields": {"remark": null, "update_datetime": "2024-05-30", "create_datetime": "2024-05-30", "sort": 1, "operation": "

    123

    ", "expect": "123", "result": "

    123

    ", "passed": "3", "case": 446}}, {"model": "project.casestep", "pk": 656, "fields": {"remark": null, "update_datetime": "2024-07-29", "create_datetime": "2024-07-29", "sort": 1, "operation": "使用LDRA TestBed软件和Klocwork软件工具对被测软件全部源程序进行静态分析,并配合人工以及检查单进行分析", "expect": "静态审查单全部通过,且源代码满足编码规则和质量度量要求", "result": "静态度量结果符合国军标要求,静态分析审查单全部通过", "passed": "3", "case": 538}}, {"model": "project.casestep", "pk": 657, "fields": {"remark": null, "update_datetime": "2024-07-29", "create_datetime": "2024-07-29", "sort": 1, "operation": "通过人工审查及借助工具辅助分析的方式开展代码审查,审查代码编程准则的符合性、代码流程实现的正确性、代码结构的合理性以及代码实现需求的正确性;人工审查中发现的问题,审查人员应及时记录", "expect": "代码设计正确,满足审查单要求,无不符合项", "result": "代码设计正确,满足审查单要求,无不符合项", "passed": "3", "case": 539}}, {"model": "project.casestep", "pk": 658, "fields": {"remark": null, "update_datetime": "2024-07-29", "create_datetime": "2024-07-29", "sort": 1, "operation": "按照测试需求中文档齐套性检查单检查需求类、设计类、用户类、测试类文档是否齐套", "expect": "文档齐套性检查单全部通过,软件文档齐套", "result": "文档齐套性检查单全部通过,软件文档齐套", "passed": "3", "case": 540}}, {"model": "project.casestep", "pk": 659, "fields": {"remark": null, "update_datetime": "2024-07-29", "create_datetime": "2024-07-29", "sort": 1, "operation": "按照测试需求中文档需求规格说明、设计文档等审查单,对相关文档进行审查", "expect": "文档满足完整性、准确性、规范性和一致性的要求", "result": "文档检查单全部审查通过,文档内容完整、文档描述准确、文档格式规范、文档文文一致", "passed": "3", "case": 540}}, {"model": "project.casestep", "pk": 704, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "operation": "按照测试需求中文档齐套性检查单检查需求类、设计类、用户类、测试类文档是否齐套", "expect": "文档齐套性检查单全部通过,软件文档齐套", "result": "文档齐套性检查单全部通过,软件文档齐套", "passed": "3", "case": 562}}, {"model": "project.casestep", "pk": 705, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "operation": "按照测试需求中文档需求规格说明、设计文档等审查单,对相关文档进行审查", "expect": "文档满足完整性、准确性、规范性和一致性的要求", "result": "文档检查单全部审查通过,文档内容完整、文档描述准确、文档格式规范、文档文文一致", "passed": "3", "case": 562}}, {"model": "project.casestep", "pk": 706, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "operation": "按照测试需求中文档齐套性检查单检查需求类、设计类、用户类、测试类文档是否齐套", "expect": "文档齐套性检查单全部通过,软件文档齐套", "result": "文档齐套性检查单全部通过,软件文档齐套", "passed": "3", "case": 563}}, {"model": "project.casestep", "pk": 707, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "operation": "按照测试需求中文档需求规格说明、设计文档等审查单,对相关文档进行审查", "expect": "文档满足完整性、准确性、规范性和一致性的要求", "result": "文档检查单全部审查通过,文档内容完整、文档描述准确、文档格式规范、文档文文一致", "passed": "3", "case": 563}}, {"model": "project.casestep", "pk": 728, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "operation": "按照测试需求中文档齐套性检查单检查需求类、设计类、用户类、测试类文档是否齐套", "expect": "文档齐套性检查单全部通过,软件文档齐套", "result": "文档齐套性检查单全部通过,软件文档齐套", "passed": "3", "case": 575}}, {"model": "project.casestep", "pk": 729, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "operation": "按照测试需求中文档需求规格说明、设计文档等审查单,对相关文档进行审查", "expect": "文档满足完整性、准确性、规范性和一致性的要求", "result": "文档检查单全部审查通过,文档内容完整、文档描述准确、文档格式规范、文档文文一致", "passed": "3", "case": 575}}, {"model": "project.casestep", "pk": 730, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "operation": "按照测试需求中文档齐套性检查单检查需求类、设计类、用户类、测试类文档是否齐套", "expect": "文档齐套性检查单全部通过,软件文档齐套", "result": "文档齐套性检查单全部通过,软件文档齐套", "passed": "3", "case": 576}}, {"model": "project.casestep", "pk": 731, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "operation": "按照测试需求中文档需求规格说明、设计文档等审查单,对相关文档进行审查", "expect": "文档满足完整性、准确性、规范性和一致性的要求", "result": "文档检查单全部审查通过,文档内容完整、文档描述准确、文档格式规范、文档文文一致", "passed": "3", "case": 576}}, {"model": "project.casestep", "pk": 732, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "operation": "按照测试需求中文档齐套性检查单检查需求类、设计类、用户类、测试类文档是否齐套", "expect": "文档齐套性检查单全部通过,软件文档齐套", "result": "文档齐套性检查单全部通过,软件文档齐套", "passed": "3", "case": 577}}, {"model": "project.casestep", "pk": 733, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "operation": "按照测试需求中文档需求规格说明、设计文档等审查单,对相关文档进行审查", "expect": "文档满足完整性、准确性、规范性和一致性的要求", "result": "文档检查单全部审查通过,文档内容完整、文档描述准确、文档格式规范、文档文文一致", "passed": "3", "case": 577}}, {"model": "project.casestep", "pk": 738, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "operation": "按照测试需求中文档齐套性检查单检查需求类、设计类、用户类、测试类文档是否齐套", "expect": "文档齐套性检查单全部通过,软件文档齐套", "result": "文档齐套性检查单全部通过,软件文档齐套", "passed": "3", "case": 581}}, {"model": "project.casestep", "pk": 739, "fields": {"remark": null, "update_datetime": "2024-08-09", "create_datetime": "2024-08-09", "sort": 1, "operation": "按照测试需求中文档需求规格说明、设计文档等审查单,对相关文档进行审查", "expect": "文档满足完整性、准确性、规范性和一致性的要求", "result": "文档检查单全部审查通过,文档内容完整、文档描述准确、文档格式规范、文档文文一致", "passed": "3", "case": 581}}, {"model": "project.casestep", "pk": 840, "fields": {"remark": null, "update_datetime": "2024-09-02", "create_datetime": "2024-09-02", "sort": 1, "operation": "通过人工审查及借助工具辅助分析的方式开展代码审查,审查代码编程准则的符合性、代码流程实现的正确性、代码结构的合理性以及代码实现需求的正确性;人工审查中发现的问题,审查人员应及时记录", "expect": "代码设计正确,满足审查单要求,无不符合项", "result": "代码设计正确,满足审查单要求,无不符合项", "passed": "3", "case": 436}}, {"model": "project.casestep", "pk": 908, "fields": {"remark": null, "update_datetime": "2024-09-05", "create_datetime": "2024-09-05", "sort": 1, "operation": "

    11112312搜索

    ", "expect": "1117777", "result": "

    aaaa

    ", "passed": "2", "case": 585}}, {"model": "project.casestep", "pk": 909, "fields": {"remark": null, "update_datetime": "2024-09-05", "create_datetime": "2024-09-05", "sort": 1, "operation": "

    无敌是多么 多么寂寞

    ", "expect": "123", "result": "

    12312311~~123

    ", "passed": "2", "case": 585}}, {"model": "project.casestep", "pk": 940, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "operation": "使用LDRA TestBed软件和Klocwork软件工具对被测软件全部源程序进行静态分析,并配合人工以及检查单进行分析", "expect": "静态审查单全部通过,且源代码满足编码规则和质量度量要求", "result": "静态度量结果符合国军标要求,静态分析审查单全部通过", "passed": "3", "case": 435}}, {"model": "project.casestep", "pk": 944, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "operation": ",,", "expect": null, "result": "", "passed": "3", "case": 646}}, {"model": "project.casestep", "pk": 945, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "operation": "123,321,123", "expect": "123", "result": "", "passed": "3", "case": 647}}, {"model": "project.casestep", "pk": 948, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "operation": "123,123,123", "expect": "123", "result": "", "passed": "3", "case": 650}}, {"model": "project.casestep", "pk": 950, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "operation": "123,123,123", "expect": "123", "result": "", "passed": "3", "case": 649}}, {"model": "project.casestep", "pk": 953, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "operation": "123,123,123", "expect": "123", "result": "

    正确

    ", "passed": "1", "case": 648}}, {"model": "project.casestep", "pk": 954, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "operation": "123,123,123", "expect": "123", "result": "

    错误

    ", "passed": "2", "case": 648}}, {"model": "project.casestep", "pk": 981, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "operation": "使用LDRA TestBed软件和Klocwork软件工具对被测软件全部源程序进行静态分析,并配合人工以及检查单进行分析", "expect": "静态审查单全部通过,且源代码满足编码规则和质量度量要求", "result": "静态度量结果符合国军标要求,静态分析审查单全部通过", "passed": "3", "case": 666}}, {"model": "project.casestep", "pk": 982, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "operation": "通过人工审查及借助工具辅助分析的方式开展代码审查,审查代码编程准则的符合性、代码流程实现的正确性、代码结构的合理性以及代码实现需求的正确性;人工审查中发现的问题,审查人员应及时记录", "expect": "代码设计正确,满足审查单要求,无不符合项", "result": "代码设计正确,满足审查单要求,无不符合项", "passed": "3", "case": 667}}, {"model": "project.casestep", "pk": 983, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "operation": "按照测试需求中文档齐套性检查单检查需求类、设计类、用户类、测试类文档是否齐套", "expect": "文档齐套性检查单全部通过,软件文档齐套", "result": "文档齐套性检查单全部通过,软件文档齐套", "passed": "3", "case": 668}}, {"model": "project.casestep", "pk": 984, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "operation": "按照测试需求中文档需求规格说明、设计文档等审查单,对相关文档进行审查", "expect": "文档满足完整性、准确性、规范性和一致性的要求", "result": "文档检查单全部审查通过,文档内容完整、文档描述准确、文档格式规范、文档文文一致", "passed": "3", "case": 668}}, {"model": "project.casestep", "pk": 992, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "operation": "设备正常启动,功能正常,设备上电,查看x变量", "expect": "x变量值正确", "result": "", "passed": "3", "case": 674}}, {"model": "project.casestep", "pk": 993, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "operation": "设备正常启动,功能正常,设备上电,查看y变量", "expect": "y变量值正确", "result": "", "passed": "3", "case": 675}}, {"model": "project.casestep", "pk": 996, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "operation": "

    通过xxx操作

    ", "expect": "设备正常报错", "result": "

    没有报错

    ", "passed": "2", "case": 676}}, {"model": "project.casestep", "pk": 997, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "operation": "

    通过xxx2操作

    ", "expect": "设备正常报错,并提示", "result": "

    没有报错

    ", "passed": "2", "case": 676}}, {"model": "project.problem", "pk": 95, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-05", "sort": 1, "ident": "1", "name": "111", "status": "2", "grade": "1", "type": "3", "closeMethod": ["1"], "operation": "

    11

    ", "result": "", "postPerson": "陈俊亦", "postDate": "2024-07-06", "designerPerson": "", "designDate": "2024-07-07", "verifyPerson": "", "verifyDate": "2024-09-05", "project": 17, "solve": "", "analysis": "", "effect_scope": "", "verify_result": "", "case": []}}, {"model": "project.problem", "pk": 98, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "ident": "2", "name": "新增WTDDDD", "status": "2", "grade": "2", "type": "3", "closeMethod": ["1", "2"], "operation": "", "result": "", "postPerson": "陈俊亦", "postDate": "2024-07-06", "designerPerson": "", "designDate": "2024-07-07", "verifyPerson": "", "verifyDate": "2024-09-09", "project": 17, "solve": "", "analysis": "", "effect_scope": "", "verify_result": "", "case": [585]}}, {"model": "project.problem", "pk": 99, "fields": {"remark": null, "update_datetime": "2024-09-09", "create_datetime": "2024-09-09", "sort": 1, "ident": "3", "name": "出现错误情况", "status": "2", "grade": "1", "type": "3", "closeMethod": ["1", "2"], "operation": "

    乱入其他支付码乱入其他支付码乱入其他支付码乱入其他支付码

    \n

    乱入其他支付码乱入其他支付码乱入其他支付码

    \n

    乱入其他支付码乱入其他支付码乱入其他支付码乱入其他支付码

    \n

    乱入其他支付码乱入其他支付码乱入其他支付码

    \n

     

    ", "result": "乱入其他支付码乱入其他支付码乱入其他支付码乱入其他支付码\n乱入其他支付码乱入其他支付码乱入其他支付码", "postPerson": "陈俊亦", "postDate": "2024-07-06", "designerPerson": "开发人员1号", "designDate": "2024-07-07", "verifyPerson": "陈俊亦", "verifyDate": "2024-09-09", "project": 17, "solve": "", "analysis": "阿萨德阿萨德", "effect_scope": "阿萨德阿萨德", "verify_result": "", "case": [648]}}, {"model": "project.problem", "pk": 102, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "1", "name": "初始化x变量异常未报错", "status": "2", "grade": "2", "type": "3", "closeMethod": ["2"], "operation": "

    初始化x变量未报错,XXXX

    ", "result": "问题的影响", "postPerson": "某测试人员3", "postDate": "2024-10-24", "designerPerson": "", "designDate": "2024-10-23", "verifyPerson": "", "verifyDate": "2024-10-21", "project": 30, "solve": "开发人员填写", "analysis": "开发人员填写", "effect_scope": "开发人员填写", "verify_result": "", "case": []}}, {"model": "project.problem", "pk": 103, "fields": {"remark": null, "update_datetime": "2024-10-21", "create_datetime": "2024-10-21", "sort": 1, "ident": "2", "name": "无关联问题单x变量", "status": "2", "grade": "1", "type": "3", "closeMethod": ["2"], "operation": "

    x变量无关联问题单

    ", "result": "xxxx", "postPerson": "某测试人员3", "postDate": "2024-10-24", "designerPerson": "", "designDate": null, "verifyPerson": "", "verifyDate": "2024-10-21", "project": 30, "solve": "", "analysis": "", "effect_scope": "", "verify_result": "", "case": [676]}}, {"model": "project.contact", "pk": 1, "fields": {"key": 1, "remark": "这是一个公司或单位信息", "update_datetime": "2024-09-06", "create_datetime": "2023-08-17", "sort": 1, "entrust_person": "某个法人", "name": "上海微小卫星工程中心", "addr": "一个有效地址1", "refer_name": "小卫星中心"}}, {"model": "project.contact", "pk": 2, "fields": {"key": 2, "remark": "123", "update_datetime": "2024-07-15", "create_datetime": "2023-08-17", "sort": 1, "entrust_person": "周冲个", "name": "上海翰讯通讯股份有限公司", "addr": "一个很好的地址a1", "refer_name": "翰讯"}}, {"model": "project.contact", "pk": 4, "fields": {"key": 3, "remark": null, "update_datetime": "2024-07-15", "create_datetime": "2024-03-11", "sort": 1, "entrust_person": "枫若离", "name": "中国科学院卫星软件测评中心", "addr": "上海市海科路99号", "refer_name": "小卫星"}}, {"model": "project.contact", "pk": 5, "fields": {"key": 4, "remark": null, "update_datetime": "2024-07-15", "create_datetime": "2024-06-20", "sort": 1, "entrust_person": "某人", "name": "某研制方单位", "addr": "某研制方单位的地址", "refer_name": "某单位"}}, {"model": "project.contact", "pk": 6, "fields": {"key": 5, "remark": null, "update_datetime": "2024-09-03", "create_datetime": "2024-06-20", "sort": 1, "entrust_person": "周冲个1122", "name": "XXX航天集团第11研究所", "addr": "上海市XXX区XXX街道999号", "refer_name": "航天11所"}}, {"model": "project.contact", "pk": 7, "fields": {"key": 7, "remark": null, "update_datetime": "2024-10-16", "create_datetime": "2024-10-16", "sort": 1, "entrust_person": "委托单位法人", "name": "某委托方单位", "addr": "委托方地址", "refer_name": "委托单位简称"}}, {"model": "project.contact", "pk": 8, "fields": {"key": 8, "remark": null, "update_datetime": "2024-10-16", "create_datetime": "2024-10-16", "sort": 1, "entrust_person": "测试方法人", "name": "某测试方单位", "addr": "测试方地址", "refer_name": "测试方单位简称"}}, {"model": "project.abbreviation", "pk": 1, "fields": {"title": "UDP", "des": "User Datagram Protocol用户数据包协议"}}, {"model": "project.abbreviation", "pk": 2, "fields": {"title": "TCP", "des": "Transmission Control Protocol传输控制协议"}}, {"model": "project.abbreviation", "pk": 4, "fields": {"title": "HTML", "des": "Hyper Text Markup Language超文本标记语言"}}, {"model": "project.abbreviation", "pk": 5, "fields": {"title": "HTTP", "des": "Hypertext Transfer Protocol超文本传输协议"}}, {"model": "project.abbreviation", "pk": 76, "fields": {"title": "FPGA", "des": "Field Programmable Gate Array可编程阵列逻辑"}}] \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000..4c2c3b4 Binary files /dev/null and b/db.sqlite3 differ diff --git a/logs/generates_logs b/logs/generates_logs new file mode 100644 index 0000000..66c937f --- /dev/null +++ b/logs/generates_logs @@ -0,0 +1 @@ +[WARNING][2025-04-29 14:20:41,997][logger.py:25][回归测试记录模块][单个问题单表格]片段:问题单4未关联用例,请检查 diff --git a/logs/root_log b/logs/root_log new file mode 100644 index 0000000..fff19dd --- /dev/null +++ b/logs/root_log @@ -0,0 +1,516 @@ +[WARNING][2025-04-28 10:43:31,783][operation.py:133]"GET - GenerateControllerDG[create_information] /api/generate/create/baseInformation" ("int() argument must be a string, a bytes-like object or a real number, not 'NoneType'",) +[ERROR][2025-04-28 10:43:31,783][errors.py:131]int() argument must be a string, a bytes-like object or a real number, not 'NoneType' +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\operation.py", line 214, 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 99, 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 490, in create_information + line_count = int(first_round_SO.total_lines) +TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType' +[ERROR][2025-04-28 10:43:31,812][log.py:248]Internal Server Error: /api/generate/create/baseInformation +[WARNING][2025-04-28 10:43:32,140][operation.py:133]"GET - GenerateControllerDG[create_codeQuality] /api/generate/create/codeQuality" ("int() argument must be a string, a bytes-like object or a real number, not 'NoneType'",) +[ERROR][2025-04-28 10:43:32,140][errors.py:131]int() argument must be a string, a bytes-like object or a real number, not 'NoneType' +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\operation.py", line 214, 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 99, 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 789, in create_codeQuality + context.update({'size': int(source_dut.total_lines)}) + ~~~^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType' +[ERROR][2025-04-28 10:43:32,148][log.py:248]Internal Server Error: /api/generate/create/codeQuality +[WARNING][2025-04-28 10:43:41,712][operation.py:133]"GET - GenerateControllerDG[create_information] /api/generate/create/baseInformation" ("int() argument must be a string, a bytes-like object or a real number, not 'NoneType'",) +[ERROR][2025-04-28 10:43:41,712][errors.py:131]int() argument must be a string, a bytes-like object or a real number, not 'NoneType' +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\operation.py", line 214, 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 99, 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 490, in create_information + line_count = int(first_round_SO.total_lines) +TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType' +[ERROR][2025-04-28 10:43:41,713][log.py:248]Internal Server Error: /api/generate/create/baseInformation +[WARNING][2025-04-28 10:43:41,986][operation.py:133]"GET - GenerateControllerDG[create_codeQuality] /api/generate/create/codeQuality" ("int() argument must be a string, a bytes-like object or a real number, not 'NoneType'",) +[ERROR][2025-04-28 10:43:41,987][errors.py:131]int() argument must be a string, a bytes-like object or a real number, not 'NoneType' +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\operation.py", line 214, 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 99, 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 789, in create_codeQuality + context.update({'size': int(source_dut.total_lines)}) + ~~~^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType' +[ERROR][2025-04-28 10:43:41,996][log.py:248]Internal Server Error: /api/generate/create/codeQuality +[WARNING][2025-04-28 10:44:37,243][operation.py:133]"GET - GenerateControllerDG[create_information] /api/generate/create/baseInformation" ("'item' is undefined",) +[ERROR][2025-04-28 10:44:37,243][errors.py:131]'item' is undefined +Traceback (most recent call last): + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\ninja_extra\operation.py", line 214, 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 99, 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 506, in create_information + return create_dg_docx('被测软件基本信息.docx', context, id) + ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\apps\createDocument\extensions\util.py", line 45, in create_dg_docx + doc.render(context) + ~~~~~~~~~~^^^^^^^^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docxtpl\template.py", line 484, in render + xml_src = self.build_xml(context, jinja_env) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docxtpl\template.py", line 431, in build_xml + xml = self.render_xml_part(xml, self.docx._part, context, jinja_env) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docxtpl\template.py", line 317, in render_xml_part + raise exc + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\docxtpl\template.py", line 308, in render_xml_part + dst_xml = template.render(context) + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\jinja2\environment.py", line 1295, in render + self.environment.handle_exception() + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^ + File "E:\pycharmProjects\cdtestplant_v1\.venv\Lib\site-packages\jinja2\environment.py", line 942, in handle_exception + raise rewrite_traceback_stack(source=source) + File "