from allauth.account import app_settings as allauth_app_settings
from allauth.account.adapter import DefaultAccountAdapter
from allauth.core import context as allauth_ctx
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import EmailMessage, EmailMultiAlternatives
from django.template import TemplateDoesNotExist
from django.template.loader import render_to_string
from jwt_allauth.utils import get_template_path
[docs]
class JWTAllAuthAdapter(DefaultAccountAdapter):
"""
Custom account adapter extending allauth's DefaultAccountAdapter with JWT-specific email handling.
Provides enhanced email confirmation functionality with template path customization
and JWT-related email content handling.
Key Features:
- Email normalization (trimming and lowercasing)
- Customizable template paths for verification emails
- Dual template support (HTML/text) with fallback handling
- Integration with JWT verification workflows
"""
[docs]
def clean_email(self, email):
"""
Normalize email addresses by trimming whitespace and converting to lowercase.
Args:
email (str): Raw email input
Returns:
str: Normalized email address
"""
email = super().clean_email(email)
email = email.strip().lower()
return email
[docs]
def send_confirmation_mail(self, request, emailconfirmation, signup):
"""
Generate and send email confirmation message with context customization.
Context Includes:
- User object
- Verification code or URL (based on EMAIL_VERIFICATION_BY_CODE_ENABLED)
- Site-specific information
Args:
request (HttpRequest): Current request object
emailconfirmation (EmailConfirmation): Email confirmation instance
signup (bool): Flag indicating if this is a signup confirmation
Returns:
str: Confirmation key used in the email
"""
ctx = {
"user": emailconfirmation.email_address.user,
}
if allauth_app_settings.EMAIL_VERIFICATION_BY_CODE_ENABLED:
ctx.update({"code": emailconfirmation.key})
else:
ctx.update(
{
"key": emailconfirmation.key,
"activate_url": self.get_email_confirmation_url(
request, emailconfirmation
),
}
)
if signup:
email_template = "email/signup/email_signup"
template_path = get_template_path(
'EMAIL_VERIFICATION',
"email/signup/email_message.html"
)
subject_path = get_template_path(
'EMAIL_VERIFICATION_SUBJECT',
"email/signup/email_subject.txt"
)
else:
email_template = "account/email/email_confirmation"
template_path = None
subject_path = None
self.send_mail(
email_template,
emailconfirmation.email_address.email,
ctx,
subject_path=subject_path,
template_path=template_path
)
return ctx['key']
[docs]
def send_mail(self, template_prefix, email, context, subject_path=None, template_path=None):
"""
Construct and send email using template configuration.
Enhances Context With:
- Current site information
- Recipient email address
Args:
template_prefix (str): Base path for template lookup
email (str|list): Recipient email address(es)
context (dict): Template context variables
subject_path (str, optional): Custom path for subject template
template_path (str, optional): Custom path for body template
"""
ctx = {
"email": email,
"current_site": get_current_site(allauth_ctx.request),
}
ctx.update(context)
msg = self.render_mail(template_prefix, email, ctx, subject_path=subject_path, template_path=template_path)
msg.send()
[docs]
def render_mail(self, template_prefix, email, context, headers=None, subject_path=None, template_path=None):
"""
Render email message with support for multiple template formats and custom paths.
Behavior:
- Generates multipart emails when both HTML and text templates exist
- Uses custom template paths when provided
- Automatically formats email subject
- Supports HTML email content as primary when specified
Args:
template_prefix (str): Base template path prefix
email (str|list): Recipient email address(es)
context (dict): Template context variables
headers (dict, optional): Custom email headers
subject_path (str, optional): Override path for subject template
template_path (str, optional): Override path for body template
Returns:
EmailMessage: Configured email message object
Raises:
TemplateDoesNotExist: If no valid template can be found
"""
to = [email] if isinstance(email, str) else email
if subject_path is None:
subject_path = "{0}_subject.txt".format(template_prefix)
subject = render_to_string(subject_path, context)
# remove superfluous line breaks
subject = " ".join(subject.splitlines()).strip()
subject = self.format_email_subject(subject)
from_email = self.get_from_email()
if template_path is None:
bodies = {}
html_ext = allauth_app_settings.TEMPLATE_EXTENSION
for ext in [html_ext, "txt"]:
try:
template_name = "{0}_message.{1}".format(template_prefix, ext)
bodies[ext] = render_to_string(
template_name,
context,
allauth_ctx.request,
).strip()
except TemplateDoesNotExist:
if ext == "txt" and not bodies:
# We need at least one body
raise
else:
html_ext = 'html'
bodies = {
html_ext: render_to_string(
template_path,
context,
allauth_ctx.request,
).strip()
}
if "txt" in bodies:
msg = EmailMultiAlternatives(
subject, bodies["txt"], from_email, to, headers=headers
)
if html_ext in bodies:
msg.attach_alternative(bodies[html_ext], "text/html")
else:
msg = EmailMessage(
subject, bodies[html_ext], from_email, to, headers=headers
)
msg.content_subtype = "html" # Main content is now text/html
return msg