19

In django rest_auth password reset, default email content look like following:-

You're receiving this email because you requested a password reset for your user account at localhost:8000.

Please go to the following page and choose a new password:

http://localhost:8000/api/reset/Kih/89a-23809182347689312b123/

Your username, in case you've forgotten: test

Thanks for using our site!

The localhost:8000 team

How to customize content of this email ?

McLosys Creative
  • 759
  • 4
  • 9
  • 19

9 Answers9

26

I recently needed to implement the same thing in one of my projects and could not find a thorough answer anywhere.

So I'm leaving my solution here for anyone who needs it in the future.

Expanding on mariodev's suggestion:

1. Subclass PasswordResetSerializer and override save method.

yourproject_app/serializers.py

from django.conf import settings
from rest_auth.serializers import PasswordResetSerializer as _PasswordResetSerializer

class PasswordResetSerializer(_PasswordResetSerializer):
    def save(self):
        request = self.context.get('request')
        opts = {
            'use_https': request.is_secure(),
            'from_email': getattr(settings, 'DEFAULT_FROM_EMAIL'),

            ###### USE YOUR TEXT FILE ######
            'email_template_name': 'example_message.txt',

            'request': request,
        }
        self.reset_form.save(**opts)

2. Configure AUTH_USER_MODEL

yourproject/settings.py

###### USE YOUR USER MODEL ######
AUTH_USER_MODEL = 'yourproject_app.ExampleUser'

3. Connect custom PasswordResetSerializer to override default

yourproject/settings.py

REST_AUTH_SERIALIZERS = {
    'PASSWORD_RESET_SERIALIZER': 
        'yourproject_app.serializers.PasswordResetSerializer',
}

4. Add the path to the directory where your custom email message text file is located to TEMPLATES

yourproject/settings.py

TEMPLATES = [
    {
        ...
        'DIRS': [os.path.join(BASE_DIR, 'yourproject/templates')],
        ...
    }
]

5. Write custom email message (default copied from Django)

yourproject/templates/example_message.txt

{% load i18n %}{% autoescape off %}
{% blocktrans %}You're receiving this email because you requested a password reset 
for your user account at {{ site_name }}.{% endblocktrans %}

{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}

{% trans "Thanks for using our site!" %}

{% blocktrans %}The {{ site_name }} team{% endblocktrans %}

{% endautoescape %}

UPDATE: This solution was written for an older version of django-rest-auth (v0.6.0). As I can tell from the comments, it seems there have been some updates made to the source package that more readily handle custom email templates out-of-box. It is always better to use methods defined in a package rather than overriding them like in my solution. Though once a necessity, it may not be so any longer.

Brian K.
  • 416
  • 1
  • 6
  • 15
  • Your solution does not work for me: I did everything from your steps, but rest-auth still uses its default email template :( I have placed my email under `project/templates/emails/password-reset-email.html` and overided `get_email_options(self)` which now returns `'html_email_template_name': 'templates/emails/password-reset-email.html'` but still no change... My custom password-serializer IS used, I verified that with the pycharm debugger. So the settings should be correct, and my overridden get_email_options is also invoked. Any ideas? Thanks! – ElectRocnic Dec 10 '18 at 23:19
  • @ElectRocnic Instead of `'templates/emails/password-reset-email.html'` try `'password-reset-email.html'`, and make sure your templates directory is specified in settings.py. Also, when I originally wrote this, `get_email_options(self)` was not included in the rest_auth package. My method requires overriding `save(self)`. – Brian K. Dec 18 '18 at 01:29
  • 2
    Thanks, although with the current version of rest_auth you need only a little code for the serializer. I may post my solution in the future. – Rik Schoonbeek Jan 15 '19 at 20:34
  • 1
    I still cant really find a current solution for this. This is really surprising since this must be a very common use-case. – Xen_mar Feb 09 '20 at 16:44
  • Same, I went through a ton of examples and none except this seemed to work for me. – Thorvald Sep 23 '21 at 19:43
12

You can inherit PasswordResetSerializer and override the get_email_options method. For example:

from rest_auth.serializers import PasswordResetSerializer


class CustomPasswordResetSerializer(PasswordResetSerializer):
    def get_email_options(self):
        return {
            'subject_template_name': 'registration/password_reset_subject.txt',
            'email_template_name': 'registration/password_reset_message.txt',
            'html_email_template_name': 'registration/'
                                    'password_reset_message.html',
            'extra_email_context': {
                'pass_reset_obj': self.your_extra_reset_obj
            }
        }
M.Void
  • 2,764
  • 2
  • 29
  • 44
  • This worked for me. Note that self.pass_reset_obj is just for example purposes. – John Q Jul 08 '20 at 02:47
  • This is the best, shortest, and the latest solution till now. – Kaustubh Trivedi Aug 17 '20 at 15:01
  • From where did you get PasswordResetSerializer that you passed into CustomPasswordResetSerializer? – Efosa Nov 21 '20 at 19:03
  • @Efosa I've just added the import in my snippet – M.Void Nov 24 '20 at 19:29
  • @M.Void, I like this solution, But I did not understand how to trigger this. I want to send an activation link when the new user is created and by clicking on the activation link users can reset their password. – bhattraideb Jan 02 '22 at 16:25
  • @bhattraideb Did you specify the CustomPasswordResetSerializer in the setting.py? I mean PASSWORD_RESET_SERIALIZER, for more details please take a look https://django-rest-auth.readthedocs.io/en/latest/configuration.html?highlight=PASSWORD_RESET_SERIALIZER#configuration – M.Void Jan 05 '22 at 18:36
  • @M.Void Its working now. thanks. – bhattraideb Jan 07 '22 at 07:40
  • 3
    I did exactly same but didn't work for me. get_email_options has no effect in my case – Qeybulla Jan 29 '22 at 17:05
7

You need to hook up your own reset password serializer (PASSWORD_RESET_SERIALIZER) with customized save method.

(ref: https://github.com/Tivix/django-rest-auth/blob/v0.6.0/rest_auth/serializers.py#L123)

Unfortunately you need to override the whole save method, due to how the e-mail options are used. We we'll make it a bit more flexible in the next release (0.7.0)

mariodev
  • 13,928
  • 3
  • 49
  • 61
7

A simple solution is Create over templates directory:

-templates
   -registration
       password_reset_email.html

with content you want. Django rest-auth use django.contrib.auth templates.

  • I tried this solution, it overrides but it doesn't show it like it should, instead it returns an email of the code back to me. I have no idea why. https://stackoverflow.com/questions/60026522/overriding-restauth-password-reset-email-issues – Opeyemi Odedeyi Feb 03 '20 at 20:10
  • This answer works, yet sadly this is "password_reset_email.html" is passed in a the plaintext template. @OpeyemiOdedeyi Thats why you will see html code as text. You have to override PasswordResetSerializer as Biran K mentioned. – chickahoona Aug 16 '20 at 10:10
7

So for the dj-rest-auth, this is how I did it:

from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site
from django.urls.base import reverse
from allauth.account import app_settings
from allauth.account.adapter import get_adapter
from allauth.account.utils import user_pk_to_url_str, user_username
from allauth.utils import build_absolute_uri
from dj_rest_auth.forms import AllAuthPasswordResetForm
from dj_rest_auth.serializers import PasswordResetSerializer

class CustomAllAuthPasswordResetForm(AllAuthPasswordResetForm):
    def save(self, request, **kwargs):
        current_site = get_current_site(request)
        email = self.cleaned_data['email']
        token_generator = kwargs.get('token_generator',
                                     default_token_generator)

        for user in self.users:

            temp_key = token_generator.make_token(user)

            # save it to the password reset model
            # password_reset = PasswordReset(user=user, temp_key=temp_key)
            # password_reset.save()

            # send the password reset email
            path = reverse(
                'password_reset_confirm',
                args=[user_pk_to_url_str(user), temp_key],
            )
            url = build_absolute_uri(None, path) # PASS NONE INSTEAD OF REQUEST

            context = {
                'current_site': current_site,
                'user': user,
                'password_reset_url': url,
                'request': request,
            }
            if app_settings.AUTHENTICATION_METHOD != app_settings.AuthenticationMethod.EMAIL:
                context['username'] = user_username(user)
            get_adapter(request).send_mail('account/email/password_reset_key',
                                           email, context)
        return self.cleaned_data['email']

class CustomPasswordResetSerializer(PasswordResetSerializer):
    @property
    def password_reset_form_class(self):
        return CustomAllAuthPasswordResetForm

# settings.py
REST_AUTH_SERIALIZERS = {
    'PASSWORD_RESET_SERIALIZER':
    'api.users.api.serializers.CustomPasswordResetSerializer',
}

By passing None to build_absolute_uri instead of the original request, it will take the value you have in django.contrib.sites module with SITE_ID=1. So whatever you have defined as your domain in the Django admin will now be the domain in the reset URL. This makes sense if you want to have the password reset URL point to your frontend, that might be a React application running on a different domain.

Edit: My PR regarding this issue was merged, with the next release this will be possible to set in your settings. Checkout the docs for dj-rest-auth to see which setting you need to set.

Özer
  • 2,059
  • 18
  • 22
6

if you want to use a html email template, an update to Brian's answer would be to add

'html_email_template_name': 'account/email/example_message.html',

just below

###### USE YOUR TEXT FILE ###### 'email_template_name': 'account/email/example_message.txt',

this way you can the email with a html template

You can see why this happens by inspecting the send_mail method of the PasswordResetForm class

class PasswordResetForm(forms.Form):
     email = forms.EmailField(label=_("Email"), max_length=254)

     def send_mail(self, subject_template_name, email_template_name,
              context, from_email, to_email, html_email_template_name=None):
              """
               Send a django.core.mail.EmailMultiAlternatives to `to_email`.
              """
             subject = loader.render_to_string(subject_template_name, context)
             # Email subject *must not* contain newlines
             subject = ''.join(subject.splitlines())
             body = loader.render_to_string(email_template_name, context)

             email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
             if html_email_template_name is not None:
                 html_email = loader.render_to_string(html_email_template_name, context)
                 email_message.attach_alternative(html_email, 'text/html')

             email_message.send()```
2

Create directory with path as following in your template folder

templates/admin/registration/

Now copy all files in django/contrib/admin/templates/registration/ into this directory you just created. You can find this directory where you have installed django. In linux, it can be find here

/usr/local/lib/python2.7/dist-packages/django/contrib/admin/templates/registration

You will need root priviliges for accessing this.

Now when you will send email, templates in you just copied in your project will be used.

Muhammad Hassan
  • 14,086
  • 7
  • 32
  • 54
2

This link might be helpful. With it I was able to find where the email templates were and how to customize them.

You can find the info at the bottom of the page under Customize the email message http://www.sarahhagstrom.com/2013/09/the-missing-django-allauth-tutorial/#Customize_the_email_message

Ryan113
  • 676
  • 1
  • 10
  • 27
  • Just to add on @Ryan113 answer, version +0.9.5 of django-rest-auth uses django-allauth email messages and templates, so instead of looking for email templates from within django-rest-auth, override templates from django-allauth as explained https://stackoverflow.com/a/51685556/6586065 – Abulo John Joshua Nov 23 '20 at 11:25
0

For anyone using all-auth in addition to dj-rest-auth, kindly note that solutions that override get_email_options() will NOT work as dj-rest-auth uses the AllAuthPasswordResetForm from all-auth pkg when it is found under installed_apps in settings. This uses different code than dj-rest-auth to send mails, so the override won't come into effect. Instead you will have to override the 'template_prefix' in the all-auth form and/or update the url as needed by extending the form, in addition to overriding the serializer to use your custom form. This tutorial is a good reference that I found. Hope it saves someone's time :)