在django应用程序中,我正在运行异步任务,并希望向用户显示进度,错误等。如果存在错误,则应将用户重定向到需要其他输入或某些操作来解决问题的页面。从芹菜工作到前端沟通的最佳方式是什么?
这是伪代码的基本结构:
# views.py
from tasks import run_task
def view_task():
run_task.delay()
return render(request, 'template.html')
# tasks.py
from compute_module import compute_fct
@shared_task
def run_task():
result = compute_fct()
# how to catch status update messages from compute_module while compute_fct is running??
if result == 'error':
handle_error()
else:
handle_succes()
# compute_module
import pandas as pd
def compute_fct():
# send message: status = loading file
df = pd.read_csv('test.csv')
# send message: status = computing
val = df['col'].mean()
if val is None:
return {'status':'error'}
else:
return {'status':'success','val':val}
我理想的是:
compute_module.py
模块使用python本机记录器。通过职责分离,我希望尽可能保持日志记录的通用性,并使用标准的python / django记录器。但它们似乎并不是为了向前端发送消息而设计的。 芹菜工人和前端之间可能有标准的沟通方式,我不知道。这种情况必须经常发生,我很惊讶它实施起来很困难。在某种程度上,应该为此设计rabbitmq消息队列或aws sns。下面是我看过的资源,但感觉不到其中任何一个都很好,但也许我只是困惑。
日志记录:这似乎更多是关于在服务器端登录,而不是向用户发送消息
Celery cam似乎是关于管理员监控任务,而不是向用户发送消息
推动我喜欢,但我不想让compute_module.py
处理它。那就是例如我不想在compute_module.py
内部进行任何pusher.com集成。猜猜我可以传递一个已经实例化的推送器对象,这样模块就可以只推送消息了但我又希望它是通用的
答案 0 :(得分:0)
我设法获得实时状态的唯一方法是简单地将一些SQL写入/ api调用放入任务本身。使用任务的返回值更容易,因为您可以编写自定义任务类。
我不完全确定使用Django是如何工作的,但它看起来应该是这样的。
class CustomTask(celery.Task):
def __call__(self, *args, **kwargs):
self.start_time = time.time()
def on_success(self, retval, task_id, args, kwargs):
do_success_stuff()
def on_failure(self, exc, task_id, args, kwargs, einfo):
do_failure_stuff()
@shared_task(base=CustomTask)
def do_stuff():
return create_widgets()
完整列表可在此处找到: http://docs.celeryproject.org/en/latest/userguide/tasks.html#handlers
答案 1 :(得分:0)
编辑:现在转移到django-channels,运行良好但比下面的解决方案更复杂。
一个:
好的,下面是关于我现在如何解决它的伪代码。基本上我使用https://pusher.com/docs/javascript_quick_start并且服务器端将实例化的对象传递到compute_module
。一个缺点是推送消息是短暂的,所以我将不得不在LogPusher
中做一些额外的工作来将它们存储在数据库中,这是另一天...
同样在我的实际实现中,我通过$.post()
中的$(document).ready()
ajax调用触发任务,因为小任务完成得太快,用户永远不会看到推送消息,因为连接尚未建立(回到那个历史性的消息问题)。
我上面没有提到的另一条路线是https://channels.readthedocs.io/en/latest/
# views.py
from tasks import run_task
def view_task():
run_task.delay('event')
return render(request, 'template.html', 'pusher_event':'event')
# tasks.py
import pusher
from django.conf import settings
from compute_module import compute_fct
class LogPusher(object):
def __init__(self, event):
self.pusher_client = pusher.Pusher(app_id=settings.PUSHER_APP_ID,
key=settings.PUSHER_KEY,
secret=settings.PUSHER_SECRET,
cluster=settings.PUSHER_CLUSTER, ssl=True)
self.event = event
def send(self, data):
self.pusher_client.trigger(settings.PUSHER_CHANNEL, self.event, json.dumps(data))
@shared_task
def run_task(pusher_event):
log_pusher = LogPusher(pusher_event)
result = compute_fct(log_pusher)
# how to catch status update messages from compute_module while compute_fct is running??
if result == 'error':
log_pusher.send('status':'error')
else:
log_pusher.send('status':'success')
# compute_module.py
import pandas as pd
def compute_fct(log_pusher):
# send message: status = loading file
log_pusher.send('status':'loading file')
df = pd.read_csv('test.csv')
# send message: status = computing
log_pusher.send('status':'computing')
val = df['col'].mean()
if val is None:
return {'status':'error'}
else:
return {'status':'success','val':val}
# context_processors.py
# see https://stackoverflow.com/questions/433162/can-i-access-constants-in-settings-py-from-templates-in-django
from django.conf import settings
def pusher(request):
return {'PUSHER_KEY': settings.PUSHER_KEY, 'PUSHER_CLUSTER': settings.PUSHER_CLUSTER , 'PUSHER_CHANNEL': settings.PUSHER_CHANNEL }
# template.html
<script>
var pusher = new Pusher("{{PUSHER_KEY}}", {
cluster: "{{PUSHER_CLUSTER}}",
encrypted: true
});
var channel = pusher.subscribe("{{PUSHER_CHANNEL}}");
channel.bind("{{pusher_event}}", function(data) {
// process data
});
</script>