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

时间:2016-04-19 13:14:45

标签: django django-models django-signals

我正在使用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文档中的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?

7 个答案:

答案 0 :(得分:25)

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

  

来自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

答案 1 :(得分: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

答案 2 :(得分:4)

这是一个古老的问题,但是我最近遇到了这种情况,并且通过执行以下操作来实现它:

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的所有更新/保存,因此更有效。

答案 3 :(得分:4)

我来晚了,但对其他人有帮助。

我们可以为此定制信号。

使用自定义信号,我们可以轻松地执行以下操作:

  1. 帖子是否已创建
  2. 帖子是否被修改
  3. 帖子已保存,但任何字段均未更改

   class Post(models.Model):
   # some fields 

自定义信号

**发出带有参数的信号**

from django.dispatch import Signal, receiver
# provide arguments for your call back function
post_signal = Signal(providing_args=['sender','instance','change','updatedfields'])

使用回调功能注册信号

# register your signal with receiver decorator 
@receiver(post_signal)
def post_signalReciever(sender,**kwargs):
    print(kwargs['updatedfields'])
    print(kwargs['change'])

从管理员后发送信号

我们从Post admin发送信号,并在对象实际修改时保存对象

#sending the signals 
class PostAdmin(admin.ModelAdmin):
   # filters or fields goes here 

   #save method 
   def save_model(self, request, obj, form, change):


    if not change and form.has_changed():  # new  post created
        super(PostAdmin, self).save_model(request, obj, form, change)
        post_signal.send(self.__class__,instance=obj,change=change,updatedfields=form.changed_data)
        print('Post created')
    elif change and form.has_changed(): # post is actually modified )
        super(PostAdmin, self).save_model(request, obj, form, change)
        post_signal.send(self.__class__,instance=obj,change=change,updatedfields=form.changed_data)
        print('Post modified')
    elif change and not form.has_changed() :
        print('Post not created or not updated only saved ')  

另请参见:

Django Signals official doc

答案 4 :(得分:1)

如果要比较保存操作之前和之后的状态,可以使用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

答案 5 :(得分:0)

post_save方法中,您有kwargs自变量,它是一个字典,并包含一些信息。您在update_fields中有kwargs可以告诉您哪些字段已更改。此字段存储为forzenset对象。您可以检查哪些字段是这样更改的:

@receiver(post_save, sender=Mode)
def post_save(sender, instance, created, **kwargs):
        if not created:
            for item in iter(kwargs.get('update_fields')):
                if item == 'field_name' and instance.field_name == "some_value":
                    # do something here

但是此解决方案存在问题。例如,如果您的字段值为10,并且再次将该字段更新为10,则该字段将再次位于update_fields中。

答案 6 :(得分:-2)

您可以在django信号中使用update_fields

@receiver(post_save, sender=Mode)
def post_save(sender, instance, created, **kwargs):

    # only update instance
    if not created:

        update_fields = kwargs.get('update_fields') or set()

        # value of `mode` has changed:
        if 'mode' in update_fields:
            # then do this
            pass
        else:
            # do that
            pass
相关问题