17

我在 django 中有一个信号回调:

@receiver(post_save, sender=MediumCategory)
def update_category_descendants(sender, **kwargs):
    
    def children_for(category):
        return MediumCategory.objects.filter(parent=category)
    
    def do_update_descendants(category):
        children = children_for(category)
        descendants = list() + list(children)
        
        for descendants_part in [do_update_descendants(child) for child in children]:
            descendants += descendants_part
        
        category.descendants.clear()
        for descendant in descendants:
            if category and not (descendant in category.descendants.all()):
                category.descendants.add(descendant)
                category.save()
        return list(descendants)
    
    # call it for update
    do_update_descendants(None)

...但是在信号处理程序的主体中,我.save()在同一模型上使用MediumCategory。这会导致再次发送信号。我怎样才能禁用它?

完美的解决方案将是一个with包含一些“魔法”的声明。

更新: 如果有人感兴趣,这是我的最终解决方案:

class MediumCategory(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(blank=True)
    parent = models.ForeignKey('self', blank=True, null=True)
    parameters = models.ManyToManyField(AdvertisementDescriptonParameter, blank=True)
    count_mediums = models.PositiveIntegerField(default=0)
    count_ads = models.PositiveIntegerField(default=0)
    
    descendants = models.ManyToManyField('self', blank=True, null=True)
    
    def save(self, *args, **kwargs):
        self.slug = slugify(self.name)
        super(MediumCategory, self).save(*args, **kwargs)
    
    def __unicode__(self):
        return unicode(self.name)
(...)
@receiver(post_save, sender=MediumCategory)
def update_category_descendants(sender=None, **kwargs):
    def children_for(category):
        return MediumCategory.objects.filter(parent=category)
    
    def do_update_descendants(category):
        children = children_for(category)
        descendants = list() + list(children)
        
        for descendants_part in [do_update_descendants(child) for child in children]:
            descendants += descendants_part
        
        if category:
            category.descendants.clear()
            for descendant in descendants:
                category.descendants.add(descendant)
        return list(descendants)
    
    # call it for update
    do_update_descendants(None)
4

2 回答 2

21

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

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

from functools import wraps

def skip_signal():
    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
    return _skip_signal

您现在可以这样使用它:

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()

希望这可以帮助。

于 2014-11-27T00:30:20.340 回答
18

也许我错了,但我认为category.save()您的代码中不需要, add() 就足够了,因为更改是在后代中进行的,但在类别中进行。

此外,为了避免信号,您可以:

  • 断开信号并重新连接。
  • 使用更新Descendant.objects.filter( pk = descendant.pk ).update( category = category )
于 2012-07-14T20:42:01.273 回答