0

I am new to python and django this year and am just struggling to figure out how to send a simple mail via send_mail to a user after their password has been updated? I have managed this via Signals with pre_save, however I don't want to make the user wait until the mail has been sent (which I can't work around as far as I know). With post_save, the previous state cannot be queried.

What would be the best way here if I gave the following user model?

class User(AbstractBaseUser):

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    email = models.EmailField(verbose_name="email address", max_length=255, unique=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)
    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

    # Tells Django that the UserManager class defined above should manage
    # objects of this type
    objects = UserManager()

    def __str__(self):
        return self.email

    class Meta:
        db_table = "login"

I had set this up with a pre_save signal, but this is not a solution for me because of the delay:

@receiver(pre_save, sender=User)
def on_change(sender, instance: User, **kwargs):
    if instance.id is None:
        pass
    else:
        previous = User.objects.get(id=instance.id)
        if previous.password != instance.password:
            send_mail(
                "Your password has changed",
                "......",
                "info@examplpe.com",
                [previous.email],
                fail_silently=False,
            )

Thanks in advance

dacx
  • 824
  • 1
  • 9
  • 18
Fabio
  • 160
  • 2
  • 12
  • You can override the save method of your form. – Marco Jan 17 '21 at 19:55
  • Could you please give me an example? I have no experience with this approach. – Fabio Jan 17 '21 at 20:00
  • Is it correct that you need to send this mail because someone other than the user changes the password (via the admin backend or similar)? Because otherwise, you should do this via the view where the user changes the password. – Marco Jan 17 '21 at 20:27

2 Answers2

1

If you are using a custom model, you can probably walk through the call to set_password() to set a flag on an instance and then detect its presence in the signal.

Try this example:

from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db.models.signals import post_save


class User(AbstractBaseUser, PermissionsMixin):
    
    ...
    
    def set_password(self, password):
        super(User, self).set_password(password)
        self._set_password = True

    @classmethod
    def user_changed(cls, sender, instance, **kwargs):
        if getattr(instance, '_set_password', False):
            # Send your mail


post_save.connect(User.user_changed, sender=User)
NKSM
  • 5,422
  • 4
  • 25
  • 38
0

You can override your save method for the User model. Here is an example from the docs alongside a check for changed values from SO:

class User(AbstractBaseUser):
    ...

    __original_password = None

    def __init__(self, *args, **kwargs):
        super(User, self).__init__(*args, **kwargs)
        self.__password = self.password

    def save(self, *args, **kwargs):
        if self.password != self.__original_password:
            notify_user_of_password_change()
        super().save(*args, **kwargs)  # Call the "real" save() method.
dacx
  • 824
  • 1
  • 9
  • 18