dj-rest-authでパスワードリセットメールをカスタマイズする(続き)
はじめに
この記事の続きです。パスワードリセット時のメールの文面をカスタマイズするためにSerialier をいじったりして、すったもんだしました。これで後はテンプレートはいじれば終わりだ・・・、と思ったらまたハマりました。
やろうとしたこと
site-package/django/contrib/admin/templates/registration
にあるpassword_reset_email.html
をコピーして、自分が設定したい変数を埋め込めんだテンプレートにした。
{% with uidb64 as uid_str %}
{% with token as token_str %}
{% url 'password_reset_confirm' uidb64=uid_str token=token_str as password_reset_confirm_url %}
{% endwith %}
{% endwith %}
{% load i18n %}{% autoescape off %}
{% blocktranslate %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktranslate %}
{% translate "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{ protocol }}://{{frontend_origin}}{{frontend_auth}}{{ uid }}/{{ token }}/
{% endblock %}
{% translate 'Your username, in case you’ve forgotten:' %} {{ user.get_username }}
{% translate "Thanks for using our site!" %}
{% blocktranslate %}The {{ site_name }} team{% endblocktranslate %}
{% endautoescape %}
実際にはこんな感じでfrontend_origin
やらrontend_auth
という変数を使いたかった。が、どーやってもこの変数が反映されない。context_processors.py を定義し、そこで context を設定してもなぜか値が入らない。テンプレートに変数を渡すので setitings.py からは持ってこれないし、なんでだー、とうんうん言ってました。
原因:メールのテンプレートは context_processors を通過しない
こちらの Q&Aによると、どうやらメールのテンプレートは context_processor を利用しないらしい。ゆえに、context_processors.py とかで設定しても無視されてしまうようである。
解決策:カスタムフォームを作成する
PasswordResetForm
のsend_mail
メソッドをオーバーライドし、メール本文を作成する前に context を追加しておく。ここではメール本文に含めたい変数は settings.py から持ってきている。
from django.contrib.auth.forms import PasswordResetForm
from django.template import loader
from django.core.mail import EmailMultiAlternatives
from django.conf import settings
class CustomPasswordResetForm(PasswordResetForm):
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])
context["site_name"] = getattr(settings, "EMAIL_SITE_NAME", None)
context["frontend_origin"] = getattr(settings, "FRONTEND_ELB_HOST", None)
context["frontend_auth"] = getattr(settings, "FRONTEND_AUTH_URL", None)
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()
また、前回作成した CustomPasswordResetSerializer を編集して、この Serializer が CustomPasswordResetForm を見に行くようにしておく。これで解決。
from .forms import CustomPasswordResetForm
class CustomPasswordResetSerializer(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 CustomPasswordResetForm
def get_email_options(self, **kwargs):
return {
"html_email_template_name": "custom_password_reset_email.html",
"from_email": os.environ.get("EMAIL_HOST_USER"),
}
おまけ
認証用のリンクの URL を変えるだけなら AccountAdapter をいじってもできる。
from allauth.account.adapter import DefaultAccountAdapter
from django.conf import settings
class CustomAccountAdapter(DefaultAccountAdapter):
def get_email_confirmation_url(self, request, emailconfirmation):
"""
Changing the confirmation URL to fit the domain that we are working on
"""
backend_origin = getattr(settings, "BACKEND_ELB_HOST", None)
url = (
f"{request.scheme}://{backend_origin}/accounts/confirm-email/"
+ emailconfirmation.key
)
return url
settings.py
にACCOUNT_ADAPTER = "backend.views.CustomAccountAdapter"
を追加しておくこと。
Discussion