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

Django扫描项目:修改Fetch请求实现模态框与扫描后跳转

问题解决:Django扫描任务异步处理+前端交互优化

首先说你的线程写法问题——这完全不正确,还有几个核心坑需要修正:

  1. 视图返回值错误:x.start()返回的是None,但Django视图必须返回HttpResponse(或其子类如JsonResponse),返回None会直接导致服务器500错误。
  2. messages框架在后台线程不可用:Django的messages和当前请求上下文绑定,当视图返回响应后,请求对象会被回收,后台线程里的request已经失效,消息根本存不到用户session里。
  3. 线程管理风险:普通线程在Django生产环境中不可靠(服务器重启/进程回收会直接杀死线程),开发阶段可以临时用,但生产建议用Celery这类任务队列。

第一步:修正后端视图逻辑

我们需要让启动扫描的视图立刻返回响应,后台线程完成后把状态存在session里,再通过一个状态查询接口让前端轮询结果:

import uuid
from django.contrib.sessions.backends.db import SessionStore
from django.http import JsonResponse

def free_scan_crop(request):
    # 改成POST请求(GET不适合传递表单数据)
    form = FreeScanForm(request.POST)
    if form.is_valid():
        file_name = form.cleaned_data['file_name']
        # 生成唯一任务ID,用来追踪扫描状态
        task_id = str(uuid.uuid4())
        # 初始化任务状态为pending
        request.session[f'scan_task_{task_id}'] = 'pending'
        
        # 启动守护线程(避免服务器退出时残留线程)
        scan_thread = threading.Thread(
            target=scan_crop, 
            args=(request.session.session_key, task_id, file_name, top_left_x, top_left_y, bottom_right_x, bottom_right_y, dpi),
        )
        scan_thread.daemon = True
        scan_thread.start()
        
        # 返回任务ID给前端,用于轮询
        return JsonResponse({"status": "started", "task_id": task_id})
    # 表单验证失败的情况
    return JsonResponse({"status": "error", "message": "Invalid form data"})

def scan_crop(session_key, task_id, file_name, top_left_x, top_left_y, bottom_right_x, bottom_right_y, dpi):
    # 手动加载session(线程里没有请求上下文)
    session = SessionStore(session_key=session_key)
    try:
        image = ScannerServiceConnection().scan_roi(
            top_left_x, top_left_y, bottom_right_x, bottom_right_y, dpi
        )
        if image is None:
            # 扫描失败,更新状态
            session[f'scan_task_{task_id}'] = {
                "status": "failed", 
                "message": "Check scanner service status"
            }
        else:
            image.save('<你的文件路径>')
            session[f'scan_task_{task_id}'] = {
                "status": "success", 
                "message": "Image saved successfully"
            }
        session.save()  # 必须手动保存session
    except Exception as e:
        session[f'scan_task_{task_id}'] = {
            "status": "failed", 
            "message": f"Scan error: {str(e)}"
        }
        session.save()

# 新增:查询扫描状态的视图
def scan_status(request):
    task_id = request.GET.get('task_id')
    if not task_id:
        return JsonResponse({"status": "error", "message": "Missing task ID"})
    
    task_key = f'scan_task_{task_id}'
    if task_key in request.session:
        task_data = request.session[task_key]
        # 任务完成后清除session里的临时状态
        if task_data != 'pending':
            del request.session[task_key]
        return JsonResponse(task_data)
    return JsonResponse({"status": "error", "message": "Task not found"})

第二步:修改前端Fetch逻辑,实现模态框+跳转需求

先确保你的表单有id(比如<form id="scanForm">),然后替换JS代码:

<script>
const formElem = document.getElementById('scanForm');
// 初始化Bootstrap模态框实例
const scanModal = new bootstrap.Modal(document.getElementById('scanDocument'));

formElem.onsubmit = async (e) => {
    e.preventDefault();
    // 弹出扫描等待模态框
    scanModal.show();

    try {
        // 用POST请求,必须携带Django的CSRF令牌
        const response = await fetch("{% url 'core:free_scan' %}", {
            method: 'POST',
            body: new FormData(formElem),
            headers: {
                'X-CSRFToken': '{{ csrf_token }}'
            }
        });

        if (!response.ok) throw new Error('Failed to start scan');
        const result = await response.json();

        if (result.status === 'started') {
            // 每秒轮询一次扫描状态
            const pollInterval = setInterval(async () => {
                const statusRes = await fetch(`{% url 'core:scan_status' %}?task_id=${result.task_id}`, {
                    headers: {
                        'X-CSRFToken': '{{ csrf_token }}'
                    }
                });
                const statusResult = await statusRes.json();

                if (statusResult.status !== 'pending') {
                    clearInterval(pollInterval);
                    scanModal.hide(); // 关闭模态框

                    // 把消息存在localStorage,跳转到首页后显示
                    localStorage.setItem('scanMessage', JSON.stringify(statusResult));
                    window.location.href = "{% url 'core:home' %}";
                }
            }, 1000);
        } else {
            scanModal.hide();
            alert(result.message);
        }
    } catch (err) {
        scanModal.hide();
        alert('An error occurred while starting the scan');
        console.error(err);
    }
};
</script>

第三步:首页显示扫描结果消息

在首页模板(home.html)底部加这段JS,读取localStorage里的消息并显示:

<script>
window.addEventListener('load', () => {
    const scanMsg = localStorage.getItem('scanMessage');
    if (scanMsg) {
        const msgData = JSON.parse(scanMsg);
        // 创建Bootstrap Alert组件显示消息
        const alertDiv = document.createElement('div');
        alertDiv.className = `alert alert-${msgData.status === 'success' ? 'success' : 'danger'} alert-dismissible fade show`;
        alertDiv.innerHTML = `
            ${msgData.message}
            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
        `;
        // 把alert加到页面顶部
        document.body.insertBefore(alertDiv, document.body.firstChild);
        
        // 移除localStorage里的消息
        localStorage.removeItem('scanMessage');
        
        // 可选:5秒后自动关闭alert
        setTimeout(() => alertDiv.remove(), 5000);
    }
});
</script>

最后补充几个注意点:

  • 别忘了在urls.py里注册新增的scan_status视图
  • 生产环境不要用普通线程,建议替换成Celery+Redis/RabbitMQ处理后台任务
  • 如果扫描任务特别长,轮询可以改成WebSocket(比如用Django Channels),但轮询对新手更友好

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 22:43:10