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 """
import base64
import email.utils
import logging
import os
@ -9,6 +10,7 @@ from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import magic
from mako.template import Template as MakoTemplate
from mylib.config import (
@ -22,6 +24,14 @@ from mylib.config import (
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(
ConfigurableObject
): # pylint: disable=useless-object-inheritance,too-many-instance-attributes
@ -165,7 +175,7 @@ class EmailClient(
continue
template_type = "text" if template_type == ".txt" else template_type[1:]
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)
with open(filepath, encoding="utf8") as file_desc:
self.templates[template_name][template_type] = MakoTemplate(
@ -239,6 +249,7 @@ class EmailClient(
msg["Date"] = email.utils.formatdate(None, True)
encoding = encoding if encoding else self._get_option("encoding")
if template:
log.debug("Forge email from template %s", template)
assert template in self.templates, f"Unknown template {template}"
# Handle subject from template
if not subject:
@ -264,6 +275,9 @@ class EmailClient(
)
if self.templates[template].get("html"):
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"))
else:
parts.append((self.templates[template]["html"].format(**template_vars), "html"))
@ -296,6 +310,19 @@ class EmailClient(
msg.attach(part)
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(
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",
"--template",
action="store_true",
dest="template",
help="Template name to send (default: test)",
default="test",
)
test_opts.add_argument(
"-m",
"--mako",
action="store_true",
dest="test_mako",
help="Test mako templating",
)
test_opts.add_argument(
"--cc",
action="store",
@ -93,7 +84,7 @@ def main(argv=None): # pylint: disable=too-many-locals,too-many-statements
options.test_to,
cc=options.test_cc,
bcc=options.test_bcc,
template="test",
template=options.template,
sent_date=datetime.datetime.now(),
):
log.info("Test email sent")

View file

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