from pathlib import Path from typing import Literal from docxtpl import DocxTemplate from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.table import WD_ALIGN_VERTICAL from docx.oxml.ns import qn from docx.shared import Mm from apps.createDocument.extensions.tools import set_table_border_by_cell_position, set_cell_margins from utils.path_utils import project_path from utils.chen_response import ChenResponse from django.shortcuts import get_object_or_404 from apps.project.models import Project, Round from django.db.models import QuerySet # 创建当前轮次别名 RoundType = Literal["0", "not0", "last"] chinese_round_name: list = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'] # 通用生成静态软件项、静态硬件项、动态软件项、动态硬件信息、测评数据的context,包含fontnote和table def create_table_context(table_data: list[list[str]], doc: DocxTemplate, rounds_map: list[list[str]] = None, current_round: str = None): """ 注意:该函数会增加一列序号列,并且支持单元格内回车换行(段落换行) 传入当前轮次以及rounds_map来过滤一些其他轮次的数据,为None则不过滤 """ # 过滤数据处理 if rounds_map is not None and current_round is not None and current_round != 'last': filtered_data = [] for i, row in enumerate(table_data): # 第一行作为表头,始终保留 if i == 0: filtered_data.append(row) else: # 检查该行是否属于当前轮次 if current_round in rounds_map[i]: filtered_data.append(row) # table_data先替换后再执行下方生成表格 table_data = filtered_data subdoc = doc.new_subdoc() rows = len(table_data) cols = len(table_data[0]) + 1 table = subdoc.add_table(rows=rows, cols=cols) # 单元格处理 for row in range(rows): for col in range(cols): cell = table.cell(row, col) # 从上倒下,从左到右取cell set_cell_margins(cell, left=100, right=100, top=100, bottom=100) # 获取要显示的文本内容(字符串或按行拆分后的列表) if col == 0: # 序号列 lines = ["序号"] if row == 0 else [str(row)] else: raw_text = table_data[row][col - 1] # 按换行符 \n 拆分为多个段落 lines = raw_text.split('\n') if raw_text else [''] # 清空单元格原有段落(add_table 默认有一个段落) cell.text = "" # 删除默认段落,稍后统一添加 for para in cell.paragraphs: p = para._element p.getparent().remove(p) # 逐个添加段落 for i, line in enumerate(lines): if i == 0: para = cell.add_paragraph(line) else: para = cell.add_paragraph(line) # 设置段落对齐(第一列居中,其他左对齐,可根据需要调整) if col == 0: para.alignment = WD_ALIGN_PARAGRAPH.CENTER else: para.alignment = WD_ALIGN_PARAGRAPH.LEFT # 对第一行(表头)设置黑体字体 if row == 0: for run in para.runs: run.font.name = '黑体' run._element.rPr.rFonts.set(qn('w:eastAsia'), '黑体') run.font.bold = False # 表头段落居中(覆盖前面的 left) para.alignment = WD_ALIGN_PARAGRAPH.CENTER # 垂直居中 cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER # 设置序号列宽度 for cell in table.columns[0].cells: cell.width = Mm(15) for para in cell.paragraphs: para.alignment = WD_ALIGN_PARAGRAPH.CENTER # 表格居中 table.alignment = WD_ALIGN_PARAGRAPH.CENTER # 设置表格外边框 set_table_border_by_cell_position(table) return subdoc # 统一静态软件项、静态硬件项、动态软件项、动态硬件信息、测评数据5个的word生成 - 模版模式 def uniform_static_dynamic_response(id: int, filename: str, r_filename: str, model, current_round: str = "0", isHsm: bool = None) -> ChenResponse | None: """ 通过形参isHsm判断是否是回归测试说明 """ project_obj = get_object_or_404(Project, id=id) input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / filename # 取相同模版 doc = DocxTemplate(input_path) qs = model.objects.filter(project=project_obj) if qs.exists(): obj = qs.first() table_data = obj.table # 回归测试说明生成多轮次回归测试说明的5个接口分支 if isHsm: hround_list: QuerySet[Round] = project_obj.pField.exclude(key='0') for hround in hround_list: round_key = hround.key cname = chinese_round_name[int(round_key)] # 取中文:一、二、三... # key就是current_round,这里就解决文件名和那个的问题 subdoc = create_table_context(table_data, doc, obj.rounds_map, round_key) context = { 'fontnote': obj.fontnote, 'table': subdoc, } doc.render(context, autoescape=True) try: doc.save(Path.cwd() / "media" / project_path(id) / "output_dir/hsm" / "".join([f"第{cname}轮", r_filename])) except PermissionError as e: return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) return ChenResponse(status=200,code=200,message="多个轮次5接口渲染完毕,文档生成完毕") # 新增:传入rounds_map进行渲染 subdoc = create_table_context(table_data, doc, obj.rounds_map, current_round) context = { 'fontnote': obj.fontnote, 'table': subdoc, } doc.render(context, autoescape=True) try: doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / r_filename) return ChenResponse(status=200, code=200, message="文档生成成功!") except PermissionError as e: return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e)) return None