在执行之前,将Django Queryset序列化为JSON for Celery

时间:2017-06-01 03:19:05

标签: django celery django-queryset

我从外部库中获得了一个Queryset,并且 - 由于Querysets是惰性的 - 我想在访问它之前将它序列化为JSON ,然后在DB中执行它,所以它可以在异步Celery任务中执行。

我的问题是,有没有办法将Queryset的基本元素表示为JSON,所以我不必使用Pickle?

我知道我可以使用queryset.query获取原始查询,但由于我必须在另一端执行原始查询,所以我不太喜欢这个想法。

详细

此处的具体用例是评估任务中的Queryset,然后可以将结果导出到文件并转储到服务器上以供以后访问。

结果集可能非常大,因此作为标准请求/响应周期的一部分进行导出通常会超时。

请求是从Django Admin更改视图列表过滤器生成的。我试过回过头列表过滤器代码,但它似乎只是生成过滤器对象 - 不适合JSON序列化。

我可以查看列表过滤器查询字符串并将所有键重新评估到Queryset过滤器中,但许多过滤器都是SimpleListFilter类,它们也只返回过滤器对象,并且不会显示真实URL中的Queryset过滤器。我需要为这些过滤器重新创建逻辑,以获得可以JSON序列化的kwarg样式过滤器。

4 个答案:

答案 0 :(得分:2)

在撰写本文时,使用 Python 3.7+ 和 Django 2.2 pickle 转储/加载似乎可以工作。 最后只缺少一个 queryset.update()

我尝试了类似的东西:

# packing
queryset = MyModel.objects.filter(...)
serialized_queryset = pickle.dumps(queryset.query)

# unpacking
restored = MyModel.objects.all()
restored.query = pickle.loads(serialized_queryset)
restored.update()

而且它工作正常。尚未在 celery 任务中进行测试,但我希望它可以工作。

答案 1 :(得分:1)

更新Django 1.11&芹菜3.0

遗憾的是,这已经不再适用了。如果有人在执行之前有任何关于如何正确序列化查询集或其各种过滤器和属性的信息,请告诉我。

原始答案

事实证明my_queryset.query是在执行之前序列化QuerySet的正确而安全的方法。

  

如果您只想挑选必要的信息以便稍后从数据库重新创建QuerySet,挑选QuerySet的查询属性。然后,您可以使用以下代码重新创建原始QuerySet(不加任何结果):

import pickle
query = pickle.loads(s)     # Assuming 's' is the pickled string.
qs = MyModel.objects.all()
qs.query = query            # Restore the original 'query'.
  

query属性是一个不透明的对象。它表示查询构造的内部,不是公共API的一部分。但是,安全(并完全支持)按照此处所述腌制和取消对属性内容的修改

https://docs.djangoproject.com/en/1.11/ref/models/querysets/#pickling-querysets

这谈到了使用pickle,但它当然也适用于JSON序列化器。

# admin.py
...
    export_data.delay(queryset.query)
...

# tasks.py
@task(serializer='json')
def export_data(query):
    qs = MyModel.objects.all()
    qs.query = query
...

答案 2 :(得分:0)

您不必序列化QuerySet以在Celery任务中访问它。您可以使用Django模型并在任务中创建QuerySet。如果创建QuerySet的某种特定方式,您可以将QuerySet创建代码放在公共函数或类中,并在Web应用程序代码和Celery任务中导入此共享代码。

没有理由避免在Celery任务中使用Django ORM。

答案 3 :(得分:0)

您需要decodeencode查询。使用 Django 2.1 Celery 4.2 可以使用以下代码:

query_dump = pickle.dumps(queryset.query).decode('latin1')

query = pickle.loads(query_dump.encode('latin1'))