When using a Django signal like post_save
you can prevent it from firing when an object is first created by doing something like:
@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) )
However, ManyToMany relations are applied after an item is initially created, so no such argument is passed in, making it difficult to suppress in these cases.
@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) )
Is there an easy way to suppress an m2m_changed
signal when its being done on an object that has just been created?
I think there is no easy way to do that.
As the Django doc says, you can't associate an item with a relation until it's been saved. Example from the 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)
It's logically not possible for a relation record to know whether it is added to "a just created item" or "an item that already exists for some time", without external info.
Some workarounds I came up with:
Solution 1. add a DatetimeField in MyModel
to indicate creation time. m2m_changed handler uses the creation time to check when is the item created. It work practically in some cases, but cannot guarantee correctness
Solution 2. add a 'created' attribute in MyModel
, either in a post_save handler or in other codes. Example:
@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
Demo:
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
But be careful using this solution. Since 'created' attribute was assigned to Python instance, not saved in DB, things can go wrong as:
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
That's all about the answer. Then, caught you here! I'm zhang-z from github django-notifications group :)