111 lines
3.8 KiB
Python
111 lines
3.8 KiB
Python
import csv
|
||
import datetime
|
||
from django.http import HttpResponse
|
||
from django.utils.encoding import escape_uri_path
|
||
|
||
def export_to_csv(modeladmin, request, queryset):
|
||
"""
|
||
通用导出 CSV 的 Admin Action
|
||
支持中文编码(UTF-8 BOM),可直接用 Excel 打开
|
||
"""
|
||
opts = modeladmin.model._meta
|
||
# 设置文件名,使用模型的 verbose_name
|
||
filename = f"{opts.verbose_name}_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
|
||
|
||
response = HttpResponse(content_type='text/csv; charset=utf-8-sig')
|
||
response['Content-Disposition'] = f'attachment; filename={escape_uri_path(filename)}'
|
||
|
||
writer = csv.writer(response)
|
||
|
||
# 获取所有非多对多字段和非反向关联字段
|
||
fields = [field for field in opts.get_fields() if not field.many_to_many and not field.one_to_many]
|
||
|
||
# 写入表头 (使用字段的 verbose_name)
|
||
writer.writerow([field.verbose_name for field in fields])
|
||
|
||
# 写入数据
|
||
for obj in queryset:
|
||
data_row = []
|
||
for field in fields:
|
||
value = getattr(obj, field.name)
|
||
|
||
# 处理 Choice 字段,显示可读的标签
|
||
if hasattr(obj, f'get_{field.name}_display'):
|
||
value = getattr(obj, f'get_{field.name}_display')()
|
||
|
||
# 处理关联对象(ForeignKey)
|
||
if field.is_relation and value:
|
||
value = str(value)
|
||
|
||
# 处理日期时间
|
||
if isinstance(value, datetime.datetime):
|
||
value = value.strftime('%Y-%m-%d %H:%M:%S')
|
||
elif isinstance(value, datetime.date):
|
||
value = value.strftime('%Y-%m-%d')
|
||
|
||
# 处理 None
|
||
if value is None:
|
||
value = ""
|
||
|
||
data_row.append(str(value))
|
||
writer.writerow(data_row)
|
||
|
||
return response
|
||
|
||
export_to_csv.short_description = "导出选中项为 CSV"
|
||
|
||
def export_to_excel(modeladmin, request, queryset):
|
||
"""
|
||
导出为 Excel (需要安装 openpyxl)
|
||
"""
|
||
try:
|
||
from openpyxl import Workbook
|
||
except ImportError:
|
||
modeladmin.message_user(request, "请先安装 openpyxl 库以使用 Excel 导出功能: pip install openpyxl", level='error')
|
||
return
|
||
|
||
opts = modeladmin.model._meta
|
||
filename = f"{opts.verbose_name}_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
|
||
|
||
response = HttpResponse(
|
||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
)
|
||
response['Content-Disposition'] = f'attachment; filename={escape_uri_path(filename)}'
|
||
|
||
wb = Workbook()
|
||
ws = wb.active
|
||
# Sheet name limit is 31 chars
|
||
ws.title = str(opts.verbose_name)[:31]
|
||
|
||
fields = [field for field in opts.get_fields() if not field.many_to_many and not field.one_to_many]
|
||
|
||
# 写入表头
|
||
ws.append([str(field.verbose_name) for field in fields])
|
||
|
||
# 写入数据
|
||
for obj in queryset:
|
||
row = []
|
||
for field in fields:
|
||
value = getattr(obj, field.name)
|
||
|
||
if hasattr(obj, f'get_{field.name}_display'):
|
||
value = getattr(obj, f'get_{field.name}_display')()
|
||
|
||
# 处理关联对象(ForeignKey)
|
||
if field.is_relation and value:
|
||
value = str(value)
|
||
|
||
if isinstance(value, (datetime.datetime, datetime.date)):
|
||
# openpyxl 可以直接处理 datetime 格式,Excel 会自动识别
|
||
# 但为了避免时区问题,通常转为无时区时间或字符串
|
||
if isinstance(value, datetime.datetime):
|
||
value = value.replace(tzinfo=None)
|
||
|
||
row.append(value)
|
||
ws.append(row)
|
||
|
||
wb.save(response)
|
||
return response
|
||
|
||
export_to_excel.short_description = "导出选中项为 Excel"
|