将 Django 模型中的保存方法覆盖为使用 celery 异步的最佳实践

Posted

技术标签:

【中文标题】将 Django 模型中的保存方法覆盖为使用 celery 异步的最佳实践【英文标题】:best practice to override saving method in django model to be async using celery 【发布时间】:2014-03-20 14:06:14 【问题描述】:

我正在构建一个云系统,我有两个应用程序, 包含完整功能的服务器应用程序和仅包含输入法的客户端应用程序, 所以我在客户分支中安装客户端应用程序作为本地应用程序,

我想在本地保存模型后覆盖应用程序中的任何模型,我将调用 celery 任务将此模型添加到队列中以确保它会到达,即使互联网关闭,我也会重试直到互联网起床了,

现在我希望最好的做法是对任何模型都有一个通用的方法

我有两个选择

1- 像这样覆盖保存方法

def save(self, *args, **kwargs):
    super(Model, self).save(*args, **kwargs)
    save_task.delay(self)

或使用这样的信号

post_save.connect(save-task.delay, sender=Model)

哪一个是最佳实践,我可以使它适用于该项目的所有模型?

【问题讨论】:

似乎在您将我的答案标记为正确后的一段时间内,您为此打开了一笔赏金。我很想知道我的答案中遗漏了什么,也许可以尝试自己赢得那笔赏金。 【参考方案1】:

.save() 只是一堆一个接一个地执行的信号。这是来自the documentation的流程的简化版本:

    发出预保存信号。 [...]

    预处理数据。 [...] 大多数字段不进行预处理 [...] 仅用于具有特殊行为的字段 [...] 文档还没有包含所有字段的列表 “特殊行为。”

    为数据库准备数据。 要求每个字段以可写入到 数据库。大多数字段不需要数据准备 [...] 整数和字符串是 “准备写”作为 Python 对象 [...] 复杂的数据类型通常 需要一些修改。 [...]

    将数据插入数据库。 [...]

    发出保存后信号。 [...]

在您的情况下,您在该过程的中间没有做任何事情。您只需要在模型已保存后执行此操作。所以不需要使用信号。

现在您真正要问的是如何确保最终执行任务。嗯:

    我很确定你可以用 celery 解决这个问题 您应该将应用程序连接到单个数据库(如果可以的话),不要在本地保存内容然后更新服务器,这可能会变得很丑。

但是,如果您确实认为互联网很可能会出现故障或类似情况,并且您确定没有更好的方法来链接您的应用,我建议您添加一个跟踪更新内容的新模型。像这样的:

class Track(models.Model):
    modelname = models.CharField(max_length=20)
    f_pk = models.IntegerField()
    sent = models.BooleanField()

    def get_obj(self):
        try:
            # we want to do modelname.objects.get(pk=self.f_pk), so:
            return getattr( getattr(self.modelname, 'objects'), 'get')(pk=self.f_pk)
        except:
            return False

请注意,我没有将它链接到某个模型,而是为它提供了工具来获取任何你该死的模型。然后,对于您要跟踪的每个模型,添加以下内容:

class myModel(models.Model):
    ...
    def save(self, *args, **kwargs):
        super(Model, self).save(*args, **kwargs)
        t = Track(modelname=self.__class__.__name__, f_pk=self.pk, sent=False)
        t.save()

然后安排一个任务,将 Track 对象与 sent=False 并尝试保存它们:

unsent = Track.objects.filter(sent=False)
for t in unsent:
     obj = t.get_obj()
     # check if this object exists on the server too
     # if so:
         t.sent = True
         t.save()

附言

还记得我说过事情会变得丑陋吗?自从我发布这篇文章以来已经有一段时间了,我已经知道如何了。请注意我如何使用 pk 和模型名称来确定模型是否保存在两个位置,对吗? 但是,pk 是(默认情况下在 django 中)一个自动递增的字段。如果应用程序在两个地方运行,或者即使您在本地运行它并且发生了一次错误,那么 pks 很快就会不同步。

假设我保存了一次对象,它在本地和服务器上的 pk 均为 1。

local             server
name    pk   ++   name    pk
obj1    1    ++   obj1    1

然后我保存了另一个,但互联网中断了。

local             server
name    pk   ++   name    pk
obj1    1    ++   obj1    1
obj2    2    ++

下次启动时,我添加了一个新对象,但这发生在计划任务运行之前。所以现在我的本地数据库有 3 个对象,我的服务器有 2 个,它们有不同的 pk,明白了吗?

local             server
name    pk   ++   name    pk
obj1    1    ++   obj1    1
obj2    2    ++   obj3    2
obj3    3    ++

在计划的任务运行之后,我们将拥有这个:

local             server
name    pk   ++   name    pk
obj1    1    ++   obj1    1
obj2    2    ++   obj3    2
obj3    3    ++   obj2    3

看看这有多容易失控?为了解决这个问题,每个被跟踪的模型必须拥有某种唯一标识符,并且您需要以某种方式告诉Track 模型如何遵循它。真是头疼。最好不要在本地保存东西,而是将所有内容链接在一起

【讨论】:

这正是我要找的,我将创建一个可以接受泛型类型的模型,例如 admin logentry,将记录所有保存的实例,如果未发送此对象的状态,我将发送它然后更新日志扔芹菜,谢谢老兄

以上是关于将 Django 模型中的保存方法覆盖为使用 celery 异步的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

使用覆盖保存为抽象模型扩展 Django ModelForm

如何以 django 模型形式覆盖保存方法

django views.py 中的保存方法在模型中覆盖后不起作用,py

Django - 覆盖模型保存()

Django:覆盖保存(模型或管理部分)

Django:如何覆盖 form.save()?