217 lines
7.3 KiB
Python
217 lines
7.3 KiB
Python
import csv
|
|
import json
|
|
import datetime
|
|
from django.http import HttpResponse
|
|
from django.utils.encoding import escape_uri_path
|
|
|
|
def flatten_json(y):
|
|
"""
|
|
Flatten a nested json object
|
|
"""
|
|
out = {}
|
|
|
|
def flatten(x, name=''):
|
|
if type(x) is dict:
|
|
for a in x:
|
|
flatten(x[a], name + a + '_')
|
|
elif type(x) is list:
|
|
i = 0
|
|
for a in x:
|
|
flatten(a, name + str(i) + '_')
|
|
i += 1
|
|
else:
|
|
out[name[:-1]] = x
|
|
|
|
flatten(y)
|
|
return out
|
|
|
|
def get_signup_info_keys(queryset):
|
|
"""
|
|
Collect all unique keys from the signup_info JSON across the queryset
|
|
"""
|
|
keys = set()
|
|
for obj in queryset:
|
|
if obj.signup_info and isinstance(obj.signup_info, dict):
|
|
# Flatten the dictionary first to get all nested keys
|
|
flat_info = flatten_json(obj.signup_info)
|
|
keys.update(flat_info.keys())
|
|
return sorted(list(keys))
|
|
|
|
def get_row_data(obj):
|
|
"""
|
|
Build a dictionary of all exportable data for a single ActivitySignup object
|
|
"""
|
|
data = {}
|
|
|
|
# 1. 基础信息
|
|
data['ID'] = str(obj.id)
|
|
data['活动标题'] = obj.activity.title
|
|
data['报名时间'] = obj.signup_time
|
|
data['状态'] = obj.get_status_display()
|
|
|
|
# 2. 用户信息
|
|
if obj.user:
|
|
data['用户昵称'] = obj.user.nickname
|
|
data['用户ID'] = str(obj.user.id)
|
|
data['用户OpenID'] = obj.user.openid
|
|
data['用户绑定手机'] = obj.user.phone_number or ''
|
|
data['用户地区'] = f"{obj.user.country} {obj.user.province} {obj.user.city}".strip()
|
|
data['用户注册时间'] = obj.user.created_at
|
|
else:
|
|
data['用户昵称'] = 'Unknown'
|
|
data['用户ID'] = ''
|
|
data['用户OpenID'] = ''
|
|
data['用户绑定手机'] = ''
|
|
data['用户地区'] = ''
|
|
data['用户注册时间'] = ''
|
|
|
|
# 3. 订单/发货信息
|
|
if obj.order:
|
|
data['关联订单ID'] = str(obj.order.id)
|
|
data['订单状态'] = obj.order.get_status_display()
|
|
data['收货人姓名'] = obj.order.customer_name
|
|
data['收货电话'] = obj.order.phone_number
|
|
data['收货地址'] = obj.order.shipping_address
|
|
data['快递公司'] = obj.order.courier_name or ''
|
|
data['快递单号'] = obj.order.tracking_number or ''
|
|
data['订单总价'] = str(obj.order.total_price)
|
|
data['商户订单号'] = obj.order.out_trade_no or ''
|
|
else:
|
|
data['关联订单ID'] = ''
|
|
data['订单状态'] = ''
|
|
data['收货人姓名'] = ''
|
|
data['收货电话'] = ''
|
|
data['收货地址'] = ''
|
|
data['快递公司'] = ''
|
|
data['快递单号'] = ''
|
|
data['订单总价'] = ''
|
|
data['商户订单号'] = ''
|
|
|
|
return data
|
|
|
|
def export_signups_csv(modeladmin, request, queryset):
|
|
"""
|
|
Export selected signups to CSV, including flattened JSON fields
|
|
"""
|
|
opts = modeladmin.model._meta
|
|
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)
|
|
|
|
# Fixed headers
|
|
fixed_headers = [
|
|
'ID', '活动标题', '状态', '报名时间',
|
|
'用户ID', '用户昵称', '用户OpenID', '用户绑定手机', '用户地区', '用户注册时间',
|
|
'关联订单ID', '订单状态', '收货人姓名', '收货电话', '收货地址', '快递公司', '快递单号', '订单总价', '商户订单号'
|
|
]
|
|
|
|
# Get dynamic JSON keys
|
|
json_keys = get_signup_info_keys(queryset)
|
|
|
|
# Write header
|
|
writer.writerow(fixed_headers + json_keys)
|
|
|
|
# Write data
|
|
for obj in queryset:
|
|
row_data = get_row_data(obj)
|
|
|
|
# Build the row based on fixed_headers order
|
|
row = []
|
|
for header in fixed_headers:
|
|
val = row_data.get(header, '')
|
|
if isinstance(val, (datetime.datetime, datetime.date)):
|
|
val = val.strftime('%Y-%m-%d %H:%M:%S')
|
|
row.append(str(val))
|
|
|
|
# Add JSON data
|
|
flat_info = {}
|
|
if obj.signup_info and isinstance(obj.signup_info, dict):
|
|
flat_info = flatten_json(obj.signup_info)
|
|
|
|
for key in json_keys:
|
|
val = flat_info.get(key, '')
|
|
if val is None:
|
|
val = ''
|
|
row.append(str(val))
|
|
|
|
writer.writerow(row)
|
|
|
|
return response
|
|
|
|
export_signups_csv.short_description = "导出选中报名记录为 CSV (含发货/详细信息)"
|
|
|
|
def export_signups_excel(modeladmin, request, queryset):
|
|
"""
|
|
Export selected signups to Excel, including flattened JSON fields
|
|
"""
|
|
try:
|
|
from openpyxl import Workbook
|
|
except ImportError:
|
|
modeladmin.message_user(request, "请先安装 openpyxl 库以使用 Excel 导出功能", 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
|
|
ws.title = str(opts.verbose_name)[:31] # Sheet name limit is 31 chars
|
|
|
|
# Fixed headers
|
|
fixed_headers = [
|
|
'ID', '活动标题', '状态', '报名时间',
|
|
'用户ID', '用户昵称', '用户OpenID', '用户绑定手机', '用户地区', '用户注册时间',
|
|
'关联订单ID', '订单状态', '收货人姓名', '收货电话', '收货地址', '快递公司', '快递单号', '订单总价', '商户订单号'
|
|
]
|
|
|
|
# Get dynamic JSON keys
|
|
json_keys = get_signup_info_keys(queryset)
|
|
|
|
# Write header
|
|
ws.append(fixed_headers + json_keys)
|
|
|
|
# Write data
|
|
for obj in queryset:
|
|
row_data = get_row_data(obj)
|
|
|
|
row = []
|
|
for header in fixed_headers:
|
|
val = row_data.get(header, '')
|
|
# Excel handles datetime natively, but we need to remove timezone info if present to avoid Excel errors or warnings depending on version
|
|
# Here we convert to naive datetime for simplicity or keep as is if library handles it.
|
|
# openpyxl supports datetime, but usually it's safer to remove tzinfo for compatibility.
|
|
if isinstance(val, (datetime.datetime, datetime.date)):
|
|
if hasattr(val, 'replace'):
|
|
val = val.replace(tzinfo=None)
|
|
|
|
# Ensure None becomes empty string
|
|
if val is None:
|
|
val = ""
|
|
row.append(val)
|
|
|
|
# Add JSON data
|
|
flat_info = {}
|
|
if obj.signup_info and isinstance(obj.signup_info, dict):
|
|
flat_info = flatten_json(obj.signup_info)
|
|
|
|
for key in json_keys:
|
|
val = flat_info.get(key, '')
|
|
if val is None:
|
|
val = ''
|
|
row.append(str(val)) # JSON values as strings
|
|
|
|
ws.append(row)
|
|
|
|
wb.save(response)
|
|
return response
|
|
|
|
export_signups_excel.short_description = "导出选中报名记录为 Excel (含发货/详细信息)"
|