Compare commits
12 Commits
beb8c2d25b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dffc1d5872 | |||
| 518f2f43a7 | |||
| 46768e53c3 | |||
| 0638950286 | |||
| 74d3d22ffe | |||
| 2f58bdc668 | |||
| a76cd8674c | |||
| 0bee950a52 | |||
| a2781c902a | |||
| 007712c63c | |||
| 48a0fad7e4 | |||
| 4a1881bf32 |
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 copy import deepcopy
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from ninja_extra import api_controller, ControllerBase, route
|
from ninja_extra import api_controller, ControllerBase, route
|
||||||
from ninja_extra.permissions import IsAuthenticated
|
from ninja_extra.permissions import IsAuthenticated
|
||||||
from ninja_jwt.authentication import JWTAuth
|
from ninja_jwt.authentication import JWTAuth
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from docxtpl import DocxTemplate
|
from docxtpl import DocxTemplate
|
||||||
from apps.dict.models import Dict
|
from apps.dict.models import Dict
|
||||||
from utils.chen_response import ChenResponse
|
from utils.chen_response import ChenResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from typing import Union
|
from typing import Union
|
||||||
from docxtpl import InlineImage
|
from docxtpl import InlineImage
|
||||||
from apps.project.models import Dut, Project, Round
|
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.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.chapter_tools.csx_chapter import create_csx_chapter_dict
|
||||||
from utils.path_utils import project_path
|
from utils.path_utils import project_path
|
||||||
from apps.createDocument.extensions.util import delete_dir_files
|
from apps.createDocument.extensions.util import delete_dir_files
|
||||||
from apps.createDocument.extensions.parse_rich_text import RichParser
|
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||||
# 导入生成日志记录模块
|
# 导入生成日志记录模块
|
||||||
from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
|
from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
|
||||||
|
|
||||||
chinese_round_name: list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
chinese_round_name: list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
||||||
|
|
||||||
# @api_controller("/generateHSM", tags=['生成回归记录系列文档'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
# @api_controller("/generateHSM", tags=['生成回归记录系列文档'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||||
@api_controller("/generateHJL", tags=['生成回归记录系列文档'])
|
@api_controller("/generateHJL", tags=['生成回归记录系列文档'])
|
||||||
class GenerateControllerHJL(ControllerBase):
|
class GenerateControllerHJL(ControllerBase):
|
||||||
logger = GenerateLogger('回归测试记录')
|
logger = GenerateLogger('回归测试记录')
|
||||||
|
|
||||||
# important:删除之前的文件
|
# important:删除之前的文件
|
||||||
@route.get('/create/deleteHJLDocument', url_name='delete-hjl-document')
|
@route.get('/create/deleteHJLDocument', url_name='delete-hjl-document')
|
||||||
def delete_hjl_document(self, id: int):
|
def delete_hjl_document(self, id: int):
|
||||||
project_path_str = project_path(id)
|
project_path_str = project_path(id)
|
||||||
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl'
|
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl'
|
||||||
delete_dir_files(save_path)
|
delete_dir_files(save_path)
|
||||||
|
|
||||||
@route.get("/create/basicInformation", url_name="create-basicInformation")
|
@route.get("/create/basicInformation", url_name="create-basicInformation")
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create_basicInformation(self, id: int):
|
def create_basicInformation(self, id: int):
|
||||||
"""生成回归测试记录的被测软件基本信息"""
|
"""生成回归测试记录的被测软件基本信息"""
|
||||||
project_path_str = project_path(id)
|
project_path_str = project_path(id)
|
||||||
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hjl' / '被测软件基本信息.docx'
|
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hjl' / '被测软件基本信息.docx'
|
||||||
doc = DocxTemplate(tpl_path)
|
doc = DocxTemplate(tpl_path)
|
||||||
project_obj = get_object_or_404(Project, id=id)
|
project_obj = get_object_or_404(Project, id=id)
|
||||||
# 第一轮次对象
|
# 第一轮次对象
|
||||||
round1_obj: Union[Round, None] = project_obj.pField.filter(key='0').first()
|
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()
|
round1_so_dut: Union[Dut, None] = round1_obj.rdField.filter(type='SO').first()
|
||||||
languages = get_list_dict('language', project_obj.language)
|
languages = get_list_dict('language', project_obj.language)
|
||||||
language_list = [item['ident_version'] for item in languages]
|
language_list = [item['ident_version'] for item in languages]
|
||||||
# 取非第一轮次
|
runtimes = get_list_dict('runtime', project_obj.runtime)
|
||||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
runtime_list = [item['ident_version'] for item in runtimes]
|
||||||
if len(hround_list) < 1:
|
devplants = get_list_dict('devplant', project_obj.devplant)
|
||||||
# ***Inspect-start***
|
devplant_list = [item['ident_version'] for item in devplants]
|
||||||
self.logger.model = '回归测试记录'
|
# 取非第一轮次
|
||||||
self.logger.write_warning_log('当前文档全部片段', f'该项目没有创建轮次')
|
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||||
# ***Inspect-end***
|
if len(hround_list) < 1:
|
||||||
return ChenResponse(code=400, status=400, message='您未创建轮次,请创建完毕后再试')
|
# ***Inspect-start***
|
||||||
|
self.logger.model = '回归测试记录'
|
||||||
context = {
|
self.logger.write_warning_log('当前文档全部片段', f'该项目没有创建轮次')
|
||||||
'project_name': project_obj.name,
|
# ***Inspect-end***
|
||||||
'language': "、".join(language_list),
|
return ChenResponse(code=400, status=400, message='您未创建轮次,请创建完毕后再试')
|
||||||
'soft_type': project_obj.get_soft_type_display(),
|
|
||||||
'security_level': get_str_dict(project_obj.security_level, 'security_level'),
|
context = {
|
||||||
'runtime': get_str_dict(project_obj.runtime, 'runtime'),
|
'project_name': project_obj.name,
|
||||||
'devplant': get_str_dict(project_obj.devplant, 'devplant'),
|
'language': "、".join(language_list),
|
||||||
'recv_date': project_obj.beginTime.strftime("%Y-%m-%d"),
|
'soft_type': project_obj.get_soft_type_display(),
|
||||||
'dev_unit': project_obj.dev_unit,
|
'security_level': get_str_dict(project_obj.security_level, 'security_level'),
|
||||||
}
|
'runtime': "、".join(runtime_list),
|
||||||
|
'devplant': "、".join(devplant_list),
|
||||||
version_info = [{'version': round1_so_dut.version,
|
'recv_date': project_obj.beginTime.strftime("%Y-%m-%d"),
|
||||||
'line_count': int(round1_so_dut.total_lines),
|
'dev_unit': project_obj.dev_unit,
|
||||||
'effective_line': int(round1_so_dut.effective_lines)}]
|
}
|
||||||
# 循环回归的轮次
|
|
||||||
for hround in hround_list:
|
version_info = [{'version': round1_so_dut.version,
|
||||||
# 每个轮次独立渲染context
|
'line_count': int(round1_so_dut.total_lines),
|
||||||
context_round = deepcopy(context)
|
'effective_line': int(round1_so_dut.effective_lines)}]
|
||||||
# 取中文名称
|
# 循环回归的轮次
|
||||||
cname = chinese_round_name[int(hround.key)] # 输出二、三...
|
for hround in hround_list:
|
||||||
# 取该轮次源代码版本放入版本列表
|
# 每个轮次独立渲染context
|
||||||
so_dut: Dut = hround.rdField.filter(type='SO').first()
|
context_round = deepcopy(context)
|
||||||
if not so_dut:
|
# 取中文名称
|
||||||
return ChenResponse(code=400, status=400, message=f'您第{cname}轮次中缺少源代码被测件,请添加')
|
cname = chinese_round_name[int(hround.key)] # 输出二、三...
|
||||||
version_info.append(
|
# 取该轮次源代码版本放入版本列表
|
||||||
{'version': so_dut.version, 'line_count': int(so_dut.total_lines),
|
so_dut: Dut = hround.rdField.filter(type='SO').first()
|
||||||
'effective_line': int(so_dut.effective_lines)})
|
if not so_dut:
|
||||||
context_round['version_info'] = version_info
|
return ChenResponse(code=400, status=400, message=f'您第{cname}轮次中缺少源代码被测件,请添加')
|
||||||
# 开始渲染每个轮次的二级文档
|
version_info.append(
|
||||||
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl' / f"第{cname}轮被测软件基本信息.docx"
|
{'version': so_dut.version, 'line_count': int(so_dut.total_lines),
|
||||||
doc.render(context=context_round, autoescape=True)
|
'effective_line': int(so_dut.effective_lines)})
|
||||||
try:
|
context_round['version_info'] = version_info
|
||||||
doc.save(save_path)
|
# 开始渲染每个轮次的二级文档
|
||||||
except PermissionError:
|
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl' / f"第{cname}轮被测软件基本信息.docx"
|
||||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
doc.render(context=context_round, autoescape=True)
|
||||||
return ChenResponse(code=200, status=200, message='多轮回归说明文档基本信息生成完毕')
|
try:
|
||||||
|
doc.save(save_path)
|
||||||
@route.get("/create/caseinfo", url_name="create-caseinfo")
|
except PermissionError:
|
||||||
@transaction.atomic
|
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
||||||
def create_caseinfo(self, id: int):
|
return ChenResponse(code=200, status=200, message='多轮回归说明文档基本信息生成完毕')
|
||||||
"""生成回归测试记录的-{测试用例记录}"""
|
|
||||||
project_path_str = project_path(id)
|
@route.get("/create/caseinfo", url_name="create-caseinfo")
|
||||||
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hjl' / '测试用例记录.docx'
|
@transaction.atomic
|
||||||
doc = DocxTemplate(tpl_path)
|
def create_caseinfo(self, id: int):
|
||||||
project_obj = get_object_or_404(Project, id=id)
|
"""生成回归测试记录的-{测试用例记录}"""
|
||||||
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
project_path_str = project_path(id)
|
||||||
if len(hround_list) < 1:
|
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/hjl' / '测试用例记录.docx'
|
||||||
return None
|
doc = DocxTemplate(tpl_path)
|
||||||
demand_prefix = '3.1'
|
project_obj = get_object_or_404(Project, id=id)
|
||||||
# 循环每轮轮次对象
|
hround_list: QuerySet = project_obj.pField.exclude(key='0')
|
||||||
for hround in hround_list:
|
if len(hround_list) < 1:
|
||||||
cname = chinese_round_name[int(hround.key)] # var:输出二、三字样
|
return None
|
||||||
test_type_len = Dict.objects.get(code='testType').dictItem.count() # 测试类型的个数
|
demand_prefix = '3.1'
|
||||||
type_number_list = [i for i in range(1, test_type_len + 1)] # 测试类型编号对应的列表
|
# 循环每轮轮次对象
|
||||||
list_list = [[] for j in range(1, test_type_len + 1)] # 每个测试类型组合为一个列表[[],[],[],[]]
|
for hround in hround_list:
|
||||||
testType_list, last_chapter_items = create_csx_chapter_dict(hround)
|
cname = chinese_round_name[int(hround.key)] # var:输出二、三字样
|
||||||
testDemands = hround.rtField.all() # 本轮所有测试项
|
test_type_len = Dict.objects.get(code='testType').dictItem.count() # 测试类型的个数
|
||||||
for demand in testDemands:
|
type_number_list = [i for i in range(1, test_type_len + 1)] # 测试类型编号对应的列表
|
||||||
type_index = type_number_list.index(int(demand.testType))
|
list_list = [[] for j in range(1, test_type_len + 1)] # 每个测试类型组合为一个列表[[],[],[],[]]
|
||||||
demand_ident = get_ident(demand)
|
testType_list, last_chapter_items = create_csx_chapter_dict(hround)
|
||||||
# ~~~组装测试项~~~
|
testDemands = hround.rtField.all() # 本轮所有测试项
|
||||||
demand_last_chapter = last_chapter_items[demand.testType].index(demand.key) + 1
|
for demand in testDemands:
|
||||||
demand_chapter = ".".join([demand_prefix, str(testType_list.index(demand.testType) + 1),
|
type_index = type_number_list.index(int(demand.testType))
|
||||||
str(demand_last_chapter)])
|
demand_ident = get_ident(demand)
|
||||||
demand_dict = {
|
# ~~~组装测试项~~~
|
||||||
'name': demand.name,
|
demand_last_chapter = last_chapter_items[demand.testType].index(demand.key) + 1
|
||||||
'ident': demand_ident,
|
demand_chapter = ".".join([demand_prefix, str(testType_list.index(demand.testType) + 1),
|
||||||
'chapter': demand_chapter,
|
str(demand_last_chapter)])
|
||||||
'item': []
|
demand_dict = {
|
||||||
}
|
'name': demand.name,
|
||||||
# ~~~这里组装测试项里面的测试用例~~~
|
'ident': demand_ident,
|
||||||
for case in demand.tcField.all():
|
'chapter': demand_chapter,
|
||||||
step_list = []
|
'item': []
|
||||||
index = 1
|
}
|
||||||
for one in case.step.all():
|
# ~~~这里组装测试项里面的测试用例~~~
|
||||||
# 这里需要对operation富文本处理
|
for case in demand.tcField.all():
|
||||||
rich_parser = RichParser(one.operation)
|
step_list = []
|
||||||
desc_list = rich_parser.get_final_list(doc, img_size=68)
|
index = 1
|
||||||
rich_parser2 = RichParser(one.result)
|
for one in case.step.all():
|
||||||
res_list = rich_parser2.get_final_list(doc, img_size=75)
|
# 这里需要对operation富文本处理
|
||||||
# 组装用例里面的步骤dict
|
rich_parser = RichParser(one.operation)
|
||||||
passed = '通过'
|
desc_list = rich_parser.get_final_list(doc, img_size=68)
|
||||||
if one.passed == '2':
|
rich_parser2 = RichParser(one.result)
|
||||||
passed = '未通过'
|
res_list = rich_parser2.get_final_list(doc, img_size=75)
|
||||||
if one.passed == '3':
|
# 组装用例里面的步骤dict
|
||||||
passed = '未执行'
|
passed = '通过'
|
||||||
step_dict = {
|
if one.passed == '2':
|
||||||
'index': index,
|
passed = '未通过'
|
||||||
'operation': desc_list,
|
if one.passed == '3':
|
||||||
'expect': one.expect,
|
passed = '未执行'
|
||||||
'result': res_list,
|
step_dict = {
|
||||||
'passed': passed,
|
'index': index,
|
||||||
}
|
'operation': desc_list,
|
||||||
step_list.append(step_dict)
|
'expect': one.expect,
|
||||||
index += 1
|
'result': res_list,
|
||||||
# 查询所有的problem
|
'passed': passed,
|
||||||
problem_list = []
|
}
|
||||||
problem_prefix = "PT"
|
step_list.append(step_dict)
|
||||||
proj_ident = project_obj.ident
|
index += 1
|
||||||
for problem in case.caseField.all():
|
# 查询所有的problem
|
||||||
problem_list.append("_".join([problem_prefix, proj_ident, problem.ident]))
|
problem_list = []
|
||||||
# fpga的时序图
|
problem_prefix = "PT"
|
||||||
rich_parser3 = RichParser(case.timing_diagram)
|
proj_ident = project_obj.ident
|
||||||
timing_diagram = rich_parser3.get_final_list(doc, img_size=115, height=50)
|
for problem in case.caseField.all():
|
||||||
has_timing_diagram = False
|
problem_list.append("_".join([problem_prefix, proj_ident, problem.ident]))
|
||||||
if len(timing_diagram) > 0:
|
# fpga的时序图
|
||||||
if isinstance(timing_diagram[0], InlineImage):
|
rich_parser3 = RichParser(case.timing_diagram)
|
||||||
has_timing_diagram = True
|
timing_diagram = rich_parser3.get_final_list(doc, img_size=115, height=50)
|
||||||
# 组装用例的dict
|
has_timing_diagram = False
|
||||||
case_dict = {
|
if len(timing_diagram) > 0:
|
||||||
'name': case.name,
|
if isinstance(timing_diagram[0], InlineImage):
|
||||||
'ident': get_case_ident(demand_ident, case),
|
has_timing_diagram = True
|
||||||
'summary': case.summarize,
|
# 组装用例的dict
|
||||||
'initialization': case.initialization,
|
case_dict = {
|
||||||
'premise': case.premise,
|
'name': case.name,
|
||||||
'design_person': case.designPerson,
|
'ident': get_case_ident(demand_ident, case),
|
||||||
'test_person': case.testPerson,
|
'summary': case.summarize,
|
||||||
'monitor_person': case.monitorPerson,
|
'initialization': case.initialization,
|
||||||
'step': step_list,
|
'premise': case.premise,
|
||||||
'time': str(case.exe_time) if case.exe_time is not None else str(case.update_datetime),
|
'design_person': case.designPerson,
|
||||||
'problems': "、".join(problem_list),
|
'test_person': case.testPerson,
|
||||||
'round_num_chn': cname,
|
'monitor_person': case.monitorPerson,
|
||||||
# 2025年4月24日新增
|
'step': step_list,
|
||||||
'has_timing_diagram': has_timing_diagram,
|
'time': str(case.exe_time) if case.exe_time is not None else str(case.update_datetime),
|
||||||
'timing_diagram': timing_diagram,
|
'problems': "、".join(problem_list),
|
||||||
}
|
'round_num_chn': cname,
|
||||||
demand_dict['item'].append(case_dict)
|
# 2025年4月24日新增
|
||||||
|
'has_timing_diagram': has_timing_diagram,
|
||||||
list_list[type_index].append(demand_dict)
|
'timing_diagram': timing_diagram,
|
||||||
# 定义渲染上下文
|
}
|
||||||
context = {}
|
demand_dict['item'].append(case_dict)
|
||||||
output_list = []
|
|
||||||
for (index, li) in enumerate(list_list):
|
list_list[type_index].append(demand_dict)
|
||||||
qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1))
|
# 定义渲染上下文
|
||||||
context_str = qs.title
|
context = {}
|
||||||
sort = qs.sort
|
output_list = []
|
||||||
table = {
|
for (index, li) in enumerate(list_list):
|
||||||
"type": context_str,
|
qs = Dict.objects.get(code="testType").dictItem.get(key=str(index + 1))
|
||||||
"item": li,
|
context_str = qs.title
|
||||||
"sort": sort
|
sort = qs.sort
|
||||||
}
|
table = {
|
||||||
output_list.append(table)
|
"type": context_str,
|
||||||
# 排序
|
"item": li,
|
||||||
output_list = sorted(output_list, key=(lambda x: x["sort"]))
|
"sort": sort
|
||||||
context["data"] = output_list
|
}
|
||||||
# 最后渲染
|
output_list.append(table)
|
||||||
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl' / f"第{cname}轮测试用例记录.docx"
|
# 排序
|
||||||
doc.render(context, autoescape=True)
|
output_list = sorted(output_list, key=(lambda x: x["sort"]))
|
||||||
try:
|
context["data"] = output_list
|
||||||
doc.save(save_path)
|
# 最后渲染
|
||||||
except PermissionError:
|
save_path = Path.cwd() / 'media' / project_path_str / 'output_dir/hjl' / f"第{cname}轮测试用例记录.docx"
|
||||||
return ChenResponse(code=400, status=400, message='您打开了生成的文档,请关闭后重试')
|
doc.render(context, autoescape=True)
|
||||||
return ChenResponse(code=200, status=200, message='多轮回归测试用例记录生成完毕')
|
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
|
from pathlib import Path
|
||||||
# 导入django、ninja等模块
|
from django.db.models import QuerySet
|
||||||
from ninja_extra import api_controller, ControllerBase, route
|
# 导入django、ninja等模块
|
||||||
from django.db import transaction
|
from ninja_extra import api_controller, ControllerBase, route
|
||||||
from django.shortcuts import get_object_or_404
|
from django.db import transaction
|
||||||
# 导入文档处理模块
|
from django.shortcuts import get_object_or_404
|
||||||
from docxtpl import DocxTemplate, InlineImage
|
# 导入文档处理模块
|
||||||
# 导入ORM模型
|
from docxtpl import DocxTemplate, InlineImage
|
||||||
from apps.project.models import Project
|
# 导入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.util import get_str_abbr, get_str_dict
|
||||||
from utils.path_utils import project_path
|
from utils.chen_response import ChenResponse
|
||||||
from apps.createDocument.extensions.parse_rich_text import RichParser
|
from utils.path_utils import project_path
|
||||||
# 导入生成日志记录模块
|
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||||
from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
|
# 导入生成日志记录模块
|
||||||
|
from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
|
||||||
gloger = GenerateLogger("问题单二段文档")
|
|
||||||
|
gloger = GenerateLogger("问题单二段文档")
|
||||||
# @api_controller("/generateWtd", tags=['生成问题单文档系列'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
|
||||||
@api_controller('/generateWtd', tags=['生成问题单文档系列'])
|
# @api_controller("/generateWtd", tags=['生成问题单文档系列'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||||
class GenerateControllerWtd(ControllerBase):
|
@api_controller('/generateWtd', tags=['生成问题单文档系列'])
|
||||||
@route.get("/create/problem", url_name="create-problem")
|
class GenerateControllerWtd(ControllerBase):
|
||||||
@transaction.atomic
|
@route.get("/create/problem", url_name="create-problem")
|
||||||
def create_problem(self, id: int):
|
@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'
|
project_path_str = project_path(id)
|
||||||
doc = DocxTemplate(tpl_path)
|
tpl_path = Path.cwd() / 'media' / project_path_str / 'form_template/wtd' / '问题详情表.docx'
|
||||||
project_obj = get_object_or_404(Project, id=id)
|
doc = DocxTemplate(tpl_path)
|
||||||
problem_list = list(project_obj.projField.distinct()) # 去掉重复,因为和case是多对多
|
project_obj = get_object_or_404(Project, id=id)
|
||||||
problem_list.sort(key=lambda x: int(x.ident))
|
problem_list = list(project_obj.projField.distinct()) # 去掉重复,因为和case是多对多
|
||||||
data_list = []
|
problem_list.sort(key=lambda x: int(x.ident))
|
||||||
for problem in problem_list:
|
data_list = []
|
||||||
problem_dict = {'ident': problem.ident, 'name': problem.name}
|
for problem in problem_list:
|
||||||
# 1.生成被测对象名称、被测对象标识、被测对象版本
|
problem_dict = {'ident': problem.ident, 'name': problem.name}
|
||||||
cases = problem.case.all()
|
# 1.生成被测对象名称、被测对象标识、被测对象版本
|
||||||
# generate_log:无关联问题单进入生成日志
|
cases: QuerySet[Case] = problem.case.all()
|
||||||
if cases.count() < 1:
|
# generate_log:无关联问题单进入生成日志
|
||||||
gloger.write_warning_log('单个问题单表格', f'问题单{problem.ident}未关联用例,请检查')
|
if cases.count() < 1:
|
||||||
str_dut_name_list = []
|
gloger.write_warning_log('单个问题单表格', f'问题单{problem.ident}未关联用例,请检查')
|
||||||
str_dut_ident_list = []
|
str_dut_name_list = []
|
||||||
str_dut_version_list = []
|
str_dut_ident_list = []
|
||||||
# 2.所属用例标识
|
str_dut_version_list = []
|
||||||
case_ident_list = []
|
# 2.所属用例标识
|
||||||
# 3.获取依据要求
|
case_ident_list = []
|
||||||
case_design_list = []
|
# 3.获取依据要求
|
||||||
for case in cases:
|
case_design_list = []
|
||||||
if case.test.testType == '8':
|
for case in cases:
|
||||||
# 1.1.如果为文档审查,提取所属文档名称、文档被测件标识、文档被测件版本
|
if case.test.testType == '8':
|
||||||
str_dut_name_list.append(case.dut.name)
|
# 1.1.如果为文档审查,提取所属文档名称、文档被测件标识、文档被测件版本
|
||||||
str_dut_ident_list.append(case.dut.ref)
|
str_dut_name_list.append(case.dut.name)
|
||||||
str_dut_version_list.append(case.dut.version)
|
str_dut_ident_list.append(case.dut.ref)
|
||||||
# 对应dut名称,design章节号,design描述
|
str_dut_version_list.append(case.dut.version)
|
||||||
case_design_list.append("".join([case.dut.name, case.design.chapter]))
|
# 对应dut名称,design章节号,design描述
|
||||||
else:
|
case_design_list.append("".join([case.dut.name, case.design.chapter]))
|
||||||
# 1.2.如果不为文档审查,则提取该轮次源代码dut的信息
|
else:
|
||||||
so_dut = case.round.rdField.filter(type='SO').first()
|
# 1.2.如果不为文档审查,则提取该轮次源代码dut的信息
|
||||||
if so_dut:
|
so_dut = case.round.rdField.filter(type='SO').first()
|
||||||
str_dut_name_list.append(project_obj.name + '软件')
|
if so_dut:
|
||||||
str_dut_ident_list.append(so_dut.ref)
|
str_dut_name_list.append(project_obj.name + '软件')
|
||||||
str_dut_version_list.append(so_dut.version)
|
str_dut_ident_list.append(so_dut.ref)
|
||||||
# TODO:如何处理设计需求的内容,暂时设置为取出图片,只保留文字
|
str_dut_version_list.append(so_dut.version)
|
||||||
p_list = []
|
# TODO:如何处理设计需求的内容,暂时设置为取出图片,只保留文字
|
||||||
rich_parse_remove_img = RichParser(case.design.description)
|
p_list = []
|
||||||
rich_list = rich_parse_remove_img.get_final_list(doc)
|
rich_parse_remove_img = RichParser(case.design.description)
|
||||||
for rich in rich_list:
|
rich_list = rich_parse_remove_img.get_final_list(doc)
|
||||||
if isinstance(rich, dict) or isinstance(rich, InlineImage):
|
for rich in rich_list:
|
||||||
continue
|
if isinstance(rich, dict) or isinstance(rich, InlineImage):
|
||||||
else:
|
continue
|
||||||
p_list.append(rich)
|
else:
|
||||||
|
p_list.append(rich)
|
||||||
case_design_list.append(
|
case_design_list.append("-".join([case.dut.name, case.design.chapter + '章节' + ":" + ''.join(p_list)]))
|
||||||
"-".join([case.dut.name, case.design.chapter + '章节' + ":" + ''.join(p_list)]))
|
# 2.用例标识修改-YL_测试项类型_测试项标识_用例key+1
|
||||||
# 2.用例标识修改-YL_测试项类型_测试项标识_用例key+1
|
demand = case.test # 中间变量
|
||||||
demand = case.test # 中间变量
|
demand_testType = demand.testType # 中间变量
|
||||||
demand_testType = demand.testType # 中间变量
|
testType_abbr = get_str_abbr(demand_testType, 'testType') # 输出FT
|
||||||
testType_abbr = get_str_abbr(demand_testType, 'testType') # 输出FT
|
case_ident_list.append("_".join(
|
||||||
case_ident_list.append("_".join(
|
['YL', testType_abbr, demand.ident, str(int(case.key[-1]) + 1).rjust(3, '0')]))
|
||||||
['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_name'] = "/".join(set(str_dut_name_list))
|
problem_dict['duts_ref'] = "/".join(set(str_dut_ident_list))
|
||||||
problem_dict['duts_ref'] = "/".join(set(str_dut_ident_list))
|
problem_dict['duts_version'] = "/".join(set(str_dut_version_list))
|
||||||
problem_dict['duts_version'] = "/".join(set(str_dut_version_list))
|
temp_name_version = []
|
||||||
temp_name_version = []
|
for i in range(len(str_dut_name_list)):
|
||||||
for i in range(len(str_dut_name_list)):
|
temp_name_version.append(
|
||||||
temp_name_version.append(
|
"".join([str_dut_name_list[i] + str_dut_ident_list[i], '/V', str_dut_version_list[i]]))
|
||||||
"".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['dut_name_version'] = "\a".join(temp_name_version)
|
problem_dict['case_ident'] = ",".join(set(case_ident_list))
|
||||||
problem_dict['case_ident'] = ",".join(set(case_ident_list))
|
problem_dict['type'] = get_str_dict(problem.type, 'problemType')
|
||||||
problem_dict['type'] = get_str_dict(problem.type, 'problemType')
|
problem_dict['grade'] = get_str_dict(problem.grade, 'problemGrade')
|
||||||
problem_dict['grade'] = get_str_dict(problem.grade, 'problemGrade')
|
|
||||||
|
# 依据要求-获取其设计需求
|
||||||
# 依据要求-获取其设计需求
|
print(case_design_list)
|
||||||
problem_dict['yaoqiu'] = "\a".join(case_design_list)
|
problem_dict['yaoqiu'] = "\a".join(case_design_list)
|
||||||
# 问题操作 - HTML解析
|
# 问题操作 - HTML解析
|
||||||
desc_list = ['【问题操作】']
|
desc_list = ['【问题操作】']
|
||||||
rich_parser = RichParser(problem.operation)
|
rich_parser = RichParser(problem.operation)
|
||||||
desc_list.extend(rich_parser.get_final_list(doc))
|
desc_list.extend(rich_parser.get_final_list(doc))
|
||||||
|
|
||||||
# 问题影响
|
# 问题影响
|
||||||
desc_list_result = [f'\a【问题影响】\a{problem.result}']
|
desc_list_result = [f'\a【问题影响】\a{problem.result}']
|
||||||
desc_list.extend(desc_list_result)
|
desc_list.extend(desc_list_result)
|
||||||
# 问题描述赋值
|
# 问题描述赋值
|
||||||
problem_dict['desc'] = desc_list
|
problem_dict['desc'] = desc_list
|
||||||
|
|
||||||
# 4.原因分析
|
# 4.原因分析
|
||||||
desc_list_3 = [f'【原因分析】\a{problem.analysis}']
|
desc_list_3 = [f'【原因分析】\a{problem.analysis}']
|
||||||
problem_dict['cause'] = desc_list_3
|
problem_dict['cause'] = desc_list_3
|
||||||
|
|
||||||
# 5.影响域分析~~~~
|
# 5.影响域分析~~~~
|
||||||
desc_list_4 = [f'【影响域分析】\a{problem.effect_scope}']
|
desc_list_4 = [f'【影响域分析】\a{problem.effect_scope}']
|
||||||
problem_dict['effect_scope'] = desc_list_4
|
problem_dict['effect_scope'] = desc_list_4
|
||||||
|
|
||||||
# 6.改正措施
|
# 6.改正措施
|
||||||
problem_dict['solve'] = problem.solve
|
problem_dict['solve'] = problem.solve
|
||||||
|
|
||||||
# 7.回归验证结果
|
# 7.回归验证结果
|
||||||
desc_list_5 = []
|
desc_list_5 = []
|
||||||
rich_parser5 = RichParser(problem.verify_result)
|
rich_parser5 = RichParser(problem.verify_result)
|
||||||
desc_list_5.extend(rich_parser5.get_final_list(doc))
|
desc_list_5.extend(rich_parser5.get_final_list(doc))
|
||||||
problem_dict['verify_result'] = desc_list_5
|
problem_dict['verify_result'] = desc_list_5
|
||||||
|
|
||||||
# 8.其他日期和人员
|
# 8.其他日期和人员
|
||||||
problem_dict['postPerson'] = problem.postPerson
|
problem_dict['postPerson'] = problem.postPerson
|
||||||
problem_dict['postDate'] = problem.postDate
|
problem_dict['postDate'] = problem.postDate
|
||||||
close_str = '□修改文档 □修改程序 □不修改'
|
close_str = '□修改文档 □修改程序 □不修改'
|
||||||
if len(problem.closeMethod) < 1:
|
if len(problem.closeMethod) < 1:
|
||||||
close_str = '□修改文档 □修改程序 ■不修改'
|
close_str = '□修改文档 □修改程序 ■不修改'
|
||||||
elif len(problem.closeMethod) == 2:
|
elif len(problem.closeMethod) == 2:
|
||||||
close_str = '■修改文档 ■修改程序 □不修改'
|
close_str = '■修改文档 ■修改程序 □不修改'
|
||||||
else:
|
else:
|
||||||
if problem.closeMethod[0] == '1':
|
if problem.closeMethod[0] == '1':
|
||||||
close_str = '■修改文档 □修改程序 □不修改'
|
close_str = '■修改文档 □修改程序 □不修改'
|
||||||
elif problem.closeMethod[0] == '2':
|
elif problem.closeMethod[0] == '2':
|
||||||
close_str = '□修改文档 ■修改程序 □不修改'
|
close_str = '□修改文档 ■修改程序 □不修改'
|
||||||
else:
|
else:
|
||||||
close_str = '□修改文档 □修改程序 □不修改'
|
close_str = '□修改文档 □修改程序 □不修改'
|
||||||
problem_dict['closeMethod'] = close_str
|
problem_dict['closeMethod'] = close_str
|
||||||
problem_dict['designer'] = problem.designerPerson
|
problem_dict['designer'] = problem.designerPerson
|
||||||
problem_dict['designDate'] = problem.designDate
|
problem_dict['designDate'] = problem.designDate
|
||||||
problem_dict['verifyPerson'] = problem.verifyPerson
|
problem_dict['verifyPerson'] = problem.verifyPerson
|
||||||
problem_dict['verifyDate'] = problem.verifyDate
|
problem_dict['verifyDate'] = problem.verifyDate
|
||||||
data_list.append(problem_dict)
|
data_list.append(problem_dict)
|
||||||
context = {
|
context = {
|
||||||
'project_name': project_obj.name,
|
'project_name': project_obj.name,
|
||||||
'project_ident': project_obj.ident,
|
'project_ident': project_obj.ident,
|
||||||
'problem_list': data_list,
|
'problem_list': data_list,
|
||||||
}
|
}
|
||||||
doc.render(context, autoescape=True)
|
doc.render(context, autoescape=True)
|
||||||
try:
|
try:
|
||||||
doc.save(Path.cwd() / "media" / project_path_str / "output_dir/wtd" / '问题详情表.docx')
|
doc.save(Path.cwd() / "media" / project_path_str / "output_dir/wtd" / '问题详情表.docx')
|
||||||
return ChenResponse(status=200, code=200, message="文档生成成功!")
|
return ChenResponse(status=200, code=200, message="文档生成成功!")
|
||||||
except PermissionError as e:
|
except PermissionError as e:
|
||||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,9 @@
|
|||||||
from apps.project.models import Project
|
from apps.project.models import Project, Round, InfluenceArea
|
||||||
|
from docxtpl import DocxTemplate
|
||||||
from utils.util import *
|
from utils.util import *
|
||||||
from utils.chen_response import ChenResponse
|
from utils.chen_response import ChenResponse
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from apps.createDocument.extensions.parse_rich_text import RichParser
|
||||||
|
|
||||||
def create_round_context(project_obj: Project, round_id: str):
|
def create_round_context(project_obj: Project, round_id: str):
|
||||||
"""根据轮次,生成测评报告中的测评结果"""
|
"""根据轮次,生成测评报告中的测评结果"""
|
||||||
@@ -77,4 +79,37 @@ def create_round_context(project_obj: Project, round_id: str):
|
|||||||
'r2_dynamic_str': r2_dynamic_str,
|
'r2_dynamic_str': r2_dynamic_str,
|
||||||
'round_id': round_chinese[round_id],
|
'round_id': round_chinese[round_id],
|
||||||
}
|
}
|
||||||
return context
|
return context, round_obj
|
||||||
|
|
||||||
|
# ~~~影响域分析:内容返回influence的render_list~~~
|
||||||
|
def create_influence_context(doc: DocxTemplate, round_obj: Round, project_obj: Project) -> None | list:
|
||||||
|
area_qs = InfluenceArea.objects.filter(round=round_obj)
|
||||||
|
item_render_list = []
|
||||||
|
## 如果存在则查询items
|
||||||
|
if area_qs.exists():
|
||||||
|
area_obj = area_qs.first()
|
||||||
|
items_qs = area_obj.influence_items.all()
|
||||||
|
if items_qs.exists():
|
||||||
|
index = 1
|
||||||
|
for item in items_qs:
|
||||||
|
# 1.处理关联case - 找第一轮cases
|
||||||
|
case_str_list = []
|
||||||
|
for case in project_obj.pcField.filter(key__in=item.effect_cases):
|
||||||
|
case_ident_index = str(int(case.key.split("-")[-1]) + 1).zfill(3)
|
||||||
|
case_str_list.append("_".join(["YL", get_str_abbr(case.test.testType, "testType"), case.ident, case_ident_index]))
|
||||||
|
# 2.处理富文本框
|
||||||
|
parser = RichParser(item.change_des)
|
||||||
|
item_dict = {
|
||||||
|
"change_type": item.change_type,
|
||||||
|
"change_influ": item.change_influ,
|
||||||
|
"case_str_list": case_str_list,
|
||||||
|
"change_des": parser.get_final_list(doc, img_size=40, height=30), # 富文本未处理
|
||||||
|
"index": str(index),
|
||||||
|
}
|
||||||
|
index = index + 1
|
||||||
|
item_render_list.append(item_dict)
|
||||||
|
|
||||||
|
if len(item_render_list) > 0:
|
||||||
|
return item_render_list
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|||||||
@@ -1,131 +1,143 @@
|
|||||||
"""
|
"""
|
||||||
专门解析富文本插件tinymce的html内容
|
专门解析富文本插件tinymce的html内容
|
||||||
"""
|
"""
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from bs4.element import Tag, NavigableString
|
from bs4.element import Tag, NavigableString
|
||||||
import base64
|
import base64
|
||||||
import io
|
import io
|
||||||
from docxtpl import InlineImage
|
from docxtpl import InlineImage
|
||||||
from docx.shared import Mm, Cm
|
from docx.shared import Mm
|
||||||
import re
|
import re
|
||||||
|
|
||||||
# text.replace('\xa0', ' '))
|
# text.replace('\xa0', ' '))
|
||||||
class RichParser:
|
class RichParser:
|
||||||
def __init__(self, rich_text):
|
def __init__(self, rich_text):
|
||||||
# 将rich_text的None变为空字符串:鲁棒
|
# 将rich_text的None变为空字符串:鲁棒
|
||||||
if rich_text is None:
|
if rich_text is None:
|
||||||
rich_text = ""
|
rich_text = ""
|
||||||
# 对原始html解析后的bs对象
|
# 对原始html解析后的bs对象
|
||||||
self.bs = BeautifulSoup(rich_text, 'html.parser')
|
self.bs = BeautifulSoup(rich_text, 'html.parser')
|
||||||
self.content = self.remove_n_in_contents()
|
self.content = self.remove_n_in_contents()
|
||||||
# 最终的解析后的列表
|
# 最终的解析后的列表
|
||||||
self.data_list = []
|
self.data_list = []
|
||||||
self.line_parse()
|
self.line_parse()
|
||||||
|
# 匹配“表1-3”或“表1”等字符的正则
|
||||||
# 1.函数:将self.bs.contents去掉\n,获取每行数据
|
self.biao_pattern = re.compile(r"表\d+(?:-\d+)?")
|
||||||
def remove_n_in_contents(self):
|
|
||||||
content_list = []
|
# 1.函数:将self.bs.contents去掉\n,获取每行数据
|
||||||
for line in self.bs.contents:
|
def remove_n_in_contents(self):
|
||||||
if line != '\n':
|
content_list = []
|
||||||
content_list.append(line)
|
for line in self.bs.contents:
|
||||||
return content_list
|
if line != '\n':
|
||||||
|
content_list.append(line)
|
||||||
# 2.逐个遍历self.content,去掉table元素Tag对象单独解析
|
return content_list
|
||||||
def line_parse(self):
|
|
||||||
for tag in self.content:
|
# 2.逐个遍历self.content,去掉table元素Tag对象单独解析
|
||||||
if isinstance(tag, NavigableString):
|
def line_parse(self):
|
||||||
self.data_list.append(tag.text)
|
for tag in self.content:
|
||||||
elif isinstance(tag, Tag):
|
if isinstance(tag, NavigableString):
|
||||||
if tag.name == 'p':
|
self.data_list.append(tag.text)
|
||||||
img_list = tag.find_all('img')
|
elif isinstance(tag, Tag):
|
||||||
if len(img_list) > 0:
|
if tag.name == 'p':
|
||||||
for img_item in img_list:
|
img_list = tag.find_all('img')
|
||||||
self.data_list.append(img_item.get('src'))
|
if len(img_list) > 0:
|
||||||
else:
|
for img_item in img_list:
|
||||||
self.data_list.append(tag.text)
|
self.data_list.append(img_item.get('src'))
|
||||||
elif tag.name == 'table':
|
else:
|
||||||
df_dict_list = self.parse_tag2list(tag)
|
self.data_list.append(tag.text)
|
||||||
self.data_list.append(df_dict_list)
|
elif tag.name == 'table':
|
||||||
elif tag.name == 'div':
|
df_dict_list = self.parse_tag2list(tag)
|
||||||
table_list = tag.find_all('table')
|
self.data_list.append(df_dict_list)
|
||||||
if len(table_list) > 0:
|
elif tag.name == 'div':
|
||||||
for table in table_list:
|
table_list = tag.find_all('table')
|
||||||
df_dict_list = self.parse_tag2list(table)
|
if len(table_list) > 0:
|
||||||
self.data_list.append(df_dict_list)
|
for table in table_list:
|
||||||
|
df_dict_list = self.parse_tag2list(table)
|
||||||
# 3.1.辅助方法,将<table>的Tag对象转为[[]]二维列表格式
|
self.data_list.append(df_dict_list)
|
||||||
def parse_tag2list(self, table_tag):
|
|
||||||
# str(tag)可直接变成<table>xxx</table>
|
# 3.1.辅助方法,将<table>的Tag对象转为[[]]二维列表格式
|
||||||
pd_list = pd.read_html(io.StringIO(str(table_tag)))
|
def parse_tag2list(self, table_tag):
|
||||||
# 将dataframe变为数组
|
# str(tag)可直接变成<table>xxx</table>
|
||||||
df = pd_list[0]
|
pd_list = pd.read_html(io.StringIO(str(table_tag)))
|
||||||
# 处理第一行为数字的情况,如果为数字则删除第一行,让第二行为列名
|
# 将dataframe变为数组
|
||||||
if all(isinstance(col, int) for col in df.columns):
|
df = pd_list[0]
|
||||||
df.columns = df.iloc[0]
|
# 处理第一行为数字的情况,如果为数字则删除第一行,让第二行为列名
|
||||||
df = df.drop(0) # 删除原来的第一行
|
if all(isinstance(col, int) for col in df.columns):
|
||||||
# 转为列表的列表(二维列表)
|
df.columns = df.iloc[0]
|
||||||
# return df.values.tolist()
|
df = df.drop(0) # 删除原来的第一行
|
||||||
return df.fillna('').T.reset_index().T.values.tolist()
|
# 转为列表的列表(二维列表)
|
||||||
|
# return df.values.tolist()
|
||||||
# 3.2.辅助方法,打印解析后列表
|
return df.fillna('').T.reset_index().T.values.tolist()
|
||||||
def print_content(self):
|
|
||||||
for line in self.data_list:
|
# 3.2.辅助方法,打印解析后列表
|
||||||
print(line)
|
def print_content(self):
|
||||||
|
for line in self.data_list:
|
||||||
# 4.1.最终方法,生成给docxtpl可用的列表 -> 注意需要传递DocxTemplate对象,在接口函数里面初始化的
|
print(line)
|
||||||
def get_final_list(self, doc, /, *, img_size=100, height=80):
|
|
||||||
"""注意关键字传参可修改图片大小img_size:int=100"""
|
# 4.1.最终方法,生成给docxtpl可用的列表 -> 注意需要传递DocxTemplate对象,在接口函数里面初始化的
|
||||||
final_list = []
|
def get_final_list(self, doc, /, *, img_size=100, height=80):
|
||||||
for oneline in self.data_list:
|
"""注意关键字传参可修改图片大小img_size:int=100"""
|
||||||
# 这里要单独处理下二维列表
|
final_list = []
|
||||||
if isinstance(oneline, list):
|
for oneline in self.data_list:
|
||||||
final_list.append({'isTable': True, 'data': oneline})
|
# 这里要单独处理下二维列表
|
||||||
continue
|
if isinstance(oneline, list):
|
||||||
if oneline.startswith("data:image/png;base64"):
|
final_list.append({'isTable': True, 'data': oneline})
|
||||||
base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", ""))
|
continue
|
||||||
# ~~~设置了固定宽度、高度~~~
|
if oneline.startswith("data:image/png;base64") or oneline.startswith("data:image/jpeg;base64,") or oneline.startswith(
|
||||||
final_list.append(
|
"data:image/jpg;base64,"):
|
||||||
InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=Mm(height)))
|
base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", ""))
|
||||||
else:
|
# ~~~设置了固定宽度、高度~~~
|
||||||
final_list.append(oneline)
|
inline_image = InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=Mm(height))
|
||||||
if len(final_list) <= 0:
|
final_list.append(inline_image)
|
||||||
final_list.append("")
|
else:
|
||||||
# 针对tinymce中,粘贴表格最后一行显示句号问题,这里统一删除
|
# ~~~新增:将\xa0修改为普通空格~~~
|
||||||
if final_list[-1] == '\xa0':
|
oneline = oneline.replace('\xa0', ' ')
|
||||||
final_list.pop()
|
final_list.append(oneline)
|
||||||
return final_list
|
if len(final_list) <= 0:
|
||||||
|
final_list.append("")
|
||||||
# 4.2.最终方法,在上面方法基础上,增加格式,例如<p>增加缩进,图片居中,<p>包含“图x”则居中
|
# 针对tinymce中,粘贴表格最后一行显示句号问题,这里统一删除
|
||||||
def get_final_format_list(self, doc, /, *, img_size=115, height=80):
|
if final_list[-1] == '\xa0':
|
||||||
final_list = []
|
final_list.pop()
|
||||||
for oneline in self.data_list:
|
return final_list
|
||||||
# 这里要单独处理下二维列表
|
|
||||||
if isinstance(oneline, list):
|
# 4.2.最终方法,在上面方法基础上,增加格式,例如<p>增加缩进,图片居中,<p>包含“图x”则居中
|
||||||
final_list.append({'isTable': True, 'data': oneline})
|
def get_final_format_list(self, doc, /, *, img_size=115, height=80):
|
||||||
continue
|
final_list = []
|
||||||
if oneline.startswith("data:image/png;base64"):
|
for oneline in self.data_list:
|
||||||
base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", ""))
|
# 这里要单独处理下二维列表
|
||||||
# 1.和上面函数变化:图片更改为dict然后isCenter属性居中
|
if isinstance(oneline, list):
|
||||||
final_list.append(
|
final_list.append({'isTable': True, 'data': oneline})
|
||||||
{'isCenter': True,
|
continue
|
||||||
'data': InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=height)})
|
if oneline.startswith("data:image/png;base64"):
|
||||||
else:
|
base64_bytes = base64.b64decode(oneline.replace("data:image/png;base64,", ""))
|
||||||
# 2.和上面区别:如果<p>带有“图”则居中
|
# 1.和上面函数变化:图片更改为dict然后isCenter属性居中
|
||||||
if re.match(r"[表图]\d.*", oneline):
|
final_list.append(
|
||||||
final_list.append({"isCenter": True, "data": oneline})
|
{'isCenter': True,
|
||||||
else:
|
'data': InlineImage(doc, io.BytesIO(base64_bytes), width=Mm(img_size), height=height)})
|
||||||
final_list.append({"isCenter": False, "data": oneline})
|
else:
|
||||||
if len(final_list) <= 0:
|
# 2.和上面区别:如果<p>带有“图”则居中
|
||||||
final_list.append("")
|
if re.match(r"[表图]\d.*", oneline):
|
||||||
return final_list
|
final_list.append({"isCenter": True, "data": oneline.replace('\xa0', ' ')})
|
||||||
|
else:
|
||||||
# 5.最终方法,去掉图片和table元素 -> 纯文本列表
|
final_list.append({"isCenter": False, "data": oneline.replace('\xa0', ' ')})
|
||||||
def get_final_p_list(self):
|
if len(final_list) <= 0:
|
||||||
final_list = []
|
final_list.append("")
|
||||||
for oneline in self.data_list:
|
return final_list
|
||||||
if isinstance(oneline, list) or oneline.startswith("data:image/png;base64"):
|
|
||||||
continue
|
# 5.最终方法,去掉图片和table元素 -> 纯文本列表
|
||||||
else:
|
def get_final_p_list(self):
|
||||||
final_list.append(oneline)
|
final_list = []
|
||||||
return 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
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
from apps.project.models import TestDemand
|
from apps.project.models import TestDemand
|
||||||
|
from docx.oxml import OxmlElement
|
||||||
|
from docx.oxml.ns import qn
|
||||||
|
from docx.table import _Cell, Table
|
||||||
|
|
||||||
def demand_sort_by_designKey(demand_obj: TestDemand) -> tuple[int, ...]:
|
def demand_sort_by_designKey(demand_obj: TestDemand) -> tuple[int, ...]:
|
||||||
"""仅限于测试项排序函数,传入sorted函数的key里面"""
|
"""仅限于测试项排序函数,传入sorted函数的key里面"""
|
||||||
@@ -6,4 +9,134 @@ def demand_sort_by_designKey(demand_obj: TestDemand) -> tuple[int, ...]:
|
|||||||
sort_tuple = tuple(int(part) for part in parts)
|
sort_tuple = tuple(int(part) for part in parts)
|
||||||
return sort_tuple
|
return sort_tuple
|
||||||
|
|
||||||
__all__ = ['demand_sort_by_designKey']
|
# 传入cell设置边框
|
||||||
|
def set_cell_border(cell: _Cell, **kwargs):
|
||||||
|
tc = cell._tc
|
||||||
|
tcPr = tc.get_or_add_tcPr()
|
||||||
|
|
||||||
|
# 检查标签是否存在,如果没有找到,则创建一个
|
||||||
|
tcBorders = tcPr.first_child_found_in("w:tcBorders")
|
||||||
|
if tcBorders is None:
|
||||||
|
tcBorders = OxmlElement('w:tcBorders')
|
||||||
|
tcPr.append(tcBorders)
|
||||||
|
|
||||||
|
for border_type in ['left', 'top', 'right', 'bottom']:
|
||||||
|
# 设置为固定的“黑色加粗”
|
||||||
|
border_data = kwargs.get(border_type, {"sz": "6", "val": "single", "color": "#000000", "space": "0"})
|
||||||
|
tag = 'w:{}'.format(border_type)
|
||||||
|
element = tcBorders.find(qn(tag))
|
||||||
|
if element is None:
|
||||||
|
element = OxmlElement(tag)
|
||||||
|
tcBorders.append(element)
|
||||||
|
for key in ["sz", "val", "color", "space", "shadow"]:
|
||||||
|
if key in border_data:
|
||||||
|
element.set(qn('w:{}'.format(key)), str(border_data[key]))
|
||||||
|
|
||||||
|
# 弃用,请使用下面函数
|
||||||
|
def set_table_border(table, **kwargs):
|
||||||
|
"""docx-设置表格上下左右边框"""
|
||||||
|
# 获取或创建表格属性
|
||||||
|
tbl_pr = table._tbl.tblPr
|
||||||
|
|
||||||
|
# 查找并移除现有的边框设置
|
||||||
|
existing_borders = tbl_pr.find(qn('w:tblBorders'))
|
||||||
|
if existing_borders is not None:
|
||||||
|
tbl_pr.remove(existing_borders)
|
||||||
|
|
||||||
|
# 创建新的边框元素
|
||||||
|
borders = OxmlElement('w:tblBorders')
|
||||||
|
|
||||||
|
# 只设置外边框:top, left, bottom, right - 设置为固定“黑色加粗”
|
||||||
|
# 不设置 insideV 和 insideH(内部边框)
|
||||||
|
for border_type in ['top', 'left', 'bottom', 'right']:
|
||||||
|
border_data = kwargs.get(border_type, {"sz": "12", "val": "single", "color": "#000000"})
|
||||||
|
border_elem = OxmlElement(f'w:{border_type}')
|
||||||
|
|
||||||
|
# 设置边框属性
|
||||||
|
border_elem.set(qn('w:val'), border_data.get('val', 'single')) # 线条类型
|
||||||
|
border_elem.set(qn('w:sz'), border_data.get('sz', '12')) # 线条粗细(8代表1磅)
|
||||||
|
border_elem.set(qn('w:color'), border_data.get('color', '#000000')) # 颜色
|
||||||
|
borders.append(border_elem) # type:ignore
|
||||||
|
|
||||||
|
# 将边框设置添加到表格属性中
|
||||||
|
tbl_pr.append(borders)
|
||||||
|
|
||||||
|
# ~~~新解决方案:传入table对象,遍历cell,判断cell是否在外层~~~
|
||||||
|
def set_table_border_by_cell_position(table: Table):
|
||||||
|
"""
|
||||||
|
智能设置表格边框:外边框粗,内边框细。
|
||||||
|
"""
|
||||||
|
# 获取表格的总行数和总列数
|
||||||
|
total_rows = len(table.rows)
|
||||||
|
total_cols = len(table.columns)
|
||||||
|
|
||||||
|
for row_idx, row in enumerate(table.rows):
|
||||||
|
for col_idx, cell in enumerate(row.cells):
|
||||||
|
# 初始化边框参数字典
|
||||||
|
border_kwargs = {}
|
||||||
|
|
||||||
|
# 1. 判断上边框:如果是第一行,则设置粗上边框,否则不设置(由上一行的下边框决定,或单独设置细线)
|
||||||
|
if row_idx == 0:
|
||||||
|
border_kwargs['top'] = {"sz": "12", "val": "single", "color": "#000000"}
|
||||||
|
# 2. 判断下边框:如果是最后一行,则设置粗下边框
|
||||||
|
if row_idx == total_rows - 1:
|
||||||
|
border_kwargs['bottom'] = {"sz": "12", "val": "single", "color": "#000000"}
|
||||||
|
# 3. 判断左边框:如果是第一列,则设置粗左边框
|
||||||
|
if col_idx == 0:
|
||||||
|
border_kwargs['left'] = {"sz": "12", "val": "single", "color": "#000000"}
|
||||||
|
# 4. 判断右边框:如果是最后一列,则设置粗右边框
|
||||||
|
if col_idx == total_cols - 1:
|
||||||
|
border_kwargs['right'] = {"sz": "12", "val": "single", "color": "#000000"}
|
||||||
|
|
||||||
|
# 5. 设置内部网格线(细线)
|
||||||
|
# 内部横线 (insideH): 所有单元格都需要,但最后一行不需要(已经是外边框)
|
||||||
|
if row_idx < total_rows - 1:
|
||||||
|
border_kwargs['insideH'] = {"sz": "6", "val": "single", "color": "#000000"}
|
||||||
|
# 内部竖线 (insideV): 所有单元格都需要,但最后一列不需要(已经是外边框)
|
||||||
|
if col_idx < total_cols - 1:
|
||||||
|
border_kwargs['insideV'] = {"sz": "6", "val": "single", "color": "#000000"}
|
||||||
|
|
||||||
|
# 调用您已有的 set_cell_border 函数
|
||||||
|
set_cell_border(cell, **border_kwargs)
|
||||||
|
|
||||||
|
# 设置cell的左右边距
|
||||||
|
def set_cell_margins(cell: _Cell, **kwargs):
|
||||||
|
"""
|
||||||
|
设置单元格边距,确保在Office和WPS中均能生效。
|
||||||
|
参数示例: set_cell_margins(cell, left=50, right=50, top=100, bottom=100)
|
||||||
|
参数单位: 为二十分之一磅 (dxa, 1/1440英寸)。
|
||||||
|
"""
|
||||||
|
tc = cell._tc
|
||||||
|
tcPr = tc.get_or_add_tcPr()
|
||||||
|
|
||||||
|
# 关键步骤1:检查或创建 w:tcMar 元素
|
||||||
|
tcMar = tcPr.find(qn('w:tcMar'))
|
||||||
|
if tcMar is None:
|
||||||
|
tcMar = OxmlElement('w:tcMar')
|
||||||
|
tcPr.append(tcMar)
|
||||||
|
|
||||||
|
# 关键步骤2:为每个指定的边距方向创建元素,并同时设置新旧两套属性以保证兼容性[2](@ref)
|
||||||
|
# 定义映射:我们的参数名 -> (XML元素名, 备用的XML元素名)
|
||||||
|
margin_map = {
|
||||||
|
'left': ('left', 'start'),
|
||||||
|
'right': ('right', 'end'),
|
||||||
|
'top': ('top', None),
|
||||||
|
'bottom': ('bottom', None)
|
||||||
|
}
|
||||||
|
|
||||||
|
for margin_key, value in kwargs.items():
|
||||||
|
if margin_key in margin_map:
|
||||||
|
primary_tag, alternate_tag = margin_map[margin_key]
|
||||||
|
tags_to_set = [primary_tag]
|
||||||
|
if alternate_tag: # 如果存在备选标签(如left/start),则同时设置
|
||||||
|
tags_to_set.append(alternate_tag)
|
||||||
|
|
||||||
|
for tag in tags_to_set:
|
||||||
|
# 检查该边距元素是否已存在
|
||||||
|
margin_element = tcMar.find(qn(f'w:{tag}'))
|
||||||
|
if margin_element is None:
|
||||||
|
margin_element = OxmlElement(f'w:{tag}')
|
||||||
|
tcMar.append(margin_element) # type:ignore
|
||||||
|
# 设置边距值和单位类型
|
||||||
|
margin_element.set(qn('w:w'), str(value))
|
||||||
|
margin_element.set(qn('w:type'), 'dxa')
|
||||||
|
|||||||
@@ -95,3 +95,4 @@ def delete_dir_files(path: Path) -> Any:
|
|||||||
for file in path.iterdir():
|
for file in path.iterdir():
|
||||||
if file.is_file():
|
if file.is_file():
|
||||||
file.unlink()
|
file.unlink()
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,348 +1,352 @@
|
|||||||
"""该文件是:替换文档片段然后生成辅助生成最终文档"""
|
"""该文件是:替换文档片段然后生成辅助生成最终文档"""
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from docx import Document
|
from docx import Document
|
||||||
from docx.text.paragraph import Paragraph
|
from docx.text.paragraph import Paragraph
|
||||||
from docx.table import Table
|
from docx.table import Table
|
||||||
from docx.oxml.table import CT_Tbl
|
from docx.oxml.table import CT_Tbl
|
||||||
from docx.oxml.text.paragraph import CT_P
|
from docx.oxml.text.paragraph import CT_P
|
||||||
from docx.oxml.text.run import CT_R
|
from docx.oxml.text.run import CT_R
|
||||||
from docx.oxml.shape import CT_Picture
|
from docx.oxml.shape import CT_Picture
|
||||||
from docx.parts.image import ImagePart
|
from docx.parts.image import ImagePart
|
||||||
from docx.text.run import Run
|
from docx.text.run import Run
|
||||||
from docx.shared import Mm
|
from docx.shared import Mm
|
||||||
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
||||||
from lxml.etree import _Element
|
from lxml.etree import _Element
|
||||||
|
|
||||||
# 路径工具
|
# 路径工具
|
||||||
from utils.path_utils import project_path
|
from utils.path_utils import project_path
|
||||||
|
|
||||||
### 模块变量:定义常用图片所在区域的宽高
|
### 模块变量:定义常用图片所在区域的宽高
|
||||||
Demand_table_xqms = Mm(134) # 1.测评大纲-测试项里面-需求描述单元格
|
Demand_table_xqms = Mm(134) # 1.测评大纲-测试项里面-需求描述单元格
|
||||||
Timing_diagram_width = Mm(242) # 2.测试记录-时序图
|
Timing_diagram_width = Mm(242) # 2.测试记录-时序图
|
||||||
Test_result_width = Mm(78) # 3.测试记录-测试结果
|
Test_result_width = Mm(78) # 3.测试记录-测试结果
|
||||||
Horizatal_width = Mm(130) # 4.所有文档-页面图片的横向距离(图片宽度预设置)
|
Horizatal_width = Mm(130) # 4.所有文档-页面图片的横向距离(图片宽度预设置)
|
||||||
|
|
||||||
def getParentRunNode(node):
|
def getParentRunNode(node):
|
||||||
"""传入oxml节点对象,获取其祖先节点的CT_R"""
|
"""传入oxml节点对象,获取其祖先节点的CT_R"""
|
||||||
if isinstance(node, CT_R):
|
if isinstance(node, CT_R):
|
||||||
return node
|
return node
|
||||||
return getParentRunNode(node.getparent())
|
return getParentRunNode(node.getparent())
|
||||||
|
|
||||||
def generate_temp_doc(doc_type: str, project_id: int, round_num=None, frag_list=None):
|
def generate_temp_doc(doc_type: str, project_id: int, round_num=None, frag_list=None):
|
||||||
""" 该函数参数:
|
""" 该函数参数:
|
||||||
:param frag_list: 储存用户不覆盖的片段列表
|
:param frag_list: 储存用户不覆盖的片段列表
|
||||||
:param round_num: 只有回归说明和回归记录有
|
:param round_num: 只有回归说明和回归记录有
|
||||||
:param project_id: 项目id
|
:param project_id: 项目id
|
||||||
:param doc_type:大纲 sm:说明 jl:记录 bg:报告 hsm:回归测试说明 hjl:回归测试记录,默认路径为dg -> 所以如果传错就生成生成大纲了
|
:param doc_type:大纲 sm:说明 jl:记录 bg:报告 hsm:回归测试说明 hjl:回归测试记录,默认路径为dg -> 所以如果传错就生成生成大纲了
|
||||||
:return (to_tpl_file路径, seitai_final_file路径)
|
:return (to_tpl_file路径, seitai_final_file路径)
|
||||||
"""
|
"""
|
||||||
if frag_list is None:
|
if frag_list is None:
|
||||||
frag_list = []
|
frag_list = []
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
project_path_str = project_path(project_id)
|
project_path_str = project_path(project_id)
|
||||||
# 根据传入需要处理的文档类型,自动获路径
|
# 根据传入需要处理的文档类型,自动获路径
|
||||||
prefix = Path.cwd() / 'media' / project_path_str
|
prefix = Path.cwd() / 'media' / project_path_str
|
||||||
template_file: Path = prefix / 'form_template' / 'products' / '测评大纲.docx'
|
template_file: Path = prefix / 'form_template' / 'products' / '测评大纲.docx'
|
||||||
to_tpl_file: Path = prefix / 'temp' / '测评大纲.docx'
|
to_tpl_file: Path = prefix / 'temp' / '测评大纲.docx'
|
||||||
seitai_final_file: Path = prefix / 'final_seitai' / '测评大纲.docx'
|
seitai_final_file: Path = prefix / 'final_seitai' / '测评大纲.docx'
|
||||||
if doc_type == 'sm':
|
if doc_type == 'sm':
|
||||||
template_file = prefix / 'form_template' / 'products' / '测试说明.docx'
|
template_file = prefix / 'form_template' / 'products' / '测试说明.docx'
|
||||||
to_tpl_file = prefix / 'temp' / '测试说明.docx'
|
to_tpl_file = prefix / 'temp' / '测试说明.docx'
|
||||||
seitai_final_file: Path = prefix / 'final_seitai' / '测试说明.docx'
|
seitai_final_file: Path = prefix / 'final_seitai' / '测试说明.docx'
|
||||||
elif doc_type == 'jl':
|
elif doc_type == 'jl':
|
||||||
template_file = prefix / 'form_template' / 'products' / '测试记录.docx'
|
template_file = prefix / 'form_template' / 'products' / '测试记录.docx'
|
||||||
to_tpl_file = prefix / 'temp' / '测试记录.docx'
|
to_tpl_file = prefix / 'temp' / '测试记录.docx'
|
||||||
seitai_final_file: Path = prefix / 'final_seitai' / '测试记录.docx'
|
seitai_final_file: Path = prefix / 'final_seitai' / '测试记录.docx'
|
||||||
elif doc_type == 'bg':
|
elif doc_type == 'bg':
|
||||||
template_file = prefix / 'form_template' / 'products' / '测评报告.docx'
|
template_file = prefix / 'form_template' / 'products' / '测评报告.docx'
|
||||||
to_tpl_file = prefix / 'temp' / '测评报告.docx'
|
to_tpl_file = prefix / 'temp' / '测评报告.docx'
|
||||||
seitai_final_file: Path = prefix / 'final_seitai' / '测评报告.docx'
|
seitai_final_file: Path = prefix / 'final_seitai' / '测评报告.docx'
|
||||||
elif doc_type == 'hsm':
|
elif doc_type == 'hsm':
|
||||||
# 如果products里面存在“用户上传的第n轮回归测试说明.docx,则使用它作为模版”
|
# 如果products里面存在“用户上传的第n轮回归测试说明.docx,则使用它作为模版”
|
||||||
template_file = prefix / 'form_template' / 'products' / f'第{round_num}轮回归测试说明.docx'
|
template_file = prefix / 'form_template' / 'products' / f'第{round_num}轮回归测试说明.docx'
|
||||||
if not template_file.exists():
|
if not template_file.exists():
|
||||||
template_file = prefix / 'form_template' / 'products' / '回归测试说明.docx'
|
template_file = prefix / 'form_template' / 'products' / '回归测试说明.docx'
|
||||||
to_tpl_file = prefix / 'temp' / f'第{round_num}轮回归测试说明.docx'
|
to_tpl_file = prefix / 'temp' / f'第{round_num}轮回归测试说明.docx'
|
||||||
seitai_final_file: Path = prefix / 'final_seitai' / f'第{round_num}轮回归测试说明.docx'
|
seitai_final_file: Path = prefix / 'final_seitai' / f'第{round_num}轮回归测试说明.docx'
|
||||||
elif doc_type == 'hjl':
|
elif doc_type == 'hjl':
|
||||||
# 如果products里面存在“用户上传的第n轮回归测试记录.docx,则使用它作为模版”
|
# 如果products里面存在“用户上传的第n轮回归测试记录.docx,则使用它作为模版”
|
||||||
template_file = prefix / 'form_template' / 'products' / f'第{round_num}轮回归测试记录.docx'
|
template_file = prefix / 'form_template' / 'products' / f'第{round_num}轮回归测试记录.docx'
|
||||||
if not template_file.exists():
|
if not template_file.exists():
|
||||||
template_file = prefix / 'form_template' / 'products' / '回归测试记录.docx'
|
template_file = prefix / 'form_template' / 'products' / '回归测试记录.docx'
|
||||||
to_tpl_file = prefix / 'temp' / f'第{round_num}轮回归测试记录.docx'
|
to_tpl_file = prefix / 'temp' / f'第{round_num}轮回归测试记录.docx'
|
||||||
seitai_final_file: Path = prefix / 'final_seitai' / f'第{round_num}轮回归测试记录.docx'
|
seitai_final_file: Path = prefix / 'final_seitai' / f'第{round_num}轮回归测试记录.docx'
|
||||||
elif doc_type == 'wtd':
|
elif doc_type == 'wtd':
|
||||||
template_file = prefix / 'form_template' / 'products' / '问题单.docx'
|
template_file = prefix / 'form_template' / 'products' / '问题单.docx'
|
||||||
to_tpl_file = prefix / 'temp' / '问题单.docx'
|
to_tpl_file = prefix / 'temp' / '问题单.docx'
|
||||||
seitai_final_file: Path = prefix / 'final_seitai' / '问题单.docx'
|
seitai_final_file: Path = prefix / 'final_seitai' / '问题单.docx'
|
||||||
# 定义找寻被复制文件根路径 - 后续会根据type找子路径
|
# 定义找寻被复制文件根路径 - 后续会根据type找子路径
|
||||||
output_files_path = prefix / 'output_dir'
|
output_files_path = prefix / 'output_dir'
|
||||||
# 这里可能修改,储存大纲里面的文档片段
|
# 这里可能修改,储存大纲里面的文档片段
|
||||||
dg_copied_files = []
|
dg_copied_files = []
|
||||||
# 储存sm/jl/hsm/hjl/bg/wtd的文档片段
|
# 储存sm/jl/hsm/hjl/bg/wtd的文档片段
|
||||||
exclusive_copied_files = []
|
exclusive_copied_files = []
|
||||||
# 新:储存reuse的文档片段
|
# 新:储存reuse的文档片段
|
||||||
reuse_files = []
|
reuse_files = []
|
||||||
# 将被拷贝文件分别放入不同两个数组
|
# 将被拷贝文件分别放入不同两个数组
|
||||||
for file in output_files_path.iterdir():
|
for file in output_files_path.iterdir():
|
||||||
if file.is_file():
|
if file.is_file():
|
||||||
if file.suffix == '.docx':
|
if file.suffix == '.docx':
|
||||||
dg_copied_files.append(file)
|
dg_copied_files.append(file)
|
||||||
elif file.is_dir():
|
elif file.is_dir():
|
||||||
# 如果文件夹名称为sm/jl/hsm/hjl/bg/wtd则进入该判断
|
# 如果文件夹名称为sm/jl/hsm/hjl/bg/wtd则进入该判断
|
||||||
# 所以要求文件系统文件夹名称必须是sm/jl/hsm/hjl/bg/wtd不然无法生成
|
# 所以要求文件系统文件夹名称必须是sm/jl/hsm/hjl/bg/wtd不然无法生成
|
||||||
if file.stem == doc_type:
|
if file.stem == doc_type:
|
||||||
for f in file.iterdir():
|
for f in file.iterdir():
|
||||||
if f.suffix == '.docx':
|
if f.suffix == '.docx':
|
||||||
exclusive_copied_files.append(f)
|
exclusive_copied_files.append(f)
|
||||||
for file in (prefix / 'reuse').iterdir():
|
for file in (prefix / 'reuse').iterdir():
|
||||||
if file.is_file():
|
if file.is_file():
|
||||||
if file.suffix == '.docx':
|
if file.suffix == '.docx':
|
||||||
reuse_files.append(file)
|
reuse_files.append(file)
|
||||||
# 找到基础模版的所有std域
|
# 找到基础模版的所有std域
|
||||||
doc = Document(template_file.as_posix())
|
doc = Document(template_file.as_posix())
|
||||||
body = doc.element.body
|
body = doc.element.body
|
||||||
sdt_element_list = body.xpath('./w:sdt')
|
sdt_element_list = body.xpath('./w:sdt')
|
||||||
# 找到sdt域的名称 -> 为了对应output_dir文件 / 储存所有output_dir图片
|
# 找到sdt域的名称 -> 为了对应output_dir文件 / 储存所有output_dir图片
|
||||||
area_name_list = []
|
area_name_list = []
|
||||||
image_part_list = [] # 修改为字典两个字段{ 'name':'测评对象', 'img':ImagePart }
|
image_part_list = [] # 修改为字典两个字段{ 'name':'测评对象', 'img':ImagePart }
|
||||||
# 筛选片段【二】:用户前端要求不要覆盖的文档片段
|
# 筛选片段【二】:用户前端要求不要覆盖的文档片段
|
||||||
frag_is_cover_dict = {item.name: item.isCover for item in frag_list}
|
frag_is_cover_dict = {item.name: item.isCover for item in frag_list}
|
||||||
# 遍历所有控件 -> 放入area_name_list【这里准备提取公共代码】
|
# 遍历所有控件 -> 放入area_name_list【这里准备提取公共代码】
|
||||||
for sdt_ele in sdt_element_list:
|
for sdt_ele in sdt_element_list:
|
||||||
isLock = False
|
isLock = False
|
||||||
for elem in sdt_ele.iterchildren():
|
for elem in sdt_ele.iterchildren():
|
||||||
# 【一】用户设置lock - 下面2个if将需要被替换的(控件名称)存入area_name_list
|
# 【一】用户设置lock - 下面2个if将需要被替换的(控件名称)存入area_name_list
|
||||||
if elem.tag.endswith('sdtPr'):
|
if elem.tag.endswith('sdtPr'):
|
||||||
for el in elem.getchildren():
|
for el in elem.getchildren():
|
||||||
if el.tag.endswith('lock'):
|
if el.tag.endswith('lock'):
|
||||||
isLock = True
|
isLock = True
|
||||||
if elem.tag.endswith('sdtPr'):
|
if elem.tag.endswith('sdtPr'):
|
||||||
for el in elem.getchildren():
|
for el in elem.getchildren():
|
||||||
if el.tag.endswith('alias'):
|
if el.tag.endswith('alias'):
|
||||||
# 筛序【一】:取出用户设置lock的文档片段
|
# 筛序【一】:取出用户设置lock的文档片段
|
||||||
if len(el.attrib.values()) > 0 and (isLock == False):
|
if len(el.attrib.values()) > 0 and (isLock == False):
|
||||||
area_name = el.attrib.values()[0]
|
area_name = el.attrib.values()[0]
|
||||||
# 筛选【二】:前端用户选择要覆盖的片段
|
# 筛选【二】:前端用户选择要覆盖的片段
|
||||||
if frag_is_cover_dict.get(area_name):
|
if frag_is_cover_dict.get(area_name):
|
||||||
area_name_list.append(area_name)
|
area_name_list.append(area_name)
|
||||||
# 下面开始替换area_name_list的“域”(这时已经被筛选-因为sdtPr和sdtContent是成对出现)
|
# 下面开始替换area_name_list的“域”(这时已经被筛选-因为sdtPr和sdtContent是成对出现)
|
||||||
if elem.tag.endswith('sdtContent'):
|
if elem.tag.endswith('sdtContent'):
|
||||||
if len(area_name_list) > 0:
|
if len(area_name_list) > 0:
|
||||||
# 从第一个片段名称开始取,取到模版的“域”名称
|
# 从第一个片段名称开始取,取到模版的“域”名称
|
||||||
area_pop_name = area_name_list.pop(0)
|
area_pop_name = area_name_list.pop(0)
|
||||||
# 这里先去找media/output_dir/xx下文件,然后找media/output下文件
|
# 这里先去找media/output_dir/xx下文件,然后找media/output下文件
|
||||||
copied_file_path = ""
|
copied_file_path = ""
|
||||||
# 下面if...else是找output_dir下面文件与“域”名称匹配,匹配到存入copied_file_path
|
# 下面if...else是找output_dir下面文件与“域”名称匹配,匹配到存入copied_file_path
|
||||||
if doc_type == 'dg':
|
if doc_type == 'dg':
|
||||||
for file in dg_copied_files:
|
for file in dg_copied_files:
|
||||||
if file.stem == area_pop_name:
|
if file.stem == area_pop_name:
|
||||||
copied_file_path = file
|
copied_file_path = file
|
||||||
else:
|
else:
|
||||||
# 如果不是大纲
|
# 如果不是大纲
|
||||||
if round_num is None:
|
if round_num is None:
|
||||||
# 如果非回归说明、记录
|
# 如果非回归说明、记录
|
||||||
for file in exclusive_copied_files:
|
for file in exclusive_copied_files:
|
||||||
if file.stem == area_pop_name:
|
if file.stem == area_pop_name:
|
||||||
copied_file_path = file
|
copied_file_path = file
|
||||||
# 这里判断是否copied_file_path没取到文件,然后遍历reuse下文件
|
# 这里判断是否copied_file_path没取到文件,然后遍历reuse下文件
|
||||||
if not copied_file_path:
|
if not copied_file_path:
|
||||||
for file in reuse_files:
|
for file in reuse_files:
|
||||||
if file.stem == area_pop_name:
|
if file.stem == area_pop_name:
|
||||||
copied_file_path = file
|
copied_file_path = file
|
||||||
# 如果上面被复制文件还没找到,然后遍历output_dir下文件
|
# 如果上面被复制文件还没找到,然后遍历output_dir下文件
|
||||||
if not copied_file_path:
|
if not copied_file_path:
|
||||||
for file in dg_copied_files:
|
for file in dg_copied_files:
|
||||||
if file.stem == area_pop_name:
|
if file.stem == area_pop_name:
|
||||||
copied_file_path = file
|
copied_file_path = file
|
||||||
else:
|
else:
|
||||||
# 因为回归的轮次,前面会加 -> 第{round_num}轮
|
# 因为回归的轮次,前面会加 -> 第{round_num}轮
|
||||||
for file in exclusive_copied_files: # 这里多了第{round_num}轮
|
for file in exclusive_copied_files: # 这里多了第{round_num}轮
|
||||||
if file.stem == f"第{round_num}轮{area_pop_name}":
|
if file.stem == f"第{round_num}轮{area_pop_name}":
|
||||||
copied_file_path = file
|
copied_file_path = file
|
||||||
if not copied_file_path:
|
if not copied_file_path:
|
||||||
for file in reuse_files:
|
for file in reuse_files:
|
||||||
if file.stem == area_pop_name:
|
if file.stem == area_pop_name:
|
||||||
copied_file_path = file
|
copied_file_path = file
|
||||||
if not copied_file_path:
|
if not copied_file_path:
|
||||||
for file in dg_copied_files:
|
for file in dg_copied_files:
|
||||||
if file.stem == area_pop_name:
|
if file.stem == area_pop_name:
|
||||||
copied_file_path = file
|
copied_file_path = file
|
||||||
# 找到文档片段.docx,将其数据复制到对应area_name的“域”
|
# 找到文档片段.docx,将其数据复制到对应area_name的“域”
|
||||||
if copied_file_path:
|
if copied_file_path:
|
||||||
doc_copied = Document(copied_file_path)
|
doc_copied = Document(copied_file_path)
|
||||||
copied_element_list = []
|
copied_element_list = []
|
||||||
element_list = doc_copied.element.body.inner_content_elements
|
element_list = doc_copied.element.body.inner_content_elements
|
||||||
for elet in element_list:
|
for elet in element_list:
|
||||||
if isinstance(elet, CT_P):
|
if isinstance(elet, CT_P):
|
||||||
copied_element_list.append(Paragraph(elet, doc_copied))
|
copied_element_list.append(Paragraph(elet, doc_copied))
|
||||||
if isinstance(elet, CT_Tbl):
|
if isinstance(elet, CT_Tbl):
|
||||||
copied_element_list.append(Table(elet, doc_copied))
|
copied_element_list.append(Table(elet, doc_copied))
|
||||||
elem.clear()
|
elem.clear()
|
||||||
for para_copied in copied_element_list:
|
for para_copied in copied_element_list:
|
||||||
elem.append(para_copied._element)
|
elem.append(para_copied._element)
|
||||||
|
|
||||||
# 下面代码就是将图片全部提取到image_part_list,以便后续插入,注意这时候已经是筛选后的
|
# 下面代码就是将图片全部提取到image_part_list,以便后续插入,注意这时候已经是筛选后的
|
||||||
doc_copied = Document(copied_file_path) # 需要重新获取否则namespace错误
|
doc_copied = Document(copied_file_path) # 需要重新获取否则namespace错误
|
||||||
copied_body = doc_copied.element.body
|
copied_body = doc_copied.element.body
|
||||||
img_node_list = copied_body.xpath('.//pic:pic')
|
img_node_list = copied_body.xpath('.//pic:pic')
|
||||||
if not img_node_list:
|
if not img_node_list:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
for img_node in img_node_list:
|
for img_node in img_node_list:
|
||||||
img: CT_Picture = img_node
|
img: CT_Picture = img_node
|
||||||
# 根据节点找到图片的关联id
|
# 根据节点找到图片的关联id
|
||||||
embed = img.xpath('.//a:blip/@r:embed')[0]
|
embed = img.xpath('.//a:blip/@r:embed')[0]
|
||||||
# 这里得到ImagePart -> 马上要给新文档添加
|
# 这里得到ImagePart -> 马上要给新文档添加
|
||||||
related_part: ImagePart = doc_copied.part.related_parts[embed]
|
related_part: ImagePart = doc_copied.part.related_parts.get(embed)
|
||||||
# doc_copied.part.related_parts是一个字典
|
if related_part is None:
|
||||||
image_part_list.append({'name': area_pop_name, 'img': related_part})
|
# 可选:记录警告日志,便于排查哪些文档片段有问题
|
||||||
|
print(f"警告: 文档片段 '{area_pop_name}' 中的图片引用 {embed} 未找到,已跳过!!!!")
|
||||||
# 现在是替换后,找到替换后文档所有pic:pic,并对“域”名称进行识别
|
continue
|
||||||
graph_node_list = body.xpath('.//pic:pic')
|
# doc_copied.part.related_parts是一个字典
|
||||||
graph_node_list_transform = []
|
image_part_list.append({'name': area_pop_name, 'img': related_part})
|
||||||
for picNode in graph_node_list:
|
|
||||||
# 遍历替换后模版的所有pic,去找祖先
|
# 现在是替换后,找到替换后文档所有pic:pic,并对“域”名称进行识别
|
||||||
sdt_node = picNode.xpath('ancestor::w:sdt[1]')[0]
|
graph_node_list = body.xpath('.//pic:pic')
|
||||||
for sdt_node_child in sdt_node.iterchildren():
|
graph_node_list_transform = []
|
||||||
# 找到sdt下一级的stdPr
|
for picNode in graph_node_list:
|
||||||
if sdt_node_child.tag.endswith('sdtPr'):
|
# 遍历替换后模版的所有pic,去找祖先
|
||||||
for sdtPr_node_child in sdt_node_child.getchildren():
|
sdt_node = picNode.xpath('ancestor::w:sdt[1]')[0]
|
||||||
if sdtPr_node_child.tag.endswith('alias'):
|
for sdt_node_child in sdt_node.iterchildren():
|
||||||
yu_name = sdtPr_node_child.attrib.values()[0]
|
# 找到sdt下一级的stdPr
|
||||||
graph_node_list_transform.append({'yu_name': yu_name, 'yu_node': picNode})
|
if sdt_node_child.tag.endswith('sdtPr'):
|
||||||
for graph_node in graph_node_list_transform:
|
for sdtPr_node_child in sdt_node_child.getchildren():
|
||||||
image_run_node = getParentRunNode(graph_node['yu_node'])
|
if sdtPr_node_child.tag.endswith('alias'):
|
||||||
image_run_node.clear()
|
yu_name = sdtPr_node_child.attrib.values()[0]
|
||||||
# 循环去image_part_list找name和yu_name相等的图片
|
graph_node_list_transform.append({'yu_name': yu_name, 'yu_node': picNode})
|
||||||
for img_part in image_part_list:
|
for graph_node in graph_node_list_transform:
|
||||||
# 1.如果找到相等
|
image_run_node = getParentRunNode(graph_node['yu_node'])
|
||||||
if img_part['name'] == graph_node['yu_name']:
|
image_run_node.clear()
|
||||||
# 2.找到即可添加图片到“域”
|
# 循环去image_part_list找name和yu_name相等的图片
|
||||||
image_run_node.clear()
|
for img_part in image_part_list:
|
||||||
# 辅助:去找其父节点是否为段落,是段落则存起来,后面好居中
|
# 1.如果找到相等
|
||||||
image_run_parent_paragraph = image_run_node.getparent()
|
if img_part['name'] == graph_node['yu_name']:
|
||||||
father_paragraph = None
|
# 2.找到即可添加图片到“域”
|
||||||
if isinstance(image_run_parent_paragraph, CT_P):
|
image_run_node.clear()
|
||||||
father_paragraph = Paragraph(image_run_parent_paragraph, doc)
|
# 辅助:去找其父节点是否为段落,是段落则存起来,后面好居中
|
||||||
copied_bytes_io = BytesIO(img_part['img'].image.blob)
|
image_run_parent_paragraph = image_run_node.getparent()
|
||||||
r_element = Run(image_run_node, doc)
|
father_paragraph = None
|
||||||
inline_shape = r_element.add_picture(copied_bytes_io)
|
if isinstance(image_run_parent_paragraph, CT_P):
|
||||||
## 2.1.统一:这里设置文档片段里面的图片大小和位置
|
father_paragraph = Paragraph(image_run_parent_paragraph, doc)
|
||||||
source_width = inline_shape.width
|
copied_bytes_io = BytesIO(img_part['img'].image.blob)
|
||||||
source_height = inline_shape.height
|
r_element = Run(image_run_node, doc)
|
||||||
if source_width >= source_height:
|
inline_shape = r_element.add_picture(copied_bytes_io)
|
||||||
inline_shape.width = Mm(120)
|
## 2.1.统一:这里设置文档片段里面的图片大小和位置
|
||||||
inline_shape.height = int(inline_shape.height * (inline_shape.width / source_width))
|
source_width = inline_shape.width
|
||||||
else:
|
source_height = inline_shape.height
|
||||||
inline_shape.height = Mm(60)
|
if source_width >= source_height:
|
||||||
inline_shape.width = int(inline_shape.width * (inline_shape.height / source_height))
|
inline_shape.width = Mm(120)
|
||||||
## 2.2.设置图片所在段落居中对齐
|
inline_shape.height = int(inline_shape.height * (inline_shape.width / source_width))
|
||||||
if father_paragraph:
|
else:
|
||||||
father_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
inline_shape.height = Mm(60)
|
||||||
r_element.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
inline_shape.width = int(inline_shape.width * (inline_shape.height / source_height))
|
||||||
# 3.因为按顺序的,所以移除image_part_list中已经替换的图片
|
## 2.2.设置图片所在段落居中对齐
|
||||||
image_part_list.remove(img_part)
|
if father_paragraph:
|
||||||
break
|
father_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
||||||
try:
|
r_element.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
||||||
# 这里直接生成产品文档
|
# 3.因为按顺序的,所以移除image_part_list中已经替换的图片
|
||||||
doc.save(str(to_tpl_file))
|
image_part_list.remove(img_part)
|
||||||
return to_tpl_file, seitai_final_file
|
break
|
||||||
except PermissionError as e:
|
try:
|
||||||
return {'code': 'error', 'msg': '生成的temp文件已打开,请关闭后重试...'}
|
# 这里直接生成产品文档
|
||||||
|
doc.save(str(to_tpl_file))
|
||||||
def get_frag_from_document(doc_path: Path) -> List[Dict]:
|
return to_tpl_file, seitai_final_file
|
||||||
"""传入products的文件路径,识别出所有文档片段名称,数组返回:要求docx里面文档名称不能更变"""
|
except PermissionError as e:
|
||||||
doc = Document(doc_path.as_posix())
|
return {'code': 'error', 'msg': '生成的temp文件已打开,请关闭后重试...'}
|
||||||
sdt_element_list = doc.element.body.xpath('./w:sdt')
|
|
||||||
# 整个for循环识别文档片段名称
|
def get_frag_from_document(doc_path: Path) -> List[Dict]:
|
||||||
area_name_list = []
|
"""传入products的文件路径,识别出所有文档片段名称,数组返回:要求docx里面文档名称不能更变"""
|
||||||
for sdt_ele in sdt_element_list:
|
doc = Document(doc_path.as_posix())
|
||||||
isLock = False
|
sdt_element_list = doc.element.body.xpath('./w:sdt')
|
||||||
alias_value = None
|
# 整个for循环识别文档片段名称
|
||||||
for elem in sdt_ele.iterchildren():
|
area_name_list = []
|
||||||
if elem.tag.endswith('sdtPr'):
|
for sdt_ele in sdt_element_list:
|
||||||
for el in elem.getchildren():
|
isLock = False
|
||||||
if el.tag.endswith('alias'):
|
alias_value = None
|
||||||
alias_value = el.attrib.values()
|
for elem in sdt_ele.iterchildren():
|
||||||
# 查找是否被用户在模版上标记了Lock
|
if elem.tag.endswith('sdtPr'):
|
||||||
if el.tag.endswith('lock'):
|
for el in elem.getchildren():
|
||||||
isLock = True
|
if el.tag.endswith('alias'):
|
||||||
if alias_value and len(alias_value):
|
alias_value = el.attrib.values()
|
||||||
area_name_list.append({'frag_name': alias_value[0], 'isLock': isLock})
|
# 查找是否被用户在模版上标记了Lock
|
||||||
return area_name_list
|
if el.tag.endswith('lock'):
|
||||||
|
isLock = True
|
||||||
# 辅助函数-传入temp文件路径(已替换文档片段的temp文档),输出stdContent
|
if alias_value and len(alias_value):
|
||||||
def get_jinja_stdContent_element(temp_docx_path: Path):
|
area_name_list.append({'frag_name': alias_value[0], 'isLock': isLock})
|
||||||
doc_docx = Document(temp_docx_path.as_posix())
|
return area_name_list
|
||||||
body = doc_docx.element.body
|
|
||||||
# 储存文本片段
|
# 辅助函数-传入temp文件路径(已替换文档片段的temp文档),输出stdContent
|
||||||
text_frag_name_list = []
|
def get_jinja_stdContent_element(temp_docx_path: Path):
|
||||||
sdt_element_list = body.xpath('//w:sdt')
|
doc_docx = Document(temp_docx_path.as_posix())
|
||||||
|
body = doc_docx.element.body
|
||||||
# 注意python-docx的页头的文本片段不在body里面,而在section.header里面
|
# 储存文本片段
|
||||||
# 所以定义辅助函数,统一处理
|
text_frag_name_list = []
|
||||||
def deel_sdt_content(*args):
|
sdt_element_list = body.xpath('//w:sdt')
|
||||||
"""传入sdt_element列表,将其sdtContent加入外部的文本片段列表"""
|
|
||||||
for sdt_ele in args:
|
# 注意python-docx的页头的文本片段不在body里面,而在section.header里面
|
||||||
# 找出每个sdt下面的3个标签
|
# 所以定义辅助函数,统一处理
|
||||||
tag_value = None
|
def deel_sdt_content(*args):
|
||||||
alias_value = None
|
"""传入sdt_element列表,将其sdtContent加入外部的文本片段列表"""
|
||||||
sdtContent_ele = None
|
for sdt_ele in args:
|
||||||
for sdt_ele_child in sdt_ele.iterchildren():
|
# 找出每个sdt下面的3个标签
|
||||||
if sdt_ele_child.tag.endswith('sdtPr'):
|
tag_value = None
|
||||||
for sdtPr_ele_child in sdt_ele_child.getchildren():
|
alias_value = None
|
||||||
if sdtPr_ele_child.tag.endswith('tag'):
|
sdtContent_ele = None
|
||||||
if len(sdtPr_ele_child.attrib.values()) > 0:
|
for sdt_ele_child in sdt_ele.iterchildren():
|
||||||
tag_value = sdtPr_ele_child.attrib.values()[0]
|
if sdt_ele_child.tag.endswith('sdtPr'):
|
||||||
if sdtPr_ele_child.tag.endswith('alias'):
|
for sdtPr_ele_child in sdt_ele_child.getchildren():
|
||||||
if len(sdtPr_ele_child.attrib.values()) > 0:
|
if sdtPr_ele_child.tag.endswith('tag'):
|
||||||
alias_value = sdtPr_ele_child.attrib.values()[0]
|
if len(sdtPr_ele_child.attrib.values()) > 0:
|
||||||
if sdt_ele_child.tag.endswith('sdtContent'):
|
tag_value = sdtPr_ele_child.attrib.values()[0]
|
||||||
sdtContent_ele = sdt_ele_child
|
if sdtPr_ele_child.tag.endswith('alias'):
|
||||||
# 找出所有tag_value为jinja的文本片段
|
if len(sdtPr_ele_child.attrib.values()) > 0:
|
||||||
if tag_value == 'jinja' and alias_value is not None and sdtContent_ele is not None:
|
alias_value = sdtPr_ele_child.attrib.values()[0]
|
||||||
text_frag_name_list.append({'alias': alias_value, 'sdtContent': sdtContent_ele})
|
if sdt_ele_child.tag.endswith('sdtContent'):
|
||||||
|
sdtContent_ele = sdt_ele_child
|
||||||
deel_sdt_content(*sdt_element_list)
|
# 找出所有tag_value为jinja的文本片段
|
||||||
for section in doc_docx.sections:
|
if tag_value == 'jinja' and alias_value is not None and sdtContent_ele is not None:
|
||||||
header = section.header
|
text_frag_name_list.append({'alias': alias_value, 'sdtContent': sdtContent_ele})
|
||||||
header_sdt_list = header.part.element.xpath('//w:sdt')
|
|
||||||
deel_sdt_content(*header_sdt_list)
|
deel_sdt_content(*sdt_element_list)
|
||||||
|
for section in doc_docx.sections:
|
||||||
return text_frag_name_list, doc_docx
|
header = section.header
|
||||||
|
header_sdt_list = header.part.element.xpath('//w:sdt')
|
||||||
# 封装一个根据alias名称修改stdContent的函数 -> 在接口处理函数中取数据放入函数修改文档
|
deel_sdt_content(*header_sdt_list)
|
||||||
def stdContent_modify(modify_str: str | bool, doc_docx: Document, sdtContent: _Element):
|
|
||||||
# 正常处理
|
return text_frag_name_list, doc_docx
|
||||||
for ele in sdtContent:
|
|
||||||
if isinstance(ele, CT_R):
|
# 封装一个根据alias名称修改stdContent的函数 -> 在接口处理函数中取数据放入函数修改文档
|
||||||
run_ele = Run(ele, doc_docx)
|
def stdContent_modify(modify_str: str | bool, doc_docx: Document, sdtContent: _Element):
|
||||||
if isinstance(modify_str, bool):
|
# 正常处理
|
||||||
# 如果是True,则不修改原来
|
for ele in sdtContent:
|
||||||
if modify_str:
|
if isinstance(ele, CT_R):
|
||||||
break
|
run_ele = Run(ele, doc_docx)
|
||||||
else:
|
if isinstance(modify_str, bool):
|
||||||
modify_str = ""
|
# 如果是True,则不修改原来
|
||||||
# 有时候会int类型,转换一下防止报错
|
if modify_str:
|
||||||
if isinstance(modify_str, int):
|
break
|
||||||
modify_str = str(modify_str)
|
else:
|
||||||
run_ele.text = modify_str
|
modify_str = ""
|
||||||
sdtContent.clear()
|
# 有时候会int类型,转换一下防止报错
|
||||||
sdtContent.append(run_ele._element)
|
if isinstance(modify_str, int):
|
||||||
break
|
modify_str = str(modify_str)
|
||||||
|
run_ele.text = modify_str
|
||||||
if isinstance(ele, CT_P):
|
sdtContent.clear()
|
||||||
para_ele = Paragraph(ele, doc_docx)
|
sdtContent.append(run_ele._element)
|
||||||
if isinstance(modify_str, bool):
|
break
|
||||||
if modify_str:
|
|
||||||
break
|
if isinstance(ele, CT_P):
|
||||||
else:
|
para_ele = Paragraph(ele, doc_docx)
|
||||||
modify_str = ""
|
if isinstance(modify_str, bool):
|
||||||
para_ele.clear()
|
if modify_str:
|
||||||
para_ele.text = modify_str
|
break
|
||||||
sdtContent.clear()
|
else:
|
||||||
sdtContent.append(para_ele._element)
|
modify_str = ""
|
||||||
break
|
para_ele.clear()
|
||||||
|
para_ele.text = modify_str
|
||||||
|
sdtContent.clear()
|
||||||
|
sdtContent.append(para_ele._element)
|
||||||
|
break
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -257,7 +257,7 @@ class CaseController(ControllerBase):
|
|||||||
single_qs.key = case_key
|
single_qs.key = case_key
|
||||||
index = index + 1
|
index = index + 1
|
||||||
single_qs.save()
|
single_qs.save()
|
||||||
return ChenResponse(message="测试用例删除成功!")
|
return ChenResponse(message="测试用例删除成功!影响域分析中如果有该关联用例则被删除。")
|
||||||
|
|
||||||
# 右键测试项,根据测试子项生成用例
|
# 右键测试项,根据测试子项生成用例
|
||||||
@route.post("/case/create_by_demand", url_name='case-create-by-demand')
|
@route.post("/case/create_by_demand", url_name='case-create-by-demand')
|
||||||
@@ -396,3 +396,21 @@ class CaseController(ControllerBase):
|
|||||||
Case.objects.bulk_update(updated_cases, ['exe_time'])
|
Case.objects.bulk_update(updated_cases, ['exe_time'])
|
||||||
return ChenResponse(status=200, code=200, data=len(updated_cases),
|
return ChenResponse(status=200, code=200, data=len(updated_cases),
|
||||||
message=f"成功更新{len(updated_cases)}个用例执行时间")
|
message=f"成功更新{len(updated_cases)}个用例执行时间")
|
||||||
|
|
||||||
|
# 给级联选择器数据 -> 上一轮次所有用例
|
||||||
|
@route.get("/case/getRelatedCase", url_name='case-related-case')
|
||||||
|
def get_cases_related_case(self, id: int, round_key: str):
|
||||||
|
project_obj = get_object_or_404(Project, id=id)
|
||||||
|
previous_round_obj = project_obj.pField.filter(key=int(round_key) - 1).first()
|
||||||
|
# dut -> design
|
||||||
|
data_list = []
|
||||||
|
for dut in previous_round_obj.rdField.all():
|
||||||
|
dut_dict = {'label': dut.name, 'value': dut.id, 'key': dut.key, 'children': []}
|
||||||
|
for design in dut.rsField.all():
|
||||||
|
design_dict = {'label': design.name, 'value': design.id, 'key': design.key, 'children': []}
|
||||||
|
for case in design.dcField.all():
|
||||||
|
case_dict = {'label': case.name, 'value': case.id, 'key': case.key}
|
||||||
|
design_dict['children'].append(case_dict)
|
||||||
|
dut_dict['children'].append(design_dict)
|
||||||
|
data_list.append(dut_dict)
|
||||||
|
return ChenResponse(message='获取成功', data=data_list)
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ from ninja_jwt.authentication import JWTAuth
|
|||||||
from apps.user.models import Users
|
from apps.user.models import Users
|
||||||
from utils.chen_pagination import MyPagination
|
from utils.chen_pagination import MyPagination
|
||||||
from ninja.pagination import paginate
|
from ninja.pagination import paginate
|
||||||
|
from ninja.errors import HttpError
|
||||||
from ninja import Query
|
from ninja import Query
|
||||||
from utils.chen_response import ChenResponse
|
from utils.chen_response import ChenResponse
|
||||||
from utils.chen_crud import create, multi_delete_project
|
from utils.chen_crud import create, multi_delete_project
|
||||||
from apps.project.models import Project, Round
|
from apps.project.models import Project, Round, ProjectSoftSummary, StuctSortData, StaticSoftItem, StaticSoftHardware, DynamicSoftTable, \
|
||||||
from apps.project.schemas.project import ProjectRetrieveSchema, ProjectFilterSchema, ProjectCreateInput, DeleteSchema
|
DynamicHardwareTable, ProjectDynamicDescription, EvaluateData, EnvAnalysis
|
||||||
|
from apps.project.schemas.project import ProjectRetrieveSchema, ProjectFilterSchema, ProjectCreateInput, \
|
||||||
|
DeleteSchema, SoftSummarySchema, DataSchema, StaticDynamicData, EnvAnalysisSchema
|
||||||
from utils.util import get_str_dict
|
from utils.util import get_str_dict
|
||||||
# 时间处理模块
|
# 时间处理模块
|
||||||
from apps.project.tool.timeList import time_return_to
|
from apps.project.tool.timeList import time_return_to
|
||||||
@@ -89,12 +92,14 @@ class ProjectController(ControllerBase):
|
|||||||
try:
|
try:
|
||||||
copytree(src_dir, dist_dir) # shutil模块直接是复制并命名,如果命名文件存在则抛出FileExists异常
|
copytree(src_dir, dist_dir) # shutil模块直接是复制并命名,如果命名文件存在则抛出FileExists异常
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
return ChenResponse(code=500, status=500, message="错误,检查是否打开了服务器的conf中的文档,关闭后重试")
|
return ChenResponse(code=500, status=500,
|
||||||
|
message="错误,检查是否打开了服务器的conf中的文档,关闭后重试")
|
||||||
except FileExistsError:
|
except FileExistsError:
|
||||||
return ChenResponse(code=500, status=500, message='文件标识已存在或输入为空格,请修改')
|
return ChenResponse(code=500, status=500, message='文件标识已存在或输入为空格,请修改')
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return ChenResponse(code=500, status=500, message='文件不存在,请检查')
|
return ChenResponse(code=500, status=500, message='文件不存在,请检查')
|
||||||
return ChenResponse(code=200, status=200, message="添加项目成功,并添加第一轮测试")
|
return ChenResponse(code=200, status=200, message="添加项目成功,并添加第一轮测试")
|
||||||
|
return ChenResponse(code=400, status=400, message="未添加任何项目")
|
||||||
|
|
||||||
@route.put("/update/{project_id}")
|
@route.put("/update/{project_id}")
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@@ -135,7 +140,7 @@ class ProjectController(ControllerBase):
|
|||||||
project_media_path = media_path / ident
|
project_media_path = media_path / ident
|
||||||
try:
|
try:
|
||||||
rmtree(project_media_path)
|
rmtree(project_media_path)
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError:
|
||||||
return ChenResponse(status=400, code=400, message='项目模版目录可能不存在,可能之前已删除')
|
return ChenResponse(status=400, code=400, message='项目模版目录可能不存在,可能之前已删除')
|
||||||
return ChenResponse(message="删除成功!")
|
return ChenResponse(message="删除成功!")
|
||||||
|
|
||||||
@@ -183,8 +188,8 @@ class ProjectController(ControllerBase):
|
|||||||
# 7.将时间提取 todo:后续将计算的事件放入该页面
|
# 7.将时间提取 todo:后续将计算的事件放入该页面
|
||||||
timers = {'round_time': []}
|
timers = {'round_time': []}
|
||||||
rounds = project_obj.pField.all()
|
rounds = project_obj.pField.all()
|
||||||
timers['start_time'] = project_obj.beginTime
|
timers['start_time'] = project_obj.beginTime # type:ignore
|
||||||
timers['end_time'] = project_obj.endTime
|
timers['end_time'] = project_obj.endTime # type:ignore
|
||||||
for round in rounds:
|
for round in rounds:
|
||||||
round_number = int(round.key) + 1
|
round_number = int(round.key) + 1
|
||||||
timers['round_time'].append({
|
timers['round_time'].append({
|
||||||
@@ -197,7 +202,8 @@ class ProjectController(ControllerBase):
|
|||||||
# 9.提取测试类型下面测试项数量、用例数量
|
# 9.提取测试类型下面测试项数量、用例数量
|
||||||
data_list = []
|
data_list = []
|
||||||
for round in rounds:
|
for round in rounds:
|
||||||
round_dict = {'name': f'第{int(round.key) + 1}轮次', 'desings': [], 'method_demand': {}, 'method_case': {}}
|
round_dict = {'name': f'第{int(round.key) + 1}轮次', 'desings': [], 'method_demand': {},
|
||||||
|
'method_case': {}}
|
||||||
designs = round.dsField.all()
|
designs = round.dsField.all()
|
||||||
for design in designs:
|
for design in designs:
|
||||||
design_dict = {
|
design_dict = {
|
||||||
@@ -264,3 +270,228 @@ class ProjectController(ControllerBase):
|
|||||||
def document_time_show(self, id: int):
|
def document_time_show(self, id: int):
|
||||||
time = time_return_to(id)
|
time = time_return_to(id)
|
||||||
return time
|
return time
|
||||||
|
|
||||||
|
# [变] 项目级信息前端告警数据获取
|
||||||
|
@route.get("/project_info_status/")
|
||||||
|
@transaction.atomic
|
||||||
|
def project_info_status(self, id: int):
|
||||||
|
project_obj = self.get_project_by_id(id)
|
||||||
|
|
||||||
|
# 统一配置每个状态的检查逻辑
|
||||||
|
status_configs = {
|
||||||
|
"soft_summary": {
|
||||||
|
"model": ProjectSoftSummary,
|
||||||
|
"check": lambda qs: qs.exists() and qs.first().data_schemas.exists()
|
||||||
|
},
|
||||||
|
"interface_image": {
|
||||||
|
"model": StuctSortData,
|
||||||
|
"check": lambda qs: qs.exists()
|
||||||
|
},
|
||||||
|
"static_soft_item": {
|
||||||
|
"model": StaticSoftItem,
|
||||||
|
"check": lambda qs: qs.exists()
|
||||||
|
},
|
||||||
|
"static_soft_hardware": {
|
||||||
|
"model": StaticSoftHardware,
|
||||||
|
"check": lambda qs: qs.exists()
|
||||||
|
},
|
||||||
|
"dynamic_soft_item": {
|
||||||
|
"model": DynamicSoftTable,
|
||||||
|
"check": lambda qs: qs.exists()
|
||||||
|
},
|
||||||
|
"dynamic_soft_hardware": {
|
||||||
|
"model": DynamicHardwareTable,
|
||||||
|
"check": lambda qs: qs.exists()
|
||||||
|
},
|
||||||
|
"dynamic_des": {
|
||||||
|
"model": ProjectDynamicDescription,
|
||||||
|
"check": lambda qs: qs.exists() and qs.first().data_schemas.exists()
|
||||||
|
},
|
||||||
|
"evaluate_data": {
|
||||||
|
"model": EvaluateData,
|
||||||
|
"check": lambda qs: qs.exists()
|
||||||
|
},
|
||||||
|
"env_analysis": {
|
||||||
|
"model": EnvAnalysis,
|
||||||
|
"check": lambda qs: qs.exists()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
all_status = {}
|
||||||
|
for status_key, config in status_configs.items():
|
||||||
|
qs = config["model"].objects.filter(project=project_obj)
|
||||||
|
all_status[status_key] = config["check"](qs)
|
||||||
|
return ChenResponse(status=200, code=20000, data=all_status, message='查询成功')
|
||||||
|
|
||||||
|
# [变] 封装结构化数据新增-修改(针对project - OneToOne - DataSchemas形式)
|
||||||
|
@classmethod
|
||||||
|
def bulk_create_data_schemas(cls, parent_obj, datas: list[DataSchema]):
|
||||||
|
"""
|
||||||
|
批量创建结构化排序数据 (自动类型推断)
|
||||||
|
Args:
|
||||||
|
parent_obj: 父级对象,可以是 ProjectSoftSummary 或 Project 的实例
|
||||||
|
datas (list[DataSchema]): 数据模式对象列表
|
||||||
|
"""
|
||||||
|
# 动态确定所属父model
|
||||||
|
field_name = None # type:ignore
|
||||||
|
if isinstance(parent_obj, ProjectSoftSummary):
|
||||||
|
field_name = 'soft_summary'
|
||||||
|
elif isinstance(parent_obj, Project):
|
||||||
|
field_name = 'project'
|
||||||
|
elif isinstance(parent_obj, ProjectDynamicDescription):
|
||||||
|
field_name = 'dynamic_description'
|
||||||
|
else:
|
||||||
|
raise HttpError(400, "添加的数据未在系统内,请联系管理员")
|
||||||
|
|
||||||
|
data_list = []
|
||||||
|
for data in datas:
|
||||||
|
new_data = StuctSortData(
|
||||||
|
type=data.type,
|
||||||
|
fontnote=data.fontnote,
|
||||||
|
content=data.content,
|
||||||
|
)
|
||||||
|
setattr(new_data, field_name, parent_obj)
|
||||||
|
data_list.append(new_data)
|
||||||
|
StuctSortData.objects.bulk_create(data_list)
|
||||||
|
|
||||||
|
# 封装只有model不同 -修改和新增dataSchemas(针对project - OneToOne - DataSchemas形式)
|
||||||
|
@classmethod
|
||||||
|
def create_or_modify_data_schemas(cls, id: int, model, data):
|
||||||
|
project_obj = get_object_or_404(Project, pk=id)
|
||||||
|
qs = model.objects.filter(project=project_obj)
|
||||||
|
if qs.exists():
|
||||||
|
obj = qs.first()
|
||||||
|
# 如果存在则修改:先删除再创建
|
||||||
|
obj.data_schemas.all().delete()
|
||||||
|
cls.bulk_create_data_schemas(obj, data)
|
||||||
|
else:
|
||||||
|
parent_obj = model.objects.create(project=project_obj)
|
||||||
|
cls.bulk_create_data_schemas(parent_obj, data)
|
||||||
|
|
||||||
|
# ~~~软件概述-新增和修改~~~
|
||||||
|
@route.post('/soft_summary/')
|
||||||
|
@transaction.atomic
|
||||||
|
def soft_summary(self, payload: SoftSummarySchema):
|
||||||
|
self.create_or_modify_data_schemas(payload.id, ProjectSoftSummary, payload.data)
|
||||||
|
|
||||||
|
# ~~~动态环境描述-新增和修改~~~
|
||||||
|
@route.post('/dynamic_description/')
|
||||||
|
@transaction.atomic
|
||||||
|
def dynamic_description(self, payload: SoftSummarySchema):
|
||||||
|
self.create_or_modify_data_schemas(payload.id, ProjectDynamicDescription, payload.data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_res_from_info(cls, project_obj: Project, model) -> list[dict] | None:
|
||||||
|
"""model: 当前一对一模型,直接获取结构化数据信息数组返回"""
|
||||||
|
qs = model.objects.filter(project=project_obj)
|
||||||
|
if qs.exists():
|
||||||
|
obj = qs.first()
|
||||||
|
ds_qs = obj.data_schemas.all()
|
||||||
|
data_list = [{
|
||||||
|
"type": item.type,
|
||||||
|
"content": item.content,
|
||||||
|
"fontnote": item.fontnote,
|
||||||
|
} for item in ds_qs]
|
||||||
|
return data_list
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ~~~软件概述-获取到前端展示~~~
|
||||||
|
@route.get("/get_soft_summary/", response=list[DataSchema])
|
||||||
|
@transaction.atomic
|
||||||
|
def get_soft_summary(self, id: int):
|
||||||
|
project_obj = self.get_project_by_id(id)
|
||||||
|
data_list = self.get_res_from_info(project_obj, ProjectSoftSummary)
|
||||||
|
if data_list:
|
||||||
|
return ChenResponse(status=200, code=20000, data=data_list)
|
||||||
|
return ChenResponse(status=200, code=20000, data=[])
|
||||||
|
|
||||||
|
# ~~~动态环境描述 - 获取展示~~~
|
||||||
|
@route.get("/dynamic_des/", response=list[DataSchema])
|
||||||
|
@transaction.atomic
|
||||||
|
def get_dynamic_des(self, id: int):
|
||||||
|
project_obj = self.get_project_by_id(id)
|
||||||
|
data_list = self.get_res_from_info(project_obj, ProjectDynamicDescription)
|
||||||
|
if data_list:
|
||||||
|
return ChenResponse(status=200, code=20000, data=data_list)
|
||||||
|
return ChenResponse(status=200, code=20000, data=[])
|
||||||
|
|
||||||
|
# ~~~接口图新增或修改~~~
|
||||||
|
@route.post("/interface_image/")
|
||||||
|
@transaction.atomic
|
||||||
|
def post_interface_image(self, id: int, dataSchema: DataSchema):
|
||||||
|
project_obj = self.get_project_by_id(id)
|
||||||
|
image_qs = StuctSortData.objects.filter(project=project_obj)
|
||||||
|
if image_qs.exists():
|
||||||
|
image_qs.delete()
|
||||||
|
self.bulk_create_data_schemas(project_obj, [dataSchema])
|
||||||
|
|
||||||
|
# ~~~接口图-获取数据~~~
|
||||||
|
@route.get("/get_interface_image/", response=DataSchema)
|
||||||
|
@transaction.atomic
|
||||||
|
def get_interface_image(self, id: int):
|
||||||
|
project_obj = self.get_project_by_id(id)
|
||||||
|
image_qs = StuctSortData.objects.filter(project=project_obj)
|
||||||
|
if image_qs.exists():
|
||||||
|
# 如果存在则返回数据
|
||||||
|
image_obj = image_qs.first()
|
||||||
|
return ChenResponse(status=200, code=25001, data={
|
||||||
|
"type": image_obj.type,
|
||||||
|
"content": image_obj.content,
|
||||||
|
"fontnote": image_obj.fontnote,
|
||||||
|
})
|
||||||
|
return ChenResponse(status=200, code=25002, data=None)
|
||||||
|
|
||||||
|
# 动态返回是哪个模型
|
||||||
|
@classmethod
|
||||||
|
def get_model_from_category(cls, category: str):
|
||||||
|
mapDict = {
|
||||||
|
'静态软件项': StaticSoftItem,
|
||||||
|
'静态硬件项': StaticSoftHardware,
|
||||||
|
'动态软件项': DynamicSoftTable,
|
||||||
|
'动态硬件项': DynamicHardwareTable,
|
||||||
|
'测评数据': EvaluateData
|
||||||
|
}
|
||||||
|
return mapDict[category]
|
||||||
|
|
||||||
|
# ~~~静态软件项、静态硬件项、动态软件项、动态硬件项 - 获取~~~
|
||||||
|
@route.get("/get_static_dynamic_items/")
|
||||||
|
def get_static_dynamic_items(self, id: int, category: str):
|
||||||
|
project_obj = self.get_project_by_id(id)
|
||||||
|
item_qs = self.get_model_from_category(category).objects.filter(project=project_obj)
|
||||||
|
if item_qs.exists():
|
||||||
|
item_obj = item_qs.first()
|
||||||
|
return ChenResponse(status=200, code=25001, data={"table": item_obj.table, "fontnote": item_obj.fontnote})
|
||||||
|
return ChenResponse(status=200, code=25002, data=None)
|
||||||
|
|
||||||
|
# ~~~静态软件项、静态硬件项、动态软件项、动态硬件项 - 新增或修改~~~
|
||||||
|
@route.post("/post_static_dynamic_item/")
|
||||||
|
@transaction.atomic
|
||||||
|
def post_static_dynamic_item(self, data: StaticDynamicData):
|
||||||
|
project_obj = self.get_project_by_id(data.id)
|
||||||
|
model = self.get_model_from_category(data.category)
|
||||||
|
item_qs = model.objects.filter(project=project_obj)
|
||||||
|
if item_qs.exists():
|
||||||
|
# 如果存在则修改
|
||||||
|
item_qs.delete()
|
||||||
|
model.objects.create(project=project_obj, table=data.table, fontnote=data.fontnote)
|
||||||
|
|
||||||
|
# ~~~环境差异性分析 - 获取~~~
|
||||||
|
@route.get("/get_env_analysis/")
|
||||||
|
@transaction.atomic
|
||||||
|
def get_env_analysis(self, id: int):
|
||||||
|
project_obj = self.get_project_by_id(id)
|
||||||
|
qs = EnvAnalysis.objects.filter(project=project_obj)
|
||||||
|
if qs.exists():
|
||||||
|
obj = qs.first()
|
||||||
|
return ChenResponse(status=200, code=25001, data={"table": obj.table, "fontnote": obj.fontnote, "description": obj.description})
|
||||||
|
return ChenResponse(status=200, code=25002, data=None)
|
||||||
|
|
||||||
|
# ~~~环境差异性分析 - 新增和修改~~~
|
||||||
|
@route.post("/post_env_analysis/")
|
||||||
|
@transaction.atomic
|
||||||
|
def post_env_analysis(self, data: EnvAnalysisSchema):
|
||||||
|
project_obj = self.get_project_by_id(data.id)
|
||||||
|
qs = EnvAnalysis.objects.filter(project=project_obj)
|
||||||
|
if qs.exists():
|
||||||
|
qs.delete()
|
||||||
|
EnvAnalysis.objects.create(project=project_obj, table=data.table, fontnote=data.fontnote, description=data.description)
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ from ninja_extra import api_controller, ControllerBase, route
|
|||||||
from ninja_jwt.authentication import JWTAuth
|
from ninja_jwt.authentication import JWTAuth
|
||||||
from ninja_extra.permissions import IsAuthenticated
|
from ninja_extra.permissions import IsAuthenticated
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from apps.project.models import Round
|
from apps.project.models import Round, InfluenceArea, InfluenceItem
|
||||||
from apps.project.schemas.round import TreeReturnRound, RoundInfoOutSchema, EditSchemaIn, DeleteSchema, \
|
from apps.project.schemas.round import TreeReturnRound, RoundInfoOutSchema, EditSchemaIn, DeleteSchema, \
|
||||||
CreateRoundOutSchema, CreateRoundInputSchema
|
CreateRoundOutSchema, CreateRoundInputSchema, InfluenceItemOutSchema, InfluenceInputSchema
|
||||||
from typing import List
|
from typing import List
|
||||||
from utils.chen_response import ChenResponse
|
from utils.chen_response import ChenResponse
|
||||||
from apps.project.tools.delete_change_key import round_delete_sub_node_key
|
from apps.project.tools.delete_change_key import round_delete_sub_node_key
|
||||||
@@ -81,3 +81,62 @@ class RoundController(ControllerBase):
|
|||||||
return ChenResponse(code=400, status=400, message='标识和其他重复')
|
return ChenResponse(code=400, status=400, message='标识和其他重复')
|
||||||
Round.objects.create(**asert_dict)
|
Round.objects.create(**asert_dict)
|
||||||
return ChenResponse(message="新增轮次成功")
|
return ChenResponse(message="新增轮次成功")
|
||||||
|
|
||||||
|
# ~~~影响域分析 - 获取数据和状态~~~
|
||||||
|
@route.get("/round/get_influence", response=List[InfluenceItemOutSchema], url_name="round-get-influence-items")
|
||||||
|
@transaction.atomic
|
||||||
|
def get_influence(self, id: int, round_key: str):
|
||||||
|
round_qs = Round.objects.filter(project__id=id, key=round_key)
|
||||||
|
round_obj = round_qs.first()
|
||||||
|
influence_qs = InfluenceArea.objects.filter(round=round_obj)
|
||||||
|
if influence_qs.exists():
|
||||||
|
influence = influence_qs.first()
|
||||||
|
items_qs = influence.influence_items.all()
|
||||||
|
if items_qs.exists():
|
||||||
|
return items_qs
|
||||||
|
return ChenResponse(status=200, code=25002, data=[])
|
||||||
|
|
||||||
|
# ~~~影响域分析是否有值~~~
|
||||||
|
@route.get("/round/get_status_influence", url_name="round-get-status-influence")
|
||||||
|
@transaction.atomic
|
||||||
|
def get_status_influence(self, id: int, round_key: str):
|
||||||
|
round_qs = Round.objects.filter(project__id=id, key=round_key)
|
||||||
|
round_obj = round_qs.first()
|
||||||
|
influence_qs = InfluenceArea.objects.filter(round=round_obj)
|
||||||
|
if influence_qs.exists():
|
||||||
|
influence = influence_qs.first()
|
||||||
|
items_qs = influence.influence_items.all()
|
||||||
|
if items_qs.exists():
|
||||||
|
return ChenResponse(status=200, code=25005, data=True)
|
||||||
|
return ChenResponse(status=200, code=25006, data=False)
|
||||||
|
|
||||||
|
# ~~~影响域分析 - 修改或新增~~~
|
||||||
|
@route.post("/round/create_influence", url_name="round-influence-create")
|
||||||
|
@transaction.atomic
|
||||||
|
def post_influence(self, data: InfluenceInputSchema):
|
||||||
|
round_obj = Round.objects.filter(project_id=data.id, key=data.round_key).first()
|
||||||
|
influence_area_qs = InfluenceArea.objects.filter(round=round_obj)
|
||||||
|
if influence_area_qs.exists():
|
||||||
|
influence_area_obj = influence_area_qs.first()
|
||||||
|
influence_area_obj.influence_items.all().delete()
|
||||||
|
# 先删除再创建
|
||||||
|
data_list = []
|
||||||
|
for item in data.item_list:
|
||||||
|
new_item = InfluenceItem(influence=influence_area_obj,
|
||||||
|
change_type=item.change_type,
|
||||||
|
change_influ=item.change_influ,
|
||||||
|
change_des=item.change_des,
|
||||||
|
effect_cases=item.effect_cases)
|
||||||
|
data_list.append(new_item)
|
||||||
|
InfluenceItem.objects.bulk_create(data_list)
|
||||||
|
else:
|
||||||
|
parent_obj = InfluenceArea.objects.create(round=round_obj)
|
||||||
|
data_list = []
|
||||||
|
for item in data.item_list:
|
||||||
|
new_item = InfluenceItem(influence=parent_obj,
|
||||||
|
change_type=item.change_type,
|
||||||
|
change_influ=item.change_influ,
|
||||||
|
change_des=item.change_des,
|
||||||
|
effect_cases=item.effect_cases)
|
||||||
|
data_list.append(new_item)
|
||||||
|
InfluenceItem.objects.bulk_create(data_list)
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Generated by Django 6.0.1 on 2026-02-02 15:00
|
||||||
|
|
||||||
|
import apps.project.models
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('project', '0018_testdemandcontent_subdescription'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ProjectSoftSummary',
|
||||||
|
fields=[
|
||||||
|
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='projSoftSummary', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '软件概述表',
|
||||||
|
'verbose_name_plural': '软件概述表',
|
||||||
|
'db_table': 'project_soft_summary',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='StuctSortData',
|
||||||
|
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='显示排序')),
|
||||||
|
('type', models.CharField(choices=[('text', '文本'), ('table', '表格'), ('image', '图片')], default='text', max_length=20, verbose_name='数据类型')),
|
||||||
|
('fontnote', models.CharField(blank=True, default='', help_text='数据的题注说明', max_length=256, verbose_name='题注')),
|
||||||
|
('content', models.JSONField(default=apps.project.models.default_json_value, help_text='存储文本内容或二维表格数据或图片数据', verbose_name='内容')),
|
||||||
|
('soft_summary', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='data_schemas', to='project.projectsoftsummary', verbose_name='所属软件概述')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '结构排序化数据',
|
||||||
|
'verbose_name_plural': '结构排序化数据',
|
||||||
|
'db_table': 'data_schemas',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 6.0.2 on 2026-02-04 10:28
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('project', '0019_projectsoftsummary_stuctsortdata'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='stuctsortdata',
|
||||||
|
name='project',
|
||||||
|
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='data_schemas', to='project.project', verbose_name='该接口图所属的项目'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='stuctsortdata',
|
||||||
|
name='soft_summary',
|
||||||
|
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='data_schemas', to='project.projectsoftsummary', verbose_name='所属软件概述'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
apps/project/migrations/0021_alter_stuctsortdata_project.py
Normal file
19
apps/project/migrations/0021_alter_stuctsortdata_project.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 6.0.2 on 2026-02-04 10:31
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('project', '0020_stuctsortdata_project_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='stuctsortdata',
|
||||||
|
name='project',
|
||||||
|
field=models.OneToOneField(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='data_schemas', to='project.project', verbose_name='该接口图所属的项目'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Generated by Django 6.0.2 on 2026-02-05 09:45
|
||||||
|
|
||||||
|
import apps.project.models
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('project', '0021_alter_stuctsortdata_project'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DynamicHardwareTable',
|
||||||
|
fields=[
|
||||||
|
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='dynamic_hardware', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||||
|
('table', models.JSONField(default=apps.project.models.default_json_value, help_text='储存表格二维数组', verbose_name='储存表格二维数组')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '动态硬件项表',
|
||||||
|
'verbose_name_plural': '动态硬件项表',
|
||||||
|
'db_table': 'project_dynamic_hardware',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DynamicSoftTable',
|
||||||
|
fields=[
|
||||||
|
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='dynamic_soft_item', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||||
|
('table', models.JSONField(default=apps.project.models.default_json_value, help_text='储存表格二维数组', verbose_name='储存表格二维数组')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '动态软件项表',
|
||||||
|
'verbose_name_plural': '动态软件项表',
|
||||||
|
'db_table': 'project_dynamic_soft_item',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='StaticSoftHardware',
|
||||||
|
fields=[
|
||||||
|
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='static_hardware', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||||
|
('table', models.JSONField(default=apps.project.models.default_json_value, help_text='储存表格二维数组', verbose_name='储存表格二维数组')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '静态硬件项表',
|
||||||
|
'verbose_name_plural': '静态硬件项表',
|
||||||
|
'db_table': 'project_static_hardware',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='StaticSoftItem',
|
||||||
|
fields=[
|
||||||
|
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='static_soft_item', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||||
|
('table', models.JSONField(default=apps.project.models.default_json_value, help_text='储存表格二维数组', verbose_name='储存表格二维数组')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '静态软件项表',
|
||||||
|
'verbose_name_plural': '静态软件项表',
|
||||||
|
'db_table': 'project_static_soft_item',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 6.0.2 on 2026-02-05 11:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('project', '0022_dynamichardwaretable_dynamicsofttable_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dynamichardwaretable',
|
||||||
|
name='fontnote',
|
||||||
|
field=models.CharField(default='', help_text='数据的题注说明', max_length=256, null=True, verbose_name='题注'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dynamicsofttable',
|
||||||
|
name='fontnote',
|
||||||
|
field=models.CharField(default='', help_text='数据的题注说明', max_length=256, null=True, verbose_name='题注'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='staticsofthardware',
|
||||||
|
name='fontnote',
|
||||||
|
field=models.CharField(default='', help_text='数据的题注说明', max_length=256, null=True, verbose_name='题注'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='staticsoftitem',
|
||||||
|
name='fontnote',
|
||||||
|
field=models.CharField(default='', help_text='数据的题注说明', max_length=256, null=True, verbose_name='题注'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Generated by Django 6.0.2 on 2026-02-05 17:48
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('project', '0023_dynamichardwaretable_fontnote_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ProjectDynamicDescription',
|
||||||
|
fields=[
|
||||||
|
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='dynamic_des', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '动态环境描述',
|
||||||
|
'verbose_name_plural': '动态环境描述',
|
||||||
|
'db_table': 'project_dynamic_description',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='stuctsortdata',
|
||||||
|
name='dynamic_description',
|
||||||
|
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='data_schemas', to='project.projectdynamicdescription', verbose_name='所属动态环境描述'),
|
||||||
|
),
|
||||||
|
]
|
||||||
28
apps/project/migrations/0025_evaluatedata.py
Normal file
28
apps/project/migrations/0025_evaluatedata.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 6.0.2 on 2026-02-06 10:56
|
||||||
|
|
||||||
|
import apps.project.models
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('project', '0024_projectdynamicdescription_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EvaluateData',
|
||||||
|
fields=[
|
||||||
|
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='evaluate_data', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||||
|
('table', models.JSONField(default=apps.project.models.default_json_value, help_text='储存表格二维数组', verbose_name='储存表格二维数组')),
|
||||||
|
('fontnote', models.CharField(default='', help_text='数据的题注说明', max_length=256, null=True, verbose_name='题注')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '测评数据',
|
||||||
|
'verbose_name_plural': '测评数据',
|
||||||
|
'db_table': 'project_evaluate_data',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
29
apps/project/migrations/0026_envanalysis.py
Normal file
29
apps/project/migrations/0026_envanalysis.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 6.0.2 on 2026-02-06 13:56
|
||||||
|
|
||||||
|
import apps.project.models
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('project', '0025_evaluatedata'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EnvAnalysis',
|
||||||
|
fields=[
|
||||||
|
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='env_analysis', serialize=False, to='project.project', verbose_name='关联项目')),
|
||||||
|
('table', models.JSONField(default=apps.project.models.default_json_value, help_text='储存表格二维数组', verbose_name='储存表格二维数组')),
|
||||||
|
('fontnote', models.CharField(default='', help_text='数据的题注说明', max_length=256, null=True, verbose_name='题注')),
|
||||||
|
('description', models.CharField(default='', max_length=1024, null=True, verbose_name='差异性分析文字')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '环境差异性分析表',
|
||||||
|
'verbose_name_plural': '环境差异性分析表',
|
||||||
|
'db_table': 'project_env_analysis',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
46
apps/project/migrations/0027_influencearea_influenceitem.py
Normal file
46
apps/project/migrations/0027_influencearea_influenceitem.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Generated by Django 6.0.2 on 2026-02-06 16:05
|
||||||
|
|
||||||
|
import apps.project.models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import tinymce.models
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('project', '0026_envanalysis'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='InfluenceArea',
|
||||||
|
fields=[
|
||||||
|
('project', models.OneToOneField(db_constraint=False, help_text='关联项目', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='influence', serialize=False, to='project.round', verbose_name='关联项目')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '影响域分析',
|
||||||
|
'verbose_name_plural': '影响域分析',
|
||||||
|
'db_table': 'round_influence_area',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='InfluenceItem',
|
||||||
|
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='显示排序')),
|
||||||
|
('change_type', models.CharField(default='', help_text='更改类型', max_length=256, null=True, verbose_name='更改类型')),
|
||||||
|
('change_des', tinymce.models.HTMLField(blank=True, null=True, verbose_name='更改内容描述')),
|
||||||
|
('effect_cases', models.JSONField(default=apps.project.models.create_list, verbose_name='影响的用例key数组')),
|
||||||
|
('influence', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='influence_items', to='project.influencearea', verbose_name='所属影响域分析')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '影响域分析 - 行数据',
|
||||||
|
'verbose_name_plural': '影响域分析 - 行数据',
|
||||||
|
'db_table': 'influence_item',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 6.0.2 on 2026-02-07 13:45
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('project', '0027_influencearea_influenceitem'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='influencearea',
|
||||||
|
old_name='project',
|
||||||
|
new_name='round',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/project/migrations/0029_influenceitem_change_influ.py
Normal file
18
apps/project/migrations/0029_influenceitem_change_influ.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 6.0.2 on 2026-02-07 16:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('project', '0028_rename_project_influencearea_round'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='influenceitem',
|
||||||
|
name='change_influ',
|
||||||
|
field=models.TextField(default='', help_text='影响域分析', max_length=2048, null=True, verbose_name='影响域分析'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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="质量监督员")
|
vise_person = models.CharField(max_length=64, verbose_name="质量监督员", help_text="质量监督员")
|
||||||
config_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="安全等级",
|
security_level = models.CharField(max_length=8, blank=True, null=True, verbose_name="安全等级", help_text="安全等级")
|
||||||
help_text="安全等级")
|
test_level = models.JSONField(null=True, blank=True, help_text="测试级别", verbose_name="测试级别", default=create_list)
|
||||||
test_level = models.JSONField(null=True, blank=True, help_text="测试级别", verbose_name="测试级别",
|
plant_type = models.JSONField(null=True, blank=True, help_text="平台类型", verbose_name="平台类型", default=create_list)
|
||||||
default=create_list)
|
report_type = models.CharField(max_length=64, blank=True, null=True, verbose_name="报告类型", help_text="报告类型")
|
||||||
plant_type = models.JSONField(null=True, blank=True, help_text="平台类型", verbose_name="平台类型",
|
language = models.JSONField(null=True, blank=True, help_text="被测语言", verbose_name="被测语言", default=create_list)
|
||||||
default=create_list)
|
standard = 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_unit = models.CharField(max_length=64, verbose_name="委托方单位", help_text="委托方单位")
|
||||||
entrust_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方联系人",
|
entrust_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方联系人", help_text="委托方联系人")
|
||||||
help_text="委托方联系人")
|
entrust_contact_phone = 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="委托方电话",
|
entrust_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="委托方邮箱", help_text="委托方邮箱")
|
||||||
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_unit = models.CharField(max_length=64, verbose_name="开发方单位", help_text="开发方单位")
|
||||||
dev_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方联系人",
|
dev_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方联系人", help_text="研制方联系人")
|
||||||
help_text="研制方联系人")
|
dev_contact_phone = 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="研制方电话",
|
dev_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="研制方邮箱", help_text="研制方邮箱")
|
||||||
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_unit = models.CharField(max_length=64, verbose_name="测试方单位", help_text="测试方单位")
|
||||||
test_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心联系人",
|
test_contact = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心联系人", help_text="测评中心联系人")
|
||||||
help_text="测评中心联系人")
|
test_contact_phone = 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="测评中心电话",
|
test_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心邮箱", help_text="测评中心邮箱")
|
||||||
help_text="测评中心电话")
|
step = models.CharField(max_length=8, blank=True, null=True, verbose_name="项目阶段", help_text="项目阶段")
|
||||||
test_email = models.CharField(max_length=64, blank=True, null=True, verbose_name="测评中心邮箱",
|
abbreviation = models.JSONField(null=True, blank=True, help_text="缩略语", verbose_name="缩略语", default=create_list)
|
||||||
help_text="测评中心邮箱")
|
soft_type = models.SmallIntegerField(verbose_name='软件类型', choices=((1, '新研'), (2, '改造'), (3, '沿用')), default=1)
|
||||||
step = models.CharField(max_length=8, blank=True, null=True, verbose_name="项目阶段",
|
runtime = models.JSONField(null=True, blank=True, help_text="运行环境", verbose_name="运行环境", default=create_list)
|
||||||
help_text="项目阶段")
|
devplant = models.JSONField(null=True, blank=True, help_text="开发环境", verbose_name="开发环境", default=create_list)
|
||||||
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日新增字段:密级
|
# 9月2日新增字段:密级
|
||||||
secret = models.CharField(max_length=30, default='1', verbose_name='密级', help_text='密级')
|
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='接口类型',
|
type = models.CharField(max_length=64, blank=True, null=True, default='', verbose_name='接口类型',
|
||||||
help_text='接口类型')
|
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='接口数据')
|
help_text='接口数据')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -239,23 +218,15 @@ class Design(CoreModel):
|
|||||||
ordering = ('key',)
|
ordering = ('key',)
|
||||||
|
|
||||||
class TestDemand(CoreModel):
|
class TestDemand(CoreModel):
|
||||||
objects = models.Manager()
|
|
||||||
"""测试项"""
|
"""测试项"""
|
||||||
ident = models.CharField(max_length=64, blank=True, null=True, verbose_name="测试需求标识",
|
objects = models.Manager()
|
||||||
help_text="测试需求标识")
|
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="测试需求名称",
|
name = models.CharField(max_length=64, blank=True, null=True, verbose_name="测试需求名称", help_text="测试需求名称")
|
||||||
help_text="测试需求名称")
|
adequacy = models.CharField(max_length=2048, blank=True, null=True, verbose_name="充分条件", help_text="充分条件")
|
||||||
adequacy = models.CharField(max_length=256, blank=True, null=True, verbose_name="充分条件",
|
priority = models.CharField(max_length=8, blank=True, null=True, verbose_name="优先级", help_text="优先级")
|
||||||
help_text="充分条件")
|
testType = models.CharField(max_length=8, null=True, blank=True, help_text="测试类型", verbose_name="测试类型", default="1")
|
||||||
priority = models.CharField(max_length=8, blank=True, null=True, verbose_name="优先级",
|
testMethod = models.JSONField(blank=True, help_text="测试方法", verbose_name="测试方法", default=create_list)
|
||||||
help_text="优先级")
|
title = models.CharField(max_length=64, 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,
|
key = models.CharField(max_length=64, blank=True, null=True,
|
||||||
verbose_name="round-dut-designkey-testdemand",
|
verbose_name="round-dut-designkey-testdemand",
|
||||||
help_text="round-dut-designkey-testdemand")
|
help_text="round-dut-designkey-testdemand")
|
||||||
@@ -466,6 +437,9 @@ class Contact(CoreModel):
|
|||||||
ordering = ('create_datetime',)
|
ordering = ('create_datetime',)
|
||||||
|
|
||||||
# ~~~~~2024年2月27日新增~~~~~
|
# ~~~~~2024年2月27日新增~~~~~
|
||||||
|
def default_json_value():
|
||||||
|
return ""
|
||||||
|
|
||||||
class Abbreviation(models.Model):
|
class Abbreviation(models.Model):
|
||||||
objects = models.Manager()
|
objects = models.Manager()
|
||||||
title = models.CharField(max_length=64, verbose_name="缩略语", help_text="缩略语")
|
title = models.CharField(max_length=64, verbose_name="缩略语", help_text="缩略语")
|
||||||
@@ -478,3 +452,163 @@ class Abbreviation(models.Model):
|
|||||||
db_table = 'project_abbreviation'
|
db_table = 'project_abbreviation'
|
||||||
verbose_name = '缩略语和行业词汇'
|
verbose_name = '缩略语和行业词汇'
|
||||||
verbose_name_plural = '缩略语和行业词汇'
|
verbose_name_plural = '缩略语和行业词汇'
|
||||||
|
|
||||||
|
# 一对一项目model:软件概述
|
||||||
|
class ProjectSoftSummary(models.Model):
|
||||||
|
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False, related_name="projSoftSummary", on_delete=models.CASCADE,
|
||||||
|
verbose_name="关联项目", help_text="关联项目")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'project_soft_summary'
|
||||||
|
verbose_name = "软件概述表"
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
# 一对一项目model:动态测试环境描述
|
||||||
|
class ProjectDynamicDescription(models.Model):
|
||||||
|
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False, related_name="dynamic_des", on_delete=models.CASCADE,
|
||||||
|
verbose_name="关联项目", help_text="关联项目")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'project_dynamic_description'
|
||||||
|
verbose_name = "动态环境描述"
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
# 一对一项目model:静态软件项表
|
||||||
|
class StaticSoftItem(models.Model):
|
||||||
|
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False, related_name="static_soft_item", on_delete=models.CASCADE,
|
||||||
|
verbose_name="关联项目", help_text="关联项目")
|
||||||
|
table = models.JSONField(verbose_name="储存表格二维数组", help_text="储存表格二维数组", default=default_json_value)
|
||||||
|
fontnote = models.CharField(max_length=256, null=True, default="", verbose_name="题注", help_text="数据的题注说明")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'project_static_soft_item'
|
||||||
|
verbose_name = "静态软件项表"
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
# 一对一项目model:静态硬件项表
|
||||||
|
class StaticSoftHardware(models.Model):
|
||||||
|
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False, related_name="static_hardware", on_delete=models.CASCADE,
|
||||||
|
verbose_name="关联项目", help_text="关联项目")
|
||||||
|
table = models.JSONField(verbose_name="储存表格二维数组", help_text="储存表格二维数组", default=default_json_value)
|
||||||
|
fontnote = models.CharField(max_length=256, null=True, default="", verbose_name="题注", help_text="数据的题注说明")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'project_static_hardware'
|
||||||
|
verbose_name = "静态硬件项表"
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
# 一对一项目model:动态软件项表
|
||||||
|
class DynamicSoftTable(models.Model):
|
||||||
|
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False, related_name="dynamic_soft_item", on_delete=models.CASCADE,
|
||||||
|
verbose_name="关联项目", help_text="关联项目")
|
||||||
|
table = models.JSONField(verbose_name="储存表格二维数组", help_text="储存表格二维数组", default=default_json_value)
|
||||||
|
fontnote = models.CharField(max_length=256, null=True, default="", verbose_name="题注", help_text="数据的题注说明")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'project_dynamic_soft_item'
|
||||||
|
verbose_name = "动态软件项表"
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
# 一对一项目model:动态硬件项
|
||||||
|
class DynamicHardwareTable(models.Model):
|
||||||
|
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False, related_name="dynamic_hardware", on_delete=models.CASCADE,
|
||||||
|
verbose_name="关联项目", help_text="关联项目")
|
||||||
|
table = models.JSONField(verbose_name="储存表格二维数组", help_text="储存表格二维数组", default=default_json_value)
|
||||||
|
fontnote = models.CharField(max_length=256, null=True, default="", verbose_name="题注", help_text="数据的题注说明")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'project_dynamic_hardware'
|
||||||
|
verbose_name = "动态硬件项表"
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
# 一对一项目model:动态环境 - 测评数据
|
||||||
|
class EvaluateData(models.Model):
|
||||||
|
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False, related_name="evaluate_data", on_delete=models.CASCADE,
|
||||||
|
verbose_name="关联项目", help_text="关联项目")
|
||||||
|
table = models.JSONField(verbose_name="储存表格二维数组", help_text="储存表格二维数组", default=default_json_value)
|
||||||
|
fontnote = models.CharField(max_length=256, null=True, default="", verbose_name="题注", help_text="数据的题注说明")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'project_evaluate_data'
|
||||||
|
verbose_name = "测评数据"
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
# 一对一项目model:环境差异性分析
|
||||||
|
class EnvAnalysis(models.Model):
|
||||||
|
project = models.OneToOneField(to="Project", primary_key=True, db_constraint=False,
|
||||||
|
related_name="env_analysis", on_delete=models.CASCADE,
|
||||||
|
verbose_name="关联项目", help_text="关联项目")
|
||||||
|
table = models.JSONField(verbose_name="储存表格二维数组", help_text="储存表格二维数组", default=default_json_value)
|
||||||
|
fontnote = models.CharField(max_length=256, null=True, default="", verbose_name="题注", help_text="数据的题注说明")
|
||||||
|
description = models.CharField(max_length=1024, null=True, default="", verbose_name="差异性分析文字")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'project_env_analysis'
|
||||||
|
verbose_name = "环境差异性分析表"
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
# 结构化排序数据
|
||||||
|
class StuctSortData(CoreModel):
|
||||||
|
"""
|
||||||
|
与其他项目信息的多对一关系
|
||||||
|
"""
|
||||||
|
# 软件概述内容
|
||||||
|
soft_summary = models.ForeignKey(ProjectSoftSummary, db_constraint=False, related_name="data_schemas", verbose_name="所属软件概述",
|
||||||
|
on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
# 接口图
|
||||||
|
project = models.OneToOneField(Project, db_constraint=False, related_name="data_schemas", on_delete=models.CASCADE, null=True, blank=True,
|
||||||
|
verbose_name="该接口图所属的项目")
|
||||||
|
# 动态环境描述
|
||||||
|
dynamic_description = models.ForeignKey(ProjectDynamicDescription, db_constraint=False, related_name="data_schemas",
|
||||||
|
verbose_name="所属动态环境描述",
|
||||||
|
on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
|
||||||
|
type = models.CharField(
|
||||||
|
max_length=20,
|
||||||
|
choices=(('text', '文本'), ('table', '表格'), ('image', '图片')),
|
||||||
|
default='text',
|
||||||
|
verbose_name="数据类型",
|
||||||
|
)
|
||||||
|
# 题注字段
|
||||||
|
fontnote = models.CharField(
|
||||||
|
max_length=256,
|
||||||
|
blank=True,
|
||||||
|
default="",
|
||||||
|
verbose_name="题注",
|
||||||
|
help_text="数据的题注说明"
|
||||||
|
)
|
||||||
|
# 内容字段 - 存储字符串、列表、字典
|
||||||
|
content = models.JSONField(verbose_name="内容", help_text="存储文本内容或二维表格数据或图片数据", default=default_json_value)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'data_schemas'
|
||||||
|
verbose_name = "结构排序化数据"
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"结构排序化数据:({self.pk})"
|
||||||
|
|
||||||
|
# 影响域分析 - 隶属:轮次(不能是第一轮次)
|
||||||
|
class InfluenceArea(models.Model):
|
||||||
|
round = models.OneToOneField(to="Round", primary_key=True, db_constraint=False,
|
||||||
|
related_name="influence", on_delete=models.CASCADE,
|
||||||
|
verbose_name="关联项目", help_text="关联项目")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'round_influence_area'
|
||||||
|
verbose_name = "影响域分析"
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
class InfluenceItem(CoreModel):
|
||||||
|
# 外键:影响域分析
|
||||||
|
influence = models.ForeignKey(InfluenceArea, db_constraint=False, related_name="influence_items", verbose_name="所属影响域分析",
|
||||||
|
on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
change_type = models.CharField(max_length=256, null=True, default="", verbose_name="更改类型", help_text="更改类型")
|
||||||
|
change_influ = models.TextField(max_length=2048, null=True, default="", verbose_name="影响域分析", help_text="影响域分析")
|
||||||
|
change_des = HTMLField(blank=True, null=True, verbose_name="更改内容描述")
|
||||||
|
effect_cases = models.JSONField(default=create_list, verbose_name="影响的用例key数组")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'influence_item'
|
||||||
|
verbose_name = "影响域分析 - 行数据"
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,7 @@
|
|||||||
from ninja.errors import HttpError
|
from ninja.errors import HttpError
|
||||||
from apps.project.models import Project
|
from pyasn1_modules.rfc2315 import Data
|
||||||
|
|
||||||
|
from apps.project.models import Project, StuctSortData
|
||||||
from ninja import Schema, ModelSchema
|
from ninja import Schema, ModelSchema
|
||||||
from pydantic import field_validator
|
from pydantic import field_validator
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
@@ -39,3 +41,31 @@ class ProjectCreateInput(ModelSchema):
|
|||||||
|
|
||||||
class DeleteSchema(Schema):
|
class DeleteSchema(Schema):
|
||||||
ids: List[int]
|
ids: List[int]
|
||||||
|
|
||||||
|
# ~~~软件概述~~~
|
||||||
|
class DataSchema(Schema):
|
||||||
|
type: Optional[str] = "text"
|
||||||
|
fontnote: Optional[str] = ""
|
||||||
|
content: str | list[list[str]]
|
||||||
|
|
||||||
|
## 输入
|
||||||
|
class SoftSummarySchema(Schema):
|
||||||
|
id: int
|
||||||
|
data: list[DataSchema]
|
||||||
|
|
||||||
|
# ~~~软件接口图~~~
|
||||||
|
## 复用DataSchema
|
||||||
|
|
||||||
|
# ~~~静态软件项、静态硬件项、动态软件项、动态硬件项~~~
|
||||||
|
class StaticDynamicData(Schema):
|
||||||
|
id: int
|
||||||
|
category: str
|
||||||
|
table: list[list[str]]
|
||||||
|
fontnote: Optional[str] = ""
|
||||||
|
|
||||||
|
# ~~~环境差异性分析~~~
|
||||||
|
class EnvAnalysisSchema(Schema):
|
||||||
|
id: int
|
||||||
|
table: list[list[str]]
|
||||||
|
fontnote: Optional[str] = ""
|
||||||
|
description: Optional[str] = ""
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
from ninja import Schema, ModelSchema
|
from ninja import Schema, ModelSchema
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
from apps.project.models import Round
|
from apps.project.models import Round, InfluenceItem
|
||||||
|
|
||||||
# 输出树状信息的schema
|
# 输出树状信息的schema
|
||||||
class TreeReturnRound(Schema):
|
class TreeReturnRound(Schema):
|
||||||
@@ -55,3 +55,21 @@ class CreateRoundInputSchema(ModelSchema):
|
|||||||
fields_optional = ['best_condition_tem', 'best_condition_voltage',
|
fields_optional = ['best_condition_tem', 'best_condition_voltage',
|
||||||
'low_condition_tem', 'low_condition_voltage', 'typical_condition_tem',
|
'low_condition_tem', 'low_condition_voltage', 'typical_condition_tem',
|
||||||
'typical_condition_voltage' 'grade']
|
'typical_condition_voltage' 'grade']
|
||||||
|
|
||||||
|
# influence_item return
|
||||||
|
class InfluenceItemOutSchema(ModelSchema):
|
||||||
|
class Meta:
|
||||||
|
model = InfluenceItem
|
||||||
|
fields = ['id', 'change_type', 'change_des', 'effect_cases', 'change_influ']
|
||||||
|
|
||||||
|
# influence input
|
||||||
|
class OneItemInputSchema(Schema):
|
||||||
|
change_type: str
|
||||||
|
change_des: Optional[str] = ""
|
||||||
|
effect_cases: Optional[list[str]] = []
|
||||||
|
change_influ: Optional[str] = ""
|
||||||
|
|
||||||
|
class InfluenceInputSchema(Schema):
|
||||||
|
id: int
|
||||||
|
round_key: str
|
||||||
|
item_list: list[OneItemInputSchema]
|
||||||
|
|||||||
@@ -1,91 +1,132 @@
|
|||||||
import jwt
|
import jwt
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from threading import local
|
from threading import local
|
||||||
from django.db.models.signals import post_save, post_delete
|
from django.db.models.signals import post_save, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.functional import SimpleLazyObject
|
from django.utils.functional import SimpleLazyObject
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
# 导入日志的模型
|
# 导入用例和影响域分析Model-设计信号当删除用例对应影响域分析删除关联用例
|
||||||
from apps.user.models import TableOperationLog, Users
|
from apps.project.models import Case, InfluenceItem
|
||||||
# 导入其他模型用于排除
|
# 导入日志的模型
|
||||||
from apps.project.models import CaseStep, TestDemandContent
|
from apps.user.models import TableOperationLog, Users
|
||||||
# 导入异常处理
|
# 导入其他模型用于排除
|
||||||
from jwt.exceptions import ExpiredSignatureError
|
from apps.project.models import CaseStep, TestDemandContent
|
||||||
from utils.chen_response import ChenResponse
|
# 导入异常处理
|
||||||
# 导入中间件记录日志模型
|
from jwt.exceptions import ExpiredSignatureError
|
||||||
from apps.system.models import LoginLog
|
from utils.chen_response import ChenResponse
|
||||||
from apps.system.models import OperationLog
|
# 导入中间件记录日志模型
|
||||||
|
from apps.system.models import LoginLog
|
||||||
log_manager = TableOperationLog.objects
|
from apps.system.models import OperationLog
|
||||||
|
|
||||||
_thread_local = local()
|
log_manager = TableOperationLog.objects
|
||||||
|
|
||||||
def get_current_user():
|
_thread_local = local()
|
||||||
"""
|
|
||||||
获取当前用户对象,调用则从local对象里面获取user
|
def get_current_user():
|
||||||
:return: Users实例
|
"""
|
||||||
"""
|
获取当前用户对象,调用则从local对象里面获取user
|
||||||
return getattr(_thread_local, 'user', None)
|
:return: Users实例
|
||||||
|
"""
|
||||||
def clear_request_locals(sender, **kwargs):
|
return getattr(_thread_local, 'user', None)
|
||||||
"""
|
|
||||||
被request_finished连接的信号处理函数,请求结束后清除local里面的user信息
|
def clear_request_locals(sender, **kwargs):
|
||||||
"""
|
"""
|
||||||
_thread_local.user = None
|
被request_finished连接的信号处理函数,请求结束后清除local里面的user信息
|
||||||
|
"""
|
||||||
def set_request_locals(sender, **kwargs):
|
_thread_local.user = None
|
||||||
"""
|
|
||||||
被request_started连接的信号处理函数,_thread_local.user属性设置为当前登录用户
|
def set_request_locals(sender, **kwargs):
|
||||||
"""
|
"""
|
||||||
bearer_token = kwargs['environ'].get('HTTP_AUTHORIZATION', None)
|
被request_started连接的信号处理函数,_thread_local.user属性设置为当前登录用户
|
||||||
if not bearer_token or bearer_token == 'Bearer null':
|
"""
|
||||||
return
|
bearer_token = kwargs['environ'].get('HTTP_AUTHORIZATION', None)
|
||||||
bearer_token = bearer_token.replace('Bearer ', '')
|
if not bearer_token or bearer_token == 'Bearer null':
|
||||||
# 逻辑:先获取NINJA_JWT配置中秘钥、和加密算法信息
|
return
|
||||||
jwt_settings = settings.NINJA_JWT
|
bearer_token = bearer_token.replace('Bearer ', '')
|
||||||
jwt_secret = jwt_settings.get('SIGNING_KEY', None)
|
# 逻辑:先获取NINJA_JWT配置中秘钥、和加密算法信息
|
||||||
jwt_algo = jwt_settings.get('ALGORITHM', None)
|
jwt_settings = settings.NINJA_JWT
|
||||||
# 如果为None,则使用settings中的秘钥和['HS256']算法
|
jwt_secret = jwt_settings.get('SIGNING_KEY', None)
|
||||||
secret_key = jwt_secret or settings.SECRET_KEY
|
jwt_algo = jwt_settings.get('ALGORITHM', None)
|
||||||
algorithms_str = jwt_algo or 'HS256'
|
# 如果为None,则使用settings中的秘钥和['HS256']算法
|
||||||
# 解决bug:因为过期前面不跳转首页处理方式
|
secret_key = jwt_secret or settings.SECRET_KEY
|
||||||
try:
|
algorithms_str = jwt_algo or 'HS256'
|
||||||
jwt_dict = jwt.decode(bearer_token, secret_key, algorithms=[algorithms_str])
|
# 解决bug:因为过期前面不跳转首页处理方式
|
||||||
except ExpiredSignatureError as exc:
|
try:
|
||||||
return ChenResponse(status=403, code=500, message='您的token已过期,请重新登录')
|
jwt_dict = jwt.decode(bearer_token, secret_key, algorithms=[algorithms_str])
|
||||||
user_id = jwt_dict.get('user_id', None)
|
except ExpiredSignatureError as exc:
|
||||||
if user_id:
|
return ChenResponse(status=403, code=500, message='您的token已过期,请重新登录')
|
||||||
_thread_local.user = SimpleLazyObject(lambda: get_user_model().objects.get(id=user_id))
|
user_id = jwt_dict.get('user_id', None)
|
||||||
|
if user_id:
|
||||||
# 1.注意可以不传sender,为监听所有模型,这里来记录日志
|
_thread_local.user = SimpleLazyObject(lambda: get_user_model().objects.get(id=user_id))
|
||||||
# 2.使用get_current_user()获取当前请求用户
|
|
||||||
@receiver(post_save)
|
# 1.注意可以不传sender,为监听所有模型,这里来记录日志
|
||||||
def post_save_handler(sender, instance, created, **kwargs):
|
# 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
|
if (sender == TableOperationLog or sender == CaseStep or sender == TestDemandContent or sender == LoginLog or sender == OperationLog or sender
|
||||||
user = get_current_user()
|
== Users):
|
||||||
ope_dict = {
|
return
|
||||||
'operate_obj': str(instance),
|
user = get_current_user()
|
||||||
}
|
ope_dict = {
|
||||||
if created:
|
'operate_obj': str(instance),
|
||||||
ope_dict['operate_des'] = '新增'
|
}
|
||||||
else:
|
if created:
|
||||||
ope_dict['operate_des'] = '修改'
|
ope_dict['operate_des'] = '新增'
|
||||||
log_manager.create(user=user, **ope_dict)
|
else:
|
||||||
|
ope_dict['operate_des'] = '修改'
|
||||||
@receiver(post_delete)
|
log_manager.create(user=user, **ope_dict)
|
||||||
def post_delete_handler(sender, instance, **kwargs):
|
|
||||||
"""模型删除-操作日志填写"""
|
@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
|
if (sender == TableOperationLog or sender == CaseStep or sender == TestDemandContent or sender == LoginLog or sender == OperationLog or sender
|
||||||
user = get_current_user()
|
== Users):
|
||||||
ope_dict = {
|
return
|
||||||
'operate_obj': str(instance),
|
user = get_current_user()
|
||||||
'operate_des': '删除'
|
ope_dict = {
|
||||||
}
|
'operate_obj': str(instance),
|
||||||
log_manager.create(user=user, **ope_dict)
|
'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("⚠️ 查询到记录但未发生变更,可能数据有误")
|
||||||
Binary file not shown.
@@ -182,6 +182,8 @@ def auto_create_wd(user_name: str, dut_qs: Dut, project_obj: Project):
|
|||||||
}
|
}
|
||||||
new_wd_design_obj: Design = Design.objects.create(**wd_design_create_dict)
|
new_wd_design_obj: Design = Design.objects.create(**wd_design_create_dict)
|
||||||
# 1.1.1.自动创建demand文档审查
|
# 1.1.1.自动创建demand文档审查
|
||||||
|
is_JD = (project_obj.report_type == '9')
|
||||||
|
test_des = "本次三方文档审查内容包括软件需求规格说明、软件设计说明等"
|
||||||
wd_demand_create_dict = {
|
wd_demand_create_dict = {
|
||||||
'ident': 'WDSC',
|
'ident': 'WDSC',
|
||||||
'name': '文档审查',
|
'name': '文档审查',
|
||||||
@@ -206,7 +208,7 @@ def auto_create_wd(user_name: str, dut_qs: Dut, project_obj: Project):
|
|||||||
'13)软件研制总结报告\a'
|
'13)软件研制总结报告\a'
|
||||||
'14)软件版本说明\a'
|
'14)软件版本说明\a'
|
||||||
'15)软件产品规格说明\a'
|
'15)软件产品规格说明\a'
|
||||||
'16)固件保障手册',
|
'16)固件保障手册' if is_JD else test_des,
|
||||||
'key': ''.join([new_wd_design_obj.key, '-', '0']),
|
'key': ''.join([new_wd_design_obj.key, '-', '0']),
|
||||||
'level': '3',
|
'level': '3',
|
||||||
'project': project_obj,
|
'project': project_obj,
|
||||||
|
|||||||
BIN
cdtestplant_v1/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
cdtestplant_v1/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
cdtestplant_v1/__pycache__/settings.cpython-314.pyc
Normal file
BIN
cdtestplant_v1/__pycache__/settings.cpython-314.pyc
Normal file
Binary file not shown.
BIN
conf/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
conf/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
conf/__pycache__/env.cpython-314.pyc
Normal file
BIN
conf/__pycache__/env.cpython-314.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
conf/base_document/form_template/dg/动态测试环境说明_2.docx
Normal file
BIN
conf/base_document/form_template/dg/动态测试环境说明_2.docx
Normal file
Binary file not shown.
BIN
conf/base_document/form_template/dg/动态硬件和固件项_2.docx
Normal file
BIN
conf/base_document/form_template/dg/动态硬件和固件项_2.docx
Normal file
Binary file not shown.
BIN
conf/base_document/form_template/dg/动态软件项_2.docx
Normal file
BIN
conf/base_document/form_template/dg/动态软件项_2.docx
Normal file
Binary file not shown.
BIN
conf/base_document/form_template/dg/测评对象_2.docx
Normal file
BIN
conf/base_document/form_template/dg/测评对象_2.docx
Normal file
Binary file not shown.
BIN
conf/base_document/form_template/dg/测评数据_2.docx
Normal file
BIN
conf/base_document/form_template/dg/测评数据_2.docx
Normal file
Binary file not shown.
BIN
conf/base_document/form_template/dg/环境差异性分析_2.docx
Normal file
BIN
conf/base_document/form_template/dg/环境差异性分析_2.docx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
conf/base_document/form_template/dg/静态硬件和固件项_2.docx
Normal file
BIN
conf/base_document/form_template/dg/静态硬件和固件项_2.docx
Normal file
Binary file not shown.
BIN
conf/base_document/form_template/dg/静态软件项_2.docx
Normal file
BIN
conf/base_document/form_template/dg/静态软件项_2.docx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
166
conf/env.py
166
conf/env.py
@@ -1,83 +1,83 @@
|
|||||||
"""全部是生产环境配置"""
|
"""全部是生产环境配置"""
|
||||||
import ldap
|
import ldap
|
||||||
from django_auth_ldap.config import LDAPSearch
|
from django_auth_ldap.config import LDAPSearch
|
||||||
import environ
|
import environ
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# ***************读取LDAP的.env文件配置*************** #
|
# ***************读取LDAP的.env文件配置*************** #
|
||||||
env_file = '.env'
|
env_file = '.env'
|
||||||
env = environ.Env()
|
env = environ.Env()
|
||||||
env.read_env(env_file=Path(__file__).resolve().parent.parent / env_file)
|
env.read_env(env_file=Path(__file__).resolve().parent.parent / env_file)
|
||||||
|
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
# *************** mysql数据库 配置 *************** #
|
# *************** mysql数据库 配置 *************** #
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
# 数据库地址
|
# 数据库地址
|
||||||
DATABASE_HOST = "127.0.0.1"
|
DATABASE_HOST = "127.0.0.1"
|
||||||
# 数据库端口
|
# 数据库端口
|
||||||
DATABASE_PORT = 3307 # 生成环境配置
|
DATABASE_PORT = 3306 # 生成环境配置-注意打包时务必需改为3307!!!!!!
|
||||||
# 数据库用户名
|
# 数据库用户名
|
||||||
DATABASE_USER = "root"
|
DATABASE_USER = "root"
|
||||||
# 数据库密码
|
# 数据库密码
|
||||||
DATABASE_PASSWORD = "root"
|
DATABASE_PASSWORD = "root"
|
||||||
# 数据库名
|
# 数据库名
|
||||||
DATABASE_NAME = "chengdu_test_plant_v1"
|
DATABASE_NAME = "chengdu_test_plant_v1"
|
||||||
|
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
# ******************** celery配置 **************** #
|
# ******************** celery配置 **************** #
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
CELERY_BROKER_URL = "redis://127.0.0.1:6379/0"
|
CELERY_BROKER_URL = "redis://127.0.0.1:6379/0"
|
||||||
CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379/1"
|
CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379/1"
|
||||||
CELERY_ENABLE_UTC = False
|
CELERY_ENABLE_UTC = False
|
||||||
CELERY_TIME_ZONE = "Asia/Shanghai"
|
CELERY_TIME_ZONE = "Asia/Shanghai"
|
||||||
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间
|
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间
|
||||||
CELERY_REUSLT_SERIALIZER = "json" # celery结果序列化,接受mime类型,任务序列化形式
|
CELERY_REUSLT_SERIALIZER = "json" # celery结果序列化,接受mime类型,任务序列化形式
|
||||||
CELERY_ACCEPT_CONTENT = ['application/json']
|
CELERY_ACCEPT_CONTENT = ['application/json']
|
||||||
CELERY_TASK_SERIALIZER = 'json'
|
CELERY_TASK_SERIALIZER = 'json'
|
||||||
DJANGO_CELERY_BEAT_TZ_AWARE = False
|
DJANGO_CELERY_BEAT_TZ_AWARE = False
|
||||||
CELERY_WORKER_CONCURRENCY = 5 # 并发数量
|
CELERY_WORKER_CONCURRENCY = 5 # 并发数量
|
||||||
CELERY_MAX_TASKS_PER_CHILD = 10 # 每worker最多执行5个任务自动销毁
|
CELERY_MAX_TASKS_PER_CHILD = 10 # 每worker最多执行5个任务自动销毁
|
||||||
|
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
# ****************** 其他 配置 ****************** #
|
# ****************** 其他 配置 ****************** #
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
ALLOWED_HOSTS = ["*"] # 线上环境设置
|
ALLOWED_HOSTS = ["*"] # 线上环境设置
|
||||||
LOGIN_NO_CAPTCHA_AUTH = True # 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消
|
LOGIN_NO_CAPTCHA_AUTH = True # 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消
|
||||||
ENABLE_LOGIN_ANALYSIS_LOG = True # 启动登录详细概略获取(通过调用api获取ip详细地址)
|
ENABLE_LOGIN_ANALYSIS_LOG = True # 启动登录详细概略获取(通过调用api获取ip详细地址)
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
# *************** 接口throttle配置 *************** #
|
# *************** 接口throttle配置 *************** #
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
|
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
# *************** LDAP认证配置 *************** #
|
# *************** LDAP认证配置 *************** #
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
AUTHENTICATION_BACKENDS = [
|
AUTHENTICATION_BACKENDS = [
|
||||||
'django_auth_ldap.backend.LDAPBackend',
|
'django_auth_ldap.backend.LDAPBackend',
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
]
|
]
|
||||||
# ldap连接配置
|
# ldap连接配置
|
||||||
AUTH_LDAP_SERVER_URI = env('AUTH_LDAP_SERVER_URI', default='ldap://dns.paisat.cn:389')
|
AUTH_LDAP_SERVER_URI = env('AUTH_LDAP_SERVER_URI', default='ldap://dns.paisat.cn:389')
|
||||||
# 绑定的DN,注意大小写敏感Administrator,Users
|
# 绑定的DN,注意大小写敏感Administrator,Users
|
||||||
AUTH_LDAP_BIND_DN = env('AUTH_LDAP_BIND_DN', default="CN=Administrator,CN=Users,DC=sstc,DC=ctu")
|
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_BIND_PASSWORD = env('AUTH_LDAP_BIND_PASSWORD', default="WXWX2019!!!!!!")
|
||||||
AUTH_LDAP_USER_SEARCH = LDAPSearch(
|
AUTH_LDAP_USER_SEARCH = LDAPSearch(
|
||||||
env('BASE_DN', default='OU=all,DC=sstc,DC=ctu'),
|
env('BASE_DN', default='OU=all,DC=sstc,DC=ctu'),
|
||||||
ldap.SCOPE_SUBTREE, env('FILTER_STR', default='(sAMAccountName=%(user)s)')
|
ldap.SCOPE_SUBTREE, env('FILTER_STR', default='(sAMAccountName=%(user)s)')
|
||||||
)
|
)
|
||||||
# 如果ldap服务器是Windows的AD,需要配置上如下选项
|
# 如果ldap服务器是Windows的AD,需要配置上如下选项
|
||||||
AUTH_LDAP_CONNECTION_OPTIONS = {
|
AUTH_LDAP_CONNECTION_OPTIONS = {
|
||||||
ldap.OPT_DEBUG_LEVEL: 1,
|
ldap.OPT_DEBUG_LEVEL: 1,
|
||||||
ldap.OPT_REFERRALS: 0,
|
ldap.OPT_REFERRALS: 0,
|
||||||
}
|
}
|
||||||
# 每次LDAP认证后进行数据库更新,不包含密码
|
# 每次LDAP认证后进行数据库更新,不包含密码
|
||||||
AUTH_LDAP_ALWAYS_UPDATE_USER = True
|
AUTH_LDAP_ALWAYS_UPDATE_USER = True
|
||||||
# 看看下面是否需要password字段
|
# 看看下面是否需要password字段
|
||||||
AUTH_LDAP_USER_ATTR_MAP = {
|
AUTH_LDAP_USER_ATTR_MAP = {
|
||||||
"username": "sAMAccountName",
|
"username": "sAMAccountName",
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"email": "mail",
|
"email": "mail",
|
||||||
}
|
}
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
# *************** ...........配置 *************** #
|
# *************** ...........配置 *************** #
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
|
|||||||
1422
dbdata.sql
Normal file
1422
dbdata.sql
Normal file
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
|||||||
[WARNING][2025-04-29 14:20:41,997][logger.py:25][回归测试记录模块][单个问题单表格]片段:问题单4未关联用例,请检查
|
[WARNING][2026-04-17 14:20:04,514][logger.py:25][回归测试记录模块][字典数据缺失]片段:字典数据runtime数据缺失,请检查相应数据是否存在
|
||||||
[WARNING][2026-01-20 16:34:34,986][logger.py:25][回归测试说明模块][当前文档全部片段]片段:该项目没有创建轮次
|
[WARNING][2026-04-17 14:20:04,518][logger.py:25][回归测试记录模块][字典数据缺失]片段:字典数据devplant数据缺失,请检查相应数据是否存在
|
||||||
|
|||||||
3503
logs/root_log
3503
logs/root_log
File diff suppressed because it is too large
Load Diff
BIN
media/R2237/form_template/dg/测评对象_2.docx
Normal file
BIN
media/R2237/form_template/dg/测评对象_2.docx
Normal file
Binary file not shown.
BIN
media/R25138/form_template/dg/测评对象_2.docx
Normal file
BIN
media/R25138/form_template/dg/测评对象_2.docx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user