Merge pull request #361 from sartography/430-email-enhancements
#430 email enhancements
This commit is contained in:
commit
961925e03e
|
@ -1,29 +1,42 @@
|
|||
import sys
|
||||
import traceback
|
||||
|
||||
from crc import app
|
||||
from crc import app, session
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.file import FileModel, CONTENT_TYPES
|
||||
from crc.models.workflow import WorkflowModel
|
||||
from crc.services.document_service import DocumentService
|
||||
from crc.scripts.script import Script
|
||||
from crc.services.ldap_service import LdapService
|
||||
from crc.services.email_service import EmailService
|
||||
from crc.services.ldap_service import LdapService
|
||||
from crc.services.study_service import StudyService
|
||||
|
||||
|
||||
class Email(Script):
|
||||
"""This Script allows to be introduced as part of a workflow and called from there, specifying
|
||||
recipients and content """
|
||||
"""Send an email from a script task, as part of a workflow.
|
||||
You must specify recipients and content.
|
||||
You can also specify cc, bcc, reply_to, and attachments"""
|
||||
|
||||
def get_description(self):
|
||||
return """
|
||||
Creates an email, using the provided `subject`, `recipients`, and `cc` arguments.
|
||||
The recipients and cc arguments can contain an email address or list of email addresses.
|
||||
Creates an email, using the provided `subject` and `recipients` arguments, which are required.
|
||||
The `Element Documentation` field in the script task must contain markdown that becomes the body of the email message.
|
||||
|
||||
You can also provide `cc`, `bcc`, `reply_to` and `attachments` arguments.
|
||||
The cc, bcc, reply_to, and attachments arguments are not required.
|
||||
|
||||
The recipients, cc, and bcc arguments can contain an email address or list of email addresses.
|
||||
In place of an email address, we accept the string 'associated', in which case we
|
||||
look up the users associated with the study who have send_email set to True.
|
||||
The cc argument is not required.
|
||||
The "documentation" should contain markdown that will become the body of the email message.
|
||||
The reply_to argument can contain an email address.
|
||||
The attachments arguments can contain a doc_code or list of doc_codes.
|
||||
|
||||
Examples:
|
||||
email (subject="My Subject", recipients=["dhf8r@virginia.edu", pi.email, 'associated'])
|
||||
email (subject="My Subject", recipients=["dhf8r@virginia.edu", pi.email], cc='associated')
|
||||
email(subject="My Subject", recipients=["dhf8r@virginia.edu", pi.email, 'associated'])
|
||||
email(subject="My Subject", recipients="user@example.com", cc='associated', bcc='test_user@example.com)
|
||||
email(subject="My Subject", recipients="user@example.com", reply_to="reply_to@example.com")
|
||||
email(subject="My Subject", recipients="user@example.com", attachments='Study_App_Doc')
|
||||
email(subject="My Subject", recipients="user@example.com", attachments=['Study_App_Doc','Study_Protocol_Document'])
|
||||
"""
|
||||
|
||||
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
|
@ -37,8 +50,17 @@ email (subject="My Subject", recipients=["dhf8r@virginia.edu", pi.email], cc='as
|
|||
subject = self.get_subject(kwargs['subject'])
|
||||
recipients = self.get_email_addresses(kwargs['recipients'], study_id)
|
||||
cc = []
|
||||
bcc = []
|
||||
reply_to = None
|
||||
files = None
|
||||
if 'cc' in kwargs and kwargs['cc'] is not None:
|
||||
cc = self.get_email_addresses(kwargs['cc'], study_id)
|
||||
if 'bcc' in kwargs and kwargs['bcc'] is not None:
|
||||
bcc = self.get_email_addresses(kwargs['bcc'], study_id)
|
||||
if 'reply_to' in kwargs:
|
||||
reply_to = kwargs['reply_to']
|
||||
if 'attachments' in kwargs:
|
||||
files = self.get_files(kwargs['attachments'], study_id)
|
||||
|
||||
else:
|
||||
raise ApiError(code="missing_argument",
|
||||
|
@ -56,7 +78,10 @@ email (subject="My Subject", recipients=["dhf8r@virginia.edu", pi.email], cc='as
|
|||
content=content,
|
||||
content_html=content_html,
|
||||
cc=cc,
|
||||
study_id=study_id
|
||||
bcc=bcc,
|
||||
study_id=study_id,
|
||||
reply_to=reply_to,
|
||||
attachment_files=files
|
||||
)
|
||||
except Exception as e:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
|
@ -118,3 +143,31 @@ email (subject="My Subject", recipients=["dhf8r@virginia.edu", pi.email], cc='as
|
|||
user_info = LdapService.user_info(associate.uid)
|
||||
associated_emails.append(user_info.email_address)
|
||||
return associated_emails
|
||||
|
||||
@staticmethod
|
||||
def get_files(attachments, study_id):
|
||||
files = []
|
||||
codes = None
|
||||
if isinstance(attachments, str):
|
||||
codes = [attachments]
|
||||
elif isinstance(attachments, list):
|
||||
codes = attachments
|
||||
|
||||
if codes is not None:
|
||||
for code in codes:
|
||||
if DocumentService.is_allowed_document(code):
|
||||
workflows = session.query(WorkflowModel).filter(WorkflowModel.study_id==study_id).all()
|
||||
for workflow in workflows:
|
||||
workflow_files = session.query(FileModel).\
|
||||
filter(FileModel.workflow_id == workflow.id).\
|
||||
filter(FileModel.irb_doc_code == code).all()
|
||||
for file in workflow_files:
|
||||
files.append({'id': file.id, 'name': file.name, 'type': CONTENT_TYPES[file.type.value]})
|
||||
else:
|
||||
raise ApiError(code='bad_doc_code',
|
||||
message=f'The doc_code {code} is not valid.')
|
||||
else:
|
||||
raise ApiError(code='bad_argument_type',
|
||||
message='The attachments argument must be a string or list of strings')
|
||||
|
||||
return files
|
||||
|
|
|
@ -1,25 +1,23 @@
|
|||
import markdown
|
||||
import re
|
||||
|
||||
from datetime import datetime
|
||||
from flask import render_template, request
|
||||
from flask import render_template
|
||||
from flask_mail import Message
|
||||
from jinja2 import Template
|
||||
from sqlalchemy import desc
|
||||
|
||||
from crc import app, db, mail, session
|
||||
from crc.api.common import ApiError
|
||||
|
||||
from crc.models.study import StudyModel
|
||||
from crc.models.email import EmailModel
|
||||
|
||||
from crc.models.file import FileDataModel
|
||||
from crc.models.study import StudyModel
|
||||
|
||||
|
||||
class EmailService(object):
|
||||
"""Provides common tools for working with an Email"""
|
||||
|
||||
@staticmethod
|
||||
def add_email(subject, sender, recipients, content, content_html, cc=None, study_id=None):
|
||||
def add_email(subject, sender, recipients, content, content_html,
|
||||
cc=None, bcc=None, study_id=None, reply_to=None, attachment_files=None):
|
||||
"""We will receive all data related to an email and store it"""
|
||||
|
||||
# Find corresponding study - if any
|
||||
|
@ -35,11 +33,17 @@ class EmailService(object):
|
|||
try:
|
||||
msg = Message(subject,
|
||||
sender=sender,
|
||||
recipients=recipients)
|
||||
recipients=recipients,
|
||||
body=content,
|
||||
html=content_html,
|
||||
cc=cc,
|
||||
bcc=bcc,
|
||||
reply_to=reply_to)
|
||||
|
||||
msg.body = content
|
||||
msg.html = content_html
|
||||
msg.cc = cc
|
||||
if attachment_files is not None:
|
||||
for file in attachment_files:
|
||||
file_data = session.query(FileDataModel).filter(FileDataModel.file_model_id==file['id']).first()
|
||||
msg.attach(file['name'], file['type'], file_data.data)
|
||||
|
||||
mail.send(msg)
|
||||
except Exception as e:
|
||||
|
|
|
@ -295,7 +295,8 @@ class WorkflowService(object):
|
|||
if not hasattr(task.task_spec, 'form'): return
|
||||
for field in task.task_spec.form.fields:
|
||||
data = task.data
|
||||
if field.has_property(Task.FIELD_PROP_REPEAT):
|
||||
# If we have a repeat field, make sure it is used before processing it
|
||||
if field.has_property(Task.FIELD_PROP_REPEAT) and field.get_property(Task.FIELD_PROP_REPEAT) in task.data.keys():
|
||||
repeat_array = task.data[field.get_property(Task.FIELD_PROP_REPEAT)]
|
||||
for repeat_data in repeat_array:
|
||||
WorkflowService.__post_process_field(task, field, repeat_data)
|
||||
|
|
|
@ -19,6 +19,13 @@
|
|||
</camunda:validation>
|
||||
</camunda:formField>
|
||||
<camunda:formField id="cc" label="CC" type="string" />
|
||||
<camunda:formField id="bcc" label="Bcc" type="string" />
|
||||
<camunda:formField id="reply_to" label="Reply To" type="string" />
|
||||
<camunda:formField id="doc_code" label="Doc Code" type="string">
|
||||
<camunda:properties>
|
||||
<camunda:property id="repeat" value="doc_codes" />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_0scd96e</bpmn:incoming>
|
||||
|
@ -26,9 +33,8 @@
|
|||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="Flow_0c60gne" sourceRef="Activity_EmailForm" targetRef="Activity_SendEmail" />
|
||||
<bpmn:endEvent id="Event_EndEvent">
|
||||
<bpmn:incoming>Flow_19fqvhc</bpmn:incoming>
|
||||
<bpmn:incoming>Flow_0wv0swo</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_19fqvhc" sourceRef="Activity_SendEmail" targetRef="Event_EndEvent" />
|
||||
<bpmn:scriptTask id="Activity_SendEmail" name="Send Email">
|
||||
<bpmn:documentation>Dear Person,
|
||||
|
||||
|
@ -42,18 +48,29 @@ Yours faithfully,
|
|||
|
||||
Dan</bpmn:documentation>
|
||||
<bpmn:incoming>Flow_0c60gne</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_19fqvhc</bpmn:outgoing>
|
||||
<bpmn:outgoing>Flow_0xrm7iw</bpmn:outgoing>
|
||||
<bpmn:script>if not 'cc' in globals():
|
||||
cc=None
|
||||
email(subject=subject, recipients=recipients, cc=cc)</bpmn:script>
|
||||
if not 'bcc' in globals():
|
||||
bcc=None
|
||||
if not 'reply_to' in globals():
|
||||
reply_to=None
|
||||
|
||||
attachments = []
|
||||
if 'doc_codes' in globals():
|
||||
for item in globals()['doc_codes']:
|
||||
attachments.append(item['doc_code'])
|
||||
email(subject=subject, recipients=recipients, cc=cc, bcc=bcc, reply_to=reply_to, attachments=attachments)</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_0xrm7iw" sourceRef="Activity_SendEmail" targetRef="Activity_1lnjeej" />
|
||||
<bpmn:sequenceFlow id="Flow_0wv0swo" sourceRef="Activity_1lnjeej" targetRef="Event_EndEvent" />
|
||||
<bpmn:manualTask id="Activity_1lnjeej" name="Display Data">
|
||||
<bpmn:incoming>Flow_0xrm7iw</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0wv0swo</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_fe6205f">
|
||||
<bpmndi:BPMNEdge id="Flow_19fqvhc_di" bpmnElement="Flow_19fqvhc">
|
||||
<di:waypoint x="530" y="117" />
|
||||
<di:waypoint x="592" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0c60gne_di" bpmnElement="Flow_0c60gne">
|
||||
<di:waypoint x="374" y="117" />
|
||||
<di:waypoint x="430" y="117" />
|
||||
|
@ -62,18 +79,29 @@ email(subject=subject, recipients=recipients, cc=cc)</bpmn:script>
|
|||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="274" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0xrm7iw_di" bpmnElement="Flow_0xrm7iw">
|
||||
<di:waypoint x="530" y="117" />
|
||||
<di:waypoint x="580" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0wv0swo_di" bpmnElement="Flow_0wv0swo">
|
||||
<di:waypoint x="680" y="117" />
|
||||
<di:waypoint x="722" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0wqsfcj_di" bpmnElement="Activity_EmailForm">
|
||||
<dc:Bounds x="274" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_1wh1xsj_di" bpmnElement="Event_EndEvent">
|
||||
<dc:Bounds x="592" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1ajacra_di" bpmnElement="Activity_SendEmail">
|
||||
<dc:Bounds x="430" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_1wh1xsj_di" bpmnElement="Event_EndEvent">
|
||||
<dc:Bounds x="722" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0eb7isk_di" bpmnElement="Activity_1lnjeej">
|
||||
<dc:Bounds x="580" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
from tests.base_test import BaseTest
|
||||
from crc import mail, session
|
||||
from crc.models.study import StudyModel
|
||||
from crc import mail
|
||||
from crc.services.file_service import FileService
|
||||
from crc.services.study_service import StudyService
|
||||
import json
|
||||
|
||||
|
||||
class TestEmailScript(BaseTest):
|
||||
|
@ -20,17 +19,21 @@ class TestEmailScript(BaseTest):
|
|||
with mail.record_messages() as outbox:
|
||||
|
||||
workflow = self.create_workflow('email_script')
|
||||
|
||||
first_task = self.get_workflow_api(workflow).next_task
|
||||
|
||||
self.complete_form(workflow, first_task, {'subject': 'My Email Subject', 'recipients': 'test@example.com'})
|
||||
self.complete_form(workflow, first_task, {'subject': 'My Email Subject', 'recipients': 'test@example.com',
|
||||
'cc': 'cc@example.com', 'bcc': 'bcc@example.com',
|
||||
'reply_to': 'reply_to@example.com'})
|
||||
|
||||
self.assertEqual(1, len(outbox))
|
||||
self.assertEqual('My Email Subject', outbox[0].subject)
|
||||
self.assertEqual(['test@example.com'], outbox[0].recipients)
|
||||
self.assertEqual(['cc@example.com'], outbox[0].cc)
|
||||
self.assertEqual(['bcc@example.com'], outbox[0].bcc)
|
||||
self.assertEqual('reply_to@example.com', outbox[0].reply_to)
|
||||
self.assertIn('Thank you for using this email example', outbox[0].body)
|
||||
|
||||
def test_email_script_multiple(self):
|
||||
self.load_example_data()
|
||||
with mail.record_messages() as outbox:
|
||||
|
||||
workflow = self.create_workflow('email_script')
|
||||
|
@ -80,19 +83,30 @@ class TestEmailScript(BaseTest):
|
|||
self.assertIn(outbox[0].recipients[1], ['user@example.com', 'dhf8r@virginia.edu', 'lb3dp@virginia.edu'])
|
||||
self.assertIn(outbox[0].recipients[2], ['user@example.com', 'dhf8r@virginia.edu', 'lb3dp@virginia.edu'])
|
||||
|
||||
def test_email_script_cc(self):
|
||||
def test_email_script_attachments(self):
|
||||
self.load_example_data()
|
||||
irb_code_1 = 'Study_App_Doc'
|
||||
irb_code_2 = 'Study_Protocol_Document'
|
||||
|
||||
workflow = self.create_workflow('email_script')
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.create_user(uid='lb3dp', email='lb3dp@virginia.edu', display_name='Laura Barnes')
|
||||
StudyService.update_study_associates(workflow.study_id,
|
||||
[{'uid': 'dhf8r', 'role': 'Chief Bee Keeper', 'send_email': True, 'access': True},
|
||||
{'uid': 'lb3dp', 'role': 'Chief Cat Herder', 'send_email': True, 'access': True}])
|
||||
|
||||
FileService.add_workflow_file(workflow_id=workflow.id,
|
||||
name="something.png", content_type="text",
|
||||
binary_data=b'1234', irb_doc_code=irb_code_1)
|
||||
FileService.add_workflow_file(workflow_id=workflow.id,
|
||||
name="another.png", content_type="text",
|
||||
binary_data=b'67890', irb_doc_code=irb_code_1)
|
||||
FileService.add_workflow_file(workflow_id=workflow.id,
|
||||
name="anything.png", content_type="text",
|
||||
binary_data=b'5678', irb_doc_code=irb_code_2)
|
||||
|
||||
first_task = workflow_api.next_task
|
||||
with mail.record_messages() as outbox:
|
||||
|
||||
self.complete_form(workflow, first_task, {'subject': 'My Test Subject', 'recipients': 'user@example.com', 'cc': 'associated'})
|
||||
|
||||
self.complete_form(workflow, first_task, {'subject': 'My Test Subject', 'recipients': 'user@example.com',
|
||||
'doc_codes': [{'doc_code': irb_code_1}, {'doc_code': irb_code_2}]})
|
||||
self.assertEqual(1, len(outbox))
|
||||
self.assertEqual('user@example.com', outbox[0].recipients[0])
|
||||
self.assertIn(outbox[0].cc[0], ['dhf8r@virginia.edu', 'lb3dp@virginia.edu'])
|
||||
self.assertIn(outbox[0].cc[1], ['dhf8r@virginia.edu', 'lb3dp@virginia.edu'])
|
||||
self.assertEqual(3, len(outbox[0].attachments))
|
||||
self.assertEqual('image/png', outbox[0].attachments[0].content_type)
|
||||
self.assertEqual('something.png', outbox[0].attachments[0].filename)
|
||||
self.assertEqual(b'1234', outbox[0].attachments[0].data)
|
||||
|
|
Loading…
Reference in New Issue