membersystem/src/membership/emails.py
Benjamin Bach b3795977ed Membership invitations and order emails (#47)
* [x] Create invite emails from admin
* [x] Sign up on special invite form (create password and username)
* [x] Create email with unpaid orders and payment links
* [x] Lodge unpaid orders somewhere in UI for visibility

Reviewed-on: https://git.data.coop/data.coop/membersystem/pulls/47
Reviewed-by: valberg <valberg@orn.li>
Co-authored-by: Benjamin Bach <benjamin@overtag.dk>
Co-committed-by: Benjamin Bach <benjamin@overtag.dk>
2024-08-14 09:17:29 +00:00

126 lines
4.9 KiB
Python

"""Send email to members, using templates and contexts for the emails.
* We keep everything as plain text for now.
* Notice that emails can be multilingual
* Generally, an email consists of templates (for body and subject) and a get_context() method.
"""
from accounting.models import Order
from django.contrib import messages
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail.message import EmailMessage
from django.http import HttpRequest
from django.template import loader
from django.utils import translation
from django.utils.translation import gettext_lazy as _
from .models import Membership
class BaseEmail(EmailMessage):
"""Send emails via templated body and subjects.
This base class is extended for all email functionality.
Because all emails are sent to the Member object, we can keep them gathered here, even when they are generated by
other apps (like the accounting app).
"""
template = "membership/email/base.txt"
# Optional: Set to a template path for subject
template_subject = None
default_subject = "SET SUBJECT HERE"
def __init__(self, request: HttpRequest, *args, **kwargs) -> None:
self.context = kwargs.pop("context", {})
self.user = kwargs.pop("user", None)
if self.user:
kwargs["to"] = [self.user.email]
self.context["user"] = self.user
self.context["recipient_name"] = self.user.get_display_name()
# Necessary to set request before instantiating body and subject
self.request = request
kwargs.setdefault("subject", self.get_subject())
kwargs.setdefault("body", self.get_body())
super().__init__(*args, **kwargs)
def get_context_data(self) -> dict:
"""Resolve common context for sending emails.
When overwriting, remember to call this via super().
"""
c = self.context
site = get_current_site(self.request)
c["request"] = self.request
c["domain"] = site.domain
c["site_name"] = site.name
c["protocol"] = "http" if self.request and not self.request.is_secure() else "https"
return c
def get_body(self) -> str:
"""Build the email body from template and context."""
if self.user and self.user.language_code:
with translation.override(self.user.language_code):
body = loader.render_to_string(self.template, self.get_context_data())
else:
body = loader.render_to_string(self.template, self.get_context_data())
return body
def get_subject(self) -> str:
"""Build the email subject from template or self.default_subject."""
if self.user and self.user.language_code:
with translation.override(self.user.language_code):
if self.template_subject:
subject = loader.render_to_string(self.template_subject, self.get_context_data()).strip()
else:
subject = str(self.default_subject)
elif self.template_subject:
subject = loader.render_to_string(self.template_subject, self.get_context_data()).strip()
else:
subject = str(self.default_subject)
return subject
def send_with_feedback(self, *, success_msg: str | None = None, no_message: bool = False) -> None:
"""Send email, possibly adding feedback via django.contrib.messages."""
if not success_msg:
success_msg = _("Email successfully sent to {}").format(", ".join(self.to))
try:
self.send(fail_silently=False)
if not no_message:
messages.success(self.request, success_msg)
except RuntimeError:
messages.error(self.request, _("Not sent, something wrong with the mail server."))
class InviteEmail(BaseEmail):
template = "membership/emails/invite.txt"
default_subject = _("Invite to data.coop membership")
def __init__(self, membership: Membership, request: HttpRequest, *args, **kwargs) -> None:
self.membership = membership
kwargs["user"] = membership.user
super().__init__(request, *args, **kwargs)
def get_context_data(self) -> dict:
c = super().get_context_data()
c["membership"] = self.membership
c["token"] = default_token_generator.make_token(self.membership.user)
c["referral_code"] = self.membership.referral_code
return c
class OrderEmail(BaseEmail):
template = "membership/emails/order.txt"
default_subject = _("Your data.coop order and payment")
def __init__(self, order: Order, request: HttpRequest, *args, **kwargs) -> None:
self.order = order
kwargs["user"] = order.member
super().__init__(request, *args, **kwargs)
def get_context_data(self) -> dict:
c = super().get_context_data()
c["order"] = self.order
return c