Django扫描项目:修改Fetch请求实现模态框与扫描后跳转
问题解决:Django扫描任务异步处理+前端交互优化
首先说你的线程写法问题——这完全不正确,还有几个核心坑需要修正:
- 视图返回值错误:
x.start()返回的是None,但Django视图必须返回HttpResponse(或其子类如JsonResponse),返回None会直接导致服务器500错误。 messages框架在后台线程不可用:Django的messages和当前请求上下文绑定,当视图返回响应后,请求对象会被回收,后台线程里的request已经失效,消息根本存不到用户session里。- 线程管理风险:普通线程在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
相关产品推荐
相关产品推荐

