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

问题描述 投票:29回答:4

我正在使用django的post_save信号在保存模型后执行一些语句。

class Mode(models.Model):
    name = models.CharField(max_length=5)
    mode = models.BooleanField()


from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=Mode)
def post_save(sender, instance, created, **kwargs):
        # do some stuff
        pass

现在我想基于mode字段的值是否已更改来执行语句。

@receiver(post_save, sender=Mode)
def post_save(sender, instance, created, **kwargs):
        # if value of `mode` has changed:
        #  then do this
        # else:
        #  do that
        pass

我查看了一些SOF主题和博客,但找不到解决方案。所有这些都试图使用不是我的用例的pre_save方法或表单。 django docs中的https://docs.djangoproject.com/es/1.9/ref/signals/#post-save没有提到直接的方法来做到这一点。

下面的链接中的答案看起来很有希望,但我不知道如何使用它。我不确定最新的django版本是否支持它,因为我使用ipdb来调试它,并发现instance变量没有属性has_changed,如下面的答案所述。

Django: When saving, how can you check if a field has changed?

django django-models django-signals
4个回答
17
投票

将其设置在您模型的__init__上,以便您可以访问它。

def __init__(self, *args, **kwargs):
    super(YourModel, self).__init__(*args, **kwargs)
    self.__original_mode = self.mode

现在你可以执行以下操作:

if instance.mode != instance.__original_mode:
    # do something useful

26
投票

通常,覆盖保存方法比使用信号更好。

来自Two scoops of django:“使用信号作为最后的手段。”

我同意@scoopseven关于在init上缓存原始值的答案,但是如果可能则覆盖save方法。

class Mode(models.Model):
    name = models.CharField(max_length=5)
    mode = models.BooleanField()
    __original_mode = None

    def __init__(self, *args, **kwargs):
        super(Mode, self).__init__(*args, **kwargs)
        self.__original_mode = self.mode

    def save(self, force_insert=False, force_update=False, *args, **kwargs):
        if self.mode != self.__original_mode:
            #  then do this
        else:
            #  do that

        super(Mode, self).save(force_insert, force_update, *args, **kwargs)
        self.__original_mode = self.mode

7
投票

这是一个老问题,但我最近遇到过这种情况,我通过以下方式完成了它:

class Mode(models.Model):

    def save(self, *args, **kwargs):
        if self.pk:
            # If self.pk is not None then it's an update.
            cls = self.__class__
            old = cls.objects.get(pk=self.pk)
            # This will get the current model state since super().save() isn't called yet.
            new = self  # This gets the newly instantiated Mode object with the new values.
            changed_fields = []
            for field in cls._meta.get_fields():
                field_name = field.name
                try:
                    if getattr(old, field_name) != getattr(new, field_name):
                        changed_fields.append(field_name)
                except Exception as ex:  # Catch field does not exist exception
                    pass
            kwargs['update_fields'] = changed_fields
        super().save(*args, **kwargs)

这更有效,因为它捕获了应用程序和django-admin的所有更新/保存。


2
投票

如果要比较保存操作之前和之后的状态,可以使用pre_save信号为数据库更新后提供实例,在pre_save中,您可以读取数据库中实例的当前状态并根据差异执行某些操作。

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


@receiver(pre_save, sender=MyModel)
def on_cahnge(sender, instance: MyModel, **kwargs):
    if instance.id is None: # new object will be created
        pass # write your code hier
    else:
        previous = MyModel.objects.get(id=instance.id)
        if previous.field_a != instance.field_a: # fielad will be updated
            pass  # write your code hier
© www.soinside.com 2019 - 2024. All rights reserved.