Giter Site home page Giter Site logo

Comments (33)

mohmyo avatar mohmyo commented on August 21, 2024 17

I was able to do it similarly as you suggested.
I will share it here for anyone who comes later

Create your custom password reset serializer

from dj_rest_auth.serializers import PasswordResetSerializer

class CustomPasswordResetSerializer(PasswordResetSerializer):
    def save(self):
        request = self.context.get('request')
        # Set some values to trigger the send_email method.
        opts = {
            'use_https': request.is_secure(),
            'from_email': '[email protected]',
            'request': request,
            # here I have set my desired template to be used
            # don't forget to add your templates directory in settings to be found
            'email_template_name': 'password_reset_email.html'
        }

        opts.update(self.get_email_options())
        self.reset_form.save(**opts)

UPDATE:

If you only want to customize email parameters nothing more or less, you can ignore overriding save() method and override get_email_options() instead, check it in source code here.

Ex:

from dj_rest_auth.serializers import PasswordResetSerializer

class MyPasswordResetSerializer(PasswordResetSerializer):

    def get_email_options(self) :
      
        return {
            'email_template_name': 'password_reset_email.html'
        }

get_email_options() is being called in save() just to update opts dict with any other options before triggering reset_form, here in source code.

then point to your custom serializer in settings.py

REST_AUTH_SERIALIZERS = {
    'PASSWORD_RESET_SERIALIZER': 'path.to.your.CustomPasswordResetSerializer'
}

from dj-rest-auth.

Abishek05 avatar Abishek05 commented on August 21, 2024 3

I was able to do it similarly as you suggested.
I will share it here for anyone comes later

Create your custom password reset serializer

from dj_rest_auth.serializers import PasswordResetSerializer

class CustomPasswordResetSerializer(PasswordResetSerializer):
    def save(self):
        request = self.context.get('request')
        # Set some values to trigger the send_email method.
        opts = {
            'use_https': request.is_secure(),
            'from_email': '[email protected]',
            'request': request,
            # here I have set my desired template to be used
            # don't forget to add your templates directory in settings to be found
            'email_template_name': 'password_reset_email.html'
        }

        opts.update(self.get_email_options())
        self.reset_form.save(**opts)

UPDATE:

If you only want to customize email parameters nothing more or less, you can ignore overriding save() method and override get_email_options() instead, check it is source code here.

Ex:

from dj_rest_auth.serializers import PasswordResetSerializer

class MyPasswordResetSerializer(PasswordResetSerializer):

    def get_email_options(self) :
      
        return {
            'email_template_name': 'password_reset_email.html'
        }

get_email_options() is being called in save() just to update opts dict with any other options before triggering reset_form, here in source code.

then point to your custom serializer in settings.py

REST_AUTH_SERIALIZERS = {
    'PASSWORD_RESET_SERIALIZER': 'path.to.your.CustomPasswordResetSerializer'
}

After some digging, I found that the password reset email template is coming from django/contrib/admin/templates/registration/password_reset_email.html.

Overwriting templates\registration\password_reset_email.html with your own custom content works fine without custom Serialization.

Although, to send the HTML content you will have to write a custom password reset serializer as @mohmyo suggested and replace this 'email_template_name': 'password_reset_email.html' with 'html_email_template_name': 'password_reset_email.html'.

@mohmyo Thanks for your Solution;)

from dj-rest-auth.

NemanjaNecke avatar NemanjaNecke commented on August 21, 2024 2

Here is how I got it running. These are custom serializers:

from django.conf import settings
from dj_rest_auth.forms import AllAuthPasswordResetForm
from django.utils.encoding import force_str
from allauth.account.forms import default_token_generator
from django.utils.http import urlsafe_base64_decode as uid_decoder
from django.contrib.auth.forms import PasswordResetForm


class PasswordResetSerializer(PasswordResetSerializer):
    @property
    def password_reset_form_class(self):
        use_custom_email_template = bool(self.get_email_options().get("html_email_template_name", ''))
        if 'allauth' in settings.INSTALLED_APPS and not use_custom_email_template:
            return AllAuthPasswordResetForm
        else:
            return PasswordResetForm
    def get_email_options(self):
        return {
            'html_email_template_name': 'account/email/password_reset_email.html',
        }

class CustomPasswordResetConfirmSerializer(PasswordResetConfirmSerializer):
    def validate(self, attrs):
        # Decode the uidb64 (allauth use base36) to uid to get User object
        try:
            uid = force_str(uid_decoder(attrs['uid']))
            self.user = Account.objects.get(pk=uid)
        except (TypeError, ValueError, OverflowError, ObjectDoesNotExist):
            raise ValidationError({'uid': ['Invalid value']})
        # Have to use allauth default token generator instead of django's default one
        if not default_token_generator.check_token(self.user, attrs['token']):
            raise ValidationError({'token': ['Invalid value']})

        self.custom_validation(attrs)
        # Construct SetPasswordForm instance
        self.set_password_form = self.set_password_form_class(
            user=self.user, data=attrs,
        )
        if not self.set_password_form.is_valid():
            raise serializers.ValidationError(self.set_password_form.errors)

        return attrs

You also have to set custom serializers in settings.py
I combined approaches from this answer #345
and this one #428

from dj-rest-auth.

Erwol avatar Erwol commented on August 21, 2024 2

For anyone attempting to do this with the following dependencies:

django-allauth = "==0.50.0"
dj-rest-auth = {extras = ["with_social"], version = "==4.0.0"}

None of the above answers fully worked for me as most options were deprecated after dj-rest-auth ~2.x. Instead, after some debugging, I found this solution:

Customising reset password URL

settings.py

REST_AUTH = {
    'PASSWORD_RESET_SERIALIZER': 'yourAuthApp.serializers.CustomPasswordResetSerializer'
}

yourAuthApp serializers

from dj_rest_auth.serializers import PasswordResetSerializer

from decouple import config

RESET_PASSWORD_REDIRECT_URL = config('RESET_PASSWORD_REDIRECT_URL')


def custom_url_generator(request, user, temp_key):
    return f'{RESET_PASSWORD_REDIRECT_URL}?token={temp_key}'


class CustomPasswordResetSerializer(PasswordResetSerializer):
    def get_email_options(self):
        return {
            'url_generator': custom_url_generator
        }

.env

# Reset password settings
RESET_PASSWORD_REDIRECT_URL=http://localhost:3000/auth/reset

You don't need to use environment variables, but I encourage doing so. The above code allows customising the URL that will be sent to your users while using the default email template provided by allauth. Not sure this is documented somewhere, but the official docs cannot find a definition of get_email_options: https://dj-rest-auth.readthedocs.io/en/4.0.1/search.html?q=get_email_option&check_keywords=yes&area=default.

Using a custom mail template

Two ways to achieve this:

Easy: overriding the original mail structure

On your project's root directory, create a templates > account > email directory. Now add a base_message.txt and base_message.txt files. You can take the structure from the official allauth templates directory: https://github.com/pennersr/django-allauth/tree/main/allauth/templates/account/email

And that's it; if you leave the {{ password_reset_url }} intact, its value should be taken from the custom_url_generator we defined before.

A bit more messy

You will have to override your CustomPasswordResetSerializer password_reset_form_class function to use a CustomAllAuthPasswordResetForm that should inherit from from dj_rest_auth.forms import AllAuthPasswordResetForm. You will then need to override the forms save method to update the following line:

            get_adapter(request).send_mail(
                'account/email/password_reset_key', email, context
            )

with the path to your custom template.

Neither method seems perfect to me but I went for the first as I find it more maintainable. If someone finds a reference to how to do this in a better way please let me know.

from dj-rest-auth.

mohmyo avatar mohmyo commented on August 21, 2024 1

@aaronamelgar Nope, I have updated my above code snippet regarding the base class, check it out.
the reason you are seeing this is that registration is a separated module in dj_rest_auth

from dj-rest-auth.

Abishek05 avatar Abishek05 commented on August 21, 2024 1

I was able to do it similarly as you suggested.
I will share it here for anyone comes later

Create your custom password reset serializer

from dj_rest_auth.serializers import PasswordResetSerializer

class CustomPasswordResetSerializer(PasswordResetSerializer):
    def save(self):
        request = self.context.get('request')
        # Set some values to trigger the send_email method.
        opts = {
            'use_https': request.is_secure(),
            'from_email': '[email protected]',
            'request': request,
            # here I have set my desired template to be used
            # don't forget to add your templates directory in settings to be found
            'email_template_name': 'password_reset_email.html'
        }

        opts.update(self.get_email_options())
        self.reset_form.save(**opts)

UPDATE:

If you only want to customize email parameters nothing more or less, you can ignore overriding save() method and override get_email_options() instead, check it is source code here.

Ex:

from dj_rest_auth.serializers import PasswordResetSerializer

class MyPasswordResetSerializer(PasswordResetSerializer):

    def get_email_options(self) :
      
        return {
            'email_template_name': 'password_reset_email.html'
        }

get_email_options() is being called in save() just to update opts dict with any other options before triggering reset_form, here in source code.

then point to your custom serializer in settings.py

REST_AUTH_SERIALIZERS = {
    'PASSWORD_RESET_SERIALIZER': 'path.to.your.CustomPasswordResetSerializer'
}

@mohmyo I have followed this and the template variables such as {{ password_reset_url }} are not being rendered in the email.

Email I receive looks like this

Hello fro !

You're receiving this e-mail because you or someone else has requested a password for your user account.
It can be safely ignored if you did not request a password reset. Click the link below to reset your password.



Thank you for using !

Am I missing something??

from dj-rest-auth.

mohmyo avatar mohmyo commented on August 21, 2024 1

You are welcome @Abishek05, glad it helped.

from dj-rest-auth.

haccks avatar haccks commented on August 21, 2024 1

Just a heads-up: this works till version 2.1.4. Later versions will ignore get_email_options override. Checkout the reported bug.

from dj-rest-auth.

iMerica avatar iMerica commented on August 21, 2024

This is how I do it on my projects (DRF API Server + React SPA or Next.js):


Declare a custom serializer for registration that inherits from the base. Then you have complete control over what is included in the email.

from dj_rest_auth.registration.serializers import RegisterSerializer

class CustomRegistrationSerializer(RegisterSerializer):
  def save(self, request):
     # handle all setup logic and email here. 

then, in your Django settings:

REST_AUTH_REGISTER_SERIALIZERS = {
    'REGISTER_SERIALIZER': 'path.to.your.CustomRegistrationSerializer'
}

from dj-rest-auth.

mohmyo avatar mohmyo commented on August 21, 2024

I will try it and get back to you, thanks

from dj-rest-auth.

aaronamelgar avatar aaronamelgar commented on August 21, 2024

@mohmyo did you create your own base class for PasswordResetSerializer?

I only see RegisterSerializer in dj_rest_auth.registration.serializers

from dj-rest-auth.

Dilipsa avatar Dilipsa commented on August 21, 2024

please help me for password reset, its giving error as following

NoReverseMatch at /dj-rest-auth/password/reset/
Reverse for 'password_reset_confirm' with keyword arguments '{'uidb64': 'MQ', 'token': 'aay6u3-6409cacfd1a2284e6bfb54f61514d66b'}' not found. 1 pattern(s) tried: ['password-reset/confirm/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$']

from dj-rest-auth.

mohmyo avatar mohmyo commented on August 21, 2024

This has been answered many times, see closed issues and you will find so many helpful discussions with the solution

from dj-rest-auth.

mohmyo avatar mohmyo commented on August 21, 2024

Like this
#118 (comment)

from dj-rest-auth.

ShahriarDhruvo avatar ShahriarDhruvo commented on August 21, 2024

How to do the same (Edit the email template) for email sent to verify the email after registration?

from dj-rest-auth.

mohmyo avatar mohmyo commented on August 21, 2024

This part is controlled by django-allauth, see

from dj-rest-auth.

ShahriarDhruvo avatar ShahriarDhruvo commented on August 21, 2024

Thanks, it is done 😎. If anyone having trouble getting the key sent at the end of the url you can use {{ key }} in that template to access the key and later goto the verify email address and post the key by frontend.

from dj-rest-auth.

colonder avatar colonder commented on August 21, 2024

@mohmyo How does your template look like? Where do you exactly put a custom URL pointing to the frontend?

from dj-rest-auth.

mohmyo avatar mohmyo commented on August 21, 2024

@colonder can you be more specific?

from dj-rest-auth.

mohmyo avatar mohmyo commented on August 21, 2024

As password_reset_url parameter isn't passed to Django PasswordResetForm then I will assume that you have created your custom template, right?

from dj-rest-auth.

lkbhitesh07 avatar lkbhitesh07 commented on August 21, 2024

Hello @mohmyo and @Abishek05 , even after doing it with your approach I'm getting error which is
django_1 | [pid: 56|app: 0|req: 3/3] 172.18.0.1 () {42 vars in 696 bytes} [Tue Jan 5 20:09:35 2021] OPTIONS /api/auth/password/reset/ => generated 0 bytes in 0 msecs (HTTP/1.1 200) 7 headers in 365 bytes (1 switches on core 0) django_1 | [pid: 56|app: 0|req: 4/4] 172.18.0.1 () {42 vars in 696 bytes} [Tue Jan 5 20:09:35 2021] POST /api/auth/password/reset/ => generated 222495 bytes in 113 msecs (HTTP/1.1 500) 5 headers in 170 bytes (1 switches on core 0)

I'm contributing to an open-source org, Now let me tell you about the structure of the org, basically apps/accounts/templates/account/password_reset_email.html and serializer file apps/accounts/serializers.py where I defined my CustomPasswordResetSerializer

serializer.py
`class CustomPasswordResetSerializer(PasswordResetSerializer):

def get_email_options(self) :
  
    return {
        'html_email_template_name': 'account/password_reset_email.html'
    }`

settings.py file

REST_AUTH_SERIALIZERS = { "PASSWORD_RESET_SERIALIZER": "accounts.serializers.CustomPasswordResetSerializer" }

Will you please help me with what to do in this situation, Also if I need to do the complete process by overwriting how would I do that because here we have templates for each app so we cannot define the TEMPLATE_DIR in the settings file?

I've been stuck to this issue for the past 4-5 days, please help me out.
Thanks

from dj-rest-auth.

mohmyo avatar mohmyo commented on August 21, 2024

Hello @lkbhitesh07, is there a better error trace than the one you posted? I'm not sure where to look, and there is a lot of things that can be the cause of the issue here.

So, let's start with a better error trace and the file structure of your project.

from dj-rest-auth.

lkbhitesh07 avatar lkbhitesh07 commented on August 21, 2024

@mohmyo Thanks for replying also, I just solved this issue a few minutes back but I'm experiencing a very strange problem. I mean after customizing the PasswordResetSerializer, I made a folder templates/registration and put the templates inside, the strange part is, it's not getting recognized. I mean I have to define it like this -

class CustomPasswordResetSerializer(PasswordResetSerializer):
    def get_email_options(self):
        super().get_email_options()
        return {
            'subject_template_name': 'registration/password_reset_subject.txt',
            'email_template_name': 'registration/password_reset_email.txt',
        }

Otherwise, it's not getting recognized if I'll just do password_reset_subject.txt it will throw me an error that password_reset_subject.txt is not defined.
also one more interesting thing is that I have to put this file password_reset_email.txt as the txt extension, if I'll put it in html extension the variables will not pass.
Please do let me know if you need any more information
Thanks

from dj-rest-auth.

mohmyo avatar mohmyo commented on August 21, 2024

There is no need to do super().get_email_options(), If you look into get_email_options() you will find that it is already empty and it got called later at save() to inject any additional options later on. In other words, get_email_options() has nothing to do with the flow unless it is being overridden.

So it should be like what I have posted earlier here

from dj_rest_auth.serializers import PasswordResetSerializer

class MyPasswordResetSerializer(PasswordResetSerializer):

    def get_email_options(self) :
      
        return {
            'email_template_name': 'password_reset_email.html'
        }

The issue for template folder not recognized may be related to some configuration to your project.
here an example of a project structure and the right config for it
project structure

|-app1
|-core
  |-settings
    |-base.py
|-templates
  |-password_reset_email.html

settings.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, '../', 'templates'), ], # note that ../ is here because our base.py is under two levels
        'APP_DIRS': True,
        'OPTIONS': {
            ........
        },
    },
]

from dj-rest-auth.

lkbhitesh07 avatar lkbhitesh07 commented on August 21, 2024

@mohmyo Just one more small help please, I mean we have a directory just like below so how should I add that? What should I put in 'DIRS' as I have templates folder according to different apps.

|-apps
 |-app1
  |-templates
    |-registration
 |-app2
  |-templates
|-core
|-settings
  |-common.py

Thanks for all your help.

from dj-rest-auth.

mohmyo avatar mohmyo commented on August 21, 2024

I think this will help you a lot to pick the right DIRS for your project
https://docs.djangoproject.com/en/3.1/howto/overriding-templates/

from dj-rest-auth.

lkbhitesh07 avatar lkbhitesh07 commented on August 21, 2024

@mohmyo Yeah actually I already tried that out but didn't get it working that's why I asked you in case I was making some mistake. The error looks something like this -

django_1     | ERROR 2021-01-10 00:01:04,301 log 59 140635120949120 Internal Server Error: /api/auth/password/reset/
django_1     | Traceback (most recent call last):
django_1     |   File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
django_1     |     response = get_response(request)
django_1     |   File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 115, in _get_response
django_1     |     response = self.process_exception_by_middleware(e, request)
django_1     |   File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 113, in _get_response
django_1     |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
django_1     |   File "/usr/local/lib/python3.7/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
django_1     |     return view_func(*args, **kwargs)
django_1     |   File "/usr/local/lib/python3.7/site-packages/django/views/generic/base.py", line 71, in view
django_1     |     return self.dispatch(request, *args, **kwargs)
django_1     |   File "/usr/local/lib/python3.7/site-packages/rest_framework/views.py", line 505, in dispatch
django_1     |     response = self.handle_exception(exc)
django_1     |   File "/usr/local/lib/python3.7/site-packages/rest_framework/views.py", line 465, in handle_exception
django_1     |     self.raise_uncaught_exception(exc)
django_1     |   File "/usr/local/lib/python3.7/site-packages/rest_framework/views.py", line 476, in raise_uncaught_exception
django_1     |     raise exc
django_1     |   File "/usr/local/lib/python3.7/site-packages/rest_framework/views.py", line 502, in dispatch
django_1     |     response = handler(request, *args, **kwargs)
django_1     |   File "/usr/local/lib/python3.7/site-packages/dj_rest_auth/views.py", line 233, in post
django_1     |     serializer.save()
django_1     |   File "/usr/local/lib/python3.7/site-packages/dj_rest_auth/serializers.py", line 194, in save
django_1     |     self.reset_form.save(**opts)
django_1     |   File "/usr/local/lib/python3.7/site-packages/django/contrib/auth/forms.py", line 311, in save
django_1     |     user_email, html_email_template_name=html_email_template_name,
django_1     |   File "/usr/local/lib/python3.7/site-packages/django/contrib/auth/forms.py", line 252, in send_mail
django_1     |     body = loader.render_to_string(email_template_name, context)
django_1     |   File "/usr/local/lib/python3.7/site-packages/django/template/loader.py", line 61, in render_to_string
django_1     |     template = get_template(template_name, using=using)
django_1     |   File "/usr/local/lib/python3.7/site-packages/django/template/loader.py", line 19, in get_template
django_1     |     raise TemplateDoesNotExist(template_name, chain=chain)
django_1     | django.template.exceptions.TemplateDoesNotExist: password_reset_email.html

Thanks for your support.

from dj-rest-auth.

mohmyo avatar mohmyo commented on August 21, 2024

can you share your TEMPLATES value?

from dj-rest-auth.

lkbhitesh07 avatar lkbhitesh07 commented on August 21, 2024

Here, this is the complete file, and it is in, BASE_DIR/apps/accounts/registration/password_reset_email.html.

{% load i18n %}{% autoescape off %}Hello from {{ site_name }}!

You're receiving this e-mail because you or someone else has requested a password for your user account at {{ site_domain }}.
It can be safely ignored if you did not request a password reset.
{% 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 %}

Thanks

from dj-rest-auth.

mohmyo avatar mohmyo commented on August 21, 2024

I think you can try this

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, '../', 'apps/app1/templates'), ], 
        'APP_DIRS': True,
        'OPTIONS': {
            ........
        },
    },
]
def get_email_options(self) :
      
        return {
            'email_template_name': 'registration/password_reset_email.html'
        }

from dj-rest-auth.

AbhisarSaraswat avatar AbhisarSaraswat commented on August 21, 2024

Hi @mohmyo ,

I've a question that in PasswordResetView , Initially Link going on email is "http://127.0.0.1:8000/........". I want to change it to the other URL(i.e. url on which Frontend(Angular) is hosted on). How can I do it?

Thanks in advance.

from dj-rest-auth.

Sinanaltundag avatar Sinanaltundag commented on August 21, 2024

I add uid
def custom_url_generator(self, request, user, temp_key):
return f'{RESET_PASSWORD_REDIRECT_URL}?uid={user.id}&token={temp_key}'

base_message.txt and password_reset_key_message.txt files can added for custom template (base_message.txt duplicated)
I can't manage override template files with first method because django loads app directory templates first. so I have to move custom templates in my custom app then I move app name in settings.py over "allauth" app. I did not try second method for change template

The answer above was the most suitable for me. (django 5 dj-rest-auth 5)

from dj-rest-auth.

kumawataryan avatar kumawataryan commented on August 21, 2024

Hey @Erwol, thank you so much! What you suggested really helped me out. I was stuck on this problem for a few days, but now I've got it sorted.

from dj-rest-auth.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.