Django"模仿"批量插入/更新/删除时的数据库触发器行为

时间:2014-05-19 18:32:42

标签: python django postgresql triggers models

这是一个自我贬低的问题,但我们走了。 我在Django创建了一个商业应用程序,我并不想"传播"应用程序和数据库中的所有逻辑,但另一方面,我不想让数据库处理此任务(通过使用Triggers可能)。

所以我想"重现"数据库触发器的行为,但在Django的模型类中(嗯当前正在使用Django 1.4)。

经过一些研究,我发现用单个物体,我可以覆盖"保存"和"删除"方法" models.Model"在"之前插入" """"钩子所以它们可以在父母的保存/删除之前和之后执行。像这样:

     class MyModel(models.Model):

         def __before(self):
             pass

         def __after(self):
            pass

         @commit_on_success #the decorator is only to ensure that everything occurs inside the same transaction
         def save(self, *args, *kwargs):
             self.__before()
             super(MyModel,self).save(args, kwargs)
             self.__after()

BIG问题在于批量操作。当运行" update()" /" delete()"时,Django不会触发模型的保存/删除。来自它的QuerySet。 Insted,它使用QuerySet自己的方法。最糟糕的是,它也不会触发任何信号。

修改: 更具体一点:视图中的模型加载是动态的,因此无法定义特定模型"办法。在这种情况下,我应该创建一个抽象类并在那里处理它。

我的最后一次尝试是创建一个自定义管理器,并在此自定义管理器中覆盖更新方法,循环查询集内的模型,然后触发" save()"每个模型(考虑上面的实现,或"信号"系统)。它有效,但会导致数据库超载" (想象一下10k行的查询集正在更新)。

2 个答案:

答案 0 :(得分:1)

首先,您可以使用内置的__before__after pre_save和{{{}来代替保存以添加post_save,pre_delete,方法。 1}}信号。 https://docs.djangoproject.com/en/1.4/topics/signals/

post_delete
当您在查询集上调用from django.db.models.signals import post_save class YourModel(models.Model): pass def after_save_your_model(sender, instance, **kwargs): pass # register the signal post_save.connect(after_save_your_model, sender=YourModel, dispatch_uid=__file__) 时,

pre_deletepost_delete将被触发。

对于批量更新,您必须手动调用自己想要触发的功能。你也可以在交易中把它全部扔掉。

如果您正在使用动态模型,要调用正确的触发功能,您可以检查模型的ContentType。例如:

delete()

答案 1 :(得分:0)

有几点需要注意,您可以覆盖查询集的update方法来触发信号,同时仍然使用SQL UPDATE语句:

from django.db.models.signals import pre_save, post_save

def CustomQuerySet(QuerySet):
    @commit_on_success
    def update(self, **kwargs):
        for instance in self:
            pre_save.send(sender=instance.__class__, instance=instance, raw=False, 
                          using=self.db, update_fields=kwargs.keys())
        # use self instead of self.all() if you want to reload all data 
        # from the db for the post_save signal
        result = super(CustomQuerySet, self.all()).update(**kwargs)
        for instance in self:
            post_save.send(sender=instance.__class__, instance=instance, created=False,
                           raw=False, using=self.db, update_fields=kwargs.keys())
        return result

    update.alters_data = True

我克隆当前查询集(使用self.all()),因为update方法将清除queryset对象的缓存。

有些问题可能会或可能不会破坏您的代码。首先,它将引入竞争条件。您根据更新数据库时可能不再准确的数据在pre_save信号的接收器中执行某些操作。

大型查询集可能还存在一些严重的性能问题。与update方法不同,所有模型都必须加载到内存中,然后仍然需要执行信号。特别是如果信号本身必须与数据库交互,性能可能会慢得令人无法接受。与常规pre_save信号不同,更改模型实例不会自动导致数据库更新,因为模型实例不用于保存新数据。

可能还有一些问题会在一些边缘情况下引起问题。

无论如何,如果你能解决这些问题而不会遇到一些严重的问题,我认为这是最好的方法。它在产生尽可能少的开销的同时仍将模型加载到存储器中,这对于正确执行各种信号几乎是必需的。