title: How do I determine which elements were changed in a django manytomanyfield
body: <p>I have a model with a ManyToManyField (<em>step</em>). I'd like to delete the objects (<em>ImagePlus</em>) linked to via this field if there is no <em>step</em> element referencing them anymore.</p>
  
  <p>I tried to use the pre_delete signal and this works fine for half the problem (I can delete the referenced object if the last <em>step</em> referencing them is deleted).
  However, I struggle to delete a referenced <em>ImagePlus</em> object if it is disconnected by an update to the manytomanyfield in a <em>step</em>.</p>
  
  <p>I tried to solve it by overloading the save method or the pre_save/post_save signal of the <em>step</em> object, but the changes in the manytomanyfield are all either before or after either of those.</p>
  
  <p>I also tried to solve it by interceping the m2m_changed signal of the through table. </p>
  
  <p>Once more I can partially solve my problem. If I add/remove objects via .add or via .remove in the shell I get a proper post_add/post_remove signal that I can process like the delete one.</p>
  
  <p>The problem here is that when I try to change the contents of the manytomanyfield in Django admin, or via ajax/DRF) I always get 4 signals in this order: 'pre_clear', 'post_clear', 'pre_add' and 'post_add'.
  Now I cannot use the 'pre_clear' or 'post_clear' signals as I cannot know if the element is re-attached in a moment via an add signal. OTOH I cannot really use the 'pre_add' or 'post_add' signals as its not guaranteed that they will occur I think. (actually I am a bit confused why I don't get a single 'post_remove' signal instead of those 4 - would make life so much easier).</p>
  
  <p>I tried this pre_clean/pre_add hack as shown below and it works reasonable. However, I am not sure about possible side effects. At least it seems dangerous and it will fail if only a clean is called without an add.</p>
  
  <p>I am not really sure what my options are at this point. Make a timer that deletes the referenced objects if not reattached after 3 seconds? That would be quite clumsy and might cause database inconsistencies.</p>
  
  <pre><code>class Step(models.Model):
      instruments = ManyToManyField(ImagePlus, verbose_name="Instruments and materials", blank=True, related_name='instruments_step_set')
      previous_instruments = None
  
  class ImagePlus(models.Model):
      image = ImageField(verbose_name="Single image", upload_to=CreateImageFilename('gallery/images'), null=True, blank=True)
  
  @receiver(pre_delete, sender=Step)
  def pre_delete_step(sender, instance, **kwargs):
      for instrument in instance.instruments.all():
          if instrument.instruments_step_set.count()==1:
              instrument.delete()
  
  @receiver(m2m_changed, sender=Step.instruments.through)
  def m2m_changed_step(sender, instance, action, **kwargs):
      def cleanup (set):
          for element in set:
              if Step.instruments.through.objects.filter(imageplus__id=element).count()==0:
                  ImagePlus.objects.get(id=element).delete()  
  
      if action == 'pre_clear':
          instance.previous_instruments = set(instance.instruments.all().values_list('pk',flat=True))
          instance.previous_instruments_timer = Timer (3, cleanup, (instance.previous_instruments,)) 
          instance.previous_instruments_timer.start()
  
      if action == 'pre_add':
          pk_set = kwargs.pop('pk_set', set())
          deleted = instance.previous_instruments-pk_set
          instance.previous_instruments_timer.cancel()
          instance.previous_instruments = None
          cleanup (deleted)
  
      if action == 'post_remove':
          deleted = kwargs.pop('pk_set', None)
          cleanup (deleted)
  </code></pre>