Better hooks to customize PasswordResetForm email sending
- Dominant language
- No language data
- Stars
- 188
- Forks
- 7
- PR merge metrics
- No merged PRs in 30d
Description
### Code of Conduct
- [x] I agree to follow Django's Code of Conduct
### Feature Description
Break up `PasswordResetForm.send_mail()` to allow custom subclasses to override email rendering and email sending independently, without having to duplicate all its existing logic.
### Problem
With the introduction of `MAILERS` in Django 6.1, projects may wish to use a non-`"default"` mailer config for sending password reset emails. (E.g., to use a special high-priority queue.)
The current [`PasswordResetForm.send_mail()`](https://github.com/django/django/blob/stable/6.1.x/django/contrib/auth/forms.py#L394-L421) implementation combines rendering and composing the email with sending the resulting message. A subclass that overrides it to change to `email_message.send(using="priority")` would need to also duplicate all of the rendering code—and then carefully track any future changes in the base class.
[DEP 0018 suggested](https://github.com/django/deps/blob/main/accepted/0018-mailers.md#future-password-reset-mailer) (as `MAILERS` follow-on work) adding a `PasswordResetForm.email_using` property to simplify this. But that wouldn't handle cases where the mailer choice needs to be dynamic based on other factors, like the user's region: `email_message.send(using="eu" if context["user"].gdpr_applies() else "default")`.
Similarly, there may be cases where a project wants to adjust password reset email rendering without duplicating the sending code. (Adding to the context, ensuring the email uses the user's locale rather than the request's, etc.)
### Request or proposal
proposal
### Additional Details
_No response_
### Implementation Suggestions
Suggestion:
```python
class PasswordResetForm(forms.Form):
...
email_using = None
def send_mail(
self,
subject_template_name,
email_template_name,
context,
from_email,
to_email,
html_email_template_name=None,
):
email_message = self.build_email_message(
subject_template_name=subject_template_name,
email_template_name=email_template_name,
html_email_template_name=html_email_template_name,
context=context,
from_email=from_email,
to_email=to_email,
)
try:
self.send_email_message(
email_message=email_message, context=context
)
except Exception:
logger.exception(
"Failed to send password reset email to %s", context["user"].pk
)
def build_email_message(
self,
*,
subject_template_name,
email_template_name,
html_email_template_name,
context,
from_email,
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")
return email_message
def send_email_message(self, *, email_message, context):
email_message.send(using=self.email_using)
...
```
The `email_using` property is there to simplify non-dynamic cases, but isn't strictly required. (It's called `email_using` to avoid any potential confusion with databases' `using`.)
`context` is passed to `send_email_message()` to give access to the user, domain (site), etc. (The `from_email` and `to_email` are available in attributes of `email_message`, so aren't repeated as separate args.)
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in django/contrib/auth/forms.py by reading PasswordResetForm.send_mail() and the surrounding email-sending logic. Compare the proposal's build_email_message() and send_email_message() hooks with the current behavior, then verify that subclasses can customize rendering or the mailer independently without duplicating the existing flow.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- django, python
- Domain
- authentication, backend
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100