我有一个使用Celery卸载某些任务的Django应用程序。主要是,它推迟了数据库表中某些字段的计算。
所以,我有一个tasks.py:
from models import MyModel
from celery import shared_task
@shared_task
def my_task(id):
qs = MyModel.objects.filter(some_field=id)
for record in qs:
my_value = #do some computations
record.my_field = my_value
record.save()
并在models.py
中 from django.db import models
from tasks import my_task
class MyModel(models.Model):
field1 = models.IntegerField()
#more fields
my_field = models.FloatField(null=True)
@staticmethod
def load_from_file(file):
#parse file, set fields from file
my_task.delay(id)
现在显然,由于循环导入(models
导入tasks
和tasks
导入models
),这不会起作用。
我暂时通过从my_task.delay()
调用views.py
解决了这个问题,但将模型逻辑保留在模型类中似乎是有意义的。有没有更好的方法呢?
答案 0 :(得分:12)
约书亚发布的解决方案非常好,但是当我第一次尝试时,我发现我的@receiver
装饰器没有效果。那是因为tasks
模块没有在任何地方导入,这是因为我使用了task auto-discovery。
然而,有另一种方法可以将tasks.py
与modules.py
分开。也就是说,任务可以按名称发送,并且不必在发送它们的过程中评估(导入):
from django.db import models
#from tasks import my_task
import celery
class MyModel(models.Model):
field1 = models.IntegerField()
#more fields
my_field = models.FloatField(null=True)
@staticmethod
def load_from_file(file):
#parse file, set fields from file
#my_task.delay(id)
celery.current_app.send_task('myapp.tasks.my_task', (id,))
send_task()
是Celery应用对象的一种方法。
在此解决方案中,take care of correct, predictable names对您的任务非常重要。
答案 1 :(得分:10)
在模型中,您可以在使用它之前导入它,而不是在文件开头导入my_task
。它将解决循环进口问题。
from django.db import models
class MyModel(models.Model):
field1 = models.IntegerField()
#more fields
my_field = models.FloatField(null=True)
@staticmethod
def load_from_file(file):
#parse file, set fields from file
from tasks import my_task # import here instead of top
my_task.delay(id)
或者,您也可以在tasks.py
中执行相同的操作。您可以在使用模型之前导入模型而不是开始模型。
<强>替代:强>
您可以使用send_task
方法来调用您的任务
from celery import current_app
from django.db import models
class MyModel(models.Model):
field1 = models.IntegerField()
#more fields
my_field = models.FloatField(null=True)
@staticmethod
def load_from_file(file):
#parse file, set fields from file
current_app.send_task('myapp.tasks.my_task', (id,))
答案 2 :(得分:7)
只是为了在这个列表中再添一个不太好的解决方案,我最终做的就是依靠django's now-built-in app registry。
因此,在tasks.py
中,您可以使用apps.get_model()
来访问模型,而不是从模型中导入。
我使用一个带有健康文档的帮助方法来表达这一点,这只是为了表达这种痛苦的原因:
from django.apps import apps
def _model(model_name):
"""Generically retrieve a model object.
This is a hack around Django/Celery's inherent circular import
issues with tasks.py/models.py. In order to keep clean abstractions, we use
this to avoid importing from models, introducing a circular import.
No solutions for this are good so far (unnecessary signals, inline imports,
serializing the whole object, tasks forced to be in model, this), so we
use this because at least the annoyance is constrained to tasks.
"""
return apps.get_model('my_app', model_name)
然后:
@shared_task
def some_task(post_id):
post = _model('Post').objects.get(pk=post_id)
你当然可以直接使用apps.get_model()
。
答案 3 :(得分:5)
使用信号。
tasks.py
from models import MyModel, my_signal
from celery import shared_task
from django.dispatch import receiver
@shared_task
def my_task(id):
qs = MyModel.objects.filter(some_field=id)
for record in qs:
my_value = #do some computations
record.my_field = my_value
record.save()
@receiver(my_signal)
def my_receiver(sender, **kwargs):
my_task.delay(kwargs['id'])
models.py
from django.db import models
from tasks import my_task
from django.dispatch import Signal
my_signal = Signal(providing_args=['id'])
class MyModel(models.Model):
field1 = models.IntegerField()
#more fields
my_field = models.FloatField(null=True)
@staticmethod
def load_from_file(file):
#parse file, set fields from file
my_signal.send(sender=?, id=?)