Coverage for src/couchers/templates/v2.py: 98%

63 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-12-20 18:03 +0000

1""" 

2template mailer/push notification formatter v2 

3""" 

4 

5import logging 

6from datetime import date 

7from html import escape 

8from pathlib import Path 

9from zoneinfo import ZoneInfo 

10 

11import phonenumbers 

12from jinja2 import Environment, FileSystemLoader 

13 

14from couchers import urls 

15from couchers.config import config 

16from couchers.email import queue_email 

17from couchers.utils import get_tz_as_text, now, to_aware_datetime 

18 

19logger = logging.getLogger(__name__) 

20 

21template_folder = Path(__file__).parent / ".." / ".." / ".." / "templates" / "v2" 

22 

23loader = FileSystemLoader(template_folder) 

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

25 

26 

27def v2esc(value): 

28 return escape(str(value)) 

29 

30 

31def v2multiline(value): 

32 return "<br />".join(value.splitlines()) 

33 

34 

35def v2sf(value): 

36 return value 

37 

38 

39def v2url(value): 

40 return value 

41 

42 

43def v2phone(value): 

44 return phonenumbers.format_number(phonenumbers.parse(value), phonenumbers.PhoneNumberFormat.INTERNATIONAL) 

45 

46 

47def v2date(value, user): 

48 # todo: user locale-based date formatting 

49 if isinstance(value, str): 

50 value = date.fromisoformat(value) 

51 return value.strftime("%A %-d %B %Y") 

52 

53 

54def v2time(value, user): 

55 tz = ZoneInfo(user.timezone or "Etc/UTC") 

56 return value.astimezone(tz=tz).strftime("%-I:%M %p (%H:%M)") 

57 

58 

59def v2timestamp(value, user): 

60 tz = ZoneInfo(user.timezone or "Etc/UTC") 

61 return to_aware_datetime(value).astimezone(tz=tz).strftime("%A %-d %B %Y at %-I:%M %p (%H:%M)") 

62 

63 

64def v2avatar(user): 

65 if not user.avatar_thumbnail_url: 

66 return urls.icon_url() 

67 return user.avatar_thumbnail_url 

68 

69 

70def v2quote(value): 

71 """ 

72 Multiline quote 

73 """ 

74 return "\n> ".join([""] + value.splitlines()) 

75 

76 

77def add_filters(env): 

78 env.filters["v2esc"] = v2esc 

79 env.filters["v2multiline"] = v2multiline 

80 env.filters["v2sf"] = v2sf 

81 env.filters["v2url"] = v2url 

82 env.filters["v2phone"] = v2phone 

83 env.filters["v2date"] = v2date 

84 env.filters["v2time"] = v2time 

85 env.filters["v2timestamp"] = v2timestamp 

86 env.filters["v2avatar"] = v2avatar 

87 env.filters["v2quote"] = v2quote 

88 

89 

90add_filters(env) 

91 

92 

93def send_simple_pretty_email(session, recipient, subject, template_name, template_args): 

94 """ 

95 This is a simplified version of couchers.notifications.background._send_email_notification 

96 

97 It's for the few security emails where we don't have a user to email but send directly to an email address. 

98 """ 

99 template_args["_year"] = now().year 

100 template_args["_timezone_display"] = get_tz_as_text("Etc/UTC") 

101 

102 plain_unsub_section = "\n\n---\n\nThis is a security email, you cannot unsubscribe from it." 

103 html_unsub_section = "This is a security email, you cannot unsubscribe from it." 

104 

105 plain_tmplt = (template_folder / f"{template_name}.txt").read_text() 

106 plain = env.from_string(plain_tmplt + plain_unsub_section).render(template_args) 

107 html_tmplt = (template_folder / "generated_html" / f"{template_name}.html").read_text() 

108 html = env.from_string(html_tmplt.replace("___UNSUB_SECTION___", html_unsub_section)).render(template_args) 

109 

110 queue_email( 

111 session, 

112 sender_name=config["NOTIFICATION_EMAIL_SENDER"], 

113 sender_email=config["NOTIFICATION_EMAIL_ADDRESS"], 

114 recipient=recipient, 

115 subject=config["NOTIFICATION_PREFIX"] + subject, 

116 plain=plain, 

117 html=html, 

118 source_data=config["VERSION"] + f"/{template_name}", 

119 )