创建对象时防止m2m_changed触发

时间:2015-04-16 05:37:57

标签: python django many-to-many django-signals

使用像post_save这样的Django信号时,可以通过执行以下操作来阻止它在首次创建对象时触发:

@receiver(post_save,sender=MyModel)
def my_signal(sender, instance, created,**kwargs):
    if not created:
        pass # Do nothing, as the item is new.
    else:
        logger.INFO("The item changed - %s"%(instance) )

但是,在最初创建项目后so no such argument is passed in会应用ManyToMany关系,这使得在这些情况下难以抑制。

@receiver(m2m_changed,sender=MyModel.somerelation.though)
def my_signal(sender, instance, created,**kwargs):
    if __something__: # What goes here?
        pass # Do nothing, as the item is new.
    else:
        logger.INFO("The item changed - %s"%(instance) )

是否有一种简单的方法可以在刚刚创建的对象上对m2m_changed信号进行抑制?

3 个答案:

答案 0 :(得分:1)

我认为没有简单的方法可以做到这一点。

正如Django doc所说,在项目被保存之前,您无法将项目与关系相关联。 doc:

中的示例
>>> a1 = Article(headline='...')
>>> a1.publications.add(p1)
Traceback (most recent call last):
...
ValueError: 'Article' instance needs to have a primary key value before a many-to-many relationship can be used.

# should save Article first
>>> a1.save()
# the below statement never know it's just following a creation or not
>>> a1.publications.add(p1)

关系记录在逻辑上不可能知道它是否被添加到"刚刚创建的项目"或者"一段时间内已存在的项目",没有外部信息。

我想出了一些解决方法:

解决方案1.在MyModel中添加DatetimeField以指示创建时间。 m2m_changed处理程序使用创建时间来检查创建项目的时间。它在某些情况下实际工作,但不能保证正确性

解决方案2.添加'创建' MyModel中的属性,可以在post_save处理程序中,也可以在其他代码中。例如:

@receiver(post_save, sender=Pizza)
def pizza_listener(sender, instance, created, **kwargs):
    instance.created = created

@receiver(m2m_changed, sender=Pizza.toppings.through)
def topping_listener(sender, instance, action, **kwargs):
    if action != 'post_add':
        # as example, only handle post_add action here
        return
    if getattr(instance, 'created', False):
        print 'toppings added to freshly created Pizza'
    else:
        print 'toppings added to modified Pizza'
    instance.created = False

演示:

p1 = Pizza.objects.create(name='Pizza1')
p1.toppings.add(Topping.objects.create())
>>> toppings added to freshly created Pizza
p1.toppings.add(Topping.objects.create())
>>> toppings added to modified Pizza

p2 = Pizza.objects.create(name='Pizza2')
p2.name = 'Pizza2-1'
p2.save()
p2.toppings.add(Topping.objects.create())
>>> toppings added to modified Pizza

但要小心使用此解决方案。自从'创建'属性已分配给Python实例,未保存在DB中,可能会出错:

p3 = Pizza.objects.create(name='Pizza3')
p3_1 = Pizza.objects.get(name='Pizza3')
p3_1.toppings.add(Topping.objects.create())
>>> toppings added to modified Pizza
p3.toppings.add(Topping.objects.create())
>>> toppings added to freshly created Pizza

这完全取决于答案。然后,抓住你了!我是来自github django-notifications组的zhang-z:)

答案 1 :(得分:0)

@ZZY的回答基本上帮助我意识到,如果不存储其他字段,这是不可能的。幸运的是,我使用的django-model-utils包含TimeStampedModel,其中包含created字段。

提供足够小的delta,在捕获信号时检查创建的时间相对容易。

@receiver(m2m_changed,sender=MyModel.somerelation.though)
def my_signal(sender, instance, created,**kwargs):
    if action in ['post_add','post_remove','post_clear']:
        created = instance.created >= timezone.now() - datetime.timedelta(seconds=5)
        if created:
            logger.INFO("The item changed - %s"%(instance) )

答案 2 :(得分:0)

检查对象是否创建的更简单快捷的方法是使用 _state.adding 属性:

def m2m_change_method(sender, **kwargs):
    instance = kwargs.pop('instance', None)
    if instance:
       if instance.adding: #created object
          pk_set = list(kwargs.pop('pk_set')) #ids of object added to m2m relation
       else:
          # do something if the instance not newly created or changed
    
  # if you want to check if the m2m objects is new use pk_set query if exists()
m2m_change.connect(m2m_change_method, sender=YourModel.many_to_many_field.through)