The signal used is not post_save , but rather m2m_changed , which is sent after the model is saved to the database.
@models.signals.m2m_changed(sender=MyModel.second_m2m.through) def duplicate_other_on_this_if_empty(sender, instance, action, reverse, model, pk_set, **kwargs): # just before adding a possibly empty set in "second_m2m", check and populate. if action == 'pre_add' and not pk_set: instance.__was_empty = True pk_set.update(instance.first_m2m.values_list('pk', flat=True)) @models.signals.m2m_changed(sender=MyModel.first_m2m.through) def duplicate_this_on_other_if_empty(sender, instance, action, reverse, model, pk_set, **kwargs): # Just in case the "first_m2m" signals are sent after the other # so the actual "population" of the "second_m2m" is wrong: if action == 'post_add' and not pk_set and getattr(instance, '__was_empty'): instance.second_m2m = list(pk_set) delattr(instance, '__was_empty')
Edit: the following code is simpler and based on new knowledge on model definition
In your code, the signals "first_m2m" are sent before "second_m2m" (it really depends on the definition of your model). Therefore, we can work under the assumption that when signals "second_m2m" are received, "first_m2m" is already filled with current data.
This makes us happier, because now you only need to check m2m-pre-add:
@models.signals.m2m_changed(sender=MyModel.second_m2m.through) def duplicate_other_on_this_if_empty(sender, instance, action, reverse, model, pk_set, **kwargs): # just before adding a possibly empty set in "second_m2m", check and populate. if action == 'pre_add' and not pk_set: pk_set.update(instance.first_m2m.values_list('pk', flat=True))