Django:如何在 post_save 信号中访问原始(未修改)实例

Posted

技术标签:

【中文标题】Django:如何在 post_save 信号中访问原始(未修改)实例【英文标题】:Django: How to access original (unmodified) instance in post_save signal 【发布时间】:2011-07-31 17:51:39 【问题描述】:

我想进行数据非规范化以获得更好的性能,并将我的博客文章收到的投票总和放入 Post 模型中:

class Post(models.Model):
    """ Blog entry """
    author          = models.ForeignKey(User)
    title           = models.CharField(max_length=255)
    text            = models.TextField()
    rating          = models.IntegerField(default=0) # here is the sum of votes!

class Vote(models.Model):
    """ Vote for blog entry """
    post            = models.ForeignKey(Post)
    voter           = models.ForeignKey(User)
    value           = models.IntegerField()

当然,我需要保持Post.rating 的实际值。通常我会为此使用数据库触发器,但现在我决定发出post_save 信号(以减少数据库处理时间):

# vote was saved
@receiver(post_save, sender=Vote)
def update_post_votes(sender, instance, created, **kwargs):
    """ Update post rating """
    if created:
        instance.post.rating += instance.value
        instance.post.save()
    else:
        # if vote was updated, we need to remove the old vote value and add the new one
        # but how...?

如何在保存之前访问实例值?在数据库触发器中,我会为此预定义OLDNEW,但是在 post_save 信号中有类似的东西吗?

更新

基于马克的答案的解决方案:

# vote was saved
@receiver(pre_save, sender=Vote)
def update_post_votes_on_save(sender, instance, **kwargs):
    """ Update post rating """
    # if vote is being updated, then we must remove previous value first
    if instance.id:
        old_vote = Vote.objects.get(pk=instance.id)
        instance.post.rating -= old_vote.value
    # now adding the new vote
    instance.post.rating += instance.value
    instance.post.save()

【问题讨论】:

【参考方案1】:

我认为post_save 检索未修改版本为时已晚。顾名思义,此时数据已经写入数据库。您应该改用pre_save。在这种情况下,您可以通过 pk:old = Vote.objects.get(pk=instance.pk) 从数据库中检索模型并检查当前实例和前一个实例的差异。

【讨论】:

【参考方案2】:

不是最佳解决方案,但它确实有效。

@receiver(pre_save, sender=SomeModel)
def model_pre_save(sender, instance, **kwargs):
    try:
        instance._pre_save_instance = SomeModel.objects.get(pk=instance.pk)
    except SomeModel.DoesNotExist:
        instance._pre_save_instance = instance


@receiver(signal=post_save, sender=SomeModel)
def model_post_save(sender, instance, created, **kwargs):
    pre_save_instance = instance._pre_save_instance
    post_save_instance = instance 

【讨论】:

为什么不是最优的? @CharanjitSingh 它有效,但我不确定这是正确的方法。 是的,据我所知,它可能是最佳的,但预测性较差,因为旧实例可以从内存中删除,我不确定 django 的信号工作流程,但这可能成为一个问题。 我只建议不要保存整个实例,只需将您需要比较的数据子集存储在 post_save 中。【参考方案3】:

您可以使用 django-model-utils 中的 FieldTracker:https://django-model-utils.readthedocs.io/en/latest/utilities.html#field-tracker

【讨论】:

以上是关于Django:如何在 post_save 信号中访问原始(未修改)实例的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Django 信号(Pre_save,Post_save)从“B”模型的 ImageField 设置“A”模型的 ImageField

Django 从 post_save 信号访问 ManyToMany 字段

识别 django post_save 信号中更改的字段

Django post_save() 信号实现

忽略 django 的 post_save 信号中对 m2m 关系的更改

Django post_save 信号和 celery 任务之间可能的竞争条件