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"