Coverage for src/couchers/email/__init__.py: 100%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from html import escape
2from pathlib import Path
4import yaml
5from jinja2 import Environment, FileSystemLoader
7from couchers.config import config
8from couchers.jobs.enqueue import queue_job
9from couchers.models import BackgroundJobType
10from proto.internal import jobs_pb2
12loader = FileSystemLoader(Path(__file__).parent / ".." / ".." / ".." / "templates")
13env = Environment(loader=loader, trim_blocks=True)
16def couchers_escape(value):
17 return escape(str(value))
20def couchers_safe(value):
21 return value
24env.filters["couchers_escape"] = couchers_escape
25env.filters["couchers_safe"] = couchers_safe
27plain_base_template = env.get_template("email_base_plain.md")
28html_base_template = env.get_template("email_base_html.html")
31def render_html(text):
32 stripped_paragraphs = text.strip().split("\n\n")
33 return "\n\n".join(f"<p>{paragraph.strip()}</p>" for paragraph in stripped_paragraphs)
36def _render_email(template_file, template_args={}):
37 """
38 Renders both a plain-text and a HTML version of an email, and embeds both in their base templates
40 The template should look something like this:
42 ```
43 ---
44 subject: "Email for {{ user.name|couchers_escape }}"
45 ---
47 Hello {{ user.name|couchers_escape }}, this is a sample email
48 ```
50 The first bit, sandwiched between the first two `---`s is the "frontmatter", some YAML stuff. Currently it just
51 contains the subject.
53 We split these into the frontmatter and the message text (note the text may contain more `---`s which then denote
54 horizontal rules `<hr />` in HTML). The frontmatter is rendered through jinja2 so that you can use the template
55 arguments to modify it, e.g. use the user name or something in the subject.
57 The body is run through twice, once for the plaintext, once for HTML, and then the HTML version is run through
58 render_html above to turn it into HTML.
59 """
60 source, _, _ = loader.get_source(env, f"{template_file}.md")
62 # the file should start with a "---"
63 stub, frontmatter_source, text_source = source.split("---", 2)
64 assert stub == ""
66 frontmatter_template = env.from_string(frontmatter_source)
67 template = env.from_string(text_source)
69 rendered_frontmatter = frontmatter_template.render(**template_args, plain=True, html=False)
70 frontmatter = yaml.load(rendered_frontmatter, Loader=yaml.FullLoader)
72 plain_content = template.render({**template_args, "frontmatter": frontmatter}, plain=True, html=False)
73 html_content = render_html(template.render({**template_args, "frontmatter": frontmatter}, plain=False, html=True))
75 plain = plain_base_template.render(frontmatter=frontmatter, content=plain_content)
76 html = html_base_template.render(frontmatter=frontmatter, content=html_content)
78 return frontmatter, plain, html
81def queue_email(sender_name, sender_email, recipient, subject, plain, html):
82 payload = jobs_pb2.SendEmailPayload(
83 sender_name=sender_name,
84 sender_email=sender_email,
85 recipient=recipient,
86 subject=subject,
87 plain=plain,
88 html=html,
89 )
90 queue_job(
91 job_type=BackgroundJobType.send_email,
92 payload=payload,
93 )
96def enqueue_email_from_template(recipient, template_file, template_args={}):
97 frontmatter, plain, html = _render_email(template_file, template_args)
98 queue_email(
99 config["NOTIFICATION_EMAIL_SENDER"],
100 config["NOTIFICATION_EMAIL_ADDRESS"],
101 recipient,
102 config["NOTIFICATION_EMAIL_PREFIX"] + frontmatter["subject"],
103 plain,
104 html,
105 )