Python3.6.3+Django2.0.3:如何在模板中嵌入StreamingHttpResponse实时展示任务输出?
实现实时展示耗时任务输出的方案
嘿,作为Django新手遇到这种实时输出的需求确实有点绕,不过咱们可以用SSE(Server-Sent Events)+ 后台线程的方案来实现,刚好适配你的Django 2.0.3版本,不需要太复杂的第三方库。下面一步步来:
1. 改造独立Python脚本
首先得让你的耗时脚本把输出写到一个能被Django实时读取的地方,比如临时文件。修改你的脚本,让它把内容输出到指定文件(行缓冲模式保证实时写入):
# long_task.py(你的独立脚本) import time def run_task(output_file_path): # buffering=1 开启行缓冲,确保print内容实时写入文件 with open(output_file_path, 'w', buffering=1) as f: for i in range(10): progress_msg = f"任务进度:{i*10}%" print(progress_msg) f.write(f"{progress_msg}\n") time.sleep(1) f.write("任务执行完成!\n") if __name__ == "__main__": import sys if len(sys.argv) > 1: run_task(sys.argv[1])
2. Django视图实现
需要两个视图:一个用来触发任务(启动后台线程跑脚本),另一个用来通过SSE推送实时输出。在你的app的views.py中添加:
import os import tempfile import threading import subprocess from django.http import HttpResponse, HttpResponseBadRequest from django.shortcuts import render # 触发耗时任务的视图 def trigger_task(request): if request.method == 'POST': # 为当前用户创建唯一临时文件 temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False) temp_file.close() # 将临时文件路径存入session,关联用户与任务输出 request.session['task_output_file'] = temp_file.name # 用后台线程启动脚本,避免阻塞主请求 def run_script(): # 替换成你的脚本实际路径 subprocess.run(['python', './long_task.py', temp_file.name]) # 任务完成后可选择删除临时文件,避免磁盘占用 # os.unlink(temp_file.name) # 启动守护线程,Django进程结束时线程自动退出 threading.Thread(target=run_script, daemon=True).start() # 跳转到进度展示页面 return render(request, 'task_progress.html') return HttpResponseBadRequest("仅接受POST请求") # SSE实时推送视图 def task_stream(request): output_file = request.session.get('task_output_file') if not output_file or not os.path.exists(output_file): return HttpResponse("无运行中任务或文件不存在", status=404) # 设置SSE响应头 response = HttpResponse(content_type='text/event-stream') response['Cache-Control'] = 'no-cache' response['Connection'] = 'keep-alive' # 实时读取文件内容并推送 def stream_content(): with open(output_file, 'r') as f: # 先推送已有的内容 existing_content = f.read() if existing_content: # 替换换行符为<br>适配HTML展示 yield f"data: {existing_content.replace('\n', '<br>')}\n\n" # 监听文件新增内容 while True: new_line = f.readline() if new_line: yield f"data: {new_line.strip().replace('\n', '<br>')}\n\n" # 检测任务完成,终止流 if "任务执行完成!" in new_line: break return HttpResponse(stream_content(), content_type='text/event-stream')
3. 模板修改(适配你的{{ stream }}位置)
修改你的进度展示模板(比如命名为task_progress.html),添加SSE前端代码来接收并更新输出:
{% load pagePieces %} {% page_header %} <div class="container"> <div class="row"> <!-- 原模板内容保留,替换{{ stream }}为带ID的容器 --> <div id="stream">任务启动中...</div> </div> </div> <script> // 初始化SSE连接,拉取实时输出 const eventSource = new EventSource("{% url 'task_stream' %}"); // 接收服务器推送的内容并更新页面 eventSource.onmessage = function(event) { const streamDiv = document.getElementById('stream'); streamDiv.innerHTML = event.data; }; // 处理连接错误或任务完成 eventSource.onerror = function(error) { console.error('SSE连接出错:', error); eventSource.close(); const streamDiv = document.getElementById('stream'); streamDiv.innerHTML += "<br>连接已关闭或任务完成。"; }; </script>
4. 配置路由
在项目的urls.py中添加视图路由:
from django.urls import path from your_app_name import views # 替换为你的app名称 urlpatterns = [ # 其他路由... path('trigger-task/', views.trigger_task, name='trigger_task'), path('task-stream/', views.task_stream, name='task_stream'), ]
注意事项
- 临时文件管理:任务完成后可以手动删除临时文件,或者添加定时清理逻辑,避免磁盘占用。
- 线程安全性:如果是多用户场景,用session关联用户和临时文件能避免输出混淆。
- 兼容性:SSE支持大部分现代浏览器,若需兼容IE可考虑用WebSocket替代,但复杂度会更高。
- 脚本路径:
subprocess.run中的脚本路径要写对,建议用绝对路径避免问题。
内容的提问来源于stack exchange,提问作者ashja99
相关产品推荐
相关产品推荐

