-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add custom action provider for direct PDF sharing via email (#132)
* Allow to hook custom actions * Allow Modals * Allow direct submit * Code naming refactoring * Production JS * Changelog updated * Removed modal * Typo * Allow HTML button text * Implemented first draft of the send email modal * Use current target for event * Refactored action lookup * config for close after submit * debugging * Support status messages * Render custom actions first * Better default values * Disable send button once clicked * Added configuration option to enable direct pdf sharing * Generated production JS * Changelog updated * Fix missing statusmessage in FF * Fixed missing fat arrow in coffesscript * Refactor available attribute to be a method * Removed attribute
- Loading branch information
Showing
19 changed files
with
715 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<configure | ||
xmlns="http://namespaces.zope.org/zope" | ||
xmlns:browser="http://namespaces.zope.org/browser"> | ||
|
||
<!-- PDF Download Action Provider --> | ||
<browser:page | ||
name="impress_download_pdf" | ||
for="*" | ||
class=".providers.DownloadPDF" | ||
permission="zope2.View" | ||
layer="senaite.impress.interfaces.ILayer" | ||
/> | ||
|
||
<!-- Send report PDF Action Provider --> | ||
<browser:page | ||
name="impress_send_pdf" | ||
for="*" | ||
class=".providers.SendPDF" | ||
permission="zope2.View" | ||
layer="senaite.impress.interfaces.ILayer" | ||
/> | ||
|
||
</configure> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import collections | ||
from datetime import datetime | ||
|
||
import six | ||
|
||
from bika.lims import api | ||
from bika.lims.api import mail as mailapi | ||
from bika.lims.interfaces import IAnalysisRequest | ||
from Products.Five.browser import BrowserView | ||
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile | ||
from senaite.impress import senaiteMessageFactory as _ | ||
|
||
|
||
class CustomAction(BrowserView): | ||
"""Base class for custom actions | ||
""" | ||
def __init__(self, context, request): | ||
self.context = context | ||
self.request = request | ||
self.uids = self.get_uids_from_request() | ||
|
||
def get_uids_from_request(self): | ||
"""Returns a list of uids from the request | ||
""" | ||
uids = self.request.get("uids", "") | ||
if isinstance(uids, six.string_types): | ||
uids = uids.split(",") | ||
unique_uids = collections.OrderedDict().fromkeys(uids).keys() | ||
return filter(api.is_uid, unique_uids) | ||
|
||
|
||
class DownloadPDF(CustomAction): | ||
"""Download Action | ||
""" | ||
def __init__(self, context, request): | ||
super(DownloadPDF, self).__init__(context, request) | ||
|
||
def __call__(self): | ||
pdf = self.request.get("pdf") | ||
data = "".join([x for x in pdf.xreadlines()]) | ||
return self.download(data) | ||
|
||
def download(self, data, mime_type="application/pdf"): | ||
# NOTE: The filename does not matter, because we are downloading | ||
# the PDF in the Ajax handler with createObjectURL | ||
self.request.response.setHeader( | ||
"Content-Disposition", "attachment; filename=report.pdf") | ||
self.request.response.setHeader("Content-Type", mime_type) | ||
self.request.response.setHeader("Content-Length", len(data)) | ||
self.request.response.setHeader("Cache-Control", "no-store") | ||
self.request.response.setHeader("Pragma", "no-cache") | ||
self.request.response.write(data) | ||
|
||
|
||
class SendPDF(CustomAction): | ||
"""Email Action | ||
""" | ||
template = ViewPageTemplateFile("templates/send_pdf.pt") | ||
|
||
def __init__(self, context, request): | ||
super(SendPDF, self).__init__(context, request) | ||
self.objects = map(api.get_object, self.uids) | ||
self.action_url = "{}/{}".format(api.get_url(context), self.__name__) | ||
|
||
def __call__(self): | ||
if self.request.form.get("submitted", False): | ||
self.send(REQUEST=self.request) | ||
return self.template() | ||
|
||
def add_status_message(self, message, level="info"): | ||
"""Set a portal status message | ||
""" | ||
return self.context.plone_utils.addPortalMessage(message, level) | ||
|
||
@property | ||
def laboratory(self): | ||
"""Laboratory object from the LIMS setup | ||
""" | ||
return api.get_setup().laboratory | ||
|
||
def is_sample(self, obj): | ||
"""Check if the given object is a sample | ||
""" | ||
return IAnalysisRequest.providedBy(obj) | ||
|
||
def get_email_sender_address(self): | ||
"""Sender email is either the lab email or portal email "from" address | ||
""" | ||
lab_email = self.laboratory.getEmailAddress() | ||
portal_email = api.get_registry_record("plone.email_from_address") | ||
return lab_email or portal_email or "" | ||
|
||
def get_default_recipient_emails(self): | ||
"""Return the default recipient emails | ||
""" | ||
emails = [] | ||
for obj in self.objects: | ||
if not self.is_sample(obj): | ||
continue | ||
contact = obj.getContact() | ||
if not contact: | ||
continue | ||
email = contact.getEmailAddress() | ||
if email not in emails: | ||
emails.append(email) | ||
return ", ".join(filter(mailapi.is_valid_email_address, emails)) | ||
|
||
def get_default_cc_emails(self): | ||
"""Return the default CC emails | ||
""" | ||
emails = [] | ||
for obj in self.objects: | ||
if not self.is_sample(obj): | ||
continue | ||
for contact in obj.getCCContact(): | ||
if not contact: | ||
continue | ||
email = contact.getEmailAddress() | ||
if email not in emails: | ||
emails.append(email) | ||
for email in obj.getCCEmails().split(","): | ||
if email not in emails: | ||
emails.append(email) | ||
return ", ".join(filter(mailapi.is_valid_email_address, emails)) | ||
|
||
def get_default_subject(self): | ||
"""Return the default subject | ||
""" | ||
return ", ".join(map(api.get_id, self.objects)) | ||
|
||
def get_default_body(self): | ||
"""Return the default body text | ||
""" | ||
return "" | ||
|
||
def get_default_pdf_filename(self): | ||
"""Return the default filename of the attached PDF | ||
""" | ||
timestamp = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") | ||
return "Report-{}.pdf".format(timestamp) | ||
|
||
def send(self, REQUEST=None): | ||
email_to = self.request.get("email_to", "") | ||
email_cc = self.request.get("email_cc", "") | ||
email_subject = self.request.get("email_subject", "") | ||
email_body = self.request.get("email_body", "") | ||
|
||
pdf = self.request.get("pdf") | ||
if not pdf: | ||
message = _("PDF attachment is missing") | ||
self.add_status_message(message, level="error") | ||
return False | ||
|
||
pdf_filename = self.request.get("pdf_filename") | ||
# workaround for ZPublisher.HTTPRequest.FileUpload object | ||
pdf_data = "".join(pdf.xreadlines()) | ||
pdf_attachment = mailapi.to_email_attachment(pdf_data, pdf_filename) | ||
|
||
mime_msg = mailapi.compose_email( | ||
self.get_email_sender_address(), | ||
email_to, | ||
email_subject, | ||
email_body, | ||
[pdf_attachment]) | ||
mime_msg["CC"] = mailapi.to_email_address(email_cc) | ||
|
||
sent = mailapi.send_email(mime_msg) | ||
|
||
if not sent: | ||
message = _("Failed to send Email") | ||
self.add_status_message(message, level="error") | ||
return False | ||
|
||
message = _("Email sent") | ||
self.add_status_message(message, level="info") | ||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
<div class="send-pdf-modal modal-dialog modal-dialog-centered"> | ||
<div class="modal-content"> | ||
<div class="modal-header"> | ||
<h5 class="modal-title" i18n:translate="">Send PDF by Email</h5> | ||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> | ||
<span aria-hidden="true">×</span> | ||
</button> | ||
</div> | ||
<div class="modal-body"> | ||
<!-- Show status messages inside the modal --> | ||
<tal:message tal:content="structure provider:plone.globalstatusmessage"/> | ||
<form name="send-pdf-form" | ||
class="form" | ||
method="POST" | ||
enctype="multipart/form-data" | ||
action="." | ||
tal:attributes="action python:view.action_url"> | ||
|
||
<!-- TO --> | ||
<div class="form-group row"> | ||
<label i18n:translate="label_email_to" | ||
for="input-to" | ||
class="col-sm-3 col-form-label"> | ||
To | ||
</label> | ||
<div class="col-sm-9"> | ||
<input type="email" multiple | ||
required | ||
tal:attributes="value python:request.get('email_to') or view.get_default_recipient_emails()" | ||
name="email_to" autocomplete="off" class="form-control form-control-sm" id="input-to" /> | ||
</div> | ||
</div> | ||
|
||
<!-- CC --> | ||
<div class="form-group row"> | ||
<label i18n:translate="label_email_cc" | ||
for="input-cc" | ||
class="col-sm-3 col-form-label"> | ||
CC | ||
</label> | ||
<div class="col-sm-9"> | ||
<input type="email" multiple | ||
tal:attributes="value python:request.get('email_cc') or view.get_default_cc_emails()" | ||
name="email_cc" autocomplete="off" class="form-control form-control-sm" id="input-email" /> | ||
</div> | ||
</div> | ||
|
||
<!-- SUBJECT --> | ||
<div class="form-group form-group-sm row"> | ||
<label i18n:translate="label_email_subject" | ||
for="input-subject" | ||
class="col-sm-3 col-form-label"> | ||
Subject | ||
</label> | ||
<div class="col-sm-9"> | ||
<input type="text" | ||
tal:attributes="value python:request.get('email_subject') or view.get_default_subject()" | ||
name="email_subject" class="form-control form-control-sm" id="input-subject" /> | ||
</div> | ||
</div> | ||
|
||
<!-- BODY --> | ||
<div class="form-group form-group-sm row"> | ||
<div class="col-sm-12"> | ||
<textarea name="email_body" | ||
tal:content="python:request.get('email_body') or view.get_default_body()" | ||
rows="5" class="form-control form-control-sm" id="input-body"> | ||
</textarea> | ||
</div> | ||
</div> | ||
|
||
<!-- PDF Filename --> | ||
<div class="form-group form-group-sm row"> | ||
<label i18n:translate="label_email_pdf_filename" | ||
for="input-subject" | ||
class="col-sm-3 col-form-label"> | ||
Filename | ||
</label> | ||
<div class="col-sm-9"> | ||
<input type="text" | ||
value="Report.pdf" | ||
tal:attributes="value python:request.get('pdf_filename') or view.get_default_pdf_filename()" | ||
name="pdf_filename" class="form-control form-control-sm" id="input-filename" /> | ||
</div> | ||
</div> | ||
|
||
<div class="form-group mt-2"> | ||
<input class="btn btn-sm btn-primary" | ||
type="submit" | ||
name="send" | ||
i18n:attributes="value" | ||
value="Send Email" /> | ||
</div> | ||
|
||
<!-- hidden fields --> | ||
<input type="hidden" name="submitted" value="1" /> | ||
<input tal:replace="structure context/@@authenticator/authenticator"/> | ||
<input type="hidden" name="uids" value="" tal:attributes="value request/uids|nothing" /> | ||
|
||
</form> | ||
</div> | ||
</div> | ||
|
||
<!-- Modal helper JS --> | ||
<script type="text/javascript"> | ||
console.info("*** SEND PDF Modal Loaded ***"); | ||
let form = document.querySelector("form[name='send-pdf-form']"); | ||
let button = form.querySelector("input[name='send']"); | ||
form.addEventListener("submit", (event) => { | ||
// avoid double click | ||
button.disabled = true; | ||
}); | ||
</script> | ||
</div> |
Oops, something went wrong.