59 lines
1.7 KiB
Python
59 lines
1.7 KiB
Python
|
|
"""CMS 导入 / 导出通用工具(基于 openpyxl)。"""
|
|||
|
|
import io
|
|||
|
|
|
|||
|
|
from django.http import HttpResponse
|
|||
|
|
from openpyxl import Workbook, load_workbook
|
|||
|
|
|
|||
|
|
XLSX_CONTENT_TYPE = (
|
|||
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def xlsx_response(filename, headers, rows):
|
|||
|
|
"""生成 .xlsx 下载响应。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
filename: 下载文件名(含 .xlsx)
|
|||
|
|
headers: 表头列表
|
|||
|
|
rows: 二维数据(list[list]);空则只导出表头(可作模板)
|
|||
|
|
"""
|
|||
|
|
wb = Workbook()
|
|||
|
|
ws = wb.active
|
|||
|
|
ws.append(list(headers))
|
|||
|
|
for row in rows:
|
|||
|
|
ws.append(list(row))
|
|||
|
|
buf = io.BytesIO()
|
|||
|
|
wb.save(buf)
|
|||
|
|
buf.seek(0)
|
|||
|
|
resp = HttpResponse(buf.getvalue(), content_type=XLSX_CONTENT_TYPE)
|
|||
|
|
resp['Content-Disposition'] = f'attachment; filename="{filename}"'
|
|||
|
|
return resp
|
|||
|
|
|
|||
|
|
|
|||
|
|
def rows_from_xlsx(file):
|
|||
|
|
"""解析上传的 .xlsx,首行为表头,返回 list[dict](表头→单元格字符串)。
|
|||
|
|
|
|||
|
|
空行跳过;单元格统一转为去空格字符串(None → '')。
|
|||
|
|
"""
|
|||
|
|
wb = load_workbook(file, read_only=True, data_only=True)
|
|||
|
|
ws = wb.active
|
|||
|
|
it = ws.iter_rows(values_only=True)
|
|||
|
|
try:
|
|||
|
|
header_cells = next(it)
|
|||
|
|
except StopIteration:
|
|||
|
|
return []
|
|||
|
|
headers = [str(h).strip() if h is not None else '' for h in header_cells]
|
|||
|
|
|
|||
|
|
result = []
|
|||
|
|
for raw in it:
|
|||
|
|
if raw is None or all(c is None or str(c).strip() == '' for c in raw):
|
|||
|
|
continue
|
|||
|
|
row = {}
|
|||
|
|
for i, h in enumerate(headers):
|
|||
|
|
if not h:
|
|||
|
|
continue
|
|||
|
|
val = raw[i] if i < len(raw) else None
|
|||
|
|
row[h] = '' if val is None else str(val).strip()
|
|||
|
|
result.append(row)
|
|||
|
|
return result
|