classExcelTemplateMetaclass(type):
def__new__(cls, name, bases, attrs):
if name == 'ExcelTemplate':
returntype.__new__(cls, name, bases, attrs)
columns = dict() # 保存Excel的列信息for k, v in attrs.items():
ifisinstance(v, ExcelColumn):
columns[k] = v
for k in columns.keys():
attrs.pop(k)
attrs['__columns__'] = columns
returntype.__new__(cls, name, bases, attrs)
定义Excel模板基类
classExcelTemplate(metaclass=ExcelTemplateMetaclass):
__sheet_name__ = 'Sheet1'
__columns__: dict[str, ExcelColumn]
headers = {
'Content-Type': 'application/vnd.ms-excel',
'Content-Disposition': f'attachment;filename=download.xlsx'
}
@staticmethodasyncdefget_download_data(query: QuerySet, limit: int=None):
"""传入一个querySet,最后导对应的数据,limit为最多允许导出的数据"""ifnot limit:
limit = settings.EXCEL_DOWNLOAD_LIMIT
pagination = query.limit(limit).offset(0) # 最多允许导出100W的数据returnawait pagination.all()
@classmethoddef_get_upload_title_mapping(cls):
return {v.name: k for k, v in cls.__columns__.items()}
@classmethoddef_get_upload_value_mappings(cls):
return {v.name: v.mapping for k, v in cls.__columns__.items()}
@staticmethoddef_get_data_validation(columnIndex, excelColumn: ExcelColumn):
dv = DataValidation(
type='list',
formula1=f'"{",".join(excelColumn.mapping_keys)}"',
allow_blank=not excelColumn.required
)
dv.error = f"{excelColumn.name}只能从str({excelColumn.mapping_keys})中选择"
dv.errorTitle = "无效的输入"
dv.prompt = '请从下拉框中选择数据'
dv.promptTitle = f"{excelColumn.name}选择"
columnName = get_column_letter(columnIndex)
dv.add(f'{columnName}{2}:{columnName}{11}') # 数据验证区域,2-11行return dv
@classmethoddef_set_data_validation(cls, sheet):
for index, (k, v) inenumerate(cls.__columns__.items(), start=1):
if v.mapping_keys:
dv = cls._get_data_validation(index, v)
sheet.data_validations.append(dv)
@classmethoddefget_title(cls):
return [val for val, _ in cls._get_title()]
@classmethoddefget_update_title(cls):
return [k for k, v in cls.__columns__.items() if v.update]
@classmethoddef_get_title(cls):
return [
(v.name, v.width) for k, v in cls.__columns__.items()
]
@classmethoddef_get_title_mapping(cls):
return {v.name: k for k, v in cls.__columns__.items()}
@classmethoddefget_template(cls) -> BytesIO:
file = BytesIO()
excel = ExcelWriter(file)
sheet = excel.write(cls.__sheet_name__, [], title=cls.__columns__.values())
cls._set_data_validation(sheet)
excel.save()
file.seek(0)
return file
@classmethoddefget_template_as_stream_response(cls) -> StreamingResponse:
return StreamingResponse(cls.get_template(), media_type='xls/xlsx', headers=cls.headers)
@classmethoddefsave_template_as_file(cls, filename: str):
withopen(filename, 'wb') as f:
f.write(cls.get_template().getvalue())
@classmethoddefread(cls, file: bytes | str) -> "":
defget_value(key, value):
mapping = value_mappings.get(key)
ifnot mapping:
return value
return mapping(value)
defget_title(key):
return title_mapping.get(key, key)
title_mapping = cls._get_upload_title_mapping()
value_mappings = cls._get_upload_value_mappings()
ifisinstance(file, bytes):
data = BytesIO()
data.write(file)
data.seek(0)
excel = ExcelReader(data)
else:
excel = ExcelReader(file)
return (
{get_title(k): get_value(k, v) for k, v in row.items()}
for row in excel.read(sheet=cls.__sheet_name__) ifany(row.values())
)
@classmethoddefwrite(cls, data):
deftranslate_row_values(row_values):
"""将行数据转换为excel可以显示的值,主要是为了将后台枚举的数字转换为用户可读的字符"""
result = {}
for k, v indict(row_values).items():
# 没有定义的列不会被导入到Excel中if k notin cls.__columns__:
continue
out_key = cls.__columns__[k].name
# 处理时区问题,Excel保存时不支持时区参数ifisinstance(v, datetime):
result[out_key] = v.replace(tzinfo=None)
continue# 将枚举数据转换为具体的值
mapping_reverse = cls.__columns__.get(k).mapping_reverse
if mapping_reverse:
result[out_key] = mapping_reverse(v)
continue
result[out_key] = v
return result
file = BytesIO()
excel = ExcelWriter(file)
sheet = excel.write(
cls.__sheet_name__,
(translate_row_values(row) for row in data), # 处理待写入的数据
title=cls.__columns__.values()
)
cls._set_data_validation(sheet)
excel.save()
file.seek(0)
return file
@classmethoddefwrite_as_stream_response(cls, data):
"""保存问一个文件响应对象,用于返回给前端"""return StreamingResponse(cls.write(data), media_type='xls/xlsx', headers=cls.headers)
@classmethoddefwrite_as_file(cls, filename: str, data):
"""将数据保存为一个文件"""withopen(filename, 'wb') as f:
f.write(cls.write(data).getvalue())
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步