Skip to content

Commit

Permalink
Qrcode (#223)
Browse files Browse the repository at this point in the history
  • Loading branch information
mamico authored Jan 23, 2025
1 parent e98116e commit c50723b
Show file tree
Hide file tree
Showing 16 changed files with 1,600 additions and 1,567 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Changelog
2.8.5 (unreleased)
------------------

- Add booking code as qrcode attachment in email messages
- isort with plone profile
[mamico]

Expand Down
4 changes: 2 additions & 2 deletions buildout.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

# use this extend one of the buildout configuration:
extends =
test-6.0.x.cfg
# test-5.2.x.cfg
# test-6.0.x.cfg
test-5.2.x.cfg

[instance]
eggs +=
Expand Down
1 change: 1 addition & 0 deletions requirements-5.2.x.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-r https://dist.plone.org/release/5.2-latest/requirements.txt
15 changes: 1 addition & 14 deletions requirements-6.0.x.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1 @@
# Keep these the same as in base.cfg please.
pip==22.2.2
setuptools==65.3.0
zc.buildout>=3.0.0rc3
wheel==0.37.1

# Windows specific down here (has to be installed here, fails in buildout)
# Dependency of zope.sendmail:
pywin32 ; platform_system == 'Windows'
# SSL Certs on Windows, because Python is missing them otherwise:
certifi ; platform_system == 'Windows'
# Dependency of collective.recipe.omelette:
ntfsutils ; platform_system == 'Windows' and python_version < '3.0'

-r https://dist.plone.org/release/6.0-latest/requirements.txt
4 changes: 1 addition & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
-c constraints-5.2.x.txt
setuptools
zc.buildout
-r requirements-6.0.x.txt
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"collective.z3cform.datagridfield>=2.0",
"pyexcel-xlsx",
"click",
"qrcode",
# FIXME: se si rimuove il profilo di caching da qui (perchè c'è?), si può togliere anche questo pin
# 3.0.0a14 e successive richiedono plone.base che è solo su plone 6
"plone.app.caching>=3.0.0a1",
Expand Down Expand Up @@ -91,7 +92,6 @@
[z3c.autoinclude.plugin]
target = plone
[console_scripts]
update_locale = redturtle.prenotazioni.locales.update:update_locale
app_io = redturtle.prenotazioni.scripts.app_io:main
notify_upcoming_bookings = redturtle.prenotazioni.scripts.notify_upcoming_bookings:main
""",
Expand Down
6 changes: 5 additions & 1 deletion src/redturtle/prenotazioni/actions/mail.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# DEPRECATED
from Acquisition import aq_base
from Acquisition import aq_inner
from collective.contentrules.mailfromfield import logger
Expand All @@ -13,6 +14,7 @@
from redturtle.prenotazioni.prenotazione_event import IMovedPrenotazione
from six.moves import filter
from zope.component import adapter
from zope.deprecation import deprecate
from zope.interface import implementer
from zope.interface import Interface

Expand All @@ -22,6 +24,7 @@
class MailActionExecutor(BaseExecutor):
"""The executor for this action."""

@deprecate("contentrules in redturtle.prenotazioni are deprecated")
def get_target_obj(self):
"""Get's the target object, i.e. the object that will provide the field
with the email address
Expand All @@ -41,11 +44,11 @@ def get_target_obj(self):
raise ValueError(target)
return aq_base(aq_inner(obj))

@deprecate("contentrules in redturtle.prenotazioni are deprecated")
def get_recipients(self):
"""
The recipients of this mail
"""

if self.event.object.portal_type != "Prenotazione":
return super().get_recipients()

Expand All @@ -68,6 +71,7 @@ def get_recipients(self):
return []
return list(filter(bool, recipients))

@deprecate("contentrules in redturtle.prenotazioni are deprecated")
def manage_attachments(self, msg):
booking = self.event.object
action = getattr(self.event, "action", "")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,15 @@ class INotificationEmail(model.Schema):
defaultFactory=notify_as_reminder_message_default_factory,
required=False,
)
attach_qrcode = schema.Bool(
title=_("attach_qrcode_label", default="Attach QR Code"),
description=_(
"attach_qrcode_help",
default="Attach a QR Code with booking code to the email notification.",
),
default=False,
required=False,
)

model.fieldset(
"email_notifications",
Expand All @@ -325,6 +334,7 @@ class INotificationEmail(model.Schema):
"notify_on_cancel_message",
"notify_as_reminder_subject",
"notify_as_reminder_message",
"attach_qrcode",
],
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
"""Email notification templates"""

from email.charset import Charset
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from io import BytesIO
from plone import api
from plone.app.event.base import default_timezone
from plone.event.interfaces import IICalendar
Expand All @@ -24,6 +26,7 @@
from zope.interface import Interface

import os
import qrcode


CTE = os.environ.get("MAIL_CONTENT_TRANSFER_ENCODING", None)
Expand Down Expand Up @@ -93,7 +96,7 @@ def message(self) -> MIMEMultipart:
return msg


class PrenotazioneEventMessageICalMixIn:
class PrenotazioneEventMessageMixIn:
@property
def message(self, *args, **kwargs):
message = super().message
Expand All @@ -104,24 +107,54 @@ def message(self, *args, **kwargs):
)
return None

# TODO: verificare la resa nelle email del QRCode come allegato,
# e se non soddisfacente spostarlo nel messaggio (che mi pare html)
# cfr:https://mailtrap.io/blog/embedding-images-in-html-email-have-the-rules-changed/
# <p><img src="cid:qrcode"></img></p>
if (
getattr(self.prenotazioni_folder, "attach_qrcode", None)
and self.prenotazione.getBookingCode()
):
# Generate QR Code
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(self.prenotazione.getBookingCode())
qr.make(fit=True)
img = qr.make_image()
img_buffer = BytesIO()
img.save(img_buffer, format="PNG")
img_bytes = img_buffer.getvalue()
qrcodepart = MIMEImage(img_bytes, name="qrcode")
qrcodepart.add_header(
"Content-Disposition",
f"attachment; filename={self.prenotazione.getBookingCode()}.png",
)
message.attach(qrcodepart)

# ICAL
message.add_header("Content-class", "urn:content-classes:calendarmessage")
name = f"{self.prenotazione.getId()}.ics"

ical = getAdapter(object=self.prenotazione, interface=IICalendar)
icspart = MIMEText(ical.to_ical().decode("utf-8"), "calendar")

icspart.add_header("Filename", name)
icspart.add_header("Content-Disposition", f"attachment; filename={name}")

message.attach(icspart)

return message


# BBB: nel caso la classe viene usata, con questo nome, da eventuali addon
PrenotazioneEventMessageICalMixIn = PrenotazioneEventMessageMixIn


@implementer(IBookingEmailMessage)
@adapter(IPrenotazione, IMovedPrenotazione)
class PrenotazioneMovedICalEmailMessage(
PrenotazioneEventMessageICalMixIn, PrenotazioneEmailMessage
PrenotazioneEventMessageMixIn, PrenotazioneEmailMessage
):
@property
def message_history(self) -> str:
Expand Down Expand Up @@ -188,6 +221,8 @@ def message_text(self) -> MIMEText:
None,
),
)
# TODO: verificare la resa nelle email del QRCode come allegato, e se non soddisfacente
# spostarlo nel testo del messaggio
if CTE:
cs = Charset("utf-8")
cs.body_encoding = CTE # e.g. 'base64'
Expand All @@ -199,7 +234,7 @@ def message_text(self) -> MIMEText:
@implementer(IBookingEmailMessage)
@adapter(IPrenotazione, IAfterTransitionEvent)
class PrenotazioneAfterTransitionEmailICalMessage(
PrenotazioneEventMessageICalMixIn, PrenotazioneAfterTransitionEmailMessage
PrenotazioneEventMessageMixIn, PrenotazioneAfterTransitionEmailMessage
):
pass

Expand All @@ -221,15 +256,15 @@ def message_subject(self) -> str:

@property
def message_text(self) -> MIMEText:
text = IStringInterpolator(IContextWrapper(self.prenotazione)())(
html = IStringInterpolator(IContextWrapper(self.prenotazione)())(
getattr(self.prenotazioni_folder, "notify_as_reminder_message", None),
)
if CTE:
cs = Charset("utf-8")
cs.body_encoding = CTE # e.g. 'base64'
return MIMEText(text, "html", cs)
return MIMEText(html, "html", cs)
else:
return MIMEText(text, "html")
return MIMEText(html, "html")


# NOTE: We are talking about the booking created message here
Expand All @@ -238,7 +273,7 @@ def message_text(self) -> MIMEText:
@implementer(IBookingEmailMessage)
@adapter(IPrenotazione, Interface)
class PrenotazioneManagerEmailMessage(
PrenotazioneEventMessageICalMixIn, PrenotazioneEmailMessage
PrenotazioneEventMessageMixIn, PrenotazioneEmailMessage
):
"""
This is not fired from an event, but used in booker.
Expand Down Expand Up @@ -285,13 +320,10 @@ def message(self) -> MIMEMultipart:
# ical part
msg.add_header("Content-class", "urn:content-classes:calendarmessage")
name = f"{self.prenotazione.getId()}.ics"

ical = getAdapter(object=self.prenotazione, interface=IICalendar)
icspart = MIMEText(ical.to_ical().decode("utf-8"), "calendar")

icspart.add_header("Filename", name)
icspart.add_header("Content-Disposition", f"attachment; filename={name}")

msg.attach(icspart)
return msg

Expand Down
Loading

0 comments on commit c50723b

Please sign in to comment.