新增:静态、动态环境内容
This commit is contained in:
Binary file not shown.
@@ -4,16 +4,18 @@ from typing import Any
|
||||
from datetime import datetime
|
||||
from docx.shared import Mm
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
from docx.enum.table import WD_ALIGN_VERTICAL
|
||||
from docx.oxml.ns import qn
|
||||
from ninja.errors import HttpError
|
||||
from ninja_extra import ControllerBase, api_controller, route
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from docxtpl import DocxTemplate, InlineImage
|
||||
from docxtpl import DocxTemplate, InlineImage, Subdoc
|
||||
from pathlib import Path
|
||||
from utils.chen_response import ChenResponse
|
||||
# 导入数据库ORM
|
||||
from apps.project.models import Project, Contact, Abbreviation, ProjectSoftSummary, StuctSortData
|
||||
from apps.project.models import Project, Contact, Abbreviation, ProjectSoftSummary, StuctSortData, StaticSoftItem, StaticSoftHardware, \
|
||||
DynamicSoftTable, DynamicHardwareTable
|
||||
from apps.dict.models import Dict
|
||||
# 导入工具函数
|
||||
from utils.util import get_str_dict, get_list_dict, get_testType, get_ident, get_str_abbr
|
||||
@@ -29,7 +31,7 @@ from apps.createSeiTaiDocument.extensions.logger import GenerateLogger
|
||||
# 导入mixins-处理文档片段
|
||||
from apps.createDocument.extensions.mixins import FragementToolsMixin
|
||||
# 导入工具
|
||||
from apps.createDocument.extensions.tools import demand_sort_by_designKey, set_table_border
|
||||
from apps.createDocument.extensions.tools import demand_sort_by_designKey, set_table_border_by_cell_position, set_cell_margins
|
||||
|
||||
# @api_controller("/generate", tags=['生成大纲文档'], auth=JWTAuth(), permissions=[IsAuthenticated])
|
||||
@api_controller("/generate", tags=['生成大纲文档'])
|
||||
@@ -312,9 +314,7 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
subdoc = doc.new_subdoc()
|
||||
rows = len(data_obj.content)
|
||||
cols = len(data_obj.content[0])
|
||||
table = subdoc.add_table(rows=rows, cols=cols, style='Table Grid')
|
||||
# 设置边框
|
||||
set_table_border(table)
|
||||
table = subdoc.add_table(rows=rows, cols=cols)
|
||||
# 单元格处理
|
||||
for row in range(rows):
|
||||
for col in range(cols):
|
||||
@@ -330,8 +330,12 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
run._element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')
|
||||
run.font.bold = False
|
||||
pa.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
# 垂直居中
|
||||
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
||||
# 表格居中
|
||||
table.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
# 设置边框
|
||||
set_table_border_by_cell_position(table)
|
||||
item_context['content'] = subdoc
|
||||
elif data_obj.type == 'image':
|
||||
base64_bytes = base64.b64decode(data_obj.content.replace("data:image/png;base64,", ""))
|
||||
@@ -451,9 +455,85 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
|
||||
# 通用生成静态软件项、静态硬件项、动态软件项、动态硬件信息的context,包含fontnote和table
|
||||
@classmethod
|
||||
def create_table_context(cls, table_data: list[list[str]], doc: DocxTemplate) -> Subdoc:
|
||||
"""注意:该函数会增加一列序号列"""
|
||||
subdoc = doc.new_subdoc()
|
||||
rows = len(table_data)
|
||||
cols = len(table_data[0]) + 1 # 多渲染序号列
|
||||
table = subdoc.add_table(rows=rows, cols=cols)
|
||||
# 单元格处理
|
||||
for row in range(rows):
|
||||
for col in range(cols):
|
||||
cell = table.cell(row, col) # 单元格数据
|
||||
# 设置边距 - 所有单元格
|
||||
set_cell_margins(cell, left=100, right=100, top=100, bottom=100)
|
||||
pa = cell.paragraphs[0]
|
||||
# 处理第一列 - 要居中
|
||||
if col == 0:
|
||||
if row == 0:
|
||||
cell.text = "序号"
|
||||
else:
|
||||
cell.text = str(row)
|
||||
pa.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
# 处理非第一列
|
||||
else:
|
||||
cell.text = table_data[row][col - 1]
|
||||
# 单独处理第一行
|
||||
for col in range(cols):
|
||||
cell = table.cell(0, col)
|
||||
cell.text = ""
|
||||
pa = cell.paragraphs[0]
|
||||
if col == 0:
|
||||
run = pa.add_run("序号")
|
||||
else:
|
||||
run = pa.add_run(str(table_data[0][col - 1]))
|
||||
run.font.name = '黑体'
|
||||
run._element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')
|
||||
run.font.bold = False
|
||||
pa.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
# 设置序号列宽度 - 先自动调整为False然后设置True
|
||||
for cell in table.columns[0].cells:
|
||||
cell.width = Mm(15)
|
||||
pa = cell.paragraphs[0]
|
||||
pa.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
# 表格居中
|
||||
table.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
# 最后设置表格外边框
|
||||
set_table_border_by_cell_position(table)
|
||||
return subdoc
|
||||
|
||||
# 统一静态软件项、静态硬件项、动态软件项、动态硬件信息的word生成 - 模版模式
|
||||
@classmethod
|
||||
def uniform_static_dynamic_response(cls, id: int, filename: str, r_filename: str, model) -> ChenResponse | None:
|
||||
project_obj = get_object_or_404(Project, id=id)
|
||||
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / filename
|
||||
doc = DocxTemplate(input_path)
|
||||
qs = model.objects.filter(project=project_obj)
|
||||
if qs.exists():
|
||||
obj = qs.first()
|
||||
table_data = obj.table
|
||||
subdoc = cls.create_table_context(table_data, doc)
|
||||
context = {
|
||||
'fontnote': obj.fontnote,
|
||||
'table': subdoc,
|
||||
}
|
||||
doc.render(context, autoescape=True)
|
||||
try:
|
||||
doc.save(Path.cwd() / "media" / project_path(id) / "output_dir" / r_filename)
|
||||
return ChenResponse(status=200, code=200, message="文档生成成功!")
|
||||
except PermissionError as e:
|
||||
return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))
|
||||
return None
|
||||
|
||||
# 静态软件项
|
||||
@route.get('/create/static_soft', url_name='create-static_soft')
|
||||
def create_static_soft(self, id: int):
|
||||
res = self.uniform_static_dynamic_response(id, '静态软件项_2.docx', '静态软件项.docx', StaticSoftItem)
|
||||
if res is not None:
|
||||
return res
|
||||
|
||||
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '静态软件项.docx'
|
||||
doc = DocxTemplate(input_path)
|
||||
replace, frag, rich_text_list = self._generate_frag(id, doc, '静态软件项')
|
||||
@@ -466,6 +546,10 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
# 静态硬件和固件项
|
||||
@route.get('/create/static_hard', url_name='create-static_hard')
|
||||
def create_static_hard(self, id: int):
|
||||
res = self.uniform_static_dynamic_response(id, '静态硬件和固件项_2.docx', '静态硬件和固件项.docx', StaticSoftHardware)
|
||||
if res is not None:
|
||||
return res
|
||||
|
||||
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '静态硬件和固件项.docx'
|
||||
doc = DocxTemplate(input_path)
|
||||
replace, frag, rich_text_list = self._generate_frag(id, doc, '静态硬件和固件项')
|
||||
@@ -497,6 +581,10 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
# 动态软件项
|
||||
@route.get('/create/dynamic_soft', url_name='create-dynamic_soft')
|
||||
def create_dynamic_soft(self, id: int):
|
||||
res = self.uniform_static_dynamic_response(id, '动态软件项_2.docx', '动态软件项.docx', DynamicSoftTable)
|
||||
if res is not None:
|
||||
return res
|
||||
|
||||
project_obj: Project = get_object_or_404(Project, id=id)
|
||||
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '动态软件项.docx'
|
||||
doc = DocxTemplate(input_path)
|
||||
@@ -508,9 +596,14 @@ class GenerateControllerDG(ControllerBase, FragementToolsMixin):
|
||||
}
|
||||
return create_dg_docx("动态软件项.docx", context, id)
|
||||
|
||||
# 动态软件项
|
||||
# 动态硬件项
|
||||
@route.get('/create/dynamic_hard', url_name='create-dynamic_hard')
|
||||
def create_dynamic_hard(self, id: int):
|
||||
res = self.uniform_static_dynamic_response(id, '动态硬件和固件项_2.docx',
|
||||
'动态硬件和固件项.docx', DynamicHardwareTable)
|
||||
if res is not None:
|
||||
return res
|
||||
|
||||
input_path = Path.cwd() / 'media' / project_path(id) / 'form_template' / 'dg' / '动态硬件和固件项.docx'
|
||||
doc = DocxTemplate(input_path)
|
||||
replace, frag, rich_text_list = self._generate_frag(id, doc, '动态硬件和固件项')
|
||||
|
||||
Binary file not shown.
@@ -1,6 +1,7 @@
|
||||
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, ...]:
|
||||
"""仅限于测试项排序函数,传入sorted函数的key里面"""
|
||||
@@ -8,6 +9,30 @@ def demand_sort_by_designKey(demand_obj: TestDemand) -> tuple[int, ...]:
|
||||
sort_tuple = tuple(int(part) for part in parts)
|
||||
return sort_tuple
|
||||
|
||||
# 传入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-设置表格上下左右边框"""
|
||||
# 获取或创建表格属性
|
||||
@@ -21,7 +46,7 @@ def set_table_border(table, **kwargs):
|
||||
# 创建新的边框元素
|
||||
borders = OxmlElement('w:tblBorders')
|
||||
|
||||
# 只设置外边框:top, left, bottom, right
|
||||
# 只设置外边框: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"})
|
||||
@@ -36,4 +61,82 @@ def set_table_border(table, **kwargs):
|
||||
# 将边框设置添加到表格属性中
|
||||
tbl_pr.append(borders)
|
||||
|
||||
__all__ = ['demand_sort_by_designKey', 'set_table_border']
|
||||
# ~~~新解决方案:传入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')
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -14,9 +14,10 @@ from ninja.errors import HttpError
|
||||
from ninja import Query
|
||||
from utils.chen_response import ChenResponse
|
||||
from utils.chen_crud import create, multi_delete_project
|
||||
from apps.project.models import Project, Round, ProjectSoftSummary, StuctSortData
|
||||
from apps.project.models import Project, Round, ProjectSoftSummary, StuctSortData, StaticSoftItem, StaticSoftHardware, DynamicSoftTable, \
|
||||
DynamicHardwareTable
|
||||
from apps.project.schemas.project import ProjectRetrieveSchema, ProjectFilterSchema, ProjectCreateInput, \
|
||||
DeleteSchema, SoftSummarySchema, DataSchema
|
||||
DeleteSchema, SoftSummarySchema, DataSchema, StaticDynamicData
|
||||
from utils.util import get_str_dict
|
||||
# 时间处理模块
|
||||
from apps.project.tool.timeList import time_return_to
|
||||
@@ -277,6 +278,10 @@ class ProjectController(ControllerBase):
|
||||
all_status = {
|
||||
"soft_summary": False,
|
||||
"interface_image": False,
|
||||
"static_soft_item": False,
|
||||
"static_soft_hardware": False,
|
||||
"dynamic_soft_item": False,
|
||||
"dynamic_soft_hardware": False,
|
||||
}
|
||||
# 1.查看软件概述是否填写
|
||||
project_obj = self.get_project_by_id(id)
|
||||
@@ -289,6 +294,22 @@ class ProjectController(ControllerBase):
|
||||
image_qs = StuctSortData.objects.filter(project=project_obj)
|
||||
if image_qs.exists():
|
||||
all_status['interface_image'] = True
|
||||
# 3.查看静态软件项是否填写
|
||||
static_item_qs = StaticSoftItem.objects.filter(project=project_obj)
|
||||
if static_item_qs.exists():
|
||||
all_status['static_soft_item'] = True
|
||||
# 4.静态硬件项
|
||||
static_hardware_qs = StaticSoftHardware.objects.filter(project=project_obj)
|
||||
if static_hardware_qs.exists():
|
||||
all_status['static_soft_hardware'] = True
|
||||
# 5.动态软件项
|
||||
dynamic_soft_item_qs = DynamicSoftTable.objects.filter(project=project_obj)
|
||||
if dynamic_soft_item_qs.exists():
|
||||
all_status['dynamic_soft_item'] = True
|
||||
# 5.动态软件项
|
||||
dynamic_hardware_qs = DynamicHardwareTable.objects.filter(project=project_obj)
|
||||
if dynamic_hardware_qs.exists():
|
||||
all_status['dynamic_soft_hardware'] = True
|
||||
return ChenResponse(status=200, code=20000, data=all_status, message='查询成功')
|
||||
|
||||
@classmethod
|
||||
@@ -351,7 +372,7 @@ class ProjectController(ControllerBase):
|
||||
"content": item.content,
|
||||
"fontnote": item.fontnote,
|
||||
} for item in dataSchem_qs])
|
||||
return ChenResponse(status=200, code=25002, data=None)
|
||||
return ChenResponse(status=200, code=25002, data=[])
|
||||
|
||||
# ~~~接口图新增或修改~~~
|
||||
@route.post("/interface_image/")
|
||||
@@ -362,8 +383,6 @@ class ProjectController(ControllerBase):
|
||||
if image_qs.exists():
|
||||
image_qs.delete()
|
||||
self.bulk_create_data_schemas(project_obj, [dataSchema])
|
||||
else:
|
||||
self.bulk_create_data_schemas(project_obj, [dataSchema])
|
||||
|
||||
# ~~~接口图-获取数据~~~
|
||||
@route.get("/get_interface_image/", response=DataSchema)
|
||||
@@ -380,3 +399,36 @@ class ProjectController(ControllerBase):
|
||||
"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
|
||||
}
|
||||
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):
|
||||
print(data)
|
||||
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)
|
||||
|
||||
@@ -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='所属动态环境描述'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -466,6 +466,9 @@ class Contact(CoreModel):
|
||||
ordering = ('create_datetime',)
|
||||
|
||||
# ~~~~~2024年2月27日新增~~~~~
|
||||
def default_json_value():
|
||||
return ""
|
||||
|
||||
class Abbreviation(models.Model):
|
||||
objects = models.Manager()
|
||||
title = models.CharField(max_length=64, verbose_name="缩略语", help_text="缩略语")
|
||||
@@ -489,8 +492,64 @@ class ProjectSoftSummary(models.Model):
|
||||
verbose_name = "软件概述表"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def default_json_value():
|
||||
return ""
|
||||
# 一对一项目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
|
||||
|
||||
# 结构化排序数据
|
||||
class StuctSortData(CoreModel):
|
||||
@@ -503,6 +562,11 @@ class StuctSortData(CoreModel):
|
||||
# 接口图
|
||||
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', '图片')),
|
||||
|
||||
Binary file not shown.
@@ -55,3 +55,10 @@ class SoftSummarySchema(Schema):
|
||||
|
||||
# ~~~软件接口图~~~
|
||||
## 复用DataSchema
|
||||
|
||||
# ~~~静态软件项、静态硬件项、动态软件项、动态硬件项~~~
|
||||
class StaticDynamicData(Schema):
|
||||
id: int
|
||||
category: str
|
||||
table: list[list[str]]
|
||||
fontnote: Optional[str] = ""
|
||||
|
||||
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.
3492
logs/root_log
3492
logs/root_log
File diff suppressed because it is too large
Load Diff
Binary file not shown.
BIN
media/R25999/form_template/dg/动态硬件和固件项_2.docx
Normal file
BIN
media/R25999/form_template/dg/动态硬件和固件项_2.docx
Normal file
Binary file not shown.
BIN
media/R25999/form_template/dg/动态软件项_2.docx
Normal file
BIN
media/R25999/form_template/dg/动态软件项_2.docx
Normal file
Binary file not shown.
BIN
media/R25999/form_template/dg/静态硬件和固件项_2.docx
Normal file
BIN
media/R25999/form_template/dg/静态硬件和固件项_2.docx
Normal file
Binary file not shown.
BIN
media/R25999/form_template/dg/静态软件项_2.docx
Normal file
BIN
media/R25999/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.
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.
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.
@@ -29,6 +29,7 @@ dependencies = [
|
||||
"python-ldap",
|
||||
"ua-parser-builtins>=202601",
|
||||
"user-agents>=2.2.0",
|
||||
"waitress>=3.0.2",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
|
||||
11
uv.lock
generated
11
uv.lock
generated
@@ -94,6 +94,7 @@ dependencies = [
|
||||
{ name = "python-ldap" },
|
||||
{ name = "ua-parser-builtins" },
|
||||
{ name = "user-agents" },
|
||||
{ name = "waitress" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
@@ -122,6 +123,7 @@ requires-dist = [
|
||||
{ name = "python-ldap", path = "python_ldap-3.4.5-cp313-cp313-win_amd64.whl" },
|
||||
{ name = "ua-parser-builtins", specifier = ">=202601" },
|
||||
{ name = "user-agents", specifier = ">=2.2.0" },
|
||||
{ name = "waitress", specifier = ">=3.0.2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1196,6 +1198,15 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/8f/1c/20bb3d7b2bad56d881e3704131ddedbb16eb787101306887dff349064662/user_agents-2.2.0-py3-none-any.whl", hash = "sha256:a98c4dc72ecbc64812c4534108806fb0a0b3a11ec3fd1eafe807cee5b0a942e7" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "waitress"
|
||||
version = "3.0.2"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/bf/cb/04ddb054f45faa306a230769e868c28b8065ea196891f09004ebace5b184/waitress-3.0.2.tar.gz", hash = "sha256:682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/8d/57/a27182528c90ef38d82b636a11f606b0cbb0e17588ed205435f8affe3368/waitress-3.0.2-py3-none-any.whl", hash = "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.5.3"
|
||||
|
||||
Reference in New Issue
Block a user