日常修复内容
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,212 +1,216 @@
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from ninja_extra import api_controller, ControllerBase, route
|
||||
from ninja_extra.permissions import IsAuthenticated
|
||||
from ninja_jwt.authentication import JWTAuth
|
||||
from django.db import transaction
|
||||
from django.db.models import QuerySet
|
||||
from docxtpl import DocxTemplate
|
||||
from apps.dict.models import Dict
|
||||
from utils.chen_response import ChenResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from typing import Union
|
||||
from docxtpl import InlineImage
|
||||
from apps.project.models import Dut, Project, Round
|
||||
from utils.util import get_list_dict, get_str_dict, get_ident, get_case_ident
|
||||
from utils.chapter_tools.csx_chapter import create_csx_chapter_dict
|
||||
from utils.path_utils import project_path
|
||||
from apps.createDocument.extensions.util import delete_dir_files
|
||||
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||
# 导入生成日志记录模块
|
||||
from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
|
||||
|
||||
chinese_round_name: list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
||||
|
||||
# @api_controller("/generateHSM", tags=['生成回归记录系列文档'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||
@api_controller("/generateHJL", tags=['生成回归记录系列文档'])
|
||||
class GenerateControllerHJL(ControllerBase):
|
||||
logger = GenerateLogger('回归测试记录')
|
||||
|
||||
# important:删除之前的文件
|
||||
@route.get('/create/deleteHJLDocument', url_name='delete-hjl-document')
|
||||
def delete_hjl_document(self, id: int):
|
||||
project_path_str = project_path(id)
|
||||
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl'
|
||||
delete_dir_files(save_path)
|
||||
|
||||
@route.get("/create/basicInformation", url_name="create-basicInformation")
|
||||
@transaction.atomic
|
||||
def create_basicInformation(self, id: int):
|
||||
"""生成回归测试记录的被测软件基本信息"""
|
||||
project_path_str = project_path(id)
|
||||
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hjl' / '被测软件基本信息.docx'
|
||||
doc = DocxTemplate(tpl_path)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 第一轮次对象
|
||||
round1_obj: Union[Round, None] = project_obj.pField.filter(key='0').first()
|
||||
# 第一轮源代码被测件对象
|
||||
round1_so_dut: Union[Dut, None] = round1_obj.rdField.filter(type='SO').first()
|
||||
languages = get_list_dict('language', project_obj.language)
|
||||
language_list = [item['ident_version'] for item in languages]
|
||||
# 取非第一轮次
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
# ***Inspect-start***
|
||||
self.logger.model = '回归测试记录'
|
||||
self.logger.write_warning_log('当前文档全部片段', f'该项目没有创建轮次')
|
||||
# ***Inspect-end***
|
||||
return ChenResponse(code=400, status=400, message='您未创建轮次,请创建完毕后再试')
|
||||
|
||||
context = {
|
||||
'project_name': project_obj.name,
|
||||
'language': "、".join(language_list),
|
||||
'soft_type': project_obj.get_soft_type_display(),
|
||||
'security_level': get_str_dict(project_obj.security_level, 'security_level'),
|
||||
'runtime': get_str_dict(project_obj.runtime, 'runtime'),
|
||||
'devplant': get_str_dict(project_obj.devplant, 'devplant'),
|
||||
'recv_date': project_obj.beginTime.strftime("%Y-%m-%d"),
|
||||
'dev_unit': project_obj.dev_unit,
|
||||
}
|
||||
|
||||
version_info = [{'version': round1_so_dut.version,
|
||||
'line_count': int(round1_so_dut.total_lines),
|
||||
'effective_line': int(round1_so_dut.effective_lines)}]
|
||||
# 循环回归的轮次
|
||||
for hround in hround_list:
|
||||
# 每个轮次独立渲染context
|
||||
context_round = deepcopy(context)
|
||||
# 取中文名称
|
||||
cname = chinese_round_name[int(hround.key)] # 输出二、三...
|
||||
# 取该轮次源代码版本放入版本列表
|
||||
so_dut: Dut = hround.rdField.filter(type='SO').first()
|
||||
if not so_dut:
|
||||
return ChenResponse(code=400, status=400, message=f'您第{cname}轮次中缺少源代码被测件,请添加')
|
||||
version_info.append(
|
||||
{'version': so_dut.version, 'line_count': int(so_dut.total_lines),
|
||||
'effective_line': int(so_dut.effective_lines)})
|
||||
context_round['version_info'] = version_info
|
||||
# 开始渲染每个轮次的二级文档
|
||||
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl' / f"第{cname}轮被测软件基本信息.docx"
|
||||
doc.render(context=context_round, autoescape=True)
|
||||
try:
|
||||
doc.save(save_path)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
||||
return ChenResponse(code=200, status=200, message='多轮回归说明文档基本信息生成完毕')
|
||||
|
||||
@route.get("/create/caseinfo", url_name="create-caseinfo")
|
||||
@transaction.atomic
|
||||
def create_caseinfo(self, id: int):
|
||||
"""生成回归测试记录的-{测试用例记录}"""
|
||||
project_path_str = project_path(id)
|
||||
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hjl' / '测试用例记录.docx'
|
||||
doc = DocxTemplate(tpl_path)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
return None
|
||||
demand_prefix = '3.1'
|
||||
# 循环每轮轮次对象
|
||||
for hround in hround_list:
|
||||
cname = chinese_round_name[int(hround.key)] # var:输出二、三字样
|
||||
test_type_len = Dict.objects.get(code='testType').dictItem.count() # 测试类型的个数
|
||||
type_number_list = [i for i in range(1, test_type_len + 1)] # 测试类型编号对应的列表
|
||||
list_list = [[] for j in range(1, test_type_len + 1)] # 每个测试类型组合为一个列表[[],[],[],[]]
|
||||
testType_list, last_chapter_items = create_csx_chapter_dict(hround)
|
||||
testDemands = hround.rtField.all() # 本轮所有测试项
|
||||
for demand in testDemands:
|
||||
type_index = type_number_list.index(int(demand.testType))
|
||||
demand_ident = get_ident(demand)
|
||||
# ~~~组装测试项~~~
|
||||
demand_last_chapter = last_chapter_items[demand.testType].index(demand.key) + 1
|
||||
demand_chapter = ".".join([demand_prefix, str(testType_list.index(demand.testType) + 1),
|
||||
str(demand_last_chapter)])
|
||||
demand_dict = {
|
||||
'name': demand.name,
|
||||
'ident': demand_ident,
|
||||
'chapter': demand_chapter,
|
||||
'item': []
|
||||
}
|
||||
# ~~~这里组装测试项里面的测试用例~~~
|
||||
for case in demand.tcField.all():
|
||||
step_list = []
|
||||
index = 1
|
||||
for one in case.step.all():
|
||||
# 这里需要对operation富文本处理
|
||||
rich_parser = RichParser(one.operation)
|
||||
desc_list = rich_parser.get_final_list(doc, img_size=68)
|
||||
rich_parser2 = RichParser(one.result)
|
||||
res_list = rich_parser2.get_final_list(doc, img_size=75)
|
||||
# 组装用例里面的步骤dict
|
||||
passed = '通过'
|
||||
if one.passed == '2':
|
||||
passed = '未通过'
|
||||
if one.passed == '3':
|
||||
passed = '未执行'
|
||||
step_dict = {
|
||||
'index': index,
|
||||
'operation': desc_list,
|
||||
'expect': one.expect,
|
||||
'result': res_list,
|
||||
'passed': passed,
|
||||
}
|
||||
step_list.append(step_dict)
|
||||
index += 1
|
||||
# 查询所有的problem
|
||||
problem_list = []
|
||||
problem_prefix = "PT"
|
||||
proj_ident = project_obj.ident
|
||||
for problem in case.caseField.all():
|
||||
problem_list.append("_".join([problem_prefix, proj_ident, problem.ident]))
|
||||
# fpga的时序图
|
||||
rich_parser3 = RichParser(case.timing_diagram)
|
||||
timing_diagram = rich_parser3.get_final_list(doc, img_size=115, height=50)
|
||||
has_timing_diagram = False
|
||||
if len(timing_diagram) > 0:
|
||||
if isinstance(timing_diagram[0], InlineImage):
|
||||
has_timing_diagram = True
|
||||
# 组装用例的dict
|
||||
case_dict = {
|
||||
'name': case.name,
|
||||
'ident': get_case_ident(demand_ident, case),
|
||||
'summary': case.summarize,
|
||||
'initialization': case.initialization,
|
||||
'premise': case.premise,
|
||||
'design_person': case.designPerson,
|
||||
'test_person': case.testPerson,
|
||||
'monitor_person': case.monitorPerson,
|
||||
'step': step_list,
|
||||
'time': str(case.exe_time) if case.exe_time is not None else str(case.update_datetime),
|
||||
'problems': "、".join(problem_list),
|
||||
'round_num_chn': cname,
|
||||
# 2025年4月24日新增
|
||||
'has_timing_diagram': has_timing_diagram,
|
||||
'timing_diagram': timing_diagram,
|
||||
}
|
||||
demand_dict['item'].append(case_dict)
|
||||
|
||||
list_list[type_index].append(demand_dict)
|
||||
# 定义渲染上下文
|
||||
context = {}
|
||||
output_list = []
|
||||
for (index, li) in enumerate(list_list):
|
||||
qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1))
|
||||
context_str = qs.title
|
||||
sort = qs.sort
|
||||
table = {
|
||||
"type": context_str,
|
||||
"item": li,
|
||||
"sort": sort
|
||||
}
|
||||
output_list.append(table)
|
||||
# 排序
|
||||
output_list = sorted(output_list, key=(lambda x: x["sort"]))
|
||||
context["data"] = output_list
|
||||
# 最后渲染
|
||||
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl' / f"第{cname}轮测试用例记录.docx"
|
||||
doc.render(context, autoescape=True)
|
||||
try:
|
||||
doc.save(save_path)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
||||
return ChenResponse(code=200, status=200, message='多轮回归测试用例记录生成完毕')
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from ninja_extra import api_controller, ControllerBase, route
|
||||
from ninja_extra.permissions import IsAuthenticated
|
||||
from ninja_jwt.authentication import JWTAuth
|
||||
from django.db import transaction
|
||||
from django.db.models import QuerySet
|
||||
from docxtpl import DocxTemplate
|
||||
from apps.dict.models import Dict
|
||||
from utils.chen_response import ChenResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from typing import Union
|
||||
from docxtpl import InlineImage
|
||||
from apps.project.models import Dut, Project, Round
|
||||
from utils.util import get_list_dict, get_str_dict, get_ident, get_case_ident
|
||||
from utils.chapter_tools.csx_chapter import create_csx_chapter_dict
|
||||
from utils.path_utils import project_path
|
||||
from apps.createDocument.extensions.util import delete_dir_files
|
||||
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||
# 导入生成日志记录模块
|
||||
from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
|
||||
|
||||
chinese_round_name: list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
||||
|
||||
# @api_controller("/generateHSM", tags=['生成回归记录系列文档'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||
@api_controller("/generateHJL", tags=['生成回归记录系列文档'])
|
||||
class GenerateControllerHJL(ControllerBase):
|
||||
logger = GenerateLogger('回归测试记录')
|
||||
|
||||
# important:删除之前的文件
|
||||
@route.get('/create/deleteHJLDocument', url_name='delete-hjl-document')
|
||||
def delete_hjl_document(self, id: int):
|
||||
project_path_str = project_path(id)
|
||||
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl'
|
||||
delete_dir_files(save_path)
|
||||
|
||||
@route.get("/create/basicInformation", url_name="create-basicInformation")
|
||||
@transaction.atomic
|
||||
def create_basicInformation(self, id: int):
|
||||
"""生成回归测试记录的被测软件基本信息"""
|
||||
project_path_str = project_path(id)
|
||||
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hjl' / '被测软件基本信息.docx'
|
||||
doc = DocxTemplate(tpl_path)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
# 第一轮次对象
|
||||
round1_obj: Union[Round, None] = project_obj.pField.filter(key='0').first()
|
||||
# 第一轮源代码被测件对象
|
||||
round1_so_dut: Union[Dut, None] = round1_obj.rdField.filter(type='SO').first()
|
||||
languages = get_list_dict('language', project_obj.language)
|
||||
language_list = [item['ident_version'] for item in languages]
|
||||
runtimes = get_list_dict('runtime', project_obj.runtime)
|
||||
runtime_list = [item['ident_version'] for item in runtimes]
|
||||
devplants = get_list_dict('devplant', project_obj.devplant)
|
||||
devplant_list = [item['ident_version'] for item in devplants]
|
||||
# 取非第一轮次
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
# ***Inspect-start***
|
||||
self.logger.model = '回归测试记录'
|
||||
self.logger.write_warning_log('当前文档全部片段', f'该项目没有创建轮次')
|
||||
# ***Inspect-end***
|
||||
return ChenResponse(code=400, status=400, message='您未创建轮次,请创建完毕后再试')
|
||||
|
||||
context = {
|
||||
'project_name': project_obj.name,
|
||||
'language': "、".join(language_list),
|
||||
'soft_type': project_obj.get_soft_type_display(),
|
||||
'security_level': get_str_dict(project_obj.security_level, 'security_level'),
|
||||
'runtime': "、".join(runtime_list),
|
||||
'devplant': "、".join(devplant_list),
|
||||
'recv_date': project_obj.beginTime.strftime("%Y-%m-%d"),
|
||||
'dev_unit': project_obj.dev_unit,
|
||||
}
|
||||
|
||||
version_info = [{'version': round1_so_dut.version,
|
||||
'line_count': int(round1_so_dut.total_lines),
|
||||
'effective_line': int(round1_so_dut.effective_lines)}]
|
||||
# 循环回归的轮次
|
||||
for hround in hround_list:
|
||||
# 每个轮次独立渲染context
|
||||
context_round = deepcopy(context)
|
||||
# 取中文名称
|
||||
cname = chinese_round_name[int(hround.key)] # 输出二、三...
|
||||
# 取该轮次源代码版本放入版本列表
|
||||
so_dut: Dut = hround.rdField.filter(type='SO').first()
|
||||
if not so_dut:
|
||||
return ChenResponse(code=400, status=400, message=f'您第{cname}轮次中缺少源代码被测件,请添加')
|
||||
version_info.append(
|
||||
{'version': so_dut.version, 'line_count': int(so_dut.total_lines),
|
||||
'effective_line': int(so_dut.effective_lines)})
|
||||
context_round['version_info'] = version_info
|
||||
# 开始渲染每个轮次的二级文档
|
||||
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl' / f"第{cname}轮被测软件基本信息.docx"
|
||||
doc.render(context=context_round, autoescape=True)
|
||||
try:
|
||||
doc.save(save_path)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
||||
return ChenResponse(code=200, status=200, message='多轮回归说明文档基本信息生成完毕')
|
||||
|
||||
@route.get("/create/caseinfo", url_name="create-caseinfo")
|
||||
@transaction.atomic
|
||||
def create_caseinfo(self, id: int):
|
||||
"""生成回归测试记录的-{测试用例记录}"""
|
||||
project_path_str = project_path(id)
|
||||
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hjl' / '测试用例记录.docx'
|
||||
doc = DocxTemplate(tpl_path)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||
if len(hround_list) < 1:
|
||||
return None
|
||||
demand_prefix = '3.1'
|
||||
# 循环每轮轮次对象
|
||||
for hround in hround_list:
|
||||
cname = chinese_round_name[int(hround.key)] # var:输出二、三字样
|
||||
test_type_len = Dict.objects.get(code='testType').dictItem.count() # 测试类型的个数
|
||||
type_number_list = [i for i in range(1, test_type_len + 1)] # 测试类型编号对应的列表
|
||||
list_list = [[] for j in range(1, test_type_len + 1)] # 每个测试类型组合为一个列表[[],[],[],[]]
|
||||
testType_list, last_chapter_items = create_csx_chapter_dict(hround)
|
||||
testDemands = hround.rtField.all() # 本轮所有测试项
|
||||
for demand in testDemands:
|
||||
type_index = type_number_list.index(int(demand.testType))
|
||||
demand_ident = get_ident(demand)
|
||||
# ~~~组装测试项~~~
|
||||
demand_last_chapter = last_chapter_items[demand.testType].index(demand.key) + 1
|
||||
demand_chapter = ".".join([demand_prefix, str(testType_list.index(demand.testType) + 1),
|
||||
str(demand_last_chapter)])
|
||||
demand_dict = {
|
||||
'name': demand.name,
|
||||
'ident': demand_ident,
|
||||
'chapter': demand_chapter,
|
||||
'item': []
|
||||
}
|
||||
# ~~~这里组装测试项里面的测试用例~~~
|
||||
for case in demand.tcField.all():
|
||||
step_list = []
|
||||
index = 1
|
||||
for one in case.step.all():
|
||||
# 这里需要对operation富文本处理
|
||||
rich_parser = RichParser(one.operation)
|
||||
desc_list = rich_parser.get_final_list(doc, img_size=68)
|
||||
rich_parser2 = RichParser(one.result)
|
||||
res_list = rich_parser2.get_final_list(doc, img_size=75)
|
||||
# 组装用例里面的步骤dict
|
||||
passed = '通过'
|
||||
if one.passed == '2':
|
||||
passed = '未通过'
|
||||
if one.passed == '3':
|
||||
passed = '未执行'
|
||||
step_dict = {
|
||||
'index': index,
|
||||
'operation': desc_list,
|
||||
'expect': one.expect,
|
||||
'result': res_list,
|
||||
'passed': passed,
|
||||
}
|
||||
step_list.append(step_dict)
|
||||
index += 1
|
||||
# 查询所有的problem
|
||||
problem_list = []
|
||||
problem_prefix = "PT"
|
||||
proj_ident = project_obj.ident
|
||||
for problem in case.caseField.all():
|
||||
problem_list.append("_".join([problem_prefix, proj_ident, problem.ident]))
|
||||
# fpga的时序图
|
||||
rich_parser3 = RichParser(case.timing_diagram)
|
||||
timing_diagram = rich_parser3.get_final_list(doc, img_size=115, height=50)
|
||||
has_timing_diagram = False
|
||||
if len(timing_diagram) > 0:
|
||||
if isinstance(timing_diagram[0], InlineImage):
|
||||
has_timing_diagram = True
|
||||
# 组装用例的dict
|
||||
case_dict = {
|
||||
'name': case.name,
|
||||
'ident': get_case_ident(demand_ident, case),
|
||||
'summary': case.summarize,
|
||||
'initialization': case.initialization,
|
||||
'premise': case.premise,
|
||||
'design_person': case.designPerson,
|
||||
'test_person': case.testPerson,
|
||||
'monitor_person': case.monitorPerson,
|
||||
'step': step_list,
|
||||
'time': str(case.exe_time) if case.exe_time is not None else str(case.update_datetime),
|
||||
'problems': "、".join(problem_list),
|
||||
'round_num_chn': cname,
|
||||
# 2025年4月24日新增
|
||||
'has_timing_diagram': has_timing_diagram,
|
||||
'timing_diagram': timing_diagram,
|
||||
}
|
||||
demand_dict['item'].append(case_dict)
|
||||
|
||||
list_list[type_index].append(demand_dict)
|
||||
# 定义渲染上下文
|
||||
context = {}
|
||||
output_list = []
|
||||
for (index, li) in enumerate(list_list):
|
||||
qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1))
|
||||
context_str = qs.title
|
||||
sort = qs.sort
|
||||
table = {
|
||||
"type": context_str,
|
||||
"item": li,
|
||||
"sort": sort
|
||||
}
|
||||
output_list.append(table)
|
||||
# 排序
|
||||
output_list = sorted(output_list, key=(lambda x: x["sort"]))
|
||||
context["data"] = output_list
|
||||
# 最后渲染
|
||||
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl' / f"第{cname}轮测试用例记录.docx"
|
||||
doc.render(context, autoescape=True)
|
||||
try:
|
||||
doc.save(save_path)
|
||||
except PermissionError:
|
||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
||||
return ChenResponse(code=200, status=200, message='多轮回归测试用例记录生成完毕')
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,155 +1,155 @@
|
||||
# 导入内置模块
|
||||
from pathlib import Path
|
||||
# 导入django、ninja等模块
|
||||
from ninja_extra import api_controller, ControllerBase, route
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404
|
||||
# 导入文档处理模块
|
||||
from docxtpl import DocxTemplate, InlineImage
|
||||
# 导入ORM模型
|
||||
from apps.project.models import Project
|
||||
# 导入工具
|
||||
from utils.util import get_str_abbr, get_str_dict
|
||||
from utils.chen_response import ChenResponse
|
||||
from utils.path_utils import project_path
|
||||
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||
# 导入生成日志记录模块
|
||||
from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
|
||||
|
||||
gloger = GenerateLogger("问题单二段文档")
|
||||
|
||||
# @api_controller("/generateWtd", tags=['生成问题单文档系列'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||
@api_controller('/generateWtd', tags=['生成问题单文档系列'])
|
||||
class GenerateControllerWtd(ControllerBase):
|
||||
@route.get("/create/problem", url_name="create-problem")
|
||||
@transaction.atomic
|
||||
def create_problem(self, id: int):
|
||||
"""生成问题单"""
|
||||
project_path_str = project_path(id)
|
||||
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/wtd' / '问题详情表.docx'
|
||||
doc = DocxTemplate(tpl_path)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
problem_list = list(project_obj.projField.distinct()) # 去掉重复,因为和case是多对多
|
||||
problem_list.sort(key=lambda x: int(x.ident))
|
||||
data_list = []
|
||||
for problem in problem_list:
|
||||
problem_dict = {'ident': problem.ident, 'name': problem.name}
|
||||
# 1.生成被测对象名称、被测对象标识、被测对象版本
|
||||
cases = problem.case.all()
|
||||
# generate_log:无关联问题单进入生成日志
|
||||
if cases.count() < 1:
|
||||
gloger.write_warning_log('单个问题单表格', f'问题单{problem.ident}未关联用例,请检查')
|
||||
str_dut_name_list = []
|
||||
str_dut_ident_list = []
|
||||
str_dut_version_list = []
|
||||
# 2.所属用例标识
|
||||
case_ident_list = []
|
||||
# 3.获取依据要求
|
||||
case_design_list = []
|
||||
for case in cases:
|
||||
if case.test.testType == '8':
|
||||
# 1.1.如果为文档审查,提取所属文档名称、文档被测件标识、文档被测件版本
|
||||
str_dut_name_list.append(case.dut.name)
|
||||
str_dut_ident_list.append(case.dut.ref)
|
||||
str_dut_version_list.append(case.dut.version)
|
||||
# 对应dut名称,design章节号,design描述
|
||||
case_design_list.append("".join([case.dut.name, case.design.chapter]))
|
||||
else:
|
||||
# 1.2.如果不为文档审查,则提取该轮次源代码dut的信息
|
||||
so_dut = case.round.rdField.filter(type='SO').first()
|
||||
if so_dut:
|
||||
str_dut_name_list.append(project_obj.name + '软件')
|
||||
str_dut_ident_list.append(so_dut.ref)
|
||||
str_dut_version_list.append(so_dut.version)
|
||||
# TODO:如何处理设计需求的内容,暂时设置为取出图片,只保留文字
|
||||
p_list = []
|
||||
rich_parse_remove_img = RichParser(case.design.description)
|
||||
rich_list = rich_parse_remove_img.get_final_list(doc)
|
||||
for rich in rich_list:
|
||||
if isinstance(rich, dict) or isinstance(rich, InlineImage):
|
||||
continue
|
||||
else:
|
||||
p_list.append(rich)
|
||||
|
||||
case_design_list.append(
|
||||
"-".join([case.dut.name, case.design.chapter + '章节' + ":" + ''.join(p_list)]))
|
||||
# 2.用例标识修改-YL_测试项类型_测试项标识_用例key+1
|
||||
demand = case.test # 中间变量
|
||||
demand_testType = demand.testType # 中间变量
|
||||
testType_abbr = get_str_abbr(demand_testType, 'testType') # 输出FT
|
||||
case_ident_list.append("_".join(
|
||||
['YL', testType_abbr, demand.ident, str(int(case.key[-1]) + 1).rjust(3, '0')]))
|
||||
problem_dict['duts_name'] = "/".join(set(str_dut_name_list))
|
||||
problem_dict['duts_ref'] = "/".join(set(str_dut_ident_list))
|
||||
problem_dict['duts_version'] = "/".join(set(str_dut_version_list))
|
||||
temp_name_version = []
|
||||
for i in range(len(str_dut_name_list)):
|
||||
temp_name_version.append(
|
||||
"".join([str_dut_name_list[i] + str_dut_ident_list[i], '/V', str_dut_version_list[i]]))
|
||||
problem_dict['dut_name_version'] = "\a".join(temp_name_version)
|
||||
problem_dict['case_ident'] = ",".join(set(case_ident_list))
|
||||
problem_dict['type'] = get_str_dict(problem.type, 'problemType')
|
||||
problem_dict['grade'] = get_str_dict(problem.grade, 'problemGrade')
|
||||
|
||||
# 依据要求-获取其设计需求
|
||||
problem_dict['yaoqiu'] = "\a".join(case_design_list)
|
||||
# 问题操作 - HTML解析
|
||||
desc_list = ['【问题操作】']
|
||||
rich_parser = RichParser(problem.operation)
|
||||
desc_list.extend(rich_parser.get_final_list(doc))
|
||||
|
||||
# 问题影响
|
||||
desc_list_result = [f'\a【问题影响】\a{problem.result}']
|
||||
desc_list.extend(desc_list_result)
|
||||
# 问题描述赋值
|
||||
problem_dict['desc'] = desc_list
|
||||
|
||||
# 4.原因分析
|
||||
desc_list_3 = [f'【原因分析】\a{problem.analysis}']
|
||||
problem_dict['cause'] = desc_list_3
|
||||
|
||||
# 5.影响域分析~~~~
|
||||
desc_list_4 = [f'【影响域分析】\a{problem.effect_scope}']
|
||||
problem_dict['effect_scope'] = desc_list_4
|
||||
|
||||
# 6.改正措施
|
||||
problem_dict['solve'] = problem.solve
|
||||
|
||||
# 7.回归验证结果
|
||||
desc_list_5 = []
|
||||
rich_parser5 = RichParser(problem.verify_result)
|
||||
desc_list_5.extend(rich_parser5.get_final_list(doc))
|
||||
problem_dict['verify_result'] = desc_list_5
|
||||
|
||||
# 8.其他日期和人员
|
||||
problem_dict['postPerson'] = problem.postPerson
|
||||
problem_dict['postDate'] = problem.postDate
|
||||
close_str = '□修改文档 □修改程序 □不修改'
|
||||
if len(problem.closeMethod) < 1:
|
||||
close_str = '□修改文档 □修改程序 ■不修改'
|
||||
elif len(problem.closeMethod) == 2:
|
||||
close_str = '■修改文档 ■修改程序 □不修改'
|
||||
else:
|
||||
if problem.closeMethod[0] == '1':
|
||||
close_str = '■修改文档 □修改程序 □不修改'
|
||||
elif problem.closeMethod[0] == '2':
|
||||
close_str = '□修改文档 ■修改程序 □不修改'
|
||||
else:
|
||||
close_str = '□修改文档 □修改程序 □不修改'
|
||||
problem_dict['closeMethod'] = close_str
|
||||
problem_dict['designer'] = problem.designerPerson
|
||||
problem_dict['designDate'] = problem.designDate
|
||||
problem_dict['verifyPerson'] = problem.verifyPerson
|
||||
problem_dict['verifyDate'] = problem.verifyDate
|
||||
data_list.append(problem_dict)
|
||||
context = {
|
||||
'project_name': project_obj.name,
|
||||
'project_ident': project_obj.ident,
|
||||
'problem_list': data_list,
|
||||
}
|
||||
doc.render(context, autoescape=True)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path_str / "output_dir/wtd" / '问题详情表.docx')
|
||||
return ChenResponse(status=200, code=200, message="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
# 导入内置模块
|
||||
from pathlib import Path
|
||||
from django.db.models import QuerySet
|
||||
# 导入django、ninja等模块
|
||||
from ninja_extra import api_controller, ControllerBase, route
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404
|
||||
# 导入文档处理模块
|
||||
from docxtpl import DocxTemplate, InlineImage
|
||||
# 导入ORM模型
|
||||
from apps.project.models import Project, Case
|
||||
# 导入工具
|
||||
from utils.util import get_str_abbr, get_str_dict
|
||||
from utils.chen_response import ChenResponse
|
||||
from utils.path_utils import project_path
|
||||
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||
# 导入生成日志记录模块
|
||||
from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
|
||||
|
||||
gloger = GenerateLogger("问题单二段文档")
|
||||
|
||||
# @api_controller("/generateWtd", tags=['生成问题单文档系列'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||
@api_controller('/generateWtd', tags=['生成问题单文档系列'])
|
||||
class GenerateControllerWtd(ControllerBase):
|
||||
@route.get("/create/problem", url_name="create-problem")
|
||||
@transaction.atomic
|
||||
def create_problem(self, id: int):
|
||||
"""生成问题单"""
|
||||
project_path_str = project_path(id)
|
||||
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/wtd' / '问题详情表.docx'
|
||||
doc = DocxTemplate(tpl_path)
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
problem_list = list(project_obj.projField.distinct()) # 去掉重复,因为和case是多对多
|
||||
problem_list.sort(key=lambda x: int(x.ident))
|
||||
data_list = []
|
||||
for problem in problem_list:
|
||||
problem_dict = {'ident': problem.ident, 'name': problem.name}
|
||||
# 1.生成被测对象名称、被测对象标识、被测对象版本
|
||||
cases: QuerySet[Case] = problem.case.all()
|
||||
# generate_log:无关联问题单进入生成日志
|
||||
if cases.count() < 1:
|
||||
gloger.write_warning_log('单个问题单表格', f'问题单{problem.ident}未关联用例,请检查')
|
||||
str_dut_name_list = []
|
||||
str_dut_ident_list = []
|
||||
str_dut_version_list = []
|
||||
# 2.所属用例标识
|
||||
case_ident_list = []
|
||||
# 3.获取依据要求
|
||||
case_design_list = []
|
||||
for case in cases:
|
||||
if case.test.testType == '8':
|
||||
# 1.1.如果为文档审查,提取所属文档名称、文档被测件标识、文档被测件版本
|
||||
str_dut_name_list.append(case.dut.name)
|
||||
str_dut_ident_list.append(case.dut.ref)
|
||||
str_dut_version_list.append(case.dut.version)
|
||||
# 对应dut名称,design章节号,design描述
|
||||
case_design_list.append("".join([case.dut.name, case.design.chapter]))
|
||||
else:
|
||||
# 1.2.如果不为文档审查,则提取该轮次源代码dut的信息
|
||||
so_dut = case.round.rdField.filter(type='SO').first()
|
||||
if so_dut:
|
||||
str_dut_name_list.append(project_obj.name + '软件')
|
||||
str_dut_ident_list.append(so_dut.ref)
|
||||
str_dut_version_list.append(so_dut.version)
|
||||
# TODO:如何处理设计需求的内容,暂时设置为取出图片,只保留文字
|
||||
p_list = []
|
||||
rich_parse_remove_img = RichParser(case.design.description)
|
||||
rich_list = rich_parse_remove_img.get_final_list(doc)
|
||||
for rich in rich_list:
|
||||
if isinstance(rich, dict) or isinstance(rich, InlineImage):
|
||||
continue
|
||||
else:
|
||||
p_list.append(rich)
|
||||
case_design_list.append("-".join([case.dut.name, case.design.chapter + '章节' + ":" + ''.join(p_list)]))
|
||||
# 2.用例标识修改-YL_测试项类型_测试项标识_用例key+1
|
||||
demand = case.test # 中间变量
|
||||
demand_testType = demand.testType # 中间变量
|
||||
testType_abbr = get_str_abbr(demand_testType, 'testType') # 输出FT
|
||||
case_ident_list.append("_".join(
|
||||
['YL', testType_abbr, demand.ident, str(int(case.key[-1]) + 1).rjust(3, '0')]))
|
||||
problem_dict['duts_name'] = "/".join(set(str_dut_name_list))
|
||||
problem_dict['duts_ref'] = "/".join(set(str_dut_ident_list))
|
||||
problem_dict['duts_version'] = "/".join(set(str_dut_version_list))
|
||||
temp_name_version = []
|
||||
for i in range(len(str_dut_name_list)):
|
||||
temp_name_version.append(
|
||||
"".join([str_dut_name_list[i] + str_dut_ident_list[i], '/V', str_dut_version_list[i]]))
|
||||
problem_dict['dut_name_version'] = "\a".join(set(temp_name_version))
|
||||
problem_dict['case_ident'] = ",".join(set(case_ident_list))
|
||||
problem_dict['type'] = get_str_dict(problem.type, 'problemType')
|
||||
problem_dict['grade'] = get_str_dict(problem.grade, 'problemGrade')
|
||||
|
||||
# 依据要求-获取其设计需求
|
||||
print(case_design_list)
|
||||
problem_dict['yaoqiu'] = "\a".join(case_design_list)
|
||||
# 问题操作 - HTML解析
|
||||
desc_list = ['【问题操作】']
|
||||
rich_parser = RichParser(problem.operation)
|
||||
desc_list.extend(rich_parser.get_final_list(doc))
|
||||
|
||||
# 问题影响
|
||||
desc_list_result = [f'\a【问题影响】\a{problem.result}']
|
||||
desc_list.extend(desc_list_result)
|
||||
# 问题描述赋值
|
||||
problem_dict['desc'] = desc_list
|
||||
|
||||
# 4.原因分析
|
||||
desc_list_3 = [f'【原因分析】\a{problem.analysis}']
|
||||
problem_dict['cause'] = desc_list_3
|
||||
|
||||
# 5.影响域分析~~~~
|
||||
desc_list_4 = [f'【影响域分析】\a{problem.effect_scope}']
|
||||
problem_dict['effect_scope'] = desc_list_4
|
||||
|
||||
# 6.改正措施
|
||||
problem_dict['solve'] = problem.solve
|
||||
|
||||
# 7.回归验证结果
|
||||
desc_list_5 = []
|
||||
rich_parser5 = RichParser(problem.verify_result)
|
||||
desc_list_5.extend(rich_parser5.get_final_list(doc))
|
||||
problem_dict['verify_result'] = desc_list_5
|
||||
|
||||
# 8.其他日期和人员
|
||||
problem_dict['postPerson'] = problem.postPerson
|
||||
problem_dict['postDate'] = problem.postDate
|
||||
close_str = '□修改文档 □修改程序 □不修改'
|
||||
if len(problem.closeMethod) < 1:
|
||||
close_str = '□修改文档 □修改程序 ■不修改'
|
||||
elif len(problem.closeMethod) == 2:
|
||||
close_str = '■修改文档 ■修改程序 □不修改'
|
||||
else:
|
||||
if problem.closeMethod[0] == '1':
|
||||
close_str = '■修改文档 □修改程序 □不修改'
|
||||
elif problem.closeMethod[0] == '2':
|
||||
close_str = '□修改文档 ■修改程序 □不修改'
|
||||
else:
|
||||
close_str = '□修改文档 □修改程序 □不修改'
|
||||
problem_dict['closeMethod'] = close_str
|
||||
problem_dict['designer'] = problem.designerPerson
|
||||
problem_dict['designDate'] = problem.designDate
|
||||
problem_dict['verifyPerson'] = problem.verifyPerson
|
||||
problem_dict['verifyDate'] = problem.verifyDate
|
||||
data_list.append(problem_dict)
|
||||
context = {
|
||||
'project_name': project_obj.name,
|
||||
'project_ident': project_obj.ident,
|
||||
'problem_list': data_list,
|
||||
}
|
||||
doc.render(context, autoescape=True)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path_str / "output_dir/wtd" / '问题详情表.docx')
|
||||
return ChenResponse(status=200, code=200, message="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
|
||||
Binary file not shown.
@@ -1,141 +1,143 @@
|
||||
"""
|
||||
专门解析富文本插件tinymce的html内容
|
||||
"""
|
||||
import pandas as pd
|
||||
from bs4 import BeautifulSoup
|
||||
from bs4.element import Tag, NavigableString
|
||||
import base64
|
||||
import io
|
||||
from docxtpl import InlineImage
|
||||
from docx.shared import Mm
|
||||
import re
|
||||
|
||||
# text.replace('\xa0', ' '))
|
||||
class RichParser:
|
||||
def __init__(self, rich_text):
|
||||
# 将rich_text的None变为空字符串:鲁棒
|
||||
if rich_text is None:
|
||||
rich_text = ""
|
||||
# 对原始html解析后的bs对象
|
||||
self.bs = BeautifulSoup(rich_text, 'html.parser')
|
||||
self.content = self.remove_n_in_contents()
|
||||
# 最终的解析后的列表
|
||||
self.data_list = []
|
||||
self.line_parse()
|
||||
# 匹配“表1-3”或“表1”等字符的正则
|
||||
self.biao_pattern = re.compile(r"表\d+(?:-\d+)?")
|
||||
|
||||
# 1.函数:将self.bs.contents去掉\n,获取每行数据
|
||||
def remove_n_in_contents(self):
|
||||
content_list = []
|
||||
for line in self.bs.contents:
|
||||
if line != '\n':
|
||||
content_list.append(line)
|
||||
return content_list
|
||||
|
||||
# 2.逐个遍历self.content,去掉table元素Tag对象单独解析
|
||||
def line_parse(self):
|
||||
for tag in self.content:
|
||||
if isinstance(tag, NavigableString):
|
||||
self.data_list.append(tag.text)
|
||||
elif isinstance(tag, Tag):
|
||||
if tag.name == 'p':
|
||||
img_list = tag.find_all('img')
|
||||
if len(img_list) > 0:
|
||||
for img_item in img_list:
|
||||
self.data_list.append(img_item.get('src'))
|
||||
else:
|
||||
self.data_list.append(tag.text)
|
||||
elif tag.name == 'table':
|
||||
df_dict_list = self.parse_tag2list(tag)
|
||||
self.data_list.append(df_dict_list)
|
||||
elif tag.name == 'div':
|
||||
table_list = tag.find_all('table')
|
||||
if len(table_list) > 0:
|
||||
for table in table_list:
|
||||
df_dict_list = self.parse_tag2list(table)
|
||||
self.data_list.append(df_dict_list)
|
||||
|
||||
# 3.1.辅助方法,将<table>的Tag对象转为[[]]二维列表格式
|
||||
def parse_tag2list(self, table_tag):
|
||||
# str(tag)可直接变成<table>xxx</table>
|
||||
pd_list = pd.read_html(io.StringIO(str(table_tag)))
|
||||
# 将dataframe变为数组
|
||||
df = pd_list[0]
|
||||
# 处理第一行为数字的情况,如果为数字则删除第一行,让第二行为列名
|
||||
if all(isinstance(col, int) for col in df.columns):
|
||||
df.columns = df.iloc[0]
|
||||
df = df.drop(0) # 删除原来的第一行
|
||||
# 转为列表的列表(二维列表)
|
||||
# return df.values.tolist()
|
||||
return df.fillna('').T.reset_index().T.values.tolist()
|
||||
|
||||
# 3.2.辅助方法,打印解析后列表
|
||||
def print_content(self):
|
||||
for line in self.data_list:
|
||||
print(line)
|
||||
|
||||
# 4.1.最终方法,生成给docxtpl可用的列表 -> 注意需要传递DocxTemplate对象,在接口函数里面初始化的
|
||||
def get_final_list(self, doc, /, *, img_size=100, height=80):
|
||||
"""注意关键字传参可修改图片大小img_size:int=100"""
|
||||
final_list = []
|
||||
for oneline in self.data_list:
|
||||
# 这里要单独处理下二维列表
|
||||
if isinstance(oneline, list):
|
||||
final_list.append({'isTable': True, 'data': oneline})
|
||||
continue
|
||||
if oneline.startswith("data:image/png;base64") or oneline.startswith("data:image/jpeg;base64,") or oneline.startswith(
|
||||
"data:image/jpg;base64,"):
|
||||
base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", ""))
|
||||
# ~~~设置了固定宽度、高度~~~
|
||||
inline_image = InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=Mm(height))
|
||||
final_list.append(inline_image)
|
||||
else:
|
||||
final_list.append(oneline)
|
||||
if len(final_list) <= 0:
|
||||
final_list.append("")
|
||||
# 针对tinymce中,粘贴表格最后一行显示句号问题,这里统一删除
|
||||
if final_list[-1] == '\xa0':
|
||||
final_list.pop()
|
||||
return final_list
|
||||
|
||||
# 4.2.最终方法,在上面方法基础上,增加格式,例如<p>增加缩进,图片居中,<p>包含“图x”则居中
|
||||
def get_final_format_list(self, doc, /, *, img_size=115, height=80):
|
||||
final_list = []
|
||||
for oneline in self.data_list:
|
||||
# 这里要单独处理下二维列表
|
||||
if isinstance(oneline, list):
|
||||
final_list.append({'isTable': True, 'data': oneline})
|
||||
continue
|
||||
if oneline.startswith("data:image/png;base64"):
|
||||
base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", ""))
|
||||
# 1.和上面函数变化:图片更改为dict然后isCenter属性居中
|
||||
final_list.append(
|
||||
{'isCenter': True,
|
||||
'data': InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=height)})
|
||||
else:
|
||||
# 2.和上面区别:如果<p>带有“图”则居中
|
||||
if re.match(r"[表图]\d.*", oneline):
|
||||
final_list.append({"isCenter": True, "data": oneline})
|
||||
else:
|
||||
final_list.append({"isCenter": False, "data": oneline})
|
||||
if len(final_list) <= 0:
|
||||
final_list.append("")
|
||||
return final_list
|
||||
|
||||
# 5.最终方法,去掉图片和table元素 -> 纯文本列表
|
||||
def get_final_p_list(self):
|
||||
final_list = []
|
||||
for oneline in self.data_list:
|
||||
if isinstance(oneline, list) or oneline.startswith("data:image/png;base64"):
|
||||
continue
|
||||
cleaned_line = oneline
|
||||
cleaned_line = re.sub(r'\s+', '', cleaned_line)
|
||||
cleaned_line = cleaned_line.replace(')', ')')
|
||||
cleaned_line = cleaned_line.strip()
|
||||
# 去掉以“表3”的行
|
||||
if self.biao_pattern.search(cleaned_line):
|
||||
continue
|
||||
if cleaned_line:
|
||||
final_list.append(cleaned_line)
|
||||
return final_list
|
||||
"""
|
||||
专门解析富文本插件tinymce的html内容
|
||||
"""
|
||||
import pandas as pd
|
||||
from bs4 import BeautifulSoup
|
||||
from bs4.element import Tag, NavigableString
|
||||
import base64
|
||||
import io
|
||||
from docxtpl import InlineImage
|
||||
from docx.shared import Mm
|
||||
import re
|
||||
|
||||
# text.replace('\xa0', ' '))
|
||||
class RichParser:
|
||||
def __init__(self, rich_text):
|
||||
# 将rich_text的None变为空字符串:鲁棒
|
||||
if rich_text is None:
|
||||
rich_text = ""
|
||||
# 对原始html解析后的bs对象
|
||||
self.bs = BeautifulSoup(rich_text, 'html.parser')
|
||||
self.content = self.remove_n_in_contents()
|
||||
# 最终的解析后的列表
|
||||
self.data_list = []
|
||||
self.line_parse()
|
||||
# 匹配“表1-3”或“表1”等字符的正则
|
||||
self.biao_pattern = re.compile(r"表\d+(?:-\d+)?")
|
||||
|
||||
# 1.函数:将self.bs.contents去掉\n,获取每行数据
|
||||
def remove_n_in_contents(self):
|
||||
content_list = []
|
||||
for line in self.bs.contents:
|
||||
if line != '\n':
|
||||
content_list.append(line)
|
||||
return content_list
|
||||
|
||||
# 2.逐个遍历self.content,去掉table元素Tag对象单独解析
|
||||
def line_parse(self):
|
||||
for tag in self.content:
|
||||
if isinstance(tag, NavigableString):
|
||||
self.data_list.append(tag.text)
|
||||
elif isinstance(tag, Tag):
|
||||
if tag.name == 'p':
|
||||
img_list = tag.find_all('img')
|
||||
if len(img_list) > 0:
|
||||
for img_item in img_list:
|
||||
self.data_list.append(img_item.get('src'))
|
||||
else:
|
||||
self.data_list.append(tag.text)
|
||||
elif tag.name == 'table':
|
||||
df_dict_list = self.parse_tag2list(tag)
|
||||
self.data_list.append(df_dict_list)
|
||||
elif tag.name == 'div':
|
||||
table_list = tag.find_all('table')
|
||||
if len(table_list) > 0:
|
||||
for table in table_list:
|
||||
df_dict_list = self.parse_tag2list(table)
|
||||
self.data_list.append(df_dict_list)
|
||||
|
||||
# 3.1.辅助方法,将<table>的Tag对象转为[[]]二维列表格式
|
||||
def parse_tag2list(self, table_tag):
|
||||
# str(tag)可直接变成<table>xxx</table>
|
||||
pd_list = pd.read_html(io.StringIO(str(table_tag)))
|
||||
# 将dataframe变为数组
|
||||
df = pd_list[0]
|
||||
# 处理第一行为数字的情况,如果为数字则删除第一行,让第二行为列名
|
||||
if all(isinstance(col, int) for col in df.columns):
|
||||
df.columns = df.iloc[0]
|
||||
df = df.drop(0) # 删除原来的第一行
|
||||
# 转为列表的列表(二维列表)
|
||||
# return df.values.tolist()
|
||||
return df.fillna('').T.reset_index().T.values.tolist()
|
||||
|
||||
# 3.2.辅助方法,打印解析后列表
|
||||
def print_content(self):
|
||||
for line in self.data_list:
|
||||
print(line)
|
||||
|
||||
# 4.1.最终方法,生成给docxtpl可用的列表 -> 注意需要传递DocxTemplate对象,在接口函数里面初始化的
|
||||
def get_final_list(self, doc, /, *, img_size=100, height=80):
|
||||
"""注意关键字传参可修改图片大小img_size:int=100"""
|
||||
final_list = []
|
||||
for oneline in self.data_list:
|
||||
# 这里要单独处理下二维列表
|
||||
if isinstance(oneline, list):
|
||||
final_list.append({'isTable': True, 'data': oneline})
|
||||
continue
|
||||
if oneline.startswith("data:image/png;base64") or oneline.startswith("data:image/jpeg;base64,") or oneline.startswith(
|
||||
"data:image/jpg;base64,"):
|
||||
base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", ""))
|
||||
# ~~~设置了固定宽度、高度~~~
|
||||
inline_image = InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=Mm(height))
|
||||
final_list.append(inline_image)
|
||||
else:
|
||||
# ~~~新增:将\xa0修改为普通空格~~~
|
||||
oneline = oneline.replace('\xa0', ' ')
|
||||
final_list.append(oneline)
|
||||
if len(final_list) <= 0:
|
||||
final_list.append("")
|
||||
# 针对tinymce中,粘贴表格最后一行显示句号问题,这里统一删除
|
||||
if final_list[-1] == '\xa0':
|
||||
final_list.pop()
|
||||
return final_list
|
||||
|
||||
# 4.2.最终方法,在上面方法基础上,增加格式,例如<p>增加缩进,图片居中,<p>包含“图x”则居中
|
||||
def get_final_format_list(self, doc, /, *, img_size=115, height=80):
|
||||
final_list = []
|
||||
for oneline in self.data_list:
|
||||
# 这里要单独处理下二维列表
|
||||
if isinstance(oneline, list):
|
||||
final_list.append({'isTable': True, 'data': oneline})
|
||||
continue
|
||||
if oneline.startswith("data:image/png;base64"):
|
||||
base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", ""))
|
||||
# 1.和上面函数变化:图片更改为dict然后isCenter属性居中
|
||||
final_list.append(
|
||||
{'isCenter': True,
|
||||
'data': InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=height)})
|
||||
else:
|
||||
# 2.和上面区别:如果<p>带有“图”则居中
|
||||
if re.match(r"[表图]\d.*", oneline):
|
||||
final_list.append({"isCenter": True, "data": oneline.replace('\xa0', ' ')})
|
||||
else:
|
||||
final_list.append({"isCenter": False, "data": oneline.replace('\xa0', ' ')})
|
||||
if len(final_list) <= 0:
|
||||
final_list.append("")
|
||||
return final_list
|
||||
|
||||
# 5.最终方法,去掉图片和table元素 -> 纯文本列表
|
||||
def get_final_p_list(self):
|
||||
final_list = []
|
||||
for oneline in self.data_list:
|
||||
if isinstance(oneline, list) or oneline.startswith("data:image/png;base64"):
|
||||
continue
|
||||
cleaned_line = oneline
|
||||
cleaned_line = re.sub(r'\s+', '', cleaned_line)
|
||||
cleaned_line = cleaned_line.replace(')', ')')
|
||||
cleaned_line = cleaned_line.strip()
|
||||
# 去掉以“表3”的行
|
||||
if self.biao_pattern.search(cleaned_line):
|
||||
continue
|
||||
if cleaned_line:
|
||||
final_list.append(cleaned_line)
|
||||
return final_list
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -257,7 +257,7 @@ class CaseController(ControllerBase):
|
||||
single_qs.key = case_key
|
||||
index = index + 1
|
||||
single_qs.save()
|
||||
return ChenResponse(message="测试用例删除成功!")
|
||||
return ChenResponse(message="测试用例删除成功!影响域分析中如果有该关联用例则被删除。")
|
||||
|
||||
# 右键测试项,根据测试子项生成用例
|
||||
@route.post("/case/create_by_demand", url_name='case-create-by-demand')
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 6.0.4 on 2026-04-17 13:57
|
||||
|
||||
import apps.project.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0029_influenceitem_change_influ'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='devplant',
|
||||
field=models.JSONField(blank=True, default=apps.project.models.create_list, help_text='开发环境', null=True, verbose_name='开发环境'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='runtime',
|
||||
field=models.JSONField(blank=True, default=apps.project.models.create_list, help_text='运行环境', null=True, verbose_name='运行环境'),
|
||||
),
|
||||
]
|
||||
18
apps/project/migrations/0031_alter_testdemand_adequacy.py
Normal file
18
apps/project/migrations/0031_alter_testdemand_adequacy.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.4 on 2026-04-17 16:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0030_alter_project_devplant_alter_project_runtime'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='testdemand',
|
||||
name='adequacy',
|
||||
field=models.CharField(blank=True, help_text='充分条件', max_length=2048, null=True, verbose_name='充分条件'),
|
||||
),
|
||||
]
|
||||
18
apps/project/migrations/0032_alter_design_protocal.py
Normal file
18
apps/project/migrations/0032_alter_design_protocal.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.4 on 2026-04-20 10:36
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('project', '0031_alter_testdemand_adequacy'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='design',
|
||||
name='protocal',
|
||||
field=models.CharField(blank=True, default='', help_text='接口数据', max_length=1024, null=True, verbose_name='接口数据'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -27,50 +27,29 @@ class Project(CoreModel):
|
||||
vise_person = models.CharField(max_length=64, verbose_name="质量监督员", help_text="质量监督员")
|
||||
config_person = models.CharField(max_length=64, verbose_name="配置管理员", help_text="配置管理员")
|
||||
# ~~~~~~~~~~~
|
||||
security_level = models.CharField(max_length=8, blank=True, null=True, verbose_name="安全等级",
|
||||
help_text="安全等级")
|
||||
test_level = models.JSONField(null=True, blank=True, help_text="测试级别", verbose_name="测试级别",
|
||||
default=create_list)
|
||||
plant_type = models.JSONField(null=True, blank=True, help_text="平台类型", verbose_name="平台类型",
|
||||
default=create_list)
|
||||
report_type = models.CharField(max_length=64, blank=True, null=True, verbose_name="报告类型",
|
||||
help_text="报告类型")
|
||||
language = models.JSONField(null=True, blank=True, help_text="被测语言", verbose_name="被测语言",
|
||||
default=create_list)
|
||||
standard = models.JSONField(null=True, blank=True, help_text="依据标准", verbose_name="依据标准",
|
||||
default=create_list)
|
||||
security_level = models.CharField(max_length=8, blank=True, null=True, verbose_name="安全等级", help_text="安全等级")
|
||||
test_level = models.JSONField(null=True, blank=True, help_text="测试级别", verbose_name="测试级别", default=create_list)
|
||||
plant_type = models.JSONField(null=True, blank=True, help_text="平台类型", verbose_name="平台类型", default=create_list)
|
||||
report_type = models.CharField(max_length=64, blank=True, null=True, verbose_name="报告类型", help_text="报告类型")
|
||||
language = models.JSONField(null=True, blank=True, help_text="被测语言", verbose_name="被测语言", default=create_list)
|
||||
standard = models.JSONField(null=True, blank=True, help_text="依据标准", verbose_name="依据标准", default=create_list)
|
||||
entrust_unit = models.CharField(max_length=64, verbose_name="委托方单位", help_text="委托方单位")
|
||||
entrust_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方联系人",
|
||||
help_text="委托方联系人")
|
||||
entrust_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方电话",
|
||||
help_text="委托方电话")
|
||||
entrust_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方邮箱",
|
||||
help_text="委托方邮箱")
|
||||
entrust_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方联系人", help_text="委托方联系人")
|
||||
entrust_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方电话", help_text="委托方电话")
|
||||
entrust_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方邮箱", help_text="委托方邮箱")
|
||||
dev_unit = models.CharField(max_length=64, verbose_name="开发方单位", help_text="开发方单位")
|
||||
dev_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方联系人",
|
||||
help_text="研制方联系人")
|
||||
dev_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方电话",
|
||||
help_text="研制方电话")
|
||||
dev_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方邮箱",
|
||||
help_text="研制方邮箱")
|
||||
dev_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方联系人", help_text="研制方联系人")
|
||||
dev_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方电话", help_text="研制方电话")
|
||||
dev_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方邮箱", help_text="研制方邮箱")
|
||||
test_unit = models.CharField(max_length=64, verbose_name="测试方单位", help_text="测试方单位")
|
||||
test_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心联系人",
|
||||
help_text="测评中心联系人")
|
||||
test_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心电话",
|
||||
help_text="测评中心电话")
|
||||
test_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心邮箱",
|
||||
help_text="测评中心邮箱")
|
||||
step = models.CharField(max_length=8, blank=True, null=True, verbose_name="项目阶段",
|
||||
help_text="项目阶段")
|
||||
abbreviation = models.JSONField(null=True, blank=True, help_text="缩略语", verbose_name="缩略语",
|
||||
default=create_list)
|
||||
soft_type = models.SmallIntegerField(verbose_name='软件类型',
|
||||
choices=((1, '新研'), (2, '改造'), (3, '沿用')),
|
||||
default=1)
|
||||
runtime = models.CharField(max_length=8, blank=True, null=True, verbose_name="运行环境",
|
||||
help_text="运行环境")
|
||||
devplant = models.CharField(max_length=8, blank=True, null=True, verbose_name="开发环境",
|
||||
help_text="开发环境")
|
||||
test_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心联系人", help_text="测评中心联系人")
|
||||
test_contact_phone = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心电话", help_text="测评中心电话")
|
||||
test_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心邮箱", help_text="测评中心邮箱")
|
||||
step = models.CharField(max_length=8, blank=True, null=True, verbose_name="项目阶段", help_text="项目阶段")
|
||||
abbreviation = models.JSONField(null=True, blank=True, help_text="缩略语", verbose_name="缩略语", default=create_list)
|
||||
soft_type = models.SmallIntegerField(verbose_name='软件类型', choices=((1, '新研'), (2, '改造'), (3, '沿用')), default=1)
|
||||
runtime = models.JSONField(null=True, blank=True, help_text="运行环境", verbose_name="运行环境", default=create_list)
|
||||
devplant = models.JSONField(null=True, blank=True, help_text="开发环境", verbose_name="开发环境", default=create_list)
|
||||
# 9月2日新增字段:密级
|
||||
secret = models.CharField(max_length=30, default='1', verbose_name='密级', help_text='密级')
|
||||
|
||||
@@ -226,7 +205,7 @@ class Design(CoreModel):
|
||||
type = models.CharField(max_length=64, blank=True, null=True, default='', verbose_name='接口类型',
|
||||
help_text='接口类型')
|
||||
# 注意:该字段改为接口数据
|
||||
protocal = models.CharField(max_length=64, blank=True, null=True, default='', verbose_name='接口数据',
|
||||
protocal = models.CharField(max_length=1024, blank=True, null=True, default='', verbose_name='接口数据',
|
||||
help_text='接口数据')
|
||||
|
||||
def __str__(self):
|
||||
@@ -239,23 +218,15 @@ class Design(CoreModel):
|
||||
ordering = ('key',)
|
||||
|
||||
class TestDemand(CoreModel):
|
||||
objects = models.Manager()
|
||||
"""测试项"""
|
||||
ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="测试需求标识",
|
||||
help_text="测试需求标识")
|
||||
name = models.CharField(max_length=64, blank=True, null=True, verbose_name="测试需求名称",
|
||||
help_text="测试需求名称")
|
||||
adequacy = models.CharField(max_length=256, blank=True, null=True, verbose_name="充分条件",
|
||||
help_text="充分条件")
|
||||
priority = models.CharField(max_length=8, blank=True, null=True, verbose_name="优先级",
|
||||
help_text="优先级")
|
||||
testType = models.CharField(max_length=8, null=True, blank=True, help_text="测试类型",
|
||||
verbose_name="测试类型",
|
||||
default="1")
|
||||
testMethod = models.JSONField(blank=True, help_text="测试方法", verbose_name="测试方法",
|
||||
default=create_list)
|
||||
title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称",
|
||||
help_text="树-名称")
|
||||
objects = models.Manager()
|
||||
ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="测试需求标识", help_text="测试需求标识")
|
||||
name = models.CharField(max_length=64, blank=True, null=True, verbose_name="测试需求名称", help_text="测试需求名称")
|
||||
adequacy = models.CharField(max_length=2048, blank=True, null=True, verbose_name="充分条件", help_text="充分条件")
|
||||
priority = models.CharField(max_length=8, blank=True, null=True, verbose_name="优先级", help_text="优先级")
|
||||
testType = models.CharField(max_length=8, null=True, blank=True, help_text="测试类型", verbose_name="测试类型", default="1")
|
||||
testMethod = models.JSONField(blank=True, help_text="测试方法", verbose_name="测试方法", default=create_list)
|
||||
title = models.CharField(max_length=64, blank=True, null=True, verbose_name="树-名称", help_text="树-名称")
|
||||
key = models.CharField(max_length=64, blank=True, null=True,
|
||||
verbose_name="round-dut-designkey-testdemand",
|
||||
help_text="round-dut-designkey-testdemand")
|
||||
|
||||
@@ -1,91 +1,132 @@
|
||||
import jwt
|
||||
from django.conf import settings
|
||||
from threading import local
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from django.dispatch import receiver
|
||||
from django.utils.functional import SimpleLazyObject
|
||||
from django.contrib.auth import get_user_model
|
||||
# 导入日志的模型
|
||||
from apps.user.models import TableOperationLog, Users
|
||||
# 导入其他模型用于排除
|
||||
from apps.project.models import CaseStep, TestDemandContent
|
||||
# 导入异常处理
|
||||
from jwt.exceptions import ExpiredSignatureError
|
||||
from utils.chen_response import ChenResponse
|
||||
# 导入中间件记录日志模型
|
||||
from apps.system.models import LoginLog
|
||||
from apps.system.models import OperationLog
|
||||
|
||||
log_manager = TableOperationLog.objects
|
||||
|
||||
_thread_local = local()
|
||||
|
||||
def get_current_user():
|
||||
"""
|
||||
获取当前用户对象,调用则从local对象里面获取user
|
||||
:return: Users实例
|
||||
"""
|
||||
return getattr(_thread_local, 'user', None)
|
||||
|
||||
def clear_request_locals(sender, **kwargs):
|
||||
"""
|
||||
被request_finished连接的信号处理函数,请求结束后清除local里面的user信息
|
||||
"""
|
||||
_thread_local.user = None
|
||||
|
||||
def set_request_locals(sender, **kwargs):
|
||||
"""
|
||||
被request_started连接的信号处理函数,_thread_local.user属性设置为当前登录用户
|
||||
"""
|
||||
bearer_token = kwargs['environ'].get('HTTP_AUTHORIZATION', None)
|
||||
if not bearer_token or bearer_token == 'Bearer null':
|
||||
return
|
||||
bearer_token = bearer_token.replace('Bearer ', '')
|
||||
# 逻辑:先获取NINJA_JWT配置中秘钥、和加密算法信息
|
||||
jwt_settings = settings.NINJA_JWT
|
||||
jwt_secret = jwt_settings.get('SIGNING_KEY', None)
|
||||
jwt_algo = jwt_settings.get('ALGORITHM', None)
|
||||
# 如果为None,则使用settings中的秘钥和['HS256']算法
|
||||
secret_key = jwt_secret or settings.SECRET_KEY
|
||||
algorithms_str = jwt_algo or 'HS256'
|
||||
# 解决bug:因为过期前面不跳转首页处理方式
|
||||
try:
|
||||
jwt_dict = jwt.decode(bearer_token, secret_key, algorithms=[algorithms_str])
|
||||
except ExpiredSignatureError as exc:
|
||||
return ChenResponse(status=403, code=500, message='您的token已过期,请重新登录')
|
||||
user_id = jwt_dict.get('user_id', None)
|
||||
if user_id:
|
||||
_thread_local.user = SimpleLazyObject(lambda: get_user_model().objects.get(id=user_id))
|
||||
|
||||
# 1.注意可以不传sender,为监听所有模型,这里来记录日志
|
||||
# 2.使用get_current_user()获取当前请求用户
|
||||
@receiver(post_save)
|
||||
def post_save_handler(sender, instance, created, **kwargs):
|
||||
"""模型新增-操作日志填写"""
|
||||
# 注意排除日志模块、用例步骤表、测试项步骤表
|
||||
if (sender == TableOperationLog or sender == CaseStep or sender == TestDemandContent or sender == LoginLog or sender == OperationLog or sender
|
||||
== Users):
|
||||
return
|
||||
user = get_current_user()
|
||||
ope_dict = {
|
||||
'operate_obj': str(instance),
|
||||
}
|
||||
if created:
|
||||
ope_dict['operate_des'] = '新增'
|
||||
else:
|
||||
ope_dict['operate_des'] = '修改'
|
||||
log_manager.create(user=user, **ope_dict)
|
||||
|
||||
@receiver(post_delete)
|
||||
def post_delete_handler(sender, instance, **kwargs):
|
||||
"""模型删除-操作日志填写"""
|
||||
# 注意排除日志模块、用例步骤表、测试项步骤表
|
||||
if (sender == TableOperationLog or sender == CaseStep or sender == TestDemandContent or sender == LoginLog or sender == OperationLog or sender
|
||||
== Users):
|
||||
return
|
||||
user = get_current_user()
|
||||
ope_dict = {
|
||||
'operate_obj': str(instance),
|
||||
'operate_des': '删除'
|
||||
}
|
||||
log_manager.create(user=user, **ope_dict)
|
||||
import jwt
|
||||
from django.conf import settings
|
||||
from threading import local
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from django.dispatch import receiver
|
||||
from django.utils.functional import SimpleLazyObject
|
||||
from django.contrib.auth import get_user_model
|
||||
# 导入用例和影响域分析Model-设计信号当删除用例对应影响域分析删除关联用例
|
||||
from apps.project.models import Case, InfluenceItem
|
||||
# 导入日志的模型
|
||||
from apps.user.models import TableOperationLog, Users
|
||||
# 导入其他模型用于排除
|
||||
from apps.project.models import CaseStep, TestDemandContent
|
||||
# 导入异常处理
|
||||
from jwt.exceptions import ExpiredSignatureError
|
||||
from utils.chen_response import ChenResponse
|
||||
# 导入中间件记录日志模型
|
||||
from apps.system.models import LoginLog
|
||||
from apps.system.models import OperationLog
|
||||
|
||||
log_manager = TableOperationLog.objects
|
||||
|
||||
_thread_local = local()
|
||||
|
||||
def get_current_user():
|
||||
"""
|
||||
获取当前用户对象,调用则从local对象里面获取user
|
||||
:return: Users实例
|
||||
"""
|
||||
return getattr(_thread_local, 'user', None)
|
||||
|
||||
def clear_request_locals(sender, **kwargs):
|
||||
"""
|
||||
被request_finished连接的信号处理函数,请求结束后清除local里面的user信息
|
||||
"""
|
||||
_thread_local.user = None
|
||||
|
||||
def set_request_locals(sender, **kwargs):
|
||||
"""
|
||||
被request_started连接的信号处理函数,_thread_local.user属性设置为当前登录用户
|
||||
"""
|
||||
bearer_token = kwargs['environ'].get('HTTP_AUTHORIZATION', None)
|
||||
if not bearer_token or bearer_token == 'Bearer null':
|
||||
return
|
||||
bearer_token = bearer_token.replace('Bearer ', '')
|
||||
# 逻辑:先获取NINJA_JWT配置中秘钥、和加密算法信息
|
||||
jwt_settings = settings.NINJA_JWT
|
||||
jwt_secret = jwt_settings.get('SIGNING_KEY', None)
|
||||
jwt_algo = jwt_settings.get('ALGORITHM', None)
|
||||
# 如果为None,则使用settings中的秘钥和['HS256']算法
|
||||
secret_key = jwt_secret or settings.SECRET_KEY
|
||||
algorithms_str = jwt_algo or 'HS256'
|
||||
# 解决bug:因为过期前面不跳转首页处理方式
|
||||
try:
|
||||
jwt_dict = jwt.decode(bearer_token, secret_key, algorithms=[algorithms_str])
|
||||
except ExpiredSignatureError as exc:
|
||||
return ChenResponse(status=403, code=500, message='您的token已过期,请重新登录')
|
||||
user_id = jwt_dict.get('user_id', None)
|
||||
if user_id:
|
||||
_thread_local.user = SimpleLazyObject(lambda: get_user_model().objects.get(id=user_id))
|
||||
|
||||
# 1.注意可以不传sender,为监听所有模型,这里来记录日志
|
||||
# 2.使用get_current_user()获取当前请求用户
|
||||
@receiver(post_save)
|
||||
def post_save_handler(sender, instance, created, **kwargs):
|
||||
"""模型新增-操作日志填写"""
|
||||
# 注意排除日志模块、用例步骤表、测试项步骤表
|
||||
if (sender == TableOperationLog or sender == CaseStep or sender == TestDemandContent or sender == LoginLog or sender == OperationLog or sender
|
||||
== Users):
|
||||
return
|
||||
user = get_current_user()
|
||||
ope_dict = {
|
||||
'operate_obj': str(instance),
|
||||
}
|
||||
if created:
|
||||
ope_dict['operate_des'] = '新增'
|
||||
else:
|
||||
ope_dict['operate_des'] = '修改'
|
||||
log_manager.create(user=user, **ope_dict)
|
||||
|
||||
@receiver(post_delete)
|
||||
def post_delete_handler(sender, instance, **kwargs):
|
||||
"""模型删除-操作日志填写"""
|
||||
# 注意排除日志模块、用例步骤表、测试项步骤表
|
||||
if (sender == TableOperationLog or sender == CaseStep or sender == TestDemandContent or sender == LoginLog or sender == OperationLog or sender
|
||||
== Users):
|
||||
return
|
||||
user = get_current_user()
|
||||
ope_dict = {
|
||||
'operate_obj': str(instance),
|
||||
'operate_des': '删除'
|
||||
}
|
||||
log_manager.create(user=user, **ope_dict)
|
||||
|
||||
# 信号:删除影响域分析关联的用例,将影响域分析管理用例删除
|
||||
@receiver(post_delete, sender=Case)
|
||||
def clean_up_deleted_case_reference_from_influence(sender, instance, **kwargs):
|
||||
"""
|
||||
监听 Case 的删除信号。
|
||||
仅在同一个 Project 范围内,从 InfluenceItem 的 effect_cases 中移除被删用例的 key。
|
||||
"""
|
||||
deleted_key = instance.key
|
||||
project_id = instance.project_id
|
||||
|
||||
if not deleted_key or not project_id:
|
||||
return
|
||||
|
||||
# 查询当前项目的影响域分析并且包含该用例
|
||||
items = InfluenceItem.objects.filter(
|
||||
influence__round__project_id=project_id, # 外键链锁定项目
|
||||
effect_cases__contains=[deleted_key] # JSON 字段包含该 key
|
||||
)
|
||||
|
||||
# 未发现有关联的影响域分析则不处理
|
||||
if not items.exists():
|
||||
return
|
||||
|
||||
# 更新影响域分析的关联用例
|
||||
updated_items = []
|
||||
for item in items:
|
||||
original_keys = item.effect_cases
|
||||
# 过滤掉被删除的 key,保留其他
|
||||
new_keys = [k for k in original_keys if k != deleted_key]
|
||||
|
||||
if len(new_keys) != len(original_keys):
|
||||
item.effect_cases = new_keys
|
||||
updated_items.append(item)
|
||||
|
||||
if updated_items:
|
||||
InfluenceItem.objects.bulk_update(updated_items, ['effect_cases'])
|
||||
else:
|
||||
print("⚠️ 查询到记录但未发生变更,可能数据有误")
|
||||
Reference in New Issue
Block a user