email: add load_image_as_base64 helper function

This commit is contained in:
Benjamin Renard 2024-06-12 14:03:09 +02:00
parent 384e8a441d
commit 4c51d0086f
Signed by: bn8
GPG key ID: 3E2E1CE1907115BC
5 changed files with 94 additions and 12 deletions

View file

@ -1,5 +1,6 @@
""" Email client to forge and send emails """ """ Email client to forge and send emails """
import base64
import email.utils import email.utils
import logging import logging
import os import os
@ -9,6 +10,7 @@ from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText from email.mime.text import MIMEText
import magic
from mako.template import Template as MakoTemplate from mako.template import Template as MakoTemplate
from mylib.config import ( from mylib.config import (
@ -22,6 +24,14 @@ from mylib.config import (
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def load_image_as_base64(path):
"""Load image file as base64"""
log.debug("Load image file '%s'", path)
with open(path, "rb") as file_desc:
data = file_desc.read()
return f"data:{magic.from_buffer(data, mime=True)};base64, {base64.b64encode(data).decode()}"
class EmailClient( class EmailClient(
ConfigurableObject ConfigurableObject
): # pylint: disable=useless-object-inheritance,too-many-instance-attributes ): # pylint: disable=useless-object-inheritance,too-many-instance-attributes
@ -165,7 +175,7 @@ class EmailClient(
continue continue
template_type = "text" if template_type == ".txt" else template_type[1:] template_type = "text" if template_type == ".txt" else template_type[1:]
if template_name not in self.templates: if template_name not in self.templates:
self.templates[template_name] = {} self.templates[template_name] = {"path": templates_path}
log.debug("Load email template %s %s from %s", template_name, template_type, filepath) log.debug("Load email template %s %s from %s", template_name, template_type, filepath)
with open(filepath, encoding="utf8") as file_desc: with open(filepath, encoding="utf8") as file_desc:
self.templates[template_name][template_type] = MakoTemplate( self.templates[template_name][template_type] = MakoTemplate(
@ -239,6 +249,7 @@ class EmailClient(
msg["Date"] = email.utils.formatdate(None, True) msg["Date"] = email.utils.formatdate(None, True)
encoding = encoding if encoding else self._get_option("encoding") encoding = encoding if encoding else self._get_option("encoding")
if template: if template:
log.debug("Forge email from template %s", template)
assert template in self.templates, f"Unknown template {template}" assert template in self.templates, f"Unknown template {template}"
# Handle subject from template # Handle subject from template
if not subject: if not subject:
@ -264,6 +275,9 @@ class EmailClient(
) )
if self.templates[template].get("html"): if self.templates[template].get("html"):
if isinstance(self.templates[template]["html"], MakoTemplate): if isinstance(self.templates[template]["html"], MakoTemplate):
template_vars["load_image_as_base64"] = self.template_image_loader(
self.templates[template].get("path")
)
parts.append((self.templates[template]["html"].render(**template_vars), "html")) parts.append((self.templates[template]["html"].render(**template_vars), "html"))
else: else:
parts.append((self.templates[template]["html"].format(**template_vars), "html")) parts.append((self.templates[template]["html"].format(**template_vars), "html"))
@ -296,6 +310,19 @@ class EmailClient(
msg.attach(part) msg.attach(part)
return msg return msg
@staticmethod
def template_image_loader(directory_path):
"""Return wrapper for the load_image_as_base64 function bind on the template directory"""
def _load_image_as_base64(path):
return load_image_as_base64(
os.path.join(directory_path, path)
if directory_path and not os.path.isabs(path)
else path
)
return _load_image_as_base64
def send( def send(
self, recipients, msg=None, subject=None, just_try=None, cc=None, bcc=None, **forge_args self, recipients, msg=None, subject=None, just_try=None, cc=None, bcc=None, **forge_args
): ):

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="92.738403mm"
height="17.141003mm"
viewBox="0 0 92.738403 17.141003"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="header.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.84096521"
inkscape:cx="92.156012"
inkscape:cy="315.11411"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2" />
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-44.730637,-68.858589)">
<rect
style="fill:none;stroke:#004787;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect234"
width="90.738403"
height="15.141003"
x="45.730637"
y="69.858589" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.29167px;line-height:125%;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="82.664459"
y="79.623909"
id="text2144"><tspan
sodipodi:role="line"
id="tspan2142"
style="fill:#004787;fill-opacity:1;stroke-width:0.264583px"
x="82.664459"
y="79.623909">Header</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -1 +1,2 @@
<strong>Just a test email.</strong> <small>(sent at ${sent_date})</small> <img src="${load_image_as_base64('header.svg')}" style="display: block" />
<p><strong>Just a test email.</strong> <small>(sent at ${sent_date})</small></p>

View file

@ -38,19 +38,10 @@ def main(argv=None): # pylint: disable=too-many-locals,too-many-statements
"-T", "-T",
"--template", "--template",
action="store_true", action="store_true",
dest="template",
help="Template name to send (default: test)", help="Template name to send (default: test)",
default="test", default="test",
) )
test_opts.add_argument(
"-m",
"--mako",
action="store_true",
dest="test_mako",
help="Test mako templating",
)
test_opts.add_argument( test_opts.add_argument(
"--cc", "--cc",
action="store", action="store",
@ -93,7 +84,7 @@ def main(argv=None): # pylint: disable=too-many-locals,too-many-statements
options.test_to, options.test_to,
cc=options.test_cc, cc=options.test_cc,
bcc=options.test_bcc, bcc=options.test_bcc,
template="test", template=options.template,
sent_date=datetime.datetime.now(), sent_date=datetime.datetime.now(),
): ):
log.info("Test email sent") log.info("Test email sent")

View file

@ -22,6 +22,7 @@ extras_require = {
"pytz", "pytz",
], ],
"email": [ "email": [
"python-magic",
"mako", "mako",
], ],
"pgsql": [ "pgsql": [