Skip to content

Commit 4215a83

Browse files
committed
Added download QR code page to docx and pdf files to be signed
1 parent f7c5959 commit 4215a83

File tree

4 files changed

+259
-26
lines changed

4 files changed

+259
-26
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ Changelog
4848
[chris-adam]
4949
- Replaced default actions bar by actionspanel for iconified categories.
5050
[chris-adam]
51+
- Added download QR code page to docx and pdf files to be signed.
52+
[chris-adam]
5153

5254
3.0 (2021-09-30)
5355
----------------

imio/dms/mail/adapters.py

Lines changed: 116 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
from collective.dms.scanbehavior.behaviors.behaviors import IScanFields
1717
from collective.documentgenerator import _ as _dg
1818
from collective.documentgenerator.utils import convert_and_save_file
19+
from collective.documentgenerator.utils import convert_odt
1920
from collective.documentgenerator.utils import get_original_template
2021
from collective.documentgenerator.utils import odfsplit
2122
from collective.documentgenerator.utils import update_dict_with_validation
23+
from collective.documentviewer.convert import Converter
2224
from collective.iconifiedcategory.adapter import CategorizedObjectInfoAdapter
2325
from collective.iconifiedcategory.utils import get_category_object
2426
from collective.iconifiedcategory.utils import update_categorized_elements
@@ -50,6 +52,7 @@
5052
from imio.helpers.content import uuidToCatalogBrain
5153
from imio.helpers.content import uuidToObject
5254
from imio.helpers.emailer import validate_email_address
55+
from imio.helpers.pdf import merge_pdf
5356
from imio.helpers.workflow import do_transitions
5457
from imio.pm.wsclient.interfaces import ISendableAnnexesToPM
5558
from imio.prettylink.adapters import PrettyLinkAdapter
@@ -61,6 +64,7 @@
6164
from plone.app.contentmenu.menu import FactoriesSubMenuItem as OrigFactoriesSubMenuItem
6265
from plone.app.contentmenu.menu import WorkflowMenu as OrigWorkflowMenu
6366
from plone.app.contenttypes.indexers import _unicode_save_string_concat
67+
from plone.dexterity.utils import createContentInContainer
6468
from plone.indexer import indexer
6569
from plone.namedfile.file import NamedBlobFile
6670
from plone.registry.interfaces import IRegistry
@@ -1617,8 +1621,11 @@ def approve_file(self, afile, userid, values=None, transition=None, c_a=None):
16171621
message=_(
16181622
u"The file '${file}' has been approved by ${user}. However, there is/are yet ${nb} files "
16191623
u"to approve on this mail.",
1620-
mapping={"file": safe_unicode(afile.Title()), "user": safe_unicode(fullname),
1621-
"nb": len(yet_to_approve)},
1624+
mapping={
1625+
"file": safe_unicode(afile.Title()),
1626+
"user": safe_unicode(fullname),
1627+
"nb": len(yet_to_approve),
1628+
},
16221629
),
16231630
request=request,
16241631
type="info",
@@ -1635,8 +1642,10 @@ def approve_file(self, afile, userid, values=None, transition=None, c_a=None):
16351642
self.context.reindexObjectSecurity() # to update local roles from adapter
16361643
message += u"Next approval number is ${nb}."
16371644
api.portal.show_message(
1638-
message=_(message, mapping={"file": safe_unicode(afile.Title()), "user": safe_unicode(fullname),
1639-
"nb": c_a + 1}),
1645+
message=_(
1646+
message,
1647+
mapping={"file": safe_unicode(afile.Title()), "user": safe_unicode(fullname), "nb": c_a + 1},
1648+
),
16401649
request=request,
16411650
type="info",
16421651
)
@@ -1714,6 +1723,37 @@ def unapprove_file(self, afile, signer_userid):
17141723

17151724
self.start_approval_process()
17161725

1726+
def _render_download_template_to_pdf(self, uid):
1727+
"""Render the download subtemplate (QR code page) to PDF bytes.
1728+
1729+
:param download_template: the POD template object to render
1730+
:param download_url: the URL to encode in the QR/barcode
1731+
:return: PDF bytes, or empty string on failure
1732+
"""
1733+
try:
1734+
download_template = api.portal.get().templates.om.get("download_barcode")
1735+
download_url, _short_uid = get_file_download_url(uid, short_uid=get_suid_from_uuid(uid))
1736+
helper_view = getMultiAdapter(
1737+
(self.context, self.context.REQUEST),
1738+
name="document_generation_helper_view",
1739+
)
1740+
helper_view.pod_template = download_template.UID()
1741+
helper_view.output_format = "pdf"
1742+
gen_context = {
1743+
"context": self.context,
1744+
"portal": api.portal.get(),
1745+
"view": helper_view,
1746+
"download_barcode": generate_barcode(download_url).read(),
1747+
"download_url": download_url,
1748+
"max_download_date": get_max_download_date(None, adate=datetime.date.today()),
1749+
"render_download_barcode": True,
1750+
}
1751+
template_file = NamedBlobFile(download_template.get_file().data, filename=u"download_template.odt")
1752+
return convert_odt(template_file, fmt="pdf", gen_context=gen_context)
1753+
except Exception as e:
1754+
logger.exception("Could not render download template to PDF")
1755+
return ""
1756+
17171757
def _create_pdf_file(self, orig_fobj, nbf, f_title, f_uid, file_index, session_file_uids):
17181758
"""Create a pdf version file.
17191759
@@ -1725,33 +1765,70 @@ def _create_pdf_file(self, orig_fobj, nbf, f_title, f_uid, file_index, session_f
17251765
:param session_file_uids: list to append created pdf file uids
17261766
:return: created pdf file object
17271767
"""
1728-
new_filename = u"{}.pdf".format(f_title)
1768+
new_filename = "{}.pdf".format(f_title)
17291769
if nbf.contentType == "application/pdf":
17301770
pdf_file = orig_fobj
1771+
new_uid = uuid.uuid4().hex
1772+
download_page = self._render_download_template_to_pdf(new_uid)
1773+
if download_page:
1774+
merged = merge_pdf(nbf.data, download_page)
1775+
file_object = NamedBlobFile(merged, filename=safe_unicode(new_filename))
1776+
pdf_file = createContentInContainer(
1777+
self.context,
1778+
orig_fobj.portal_type,
1779+
title=safe_unicode(new_filename),
1780+
file=file_object,
1781+
content_category=orig_fobj.content_category,
1782+
scan_id=orig_fobj.scan_id,
1783+
conv_from_uid=f_uid,
1784+
**{"_plone.uuid": new_uid}
1785+
)
1786+
annot = IAnnotations(pdf_file)
1787+
annot["documentgenerator"] = {"conv_from_uid": f_uid}
1788+
pdf_file.to_sign = True
1789+
pdf_file.to_approve = False
1790+
pdf_file.approved = orig_fobj.approved
1791+
update_categorized_elements(
1792+
self.context,
1793+
pdf_file,
1794+
get_category_object(self.context, pdf_file.content_category),
1795+
limited=True,
1796+
sort=False,
1797+
logging=True,
1798+
)
17311799
elif nbf.contentType in get_allowed_omf_content_types(esign=True):
17321800
gen_context = {}
17331801
new_uid = uuid.uuid4().hex
1734-
download_url, s_uid = get_file_download_url(new_uid, short_uid=get_suid_from_uuid(new_uid))
1802+
download_url, _s_uid = get_file_download_url(new_uid, short_uid=get_suid_from_uuid(new_uid))
17351803
orig_template = get_original_template(orig_fobj)
1804+
doc_cb_download_added = False
17361805
if orig_template and nbf.contentType == "application/vnd.oasis.opendocument.text": # own document
1737-
helper_view = getMultiAdapter((self.context, self.context.REQUEST),
1738-
name='document_generation_helper_view')
1806+
helper_view = getMultiAdapter(
1807+
(self.context, self.context.REQUEST), name="document_generation_helper_view"
1808+
)
17391809
helper_view.pod_template = orig_template.UID()
17401810
helper_view.output_format = "pdf"
17411811
gen_context = {"context": self.context, "portal": api.portal.get(), "view": helper_view}
17421812
# update_dict_with_validation(gen_context, self._get_context_variables(pod_template),
17431813
# _("Error when merging context_variables in generation context"))
1744-
merge_templates = [dic["template"] for dic in orig_template.merge_templates
1745-
if dic["pod_context_name"] == "doc_cb_download"]
1814+
merge_templates = [
1815+
dic["template"]
1816+
for dic in orig_template.merge_templates
1817+
if dic["pod_context_name"] == "doc_cb_download"
1818+
]
17461819
if merge_templates:
17471820
download_template = uuidToObject(merge_templates[0])
17481821
if download_template:
17491822
gen_context["doc_cb_download"] = download_template
1823+
doc_cb_download_added = True
17501824
update_dict_with_validation(
17511825
gen_context,
1752-
{"download_barcode": generate_barcode(download_url).read(), "download_url": download_url,
1753-
"max_download_date": get_max_download_date(None, adate=datetime.date.today()),
1754-
"render_download_barcode": True},
1826+
{
1827+
"download_barcode": generate_barcode(download_url).read(),
1828+
"download_url": download_url,
1829+
"max_download_date": get_max_download_date(None, adate=datetime.date.today()),
1830+
"render_download_barcode": True,
1831+
},
17551832
_dg("Error when merging 'download_barcode' in generation context"),
17561833
)
17571834

@@ -1783,14 +1860,21 @@ def _create_pdf_file(self, orig_fobj, nbf, f_title, f_uid, file_index, session_f
17831860
sort=False,
17841861
logging=True,
17851862
)
1863+
1864+
# For non-ODT files (e.g. DOC, DOCX), the subtemplate cannot be merged during conversion.
1865+
# Render it to PDF separately and append it to the converted PDF.
1866+
if not doc_cb_download_added:
1867+
download_page = self._render_download_template_to_pdf(new_uid)
1868+
if download_page:
1869+
merged = merge_pdf(pdf_file.file.data, download_page)
1870+
pdf_file.file = NamedBlobFile(merged, filename=pdf_file.file.filename)
1871+
Converter(pdf_file)() # Refresh pdf preview
17861872
else:
1787-
raise NotImplementedError(
1788-
"Cannot convert file of type '{}' to pdf for signing.".format(nbf.contentType)
1789-
)
1873+
raise NotImplementedError("Cannot convert file of type '{}' to pdf for signing.".format(nbf.contentType))
17901874
pdf_uid = pdf_file.UID()
17911875
self.pdf_files_uids[file_index].append(pdf_uid)
17921876
# we rename the pdf filename to include pdf uid. So after the file is later consumed, we can retrieve object
1793-
pdf_file.file.filename = u"{}__{}.pdf".format(f_title, pdf_uid)
1877+
pdf_file.file.filename = "{}__{}.pdf".format(f_title, pdf_uid)
17941878
session_file_uids.append(pdf_uid)
17951879

17961880
def add_mail_files_to_session(self):
@@ -1856,21 +1940,28 @@ def add_mail_files_to_session(self):
18561940
signers.append((signer, email, name, label))
18571941
watcher_users = api.user.get_users(groupname="esign_watchers")
18581942
watcher_emails = [user.getProperty("email") for user in watcher_users]
1859-
session_id, session = add_files_to_session(signers, session_file_uids, bool(self.context.seal),
1860-
title=_("[ia.docs] Session {sign_id}"),
1861-
watchers=watcher_emails)
1943+
session_id, _session = add_files_to_session(
1944+
signers,
1945+
session_file_uids,
1946+
bool(self.context.seal),
1947+
title=_("[ia.docs] Session {sign_id}"),
1948+
watchers=watcher_emails,
1949+
)
18621950
self.annot["session_id"] = session_id
18631951
session_len = len(session_file_uids)
18641952
if session_len > 1:
1865-
return True, _("${count} files added to session number ${session_id}",
1866-
mapping={"count": session_len, "session_id": session_id})
1953+
return True, _(
1954+
"${count} files added to session number ${session_id}",
1955+
mapping={"count": session_len, "session_id": session_id},
1956+
)
18671957
else:
1868-
return True, _("${count} file added to session number ${session_id}",
1869-
mapping={"count": session_len, "session_id": session_id})
1958+
return True, _(
1959+
"${count} file added to session number ${session_id}",
1960+
mapping={"count": session_len, "session_id": session_id},
1961+
)
18701962

18711963

18721964
class DmsCategorizedObjectInfoAdapter(CategorizedObjectInfoAdapter):
1873-
18741965
def get_infos(self, category, limited=False):
18751966
base_infos = super(DmsCategorizedObjectInfoAdapter, self).get_infos(category, limited=limited)
18761967
base_infos["scan_id"] = getattr(self.obj, "scan_id", None)

0 commit comments

Comments
 (0)