【问题标题】:Django, post_save signal recrusion. How to bypass signal firingDjango,post_save 信号递归。如何绕过信号触发
【发布时间】:2012-01-29 12:45:13
【问题描述】:

我有一种情况,当我的一个模型被保存 MyModel 时,我想检查一个字段,并在具有相同 some_key. 的任何其他模型中触发相同的更改

代码工作正常,但它递归调用信号。结果,我浪费了 CPU/DB/API 调用。我基本上想在.save(). 期间绕过信号有什么建议吗?

class MyModel(models.Model):
    #bah
    some_field = #
    some_key = #

#in package code __init__.py 
@receiver(models_.post_save_for, sender=MyModel)
def my_model_post_processing(sender, **kwargs):
 # do some unrelated logic...
 logic = 'fun!  '


 #if something has changed... update any other field with the same id
 cascade_update = MyModel.exclude(id=sender.id).filter(some_key=sender.some_key)
 for c in cascade_update:
     c.some_field  = sender.some_field 
     c.save()

【问题讨论】:

    标签: django google-app-engine signals djangoappengine


    【解决方案1】:

    一个解决方案可能是使用 update() 方法绕过信号:

    cascade_update = MyModel.exclude(
                         id=sender.id).filter(
                         some_key=sender.some_key).update(
                         some_field  = sender.some_field )
    

    "Be aware that the update() method is converted directly to an SQL statement. It is a bulk operation for direct updates. It doesn't run any save() methods on your models, or emit the pre_save or post_save signals"

    【讨论】:

      【解决方案2】:

      在调用save之前断开信号,然后重新连接:

      post_save.disconnect(my_receiver_function, sender=MyModel)
      instance.save()
      post_save.connect(my_receiver_function, sender=MyModel)
      

      【讨论】:

      • 这让我很害怕(以防万一出现问题......)......但它会起作用。
      • 会出现什么问题?断开和连接之间唯一运行的是保存,如果这不起作用,你甚至不会在这里。
      • 重新连接它可能会出错...我认为这是一种方法,它只是属于看起来非常可怕的类别。
      • 这实际上是一种非官方认可的方法。许多人,包括我自己,在他们的应用程序中的某个地方都有这样的代码,而且它工作得完美无缺。连接信号永远不会有问题,除非您首先传递无效数据(不存在的方法,不存在的模型作为发送方等)。
      • 断开信号连接不是一个干燥且一致的解决方案。如果同一模型的另一个实例同时保存,则不会触发信号。
      【解决方案3】:

      您可以将相关对象更新代码移动到MyModel.save 方法中。那么就不需要玩信号了:

      class MyModel(models.Model):
          some_field = #
          some_key = #
      
          def save(self, *args, **kwargs):
              super(MyModel, self).save(*args, **kwargs)
              for c in MyModel.objects.exclude(id=self.id).filter(some_key=self.some_key):
                  c.some_field = self.some_field 
                  c.save()
      

      【讨论】:

      • 这可行,但我们真的希望在信号代码中使用这种逻辑。
      • 除了建议的方法之外,如果在调用 save 之前向模型添加一些属性,并在信号处理程序中检查它会怎样。如果设置了属性 - 跳过处理。
      【解决方案4】:

      断开信号不是DRY一致的解决方案,例如使用update()而不是save()。

      要绕过模型上的信号触发,一种简单的方法是在当前实例上设置一个属性以防止即将发生的信号触发。

      这可以使用一个简单的装饰器来完成,该装饰器检查给定实例是否具有 'skip_signal' 属性,如果是,则阻止调用该方法:

      from functools import wraps
      
      def skip_signal(signal_func):
          @wraps(signal_func)
          def _decorator(sender, instance, **kwargs):
              if hasattr(instance, 'skip_signal'):
                  return None
              return signal_func(sender, instance, **kwargs)  
          return _decorator
      

      根据您的示例,这给了我们:

      from django.db.models.signals import post_save
      from django.dispatch import receiver
      
      @receiver(post_save, sender=MyModel)
      @skip_signal()
      def my_model_post_save(sender, instance, **kwargs):
          instance.some_field = my_value
          # Here we flag the instance with 'skip_signal'
          # and my_model_post_save won't be called again
          # thanks to our decorator, avoiding any signal recursion
          instance.skip_signal  = True
          instance.save()
      

      希望这会有所帮助。

      【讨论】:

      • 这应该是公认的答案,它是一种更加稳定和可扩展的方式。
      • 这是一个很好的解决方案,我认为 django 在保存之前会清除未建模的字段,但事实并非如此。像魅力一样工作。
      猜你喜欢
      • 1970-01-01
      • 2018-08-09
      • 1970-01-01
      • 1970-01-01
      • 2012-10-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多