init: 初始化 dpb 桃育种系统代码库

前后端 + 后端 FastAPI 全量源码、部署脚本与文档。
This commit is contained in:
34047007@qq.com
2026-08-06 00:17:49 +08:00
commit b95053c52c
1469 changed files with 322298 additions and 0 deletions
@@ -0,0 +1 @@
# -*- coding: utf-8 -*-
@@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
import urllib.parse
from typing import Annotated
from fastapi import APIRouter, Body, Depends, File, Path, Query, UploadFile, status
from fastapi.responses import JSONResponse, StreamingResponse
from sqlalchemy.ext.asyncio import AsyncSession
from app.common.response import ResponseSchema, StreamResponse, SuccessResponse
from app.core.base_schema import AuthSchema, BatchSetAvailable, PageResultSchema, PaginationQueryParam
from app.core.dependencies import AuthPermission, db_getter
from app.core.router_class import OperationLogRoute
from app.utils.common_util import bytes2file_response
from .schema import {{ class_name }}CreateSchema, {{ class_name }}OutSchema, {{ class_name }}QueryParam, {{ class_name }}UpdateSchema
from .service import {{ class_name }}Service
{{ class_name }}Router = APIRouter(route_class=OperationLogRoute, prefix="/{{ module_name }}", tags=["{{ function_name }}模块"])
@{{ class_name }}Router.get("/detail/{id}", summary="获取{{ function_name }}详情", response_model=ResponseSchema[{{ class_name }}OutSchema])
async def get_obj_detail_controller(
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:detail"]))],
id: Annotated[int, Path(description="{{ function_name }}ID")],
db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse:
service = {{ class_name }}Service(auth, db)
result_dict = await service.detail(id=id)
return SuccessResponse(data=result_dict, msg="获取{{ function_name }}详情成功")
@{{ class_name }}Router.get("/list", summary="分页查询{{ function_name }}", response_model=ResponseSchema[PageResultSchema[{{ class_name }}OutSchema]])
async def get_obj_list_controller(
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:query"]))],
page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[{{ class_name }}QueryParam, Query()],
db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse:
service = {{ class_name }}Service(auth, db)
result_dict = await service.page(
page_no=page.page_no,
page_size=page.page_size,
search=search,
order_by=page.order_by,
)
return SuccessResponse(data=result_dict, msg="查询{{ function_name }}列表成功")
@{{ class_name }}Router.post("/create", status_code=status.HTTP_201_CREATED, summary="创建{{ function_name }}", response_model=ResponseSchema[{{ class_name }}OutSchema])
async def create_obj_controller(
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:create"]))],
data: Annotated[{{ class_name }}CreateSchema, Body(description="创建参数")],
db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse:
service = {{ class_name }}Service(auth, db)
result_dict = await service.create(data=data)
return SuccessResponse(data=result_dict, msg="创建{{ function_name }}成功")
@{{ class_name }}Router.put("/update/{id}", summary="修改{{ function_name }}", response_model=ResponseSchema[{{ class_name }}OutSchema])
async def update_obj_controller(
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:update"]))],
id: Annotated[int, Path(description="{{ function_name }}ID")],
data: Annotated[{{ class_name }}UpdateSchema, Body(description="修改参数")],
db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse:
service = {{ class_name }}Service(auth, db)
result_dict = await service.update(id=id, data=data)
return SuccessResponse(data=result_dict, msg="修改{{ function_name }}成功")
@{{ class_name }}Router.delete("/delete", summary="删除{{ function_name }}", response_model=ResponseSchema[None])
async def delete_obj_controller(
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:delete"]))],
ids: Annotated[list[int], Body(description="ID列表")],
db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse:
service = {{ class_name }}Service(auth, db)
await service.delete(ids=ids)
return SuccessResponse(msg="删除{{ function_name }}成功")
@{{ class_name }}Router.patch("/status/batch", summary="批量修改{{ function_name }}状态", response_model=ResponseSchema[None])
async def batch_set_available_obj_controller(
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:patch"]))],
data: Annotated[BatchSetAvailable, Body(description="状态设置")],
db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse:
service = {{ class_name }}Service(auth, db)
await service.set_available(data=data)
return SuccessResponse(msg="批量修改{{ function_name }}状态成功")
@{{ class_name }}Router.post("/export", summary="导出{{ function_name }}")
async def export_obj_list_controller(
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:export"]))],
search: Annotated[{{ class_name }}QueryParam, Query()],
db: Annotated[AsyncSession, Depends(db_getter)],
) -> StreamingResponse:
service = {{ class_name }}Service(auth, db)
result_dict_list = await service.get_list(search=search)
export_result = {{ class_name }}Service.batch_export(obj_list=[item.model_dump() for item in result_dict_list])
return StreamResponse(
data=bytes2file_response(export_result),
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={"Content-Disposition": "attachment; filename={{ table_name }}.xlsx"},
)
@{{ class_name }}Router.post("/import", summary="导入{{ function_name }}", response_model=ResponseSchema[str])
async def import_obj_list_controller(
file: Annotated[UploadFile, File(description="导入文件")],
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:import"]))],
db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse:
service = {{ class_name }}Service(auth, db)
batch_import_result = await service.batch_import(file=file, update_support=True)
return SuccessResponse(data=batch_import_result, msg="导入{{ function_name }}成功")
@{{ class_name }}Router.post("/download/template", summary="获取{{ function_name }}导入模板", dependencies=[Depends(AuthPermission(["{{ permission_prefix }}:download"]))])
async def export_obj_template_controller() -> StreamingResponse:
import_template_result = {{ class_name }}Service.import_template_download()
return StreamResponse(
data=bytes2file_response(import_template_result),
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={
"Content-Disposition": f"attachment; filename={urllib.parse.quote('{{ function_name }}导入模板.xlsx')}",
"Access-Control-Expose-Headers": "Content-Disposition",
},
)
+22
View File
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.base_crud import CRUDBase
from app.core.base_schema import AuthSchema
from .model import {{ class_name }}Model
from .schema import {{ class_name }}CreateSchema, {{ class_name }}UpdateSchema
class {{ class_name }}CRUD(CRUDBase[{{ class_name }}Model, {{ class_name }}CreateSchema, {{ class_name }}UpdateSchema]):
"""{{ function_name }}数据层"""
def __init__(self, auth: AuthSchema, db: AsyncSession) -> None:
"""
初始化CRUD数据层
参数:
- auth (AuthSchema): 认证信息模型
- db (AsyncSession): 数据库会话
"""
super().__init__(model={{ class_name }}Model, auth=auth, db=db)
+52
View File
@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
{% for model_import in model_import_list %}
{{ model_import }}
{% endfor %}
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base_model import ModelMixin, UserMixin
{% if table.sub and not is_sub_entity %}
from ..{{ sub_module_name }}.model import {{ sub_model_class_name }}
{% endif %}
class {{ class_name }}Model(ModelMixin, UserMixin):
"""
{{ function_name }}表
"""
__tablename__: str = '{{ table_name }}'
__table_args__: dict[str, str] = {'comment': '{{ function_name }}'}
{% if not is_sub_entity %}
{% for column in columns %}
{% if column.column_name not in ['id', 'uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] %}
{% set sqlalchemy_type = column|get_sqlalchemy_type %}
{% if not column.is_nullable %}
{{ column.column_name }}: Mapped[{{ column.python_type }}] = mapped_column({{ sqlalchemy_type }}, {% if column.is_pk %}primary_key=True, {% endif %}{% if column.is_increment %}autoincrement=True, {% endif %}nullable=False, comment='{{ column.column_comment }}')
{% else %}
{{ column.column_name }}: Mapped[{{ column.python_type }} | None] = mapped_column({{ sqlalchemy_type }}, {% if column.is_pk %}primary_key=True, {% endif %}{% if column.is_increment %}autoincrement=True, {% endif %}nullable=True, comment='{{ column.column_comment }}')
{% endif %}
{% endif %}
{% endfor %}
{% if table.sub %}
{{ sub_rel_list_name }} = relationship('{{ sub_model_class_name }}', back_populates='{{ parent_rel_name }}')
{% endif %}
{% else %}
{% for column in columns %}
{% if column.column_name not in ['id', 'uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] %}
{% set sqlalchemy_type = column|get_sqlalchemy_type %}
{% if column.column_name == sub_table_fk_name %}
{{ column.column_name }}: Mapped[{{ column.python_type }} | None] = mapped_column({{ sqlalchemy_type }}, ForeignKey('{{ parent_table_name }}.{{ parent_pk_column_name }}', ondelete='CASCADE'), {% if column.is_pk %}primary_key=True, {% endif %}{% if column.is_increment %}autoincrement=True, {% endif %}{% if (not column.is_nullable) or column.is_pk %}nullable=False{% else %}nullable=True{% endif %}, comment='{{ column.column_comment }}')
{% else %}
{% if not column.is_nullable %}
{{ column.column_name }}: Mapped[{{ column.python_type }}] = mapped_column({{ sqlalchemy_type }}, {% if column.is_pk %}primary_key=True, {% endif %}{% if column.is_increment %}autoincrement=True, {% endif %}nullable=False, comment='{{ column.column_comment }}')
{% else %}
{{ column.column_name }}: Mapped[{{ column.python_type }} | None] = mapped_column({{ sqlalchemy_type }}, {% if column.is_pk %}primary_key=True, {% endif %}{% if column.is_increment %}autoincrement=True, {% endif %}nullable=True, comment='{{ column.column_comment }}')
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{{ parent_rel_name }} = relationship('{{ parent_model_class_name }}', back_populates='{{ parent_list_rel_name }}')
{% endif %}
+90
View File
@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
{% if table.sub %}
from typing import List
{% endif %}
from pydantic import BaseModel, ConfigDict, Field
{% for import_stmt in schema_import_list %}
{{ import_stmt }}
{% endfor %}
from app.core.base_schema import BaseSchema, UserBySchema, BaseQueryParam, UserByQueryParam
{% set has_date_validator = false %}
{% set has_time_validator = false %}
{% set has_datetime_validator = false %}
{% for column in columns %}
{% if column.python_type == 'date' %}
{% set has_date_validator = true %}
{% elif column.python_type == 'time' %}
{% set has_time_validator = true %}
{% elif column.python_type == 'datetime' %}
{% set has_datetime_validator = true %}
{% endif %}
{% endfor %}
{% if has_date_validator or has_time_validator or has_datetime_validator %}
from app.core.validator import {% if has_date_validator %}DateStr{% endif %}{% if has_time_validator %}{% if has_date_validator %}, {% endif %}TimeStr{% endif %}{% if has_datetime_validator %}{% if has_date_validator or has_time_validator %}, {% endif %}DateTimeStr{% endif %}
{% endif %}
class {{ class_name }}CreateSchema(BaseModel):
"""
{{ function_name }}新增模型
"""
{% for column in columns %}
{% if column.column_name not in ['uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] and column.column_name != pk_column_name %}
{% if column.column_name == 'status' %}
{{ column.column_name }}: {{ column.python_type }} = Field(default={% if column.python_type == "str" %}"0"{% else %}0{% endif %}{% if column.python_type == "int" %}, ge=0, le=1{% endif %}, description='{{ column.column_comment }}')
{% elif column.column_name == 'description' %}
{{ column.column_name }}: str | None = Field(default=None, description='{{ column.column_comment }}')
{% elif column.python_type == "bool" or column.html_type == "switch" %}
{{ column.column_name }}: bool = Field(default=True, description='{{ column.column_comment }}')
{% elif column.python_type == "date" %}
{{ column.column_name }}: DateStr | None = Field(default=None, description='{{ column.column_comment }}')
{% elif column.python_type == "time" %}
{{ column.column_name }}: TimeStr | None = Field(default=None, description='{{ column.column_comment }}')
{% elif column.python_type == "datetime" %}
{{ column.column_name }}: DateTimeStr | None = Field(default=None, description='{{ column.column_comment }}')
{% elif column.is_nullable %}
{{ column.column_name }}: {{ column.python_type }} | None = Field(default=None, description='{{ column.column_comment }}')
{% else %}
{{ column.column_name }}: {{ column.python_type }} = Field(default=..., description='{{ column.column_comment }}')
{% endif %}
{% endif %}
{% endfor %}
class {{ class_name }}UpdateSchema(BaseModel):
"""
{{ function_name }}更新模型
"""
{% for column in columns %}
{% if column.column_name not in ['uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] and column.column_name != pk_column_name %}
{% if column.column_name == 'status' %}
{{ column.column_name }}: {{ column.python_type }} | None = Field(default=None{% if column.python_type == "int" %}, ge=0, le=1{% endif %}, description='{{ column.column_comment }}')
{% elif column.python_type == "date" %}
{{ column.column_name }}: DateStr | None = Field(default=None, description='{{ column.column_comment }}')
{% elif column.python_type == "time" %}
{{ column.column_name }}: TimeStr | None = Field(default=None, description='{{ column.column_comment }}')
{% elif column.python_type == "datetime" %}
{{ column.column_name }}: DateTimeStr | None = Field(default=None, description='{{ column.column_comment }}')
{% else %}
{{ column.column_name }}: {{ column.python_type }} | None = Field(default=None, description='{{ column.column_comment }}')
{% endif %}
{% endif %}
{% endfor %}
class {{ class_name }}OutSchema({{ class_name }}CreateSchema, BaseSchema, UserBySchema):
"""
{{ function_name }}响应模型
"""
model_config = ConfigDict(from_attributes=True)
class {{ class_name }}QueryParam(BaseQueryParam, UserByQueryParam):
"""{{ function_name }}查询参数"""
{% for column in columns %}
{% if column.is_query and column.column_name not in ['created_time', 'updated_time', 'created_id', 'updated_id', 'tenant_id', 'is_deleted', 'deleted_time', 'deleted_id'] %}
{{ column.column_name }}: {{ column.python_type }} | None = Field(None, description="{{ column.column_comment }}"{% if column.query_type == 'LIKE' %}, json_schema_extra={"q": "like"}{% else %}, json_schema_extra={"q": "eq"}{% endif %})
{% endif %}
{% endfor %}
+222
View File
@@ -0,0 +1,222 @@
# -*- coding: utf-8 -*-
from typing import Any
from fastapi import UploadFile
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.base_schema import AuthSchema, BatchSetAvailable, PageResultSchema
from app.core.exceptions import CustomException
from app.core.logger import logger
from app.utils.common_util import search_to_dict
from app.utils.excel_util import ExcelUtil
from .crud import {{ class_name }}CRUD
from .schema import (
{{ class_name }}CreateSchema,
{{ class_name }}OutSchema,
{{ class_name }}QueryParam,
{{ class_name }}UpdateSchema,
)
class {{ class_name }}Service:
"""{{ function_name }}模块服务层"""
def __init__(self, auth: AuthSchema, db: AsyncSession) -> None:
self.auth = auth
self.db = db
async def detail(self, id: int) -> {{ class_name }}OutSchema:
obj = await {{ class_name }}CRUD(self.auth, self.db).get(id=id)
if not obj:
raise CustomException(msg="该数据不存在")
return {{ class_name }}OutSchema.model_validate(obj)
async def get_list(
self,
search: {{ class_name }}QueryParam | None = None,
order_by: list[dict[str, str]] | None = None,
) -> list[{{ class_name }}OutSchema]:
obj_list = await {{ class_name }}CRUD(self.auth, self.db).get_list(search=search_to_dict(search), order_by=order_by)
return [{{ class_name }}OutSchema.model_validate(obj) for obj in obj_list]
async def page(
self,
page_no: int,
page_size: int,
search: {{ class_name }}QueryParam | None = None,
order_by: list[dict[str, str]] | None = None,
) -> PageResultSchema[{{ class_name }}OutSchema]:
offset = (page_no - 1) * page_size
return await {{ class_name }}CRUD(self.auth, self.db).page(
offset=offset,
limit=page_size,
order_by=order_by or [{"{{ pk_column_name }}": "asc"}],
search=search_to_dict(search, {}),
out_schema={{ class_name }}OutSchema,
)
async def create(self, data: {{ class_name }}CreateSchema) -> {{ class_name }}OutSchema:
{% for column in columns %}
{% if column.is_unique and column.column_name != 'uuid' %}
obj = await {{ class_name }}CRUD(self.auth, self.db).get({{ column.column_name }}=data.{{ column.column_name }})
if obj:
raise CustomException(msg="创建失败,{{ column.column_comment }}已存在")
{% endif %}
{% endfor %}
obj = await {{ class_name }}CRUD(self.auth, self.db).create(data=data)
return {{ class_name }}OutSchema.model_validate(obj)
async def update(self, id: int, data: {{ class_name }}UpdateSchema) -> {{ class_name }}OutSchema:
obj = await {{ class_name }}CRUD(self.auth, self.db).get(id=id)
if not obj:
raise CustomException(msg="更新失败,该数据不存在")
{% for column in columns %}
{% if column.is_unique and column.column_name != 'uuid' %}
exist_obj = await {{ class_name }}CRUD(self.auth, self.db).get({{ column.column_name }}=data.{{ column.column_name }})
if exist_obj and exist_obj.{{ pk_column_name }} != id:
raise CustomException(msg="更新失败,{{ column.column_comment }}重复")
{% endif %}
{% endfor %}
obj = await {{ class_name }}CRUD(self.auth, self.db).update(id=id, data=data)
return {{ class_name }}OutSchema.model_validate(obj)
async def delete(self, ids: list[int]) -> None:
if not ids:
raise CustomException(msg="删除失败,删除对象不能为空")
objs = await {{ class_name }}CRUD(self.auth, self.db).get_list(search={"{{ pk_column_name }}": ("in", ids)})
obj_map = {o.{{ pk_column_name }}: o for o in objs}
for id_ in ids:
if id_ not in obj_map:
raise CustomException(msg="删除失败,该数据不存在")
await {{ class_name }}CRUD(self.auth, self.db).delete(ids=ids)
async def set_available(self, data: BatchSetAvailable) -> None:
await {{ class_name }}CRUD(self.auth, self.db).set(ids=data.ids, status=data.status)
@staticmethod
def batch_export(obj_list: list[dict[str, Any]]) -> bytes:
mapping_dict = {
{% for column in columns %}
{% if column.column_name not in ['uuid', 'tenant_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] %}
'{{ column.column_name }}': '{{ column.column_comment }}',
{% endif %}
{% endfor %}
}
data = obj_list.copy()
for item in data:
item["status"] = "启用" if item.get("status") == 0 else "停用"
creator_info = item.get("created_id")
if isinstance(creator_info, dict):
item["created_id"] = creator_info.get("name", "未知")
else:
item["created_id"] = "未知"
return ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping_dict)
async def batch_import(self, file: UploadFile, update_support: bool = False) -> str:
header_dict = {
{% for column in columns %}
{% if column.column_name not in ['id', 'uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] and column.column_name != pk_column_name %}
'{{ column.column_comment }}': '{{ column.column_name }}',
{% endif %}
{% endfor %}
}
try:
contents = await file.read()
rows = ExcelUtil.read_excel_to_dicts(contents)
await file.close()
if not rows:
raise CustomException(msg="导入文件为空")
missing_headers = [h for h in header_dict if h not in rows[0]]
if missing_headers:
raise CustomException(msg=f"导入文件缺少必要的列: {', '.join(missing_headers)}")
# 将中文字段名映射为英文字段
mapped_rows = []
for row in rows:
mapped_rows.append({en: row.get(ch) for ch, en in header_dict.items()})
required_fields = [
{% for column in columns %}
{% if column.is_nullable is false and column.is_pk is false and column.column_name not in ['id', 'uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] %}
"{{ column.column_name }}",
{% endif %}
{% endfor %}
]
errors = []
for field in required_fields:
missing_indices = [i + 1 for i, r in enumerate(mapped_rows) if r.get(field) is None]
if missing_indices:
field_name = next((k for k, v in header_dict.items() if v == field), field)
rows_str = "、".join(str(i) for i in missing_indices)
errors.append(f"{field_name}不能为空,第{rows_str}行")
if errors:
raise CustomException(msg=f"导入失败,以下行缺少必要字段:\n{'; '.join(errors)}")
error_msgs = []
success_count = 0
for i, row in enumerate(mapped_rows, start=1):
try:
create_schema = {{ class_name }}CreateSchema.model_validate(row)
{% for column in columns %}
{% if column.is_unique and column.column_name not in ['uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] %}
exists_obj = await {{ class_name }}CRUD(self.auth, self.db).get({{ column.column_name }}=create_schema.{{ column.column_name }})
if exists_obj:
if update_support:
await {{ class_name }}CRUD(self.auth, self.db).update(id=getattr(exists_obj, '{{ pk_column_name }}'), data=create_schema)
success_count += 1
else:
error_msgs.append(f"第{i}行: {{ column.column_comment }} {create_schema.{{ column.column_name }}} 已存在")
continue
{% endif %}
{% endfor %}
await {{ class_name }}CRUD(self.auth, self.db).create(data=create_schema)
success_count += 1
except Exception as e:
error_msgs.append(f"第{i}行: {e!s}")
continue
result = f"成功导入 {success_count} 条数据"
if error_msgs:
result += "\n错误信息:\n" + "\n".join(error_msgs)
return result
except Exception as e:
logger.error(f"批量导入失败: {e!s}")
raise CustomException(msg=f"导入失败: {e!s}")
@staticmethod
def import_template_download() -> bytes:
header_list = [
{% for column in columns %}
{% if column.column_name not in ['id', 'uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] and column.column_name != pk_column_name %}
'{{ column.column_comment }}',
{% endif %}
{% endfor %}
]
selector_header_list = []
option_list = []
{% for column in columns %}
{% if (column.html_type == 'select' or column.html_type == 'radio') and column.dict_type and column.column_name not in ['id', 'uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] and column.column_name != pk_column_name %}
selector_header_list.append('{{ column.column_comment }}')
option_list.append({'{{ column.column_comment }}': []})
{% endif %}
{% endfor %}
return ExcelUtil.get_excel_template(
header_list=header_list,
selector_header_list=selector_header_list,
option_list=option_list,
)