新增:静态、动态环境内容

This commit is contained in:
2026-02-05 18:27:04 +08:00
parent a2781c902a
commit 0bee950a52
57 changed files with 669 additions and 3250 deletions

View File

@@ -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, '动态硬件和固件项')

View File

@@ -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"})
@@ -31,9 +56,87 @@ def set_table_border(table, **kwargs):
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
borders.append(border_elem) # type:ignore
# 将边框设置添加到表格属性中
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')

View File

@@ -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/")
@@ -361,9 +382,7 @@ class ProjectController(ControllerBase):
image_qs = StuctSortData.objects.filter(project=project_obj)
if image_qs.exists():
image_qs.delete()
self.bulk_create_data_schemas(project_obj, [dataSchema])
else:
self.bulk_create_data_schemas(project_obj, [dataSchema])
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)

View File

@@ -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',
},
),
]

View File

@@ -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='题注'),
),
]

View File

@@ -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='所属动态环境描述'),
),
]

View File

@@ -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):
@@ -502,7 +561,12 @@ class StuctSortData(CoreModel):
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="该接口图所属的项目")
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', '图片')),

View File

@@ -54,4 +54,11 @@ class SoftSummarySchema(Schema):
data: list[DataSchema]
# ~~~软件接口图~~~
## 复用DataSchema
## 复用DataSchema
# ~~~静态软件项、静态硬件项、动态软件项、动态硬件项~~~
class StaticDynamicData(Schema):
id: int
category: str
table: list[list[str]]
fontnote: Optional[str] = ""

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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
View File

@@ -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"