Skip to content

Commit

Permalink
Good enough email template, for now.
Browse files Browse the repository at this point in the history
  • Loading branch information
davepeck committed Apr 19, 2024
1 parent 8e4511a commit 437f682
Show file tree
Hide file tree
Showing 17 changed files with 518 additions and 6 deletions.
9 changes: 9 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,12 @@ export DJANGO_SUPERUSER_PASSWORD=ultrasekret
# export AWS_REGION=us-east-1
# export AGCOD_ENDPOINT_HOST=agcod-v2-gamma.amazon.com
# export AGCOD_PARTNER_ID=

# Enable these for Mandrill integration. See 1Password.
# export EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
# export EMAIL_HOST=smtp.mandrillapp.com
# export EMAIL_HOST_PASSWORD=
# export EMAIL_HOST_USER=VoterBowl
# export EMAIL_PORT=587
# export EMAIL_USE_TLS=true
# export EMAIL_USE_SSL=false
29 changes: 29 additions & 0 deletions server/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,32 @@

# Sandbox endpoint: agcod-v2-gamma.amazon.com us-east-1
# Production endpoint: agcod-v2.amazon.com us-east-1


# ----------------------------------------------------------------------------
# Email Settings
# ----------------------------------------------------------------------------

# The email address that emails are sent from unless explicitly overridden
# when invoking Django's `send_mail` function
DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "[email protected]")

# The *sending* email address used when Django emails admins about errors.
# For now, we make this the same as DEFAULT_FROM_EMAIL
SERVER_EMAIL = DEFAULT_FROM_EMAIL

# The email addresses of our administrators.
ADMINS = [("Frontseat Developers", "[email protected]")]

# How to send email. We default to console-based email, which simply prints
# emails to the console. This is useful for local development. In production,
# we'll configure SMTP.
EMAIL_BACKEND = os.getenv(
"EMAIL_BACKEND", "django.core.mail.backends.console.EmailBackend"
)
EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", None)
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", None)
EMAIL_HOST = os.getenv("EMAIL_HOST", None)
EMAIL_PORT = os.getenv("EMAIL_PORT", None)
EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", "false").lower() == "true"
EMAIL_USE_SSL = os.getenv("EMAIL_USE_SSL", "false").lower() == "true"
70 changes: 70 additions & 0 deletions server/utils/email.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import dataclasses
import logging
import typing as t

from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string

logger = logging.getLogger(__name__)


@dataclasses.dataclass(frozen=True)
Expand Down Expand Up @@ -42,3 +50,65 @@ def normalize_email(
local = local.encode("ascii", "ignore").decode("ascii")
domain = domain.encode("ascii", "ignore").decode("ascii")
return f"{local}@{domain}"


def send_template_email(
to: str | t.Sequence[str],
template_base: str,
context: dict | None = None,
from_email: str | None = None,
) -> bool:
"""
Send a templatized email.
Send an email to the `to` address, using the template files found under
the `template_base` to render contents.
The following named templates must be found underneath `template_base`:
- `subject.txt`: renders the subject line
- `body.txt`: renders the plain-text body
- `body.dhtml`: renders the HTML body
Django's template system is flexible and can load templates from just about
anywhere, provided you write a plugin. But! By default, we're going to load
them from the filesystem; `template_base` is simply the name of the
directory that contains these three files, relative to the `templates`
directory in the app.
For instance, if we have `subject`/`body` templates in
`server/assistant/templates/email/registration`, then `template_base` is
`email/registration`.
"""
to_array = [to] if isinstance(to, str) else to

message = create_message(to_array, template_base, context, from_email)
try:
message.send()
return True
except Exception:
logger.exception(f"failed to send email to {to}")
return False


def create_message(
to: t.Sequence[str],
template_base: str,
context: dict[str, t.Any] | None = None,
from_email: str | None = None,
) -> EmailMultiAlternatives:
"""Create the underlying email message to send."""
context = context or {}
from_email = from_email or settings.DEFAULT_FROM_EMAIL
context.setdefault("BASE_URL", settings.BASE_URL)

subject = render_to_string(f"{template_base}/subject.txt", context).strip()
text = render_to_string(f"{template_base}/body.txt", context)
html = render_to_string(f"{template_base}/body.dhtml", context)

message = EmailMultiAlternatives(
from_email=from_email, to=to, subject=subject, body=text
)
message.attach_alternative(html, "text/html")

return message
2 changes: 1 addition & 1 deletion server/vb/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ def school(self) -> School:
@property
def relative_url(self) -> str:
"""Return the relative URL for the email validation link."""
return reverse("vb:verify_email", args=[self.contest.school.slug, self.token])
return reverse("vb:validate_email", args=[self.contest.school.slug, self.token])

@property
def absolute_url(self) -> str:
Expand Down
16 changes: 14 additions & 2 deletions server/vb/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.db import transaction

from server.utils.agcod import AGCODClient
from server.utils.email import send_template_email
from server.utils.tokens import make_token

from .models import Contest, EmailValidationLink, GiftCard, School, Student
Expand Down Expand Up @@ -124,9 +125,20 @@ def send_validation_link_email(
student: Student, contest: Contest, email: str
) -> EmailValidationLink:
"""Generate a validation link to a student for a contest."""
# TODO dave
link = EmailValidationLink.objects.create(
student=student, contest=contest, email=email, token=make_token(12)
)
print("TODO SENDING A VALIDATION LINK: ", link.absolute_url)
success = send_template_email(
to=email,
template_base="email/validate",
context={
"student": student,
"contest": contest,
"email": email,
"link": link,
"title": f"Get my ${contest.amount} gift card",
},
)
if not success:
logger.error(f"Failed to send email validation link to {email}")
return link
Loading

0 comments on commit 437f682

Please sign in to comment.