overriding save method on many to many table doesn't work

Hi.

I’m building a file storing and sharing system.

I want to send email to new users added in a m2m (many to many) table, that m2m table shows which file is shared with which user.

The m2m field is used in the file model, it looks like this:

access = models.ManyToManyField(User, related_name='access', through='DicomFile_User', blank=True)

And the m2m table looks like this:


class DicomFile_User(models.Model):
    dicomfile = models.ForeignKey(DicomFile, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    added_time = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['added_time']

When a file is shared with a new user, I want to send email to that user.

I thought to override save() method of the m2m table, so I can send email to new added user after saving. But save() method of m2m model doesn’t even run.

How do I send email to users when a new value is added to that m2m table?

You have (at least) two options I can see at the moment:

  • Add the logic to the view where those entries are added.

  • Listen to the m2m_changed signal. (See .add and m2m_changed.)

1 Like

You marked it as a solution.
Could you share which one you used and the working code?
That would really help me and others learn from your experience

Yes, I used m2m_changed signal.

Here’s the code

// DicomFile_User mode, a custom many to many table connecting DicomFile and User instances
// Normally you don't need such tables, but I wanted to add extra column named "added_time".
class DicomFile_User(models.Model):
    dicomfile = models.ForeignKey(DicomFile, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    added_time = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['added_time']

//The handler
def access_changed(sender, action, pk_set, instance, **kwargs):
    if action == 'post_add':
        recipient_list = list()
        for pk in pk_set:
            user = instance.access.get(id=pk)
            recipient_list.append(user.email)
       
        params = {'dicom_file': instance, 'host': constants.HOST} //constants is just custom py file I wrote myself to keep some cosntant values
        email_title = f"{instance.owner} shared a DICOM file with you"
        msg_plain = render_to_string('dicoms/shared_dicom_email.txt', params)
        msg_html = render_to_string('dicoms/shared_dicom_email.html', params)

        send_mail(
            email_title,
            msg_plain,
            "test@medimageshub.com",
            recipient_list,
            html_message=msg_html)

// Here's the call
m2m_changed.connect(access_changed, sender=DicomFile_User)

DicomFile_User is custom model for a ManyToMany table

You just have to put this on DicomFile model:

access = models.ManyToManyField(User, related_name='access', through='DicomFile_User', blank=True)

1 Like

Thank you :blush: That really helps us all learn