如何将Django对象发送到Celery任务?

12

你好,厉害的人们!

在我的问题之前,我试过这些 Stack Overflow 的帖子:

但它们都不能正常工作!

我想让网站上的用户了解新课程并更新。使用 Courses 的查询集,我想通过电子邮件将它们发送出去。

send_daemon_email.delay(instance=instance,all_courses=Course.objects.all())

而我的函数看起来像:

@shared_task
def send_daemon_email(instance,all_courses):
    ctx = {'instance':instance,'all_courses':all_courses}
    message = get_template("emails/ads.html").render(ctx)
    ''' '''

当我尝试将电子邮件发送给特定用户时,出现了以下错误:

<User: 名字> 无法进行 JSON 序列化

这是因为 celery 的 delay() 收到了非序列化数据。

我该如何向 celery 任务发送 Django 对象,以便在模板中使用它呢? 我知道可以将所需信息作为 Python 对象发送。

send_daemon_email.delay(first_name='Name',
      last_name='Lapr',all_courses = [{'title1':'title1',},{'title2':'title2',}])

但这会有太多的信息。

任何提示都将不胜感激。 谢谢!


1
你不能直接传递对象本身,因为它们不可序列化,但是你可以传递主键等其他信息。 - Willem Van Onsem
有了 pk,我可以在函数中检索对象?这是一个很好的观点。 - user9865749
2个回答

9

通常像 celery 这样的工具会使用一种格式来传递消息。这里使用的是 JSON,但并不是每个 Python 对象都可以默认转换成 JSON 对象。

但我们可以传递主键,然后在接收端再将其转换为对象。例如:

send_daemon_email.delay(
    instance=<b>instance.pk</b>,
    all_courses=<b>list(</b>Course.objects.all()<b>.values_list('pk', flat=True))</b>
)

然后在接收器的侧面,我们可以使用以下代码获取对象:

@shared_task
def send_daemon_email(instance,all_courses):
    ctx = {
        'instance': <b>User.objects.get(pk=instance)</b>,
        'all_courses': <b>Course.objects.filter(pk__in=all_courses)</b>
    }
    message = get_template("emails/ads.html").render(ctx)

当然,我们并不一定需要传递主键:任何可以JSON序列化的对象(或手动序列化)都可以使用。虽然我不会让它变得太复杂,通常简单的东西比更复杂的东西更好(这是Python的信条之一)。

谢谢! 我们查询两次。序列化数据可能更好。 - user9865749
3
如果您仅需要直接属性,则可以使用 model_to_dict。但是序列化也有成本:因为Python需要将数据转储并恢复为JSON流。 - Willem Van Onsem
这种方法存在的问题是,如果在工人选择任务之前对象已被更改,则工人将在更改后的对象上工作,而不是与任务实例化的相同对象。 - Coderji
肯定的,这取决于具体情况。在我的情况下,工作是根据客户的请求运行的,并且应该使用客户请求的数据进行处理。这非常棘手,因为如果您传递给celery任务的实例具有关系,而这些关系也有自己的关系,那么这将失控,并且不能简单地编码为JSON。 - Coderji
@Coderji:也许你可以使用 django-simple-history,并传递一个“历史实例”的引用:https://django-simple-history.readthedocs.io/en/latest/ 这样你就可以在任何时间点查看对象,从而基本上获取你将作业提交给工作者时的数据。 - Willem Van Onsem
显示剩余3条评论

9

Django对象无法在Celery任务中发送,您可以使用Django序列化程序(from django.core import serializers)以字段为模板提供所需字段,并且查询将像Django对象一样在模板中工作。

注意:使用序列化程序需要转储和加载数据

或者只需像以下方式将查询集转换为列表:

send_daemon_email.delay(
    instance = User.objects.filter(pk=user.pk).values('first_name','last_name'),
    all_courses= list(Course.objects.values('title','other_field'))
)

您只需要在模板中提供真正需要的字段,使用values('')进行操作。

@shared_task
def send_daemon_email(instance,all_courses):
    ctx = {
        'instance': instance,
        'all_courses': all_courses,
    }
    message = get_template("emails/ads.html").render(ctx)

在模板中,{% for course in all_courses %}{{course}}{% endfor %}将显示所有课程,{{ instance.first_name }}将显示用户的名字。

这是一个有趣的答案!让我试一试。 - user9865749
太好了!它有效了,values()比两次查询更好,@WillemVanOnsem的答案很好,更加详细。 - user9865749

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接