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

Django生成zip完成后自动刷新HTML页面及CSRF、按钮问题解决

问题根因
  • 连续提交触发CSRF报错:直接返回文件流的响应不会触发页面刷新,页面内嵌的CSRF token始终是首次加载时的旧值,下次提交时后端校验新旧token不匹配就会抛错,手动刷新相当于重新拉取了带最新token的页面,所以能临时解决问题。
  • 绑定onclick后后端无响应:在按钮的click事件里直接禁用按钮,会阻断表单默认的提交行为,请求根本没有发送到后端。
  • 无法实现下载后自动刷新:原生同步表单提交返回文件流时,前端没有对应的完成回调钩子,没法感知下载结束的时机。

解决方案

一、后端views.py调整

不要在表单POST的同步响应里直接返回文件流,先把生成的zip存入临时缓存,给前端返回下载标识,单独提供下载接口返回文件,去掉多余的手动设置CSRF cookie逻辑(Django自带中间件会自动处理CSRF cookie,手动设置签名错误反而会导致校验失败)。
修正后的代码如下:

from django.shortcuts import render
from django.http import HttpResponse, JsonResponse
from .forms import FileFieldForm
from .perform_conversion import FileConverter
import zipfile
import io
import uuid
from django.core.cache import cache

def FileFieldFormView(request, *args, **kwargs):
    if request.method == 'POST':
        form = FileFieldForm(request.POST, request.FILES)
        files = request.FILES.getlist('file_field')
        if form.is_valid():
            zip_buffer = io.BytesIO()   
            with zipfile.ZipFile(zip_buffer, "w", False) as zip_file:
                for f in files:
                    fileconverter = FileConverter(f.name)
                    fileconverter.run(f.file)
                    for img_name, img in fileconverter.output.items():
                        data = io.BytesIO(img)
                        zip_file.writestr(img_name, data.getvalue())
            # 生成唯一下载ID,zip内容存入缓存10分钟过期
            download_id = str(uuid.uuid4())
            cache.set(f"zip_download_{download_id}", zip_buffer.getvalue(), 600)
            return JsonResponse({
                "code": 200,
                "download_id": download_id
            })
        else:
            return JsonResponse({"code": 400, "msg": "表单校验失败,请检查上传文件"}, status=400)
    # GET请求正常渲染页面
    return render(request, 'file_converter.html')

# 新增独立下载接口
def zip_download(request, download_id):
    zip_content = cache.get(f"zip_download_{download_id}")
    if not zip_content:
        return HttpResponse("下载链接已过期,请重新提交文件", status=404)
    # 下载后删除缓存避免占用内存
    cache.delete(f"zip_download_{download_id}")
    response = HttpResponse(zip_content, content_type='application/octet-stream')
    response['Content-Disposition'] = "attachment; filename=converted.zip"
    return response

别忘了在urls.py中新增下载路由,对应zip_download视图,示例:path('download/<str:download_id>/', zip_download, name='zip_download')

二、前端代码调整

核心逻辑:

  • 拦截原生表单提交事件,改用异步请求发送表单数据,方便感知请求完成时机
  • 点击提交后立刻禁用按钮、显示处理提示,从根源上避免重复提交
  • 拿到后端返回的下载ID后,动态创建a标签触发文件下载
  • 下载触发后自动刷新页面,页面重载后CSRF token自动更新为最新值,按钮也自动恢复可用状态

替换原有HTML/JS代码即可:

{% load static %}
<html>
<head>
    <link rel="stylesheet" href="{% static '/styleSheet.css' %}">
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;700&display=swap" rel="stylesheet">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
   <form enctype="multipart/form-data" id="convertForm" method="post">
    {% csrf_token %}
    <div class="main_Body">
        <div class="section">
            <h1>Fileconverter</h1>
            <br>
            <label for="file_field" class="custom-file-upload">
                <i class="fa fa-cloud-upload"></i>Upload File(s)</label>
            <input type="file" id="file_field" name="file_field" class="file-upload_button" multiple>
            <label id="file_name"></label>
            <br><br><br><br>
            <br>
            <input type="submit" class="file-submit__button" id="submitButton" value="提交转换">
            <p id="tipMsg" style="color:red;display:none;">文件处理中,请不要关闭页面...</p>
        </div>
    </div>
</form> 
</body>
<footer>
    <p>点击"选择文件"选中需要转换的文件</p>
    <p>点击"提交"后开始转换任务</p>
    <p>转换完成后浏览器会自动下载zip压缩包</p>
    <p>处理过程中请勿重复点击提交按钮</p>
</footer>
</html>

<script>
// 选中文件后显示文件名
document.querySelector("#file_field").onchange = function(){
    if(this.files.length > 0){
        let fileNameList = Array.from(this.files).map(f => f.name).join(', ');
        document.querySelector("#file_name").textContent = fileNameList;
    }
}

// 拦截表单提交逻辑
const form = document.getElementById('convertForm');
const submitBtn = document.getElementById('submitButton');
const tipMsg = document.getElementById('tipMsg');

form.addEventListener('submit', function(e){
    // 阻断原生表单提交
    e.preventDefault();
    // 立刻禁用按钮、显示提示,防止重复点击
    submitBtn.disabled = true;
    tipMsg.style.display = 'block';

    // 组装表单数据发异步请求
    const formData = new FormData(form);
    fetch("", {
        method: "POST",
        body: formData,
        credentials: "same-origin"
    }).then(res => res.json())
    .then(res => {
        if(res.code === 200){
            // 触发文件下载
            const a = document.createElement('a');
            a.href = `/download/${res.download_id}/`;
            a.download = "converted.zip";
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            // 下载触发后自动刷新页面,重置表单和CSRF token
            window.location.reload();
        }else{
            alert(res.msg || "转换失败,请重试");
            submitBtn.disabled = false;
            tipMsg.style.display = 'none';
        }
    }).catch(err => {
        alert("请求出错,请重试");
        submitBtn.disabled = false;
        tipMsg.style.display = 'none';
    })
})
</script>

三、注意事项

  • 如果是多进程/多服务器部署,临时缓存不要用Django默认的本地内存缓存,换成Redis等集中式缓存,避免下载时找不到对应zip文件
  • 原代码中X-UA-Compatible属性写的IE=edstore是笔误,已修正为IE=edge,避免IE兼容模式异常
  • 如果单文件转换耗时超过1分钟,建议额外增加转换进度提示,避免用户误以为页面卡顿

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 15:48:44