You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django 4中下载生成CSV文件同时传递模板上下文的实现方案

解决思路:同时实现CSV下载与处理状态提示

在Django中,同一个HTTP请求只能返回一个响应,所以你没法在返回CSV下载的同时直接把处理状态传递给模板渲染。下面提供三种可行的实现方案,按需选择:


方案一:AJAX预请求 + 触发下载

先通过AJAX请求获取处理状态,再触发CSV下载,能实现实时反馈,用户体验最好。

后端修改

from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.core.cache import cache
from django.http import JsonResponse
import uuid
from io import StringIO

class InvoiceView(TemplateView):
    model = Cost
    template_name = "pipeline/invoices_list.html"
    costs = Cost.objects.all()

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # 原有上下文逻辑
        return context

    @method_decorator(csrf_exempt)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

    def post(self, request, *args, **kwargs):
        # 第一步:请求处理状态与缓存CSV
        if "batch-pay-status" in request.POST:
            csv_content, batch_payment_status = create_batch_payment_file(self.costs)
            # 生成唯一缓存键,存储CSV内容(5分钟过期)
            csv_key = f"batch_csv_{uuid.uuid4().hex}"
            cache.set(csv_key, csv_content, timeout=300)
            return JsonResponse({
                "processing_status": batch_payment_status,
                "csv_key": csv_key
            })
        # 第二步:根据缓存键下载CSV
        elif "batch-pay-download" in request.POST:
            csv_key = request.POST.get("csv_key")
            csv_content = cache.get(csv_key)
            if not csv_content:
                return JsonResponse({"error": "文件已过期或不存在"}, status=400)
            response = HttpResponse(
                csv_content,
                content_type='text/csv',
                headers={'Content-Disposition': 'attachment; filename="WISE_BATCH_PAYMENT.csv"'},
            )
            cache.delete(csv_key)  # 下载后清理缓存
            return response
        return super().post(request, *args, **kwargs)

# 修改utils.py中的函数,返回CSV字符串而非HttpResponse
def create_batch_payment_file(costs):
    invoices = costs.filter(invoice_status__in=["REC", "REC2"])
    processing_status = {} 

    csv_buffer = StringIO()
    # 这里写CSV写入逻辑,例如:
    # import csv
    # writer = csv.writer(csv_buffer)
    # writer.writerow(["PO编号", "金额", "状态"])
    # for invoice in invoices:
    #     # 处理发票数据,写入行
    #     processing_status[invoice.po_number] = {"status": "success", "message": "已添加"}

    return csv_buffer.getvalue(), processing_status

前端模板修改

<button id="batch-pay-btn">生成批量付款文件</button>

<!-- Toast提示组件 -->
<div class="toast position-fixed bottom-0 end-0 m-3" id="processing-toast" role="alert" aria-live="assertive" aria-atomic="true">
  <div class="toast-header">
    <strong class="me-auto">处理状态</strong>
    <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
  </div>
  <div class="toast-body"></div>
</div>

<script>
document.getElementById('batch-pay-btn').addEventListener('click', async function() {
    // 1. 请求处理状态与缓存键
    const statusRes = await fetch(window.location.href, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'X-CSRFToken': '{{ csrf_token }}'
        },
        body: 'batch-pay-status=1'
    });
    const result = await statusRes.json();

    // 2. 显示处理状态Toast
    let toastHtml = '';
    for (const [poNum, status] of Object.entries(result.processing_status)) {
        toastHtml += `<p>${poNum}: ${status.status} - ${status.message}</p>`;
    }
    const toastEl = document.getElementById('processing-toast');
    toastEl.querySelector('.toast-body').innerHTML = toastHtml;
    new bootstrap.Toast(toastEl).show();

    // 3. 动态生成表单触发下载
    const downloadForm = document.createElement('form');
    downloadForm.method = 'POST';
    downloadForm.action = window.location.href;
    downloadForm.innerHTML = `
        <input type="hidden" name="batch-pay-download" value="1">
        <input type="hidden" name="csv_key" value="${result.csv_key}">
        <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
    `;
    document.body.appendChild(downloadForm);
    downloadForm.submit();
    document.body.removeChild(downloadForm);
});
</script>

方案二:Session存储状态 + 页面刷新

实现最简单,依赖Django的Session存储状态,下载后刷新页面读取状态。

后端修改

import json

class InvoiceView(TemplateView):
    model = Cost
    template_name = "pipeline/invoices_list.html"
    costs = Cost.objects.all()

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # 从Session读取状态并清空
        if self.request.session.get('show_batch_status'):
            context['batch_payment_status'] = self.request.session.pop('batch_payment_status', {})
            self.request.session.pop('show_batch_status')
        return context

    def post(self, request, *args, **kwargs):
        if "batch-pay-csv" in request.POST:
            response, batch_payment_status = create_batch_payment_template(self.costs)
            # 状态存入Session
            request.session['batch_payment_status'] = batch_payment_status
            request.session['show_batch_status'] = True
            # 下载后自动刷新页面
            response['Refresh'] = f'0; url={request.path}'
            return response
        return super().post(request, *args, **kwargs)

前端模板修改

<form method="POST" action="">
    {% csrf_token %}
    <button type="submit" name="batch-pay-csv">生成批量付款文件</button>
</form>

<!-- Toast提示组件 -->
<div class="toast position-fixed bottom-0 end-0 m-3" id="processing-toast" role="alert" aria-live="assertive" aria-atomic="true">
  <div class="toast-header">
    <strong class="me-auto">处理状态</strong>
    <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
  </div>
  <div class="toast-body"></div>
</div>

{% if batch_payment_status %}
<script>
document.addEventListener('DOMContentLoaded', function() {
    let toastHtml = '';
    {% for po_num, status in batch_payment_status.items %}
        toastHtml += `<p>{{ po_num }}: {{ status.status }} - {{ status.message }}</p>`;
    {% endfor %}
    const toastEl = document.getElementById('processing-toast');
    toastEl.querySelector('.toast-body').innerHTML = toastHtml;
    new bootstrap.Toast(toastEl).show();
});
</script>
{% endif %}

方案三:Cookie传递状态

无需AJAX和页面刷新,将状态存入Cookie,前端读取后显示提示(注意Cookie大小限制为4KB)。

后端修改

import json

class InvoiceView(TemplateView):
    # 原有属性...

    def post(self, request, *args, **kwargs):
        if "batch-pay-csv" in request.POST:
            response, batch_payment_status = create_batch_payment_template(self.costs)
            # 状态转JSON存入Cookie,1分钟过期
            response.set_cookie(
                'batch_payment_status',
                json.dumps(batch_payment_status),
                max_age=60,
                httponly=False  # 允许JS读取
            )
            return response
        return super().post(request, *args, **kwargs)

前端模板修改

<form method="POST" action="">
    {% csrf_token %}
    <button type="submit" name="batch-pay-csv">生成批量付款文件</button>
</form>

<!-- Toast提示组件 -->
<div class="toast position-fixed bottom-0 end-0 m-3" id="processing-toast" role="alert" aria-live="assertive" aria-atomic="true">
  <div class="toast-header">
    <strong class="me-auto">处理状态</strong>
    <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
  </div>
  <div class="toast-body"></div>
</div>

<script>
document.addEventListener('DOMContentLoaded', function() {
    // 读取Cookie中的状态
    const statusCookie = document.cookie.split('; ').find(row => row.startsWith('batch_payment_status='));
    if (statusCookie) {
        const batchStatus = JSON.parse(decodeURIComponent(statusCookie.split('=')[1]));
        // 渲染Toast
        let toastHtml = '';
        for (const [poNum, status] of Object.entries(batchStatus)) {
            toastHtml += `<p>${poNum}: ${status.status} - ${status.message}</p>`;
        }
        const toastEl = document.getElementById('processing-toast');
        toastEl.querySelector('.toast-body').innerHTML = toastHtml;
        new bootstrap.Toast(toastEl).show();
        // 删除Cookie避免重复显示
        document.cookie = 'batch_payment_status=; Max-Age=0; path=/';
    }
});
</script>

内容的提问来源于stack exchange,提问作者jconnorbuilds

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 00:54:59