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

40 statements  

1from html import escape 

2from pathlib import Path 

3 

4import yaml 

5from jinja2 import Environment, FileSystemLoader 

6 

7from couchers.config import config 

8from couchers.jobs.enqueue import queue_job 

9from couchers.models import BackgroundJobType 

10from proto.internal import jobs_pb2 

11 

12loader = FileSystemLoader(Path(__file__).parent / ".." / ".." / ".." / "templates") 

13env = Environment(loader=loader, trim_blocks=True) 

14 

15 

16def couchers_escape(value): 

17 return escape(str(value)) 

18 

19 

20def couchers_safe(value): 

21 return value 

22 

23 

24env.filters["couchers_escape"] = couchers_escape 

25env.filters["couchers_safe"] = couchers_safe 

26 

27plain_base_template = env.get_template("email_base_plain.md") 

28html_base_template = env.get_template("email_base_html.html") 

29 

30 

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) 

34 

35 

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 

39 

40 The template should look something like this: 

41 

42 ``` 

43 --- 

44 subject: "Email for {{ user.name|couchers_escape }}" 

45 --- 

46 

47 Hello {{ user.name|couchers_escape }}, this is a sample email 

48 ``` 

49 

50 The first bit, sandwiched between the first two `---`s is the "frontmatter", some YAML stuff. Currently it just 

51 contains the subject. 

52 

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. 

56 

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") 

61 

62 # the file should start with a "---" 

63 stub, frontmatter_source, text_source = source.split("---", 2) 

64 assert stub == "" 

65 

66 frontmatter_template = env.from_string(frontmatter_source) 

67 template = env.from_string(text_source) 

68 

69 rendered_frontmatter = frontmatter_template.render(**template_args, plain=True, html=False) 

70 frontmatter = yaml.load(rendered_frontmatter, Loader=yaml.FullLoader) 

71 

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)) 

74 

75 plain = plain_base_template.render(frontmatter=frontmatter, content=plain_content) 

76 html = html_base_template.render(frontmatter=frontmatter, content=html_content) 

77 

78 return frontmatter, plain, html 

79 

80 

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 ) 

94 

95 

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 )