如何自定义Django Rest Auth密码重置电子邮件内容/模板

19
在 Django rest_auth 的密码重置中,默认的电子邮件内容如下所示:-

您正在收到此电子邮件,因为您请求在 localhost:8000 上重置用户帐户的密码。

请转到以下页面并选择一个新密码:

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

如果您忘记了用户名,请注意:test

感谢使用我们的网站!

localhost:8000 团队

如何自定义此电子邮件的内容?
9个回答

26

最近我需要在一个项目中实现同样的功能,但无法在任何地方找到详细的答案。

所以我在这里留下我的解决方案,供未来需要的人参考。

对mariodev的建议进行扩展:

1. 子类化PasswordResetSerializer并覆盖save方法。

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. 配置AUTH_USER_MODEL

yourproject/settings.py

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

3. 连接自定义的PasswordResetSerializer以覆盖默认设置。

yourproject/settings.py

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

4. 将您的自定义电子邮件消息文本文件所在目录的路径添加到TEMPLATES

yourproject/settings.py

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

5. 编写自定义电子邮件消息(默认从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 %}

更新:此解决方案适用于django-rest-auth的旧版本(v0.6.0)。从评论中可以看出,源软件包已进行了一些更新,更容易地处理自定义电子邮件模板。最好始终使用软件包中定义的方法,而不是像我的解决方案中覆盖它们。虽然曾经是必需的,但现在可能不再是这样。


你的解决方案对我不起作用:我按照你的步骤做了一切,但rest-auth仍然使用其默认的电子邮件模板:(我已经将我的电子邮件放在project/templates/emails/password-reset-email.html下,并覆盖了get_email_options(self),现在返回'html_email_template_name': 'templates/emails/password-reset-email.html',但仍然没有变化...我的自定义密码序列化程序被使用了,我通过pycharm调试器进行了验证。所以设置应该是正确的,我的重写get_email_options也被调用了。有什么想法吗?谢谢! - ElectRocnic
@ElectRocnic,尝试使用'password-reset-email.html'而不是'templates/emails/password-reset-email.html',并确保在settings.py中指定了您的模板目录。此外,在我最初编写此代码时,get_email_options(self)未包含在rest_auth软件包中。我的方法需要覆盖save(self) - Brian K.
2
谢谢,虽然在当前版本的rest_auth中,你只需要为序列化器编写少量代码。我可能会在未来发布我的解决方案。 - Rik Schoonbeek
1
我仍然无法找到当前的解决方案。这真的很令人惊讶,因为这必须是非常常见的用例。 - Xen_mar
一样,我看了很多例子,除了这个之外似乎都不适用于我。 - Thorvald

12
你可以继承 PasswordResetSerializer 并重写 get_email_options 方法。例如:
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
            }
        }

你从哪里得到了传递给CustomPasswordResetSerializer的PasswordResetSerializer? - Efosa
@Efosa 我刚刚在我的代码片段中添加了导入。 - M.Void
@M.Void,我喜欢这个解决方案,但是我不明白如何触发它。我想在创建新用户时发送激活链接,用户点击激活链接后可以重置密码。 - bhattraideb
@bhattraideb,你在setting.py中指定了CustomPasswordResetSerializer吗?我的意思是PASSWORD_RESET_SERIALIZER,请查看以下链接获取更多详细信息:https://django-rest-auth.readthedocs.io/en/latest/configuration.html?highlight=PASSWORD_RESET_SERIALIZER#configuration - M.Void
3
我做了完全相同的事情,但对我没有用。在我的情况下,get_email_options没有任何效果。 - Qeybulla
显示剩余3条评论

7
一种简单的解决方案是在模板目录下创建:
-templates
   -registration
       password_reset_email.html

您想要的内容是关于IT技术的:Django rest-auth 使用django.contrib.auth 模板。


我尝试了这个解决方案,它重写了但它没有像应该的那样显示出来,相反它会把代码的电子邮件返回给我。我不知道为什么。https://dev59.com/Ornoa4cB1Zd3GeqPIgIR - Opeyemi Odedeyi
这个答案是可行的,但不幸的是,“password_reset_email.html”被传递到明文模板中。@OpeyemiOdedeyi 这就是为什么你会看到HTML代码作为文本的原因。你必须像Biran K提到的那样覆盖PasswordResetSerializer。 - chickahoona

7

对于 dj-rest-auth,我是这样做的:

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',
}

request替换为None,而不是原始的request,使用build_absolute_uri函数时它将采用在django.contrib.sites模块中定义的SITE_ID=1的值作为域名。因此,在Django管理界面中定义的任何内容都可以作为密码重置链接URL中的域名。如果您想让密码重置URL指向前端(可能是在不同域上运行的React应用程序)时,这种方法很有意义。
编辑: 我提交的关于此问题的PR已合并,下一个版本将可以在设置中设置此项。请查看dj-rest-auth文档,以了解您需要设置哪个设置。

1
用户提示:我的有关此问题的PR已合并,下一个版本将可以在您的设置中进行设置。请查看dj-rest-auth文档以了解需要设置哪些设置。 - Özer
好久没见到这个了,它帮了我很多,谢谢。 - hama Mk
也为我修复了。 - Matthias

7

6

如果您想使用HTML电子邮件模板,则可以按照Brian的回答添加以下内容:

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

将其添加在

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

下方即可,这样您就可以使用HTML模板发送电子邮件。

通过检查PasswordResetForm类的send_mail方法,您可以了解到这是为什么发生的。

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

只是补充一下@Ryan113的答案,django-rest-auth的版本+0.9.5使用了django-allauth的电子邮件消息和模板,因此不必在django-rest-auth中查找电子邮件模板,而是按照https://dev59.com/nVUK5IYBdhLWcg3wiAUo#51685556中所述覆盖django-allauth的模板。 - Abulo John Joshua

2
在您的模板文件夹中创建以下路径的目录:

templates/admin/registration/

现在将django/contrib/admin/templates/registration/中的所有文件复制到您刚刚创建的目录中。您可以在安装django的位置找到此目录。在Linux中,可以在以下位置找到它:

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

您需要root权限才能访问此目录。
现在,当您发送电子邮件时,您在项目中刚刚复制的模板将被使用。

无法工作。我需要其他的配置才能让它工作吗? - McLosys Creative
你是否遇到了任何错误,或者它仍在使用之前的模板? - Muhammad Hassan
仍然使用之前的模板。我需要配置新复制的模板路径吗?我已经将django/contrib/admin/templates/registration/的内容复制到templates/admin/registration/中。这样对吗? - McLosys Creative
你的翻译内容如下:这是正确的。你在urls.py中有哪些忘记密码的URL? - Muhammad Hassan
url(r'^rest-auth/', include(rest_auth_urls)), - McLosys Creative
你可以在https://github.com/Tivix/django-rest-auth/tree/master/demo查看演示项目。它使用自己的模板。 - Muhammad Hassan

1

对于使用dj-rest-auth的用户,如果同时使用all-auth,请注意覆盖get_email_options()方法的解决方案将不起作用。因为当在设置中找到installed_apps时,dj-rest-auth会使用all-auth包中的AllAuthPasswordResetForm,该表单使用与dj-rest-auth发送邮件的不同代码,因此覆盖不会生效。相反,您需要覆盖all-auth表单中的'template_prefix'并/或根据需要更新url,以扩展表单,并覆盖序列化器以使用自定义表单。我发现这篇教程是一个很好的参考资料。希望能节省某些人的时间 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接