python - Django:子进程连续输出到 HTML 视图
问题描述
我的 Django 应用程序中需要一个 HTML 网页来加载并在可滚动框中显示脚本的连续输出。这可能吗?
我目前正在使用子进程来运行 Python 脚本,但 HTML 页面要等到脚本完成后才会加载(这可能需要大约 5 分钟)。我希望用户看到正在发生的事情,而不仅仅是一个旋转的圆圈。
我已经在文本中使用“\n”卸载了脚本的完整输出;如果可能的话,我希望它输出每个新行。
我的代码如下:
视图.py:
def projectprogress(request):
GenerateProjectConfig(request)
home = os.getcwd()
project_id = request.session['projectname']
staging_folder = home + "/staging/" + project_id + "/"
output = ""
os.chdir(staging_folder)
script = home + '/webscripts/terraformdeploy.py'
try:
output = subprocess.check_output(['python', script], shell=True)
except subprocess.CalledProcessError:
exit_code, error_msg = output.returncode, output.output
os.chdir(home)
return render(request, 'projectprogress.html', locals())
项目进度.html:
<style>
div.ex1 {
background-color: black;
width: 900px;
height: 500px;
overflow: scroll;
margin: 50px;
}
</style>
<body style="background-color: #565c60; font-family: Georgia, 'Times New Roman', Times, serif; color: white; margin:0"></body>
<div class="ex1">
{% if output %}<h3>{{ output }}</h3>{% endif %}
{% if exit_code %}<h3> The command returned an error: {{ error_msg }}</h3>{% endif %}
</div>
<div class="container">
<a class="button button--wide button--white" href="home.html" title="Home" style="color: white; margin: 60px;">
<span class="button__inner">
Home
</span>
</a>
</div>
</body>
</html>
解决方案
您想要的是 websockets,或 Django 中已知的 Channels。
https://channels.readthedocs.io/en/latest/
这允许您将消息从后端发送到前端,而无需在前端拉消息或重新加载页面。
值得一提的是,您还可以将输出流式传输到多个客户端,并将命令发送回您的后端。
为您的代码量身定制的方法
请注意,这是未经测试的,因为我无法访问您的代码,因此您可能需要进行一些小的调整,但我相信提供的代码应该说明这个概念。
设置.py
INSTALLED_APPS = (
#Other installed Apps
'Channels',
)
CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgiref.inmemory.ChannelLayer",
"ROUTING": "django_channels.routing.channel_routing",
},
}
routing.py(将文件添加到与 settings.py 相同的文件夹中)
from django_channels_app.consumers import message_ws, listener_add, listener_discconect
channel_routing = [
route("websocket.receive", message_ws),
route("websocket.disconnect", listener_discconect),
route("websocket.connect", listener_add),
]
在您的模块中:
import threading
from channels import Group
class PreserializeThread(threading.Thread):
def __init__(self, request, *args, **kwargs):
self.request = request
super(PreserializeThread, self).__init__(*args, **kwargs)
def run(self):
GenerateProjectConfig(request)
home = os.getcwd()
project_id = request.session['projectname']
staging_folder = home + "/staging/" + project_id + "/"
output = ""
os.chdir(staging_folder)
script = home + '/webscripts/terraformdeploy.py'
try:
output = subprocess.check_output(['python', script], shell=True)
Group("django_channels_group").send({
"text": output,
})
# NOTICE THIS WILL BLOCK;
# You could try the following, untested snippet
# proc = subprocess.Popen(['python', script], shell=True, #stdout=subprocess.PIPE)
#
# line = proc.stdout.readline()
# while line:
# line = proc.stdout.readline()
# Group("django_channels_group").send({
# "text": line,
# })
# Group("django_channels_group").send({
# "text": "Finished",
# })
except subprocess.CalledProcessError:
exit_code, error_msg = (
output.returncode,output.output)
os.chdir(home)
def listener_add(message):
Group("django_channels_group").add(
message.reply_channel)
def listener_discconect(message):
Group("django_channels_group").discard(
message.reply_channel)
def message_ws(message):
Group("django_channels_group").send({
"text": "My group message",
})
def projectprogress(request):
ProgressThread(request).start()
return render(request, 'projectprogress.html', locals())
html
<style>
div.ex1 {
background-color: black;
width: 900px;
height: 500px;
overflow: scroll;
margin: 50px;
}
</style>
<body style="background-color: #565c60; font-family: Georgia, 'Times New Roman', Times, serif; color: white; margin:0"></body>
<div id="output">
</div>
<div class="container">
<a class="button button--wide button--white" href="home.html" title="Home" style="color: white; margin: 60px;">
<span class="button__inner">
Home
</span>
</a>
</div>
</body>
</html>
<script>
socket = new WebSocket("ws://127.0.0.1:8000/"); #Or your server IP address
socket.onmessage = function(e) {
const data = JSON.parse(e.data);
document.querySelector('#ouput').value += (data.message + '\n');
}
socket.onopen = function() {
socket.send("Test message");
}
</script>
更通用的答案
后端:
聊天/consumers.py:
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
selfreturn render(request, 'projectprogress.html', locals()).channel_name
)
def send_message(self, event):
message = event['message']
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message
}))
我的网站/settings.py:
# Channels
ASGI_APPLICATION = 'mysite.routing.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
mysite/routing.py:
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chat.routing
application = ProtocolTypeRouter({
# (http->django views is added by default)
'websocket': AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns
)
),
})
聊天/路由.py:
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer),
]
前端:
<script>
const chatSocket = new WebSocket(
'ws://'
+ window.location.host
+ '/ws/chat/'
+ roomName
+ '/'
);
chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
document.querySelector('#chat-log').value += (data.message + '\n');
};
</script>
推荐阅读
- java - 如何通过反射获取和初始化字段?
- c++ - 执行 while 循环未按预期执行
- protractor - 如何在 Protractor 的 onPrepare 中同时使用函数和规范文件?
- typescript - Typescript 在不使用泛型类型的情况下推断泛型类型
- google-colaboratory - 安装谷歌驱动器时出现谷歌colab错误
- python - 即使使用说明,熊猫中的 SettingWithCopyWarning
- postgresql - liquibase 无法在分区表上创建索引和唯一约束
- reactjs - 传递空数组时如何将useState与Typescript一起使用
- vba - Application.ActiveExplorer.Selection 错误:数组索引超出范围
- android - 如何在 Kotlin Android 中将日期选择器微调器对话框月份的语言更改为印度尼西亚语?