Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding duplicate signals in Django when editing many_to_many fields

I want automatically update an external application about a change in a model. The problem is that the data is in a many2many relation between events <-> users. I tried to use the "m2m_changed" signal.

@receiver(m2m_changed, sender=models.Event.organisers.through)
def event_changed(sender, instance, action, *args, **kwargs):
    if "post" in action:
      # hey api here is the new list of organisers of this

The problem with this is that if i make a single change where i remove one user and add another then this code is called twice! That's no good and i can't just ignore one type of operation in case only that operation is called. I have thought pushing instances to stacks and ignoring dups but that seems messy. Is there a way i can make my own signal that only fires once?

like image 283
Cormac Brady Avatar asked Oct 16 '25 14:10

Cormac Brady


3 Answers

Django m2m_changed indicates change on ManyToMany model. If has 4 actions

  1. pre_add
  2. post_add
  3. pre_remove
  4. post_remove

So if you're just adding a user, this m2m_changed method would be triggered 2 times, for pre_add and post_add respectively.

You can specify at which action you want to call API. This could be done as:

    @receiver(m2m_changed, sender=models.Event.organisers.through)
    def event_changed(sender, instance, action, *args, **kwargs):
    if kwargs.get('action') == 'pre_add': # Or whatever action you want
       # Call your API here

Reference Django Docs: https://docs.djangoproject.com/en/2.2/ref/signals/#m2m-changed

like image 193
Muhammad Arsalan Avatar answered Oct 18 '25 06:10

Muhammad Arsalan


Django m2m_changed indicates change on ManyToMany model. If has 4 actions

  1. pre_add
  2. post_add
  3. pre_remove
  4. post_remove

So if you're just adding a user, this m2m_changed method would be triggered 2 times, for pre_add and post_add respectively.

You can specify at which action you want to call API. This could be done as:

    @receiver(m2m_changed, sender=models.Event.organisers.through)
    def event_changed(sender, instance, action, *args, **kwargs):
    if kwargs.get('action') == 'pre_add': # Or whatever action you want
       # Call your API here

Reference Django Docs: https://docs.djangoproject.com/en/2.2/ref/signals/#m2m-changed

like image 25
Muhammad Arsalan Avatar answered Oct 18 '25 07:10

Muhammad Arsalan


There doesn't relay seem to be a good answer to this question so here is some useful workarounds that are better than what i was imagined in the first pace.

Solution 1:

Instead of combining signals instead add the primary keys of the instances to a set to ignore duplicate signals:

updated = set()

@receiver(m2m_changed, sender=models.Event.organisers.through) 
def event_changed(sender, instance, action, *args, **kwargs):
        if "post" in action:
          updated.add(instance.pk)

def send_updates():
    for Event in updated:              # Iteration AKA for each element
       #update code here

While this requires some sort of scheduled task to run send_updates() it avoids the chance of spamming if there are many consecutive changes to the event.

Solution 2

Ignore signals altogether add last modified to the model. Then run a query to get all events between now and when the send_updates() was last called. Store the last called somewhere to disk/database to avoid having to resend everything on restart.

like image 33
Cormac Brady Avatar answered Oct 18 '25 05:10

Cormac Brady