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

Wen*_*ndy 36 django django-signals django-models

我正在使用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
Run Code Online (Sandbox Code Playgroud)

现在我想根据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
Run Code Online (Sandbox Code Playgroud)

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

下面的链接中的答案看起来很有希望,但我不知道如何使用它.我不确定最新的django版本是否支持它,因为我曾经ipdb调试过这个并且发现该instance变量没有has_changed下面回答中提到的属性.

Django:保存时,如何检查字段是否已更改?

小智 28

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

来自两勺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
Run Code Online (Sandbox Code Playgroud)

  • @Hussain虽然方法直接附加到模型,您可以在一个地方看到它的行为,信号可以放在多个不同的应用程序中,使调试变得噩梦,代码可读性降低.所以这不是关于信号是不好的,但方法更明显,如果可以,你最好坚持使用它们. (5认同)
  • 在Django中使用Signals有什么问题?它可以帮助您很好地创建工作流程. (4认同)

sco*_*ven 18

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

def __init__(self, *args, **kwargs):
    super(YourModel, self).__init__(*args, **kwargs)
    self.__original_mode = self.mode
Run Code Online (Sandbox Code Playgroud)

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

if instance.mode != instance.__original_mode:
    # do something useful
Run Code Online (Sandbox Code Playgroud)

  • 小心.这种方法有缺点,详见[此处](http://stackoverflow.com/questions/1355150).简而言之:它不是种族证明,如果你有两个python实例指向同一个db行,也可能是错误的. (5认同)
  • `instance.mode != instance.__original_mode` 将抛出 AttributeError 因为 `__original_mode` 将被破坏,如[此处](https://docs.python.org/3/tutorial/classes.html#private-variables)所述。在这种情况下,正确的条件是“instance.mode != instance._Mode__original_mode”。 (2认同)

kin*_*aii 10

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

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)
Run Code Online (Sandbox Code Playgroud)

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


Sab*_*oki 9

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
Run Code Online (Sandbox Code Playgroud)

但是这个解决方案存在一个问题。例如,如果您的字段值为 10,并且您再次使用 10 更新此字段,则此字段将update_fields再次出现。

  • 另一个问题是“update_fields”不会自动填充。它包含您在“save()”方法中显式传递的字段。 (11认同)

Rya*_*der 5

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

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


@receiver(pre_save, sender=MyModel)
def on_change(sender, instance: MyModel, **kwargs):
    if instance.id is None: # new object will be created
        pass # write your code here
    else:
        previous = MyModel.objects.get(id=instance.id)
        if previous.field_a != instance.field_a: # field will be updated
            pass  # write your code here
Run Code Online (Sandbox Code Playgroud)

  • 这种方法的问题是,如果保存失败,那么更改实际上不会发生,但您已经在 pre_save 中执行了一些代码,这将导致不一致。只有在 post_save 中你才能确认这一点。 (5认同)
  • 我对此的唯一更改是“previous = sender.objects.get(id=instance.id)” (3认同)
  • 我建议将original_fields(即保存前的模型字段)存储为pre_save中的属性,并将这些original_fields与new_fields(我们现在在保存后的新模型字段)进行比较,看看发生了什么变化并添加您的逻辑 (2认同)

Muh*_*eed 5

我迟到了,但它可以对其他人有所帮助。

我们可以为此制作自定义信号。

使用自定义信号,我们可以轻松地做这些事情:

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

   class Post(models.Model):
   # some fields 
Run Code Online (Sandbox Code Playgroud)

自定义信号

**用参数制作信号**

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

使用回调函数注册信号

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

从 post-admin 发送信号

我们从 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 ')  
Run Code Online (Sandbox Code Playgroud)

也可以看看:

Django Signals 官方文档