diff --git a/HISTORY.rst b/HISTORY.rst
index d14468108c..d5c54b33bb 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -10,6 +10,7 @@
- Add support for source column exclusion.
- Backport workflow context support.
- Backport workflow transitions field support.
+- Backport workflow email action.
3.2.5 (2019-07-05)
==================
diff --git a/docs/releases/3.3.rst b/docs/releases/3.3.rst
index 6a66ffeb19..d542585200 100644
--- a/docs/releases/3.3.rst
+++ b/docs/releases/3.3.rst
@@ -22,6 +22,7 @@ Changes
- Add support for source column exclusion.
- Backport workflow context support.
- Backport workflow transitions field support.
+- Backport workflow email action.
Removals
--------
diff --git a/mayan/apps/mailer/tests/literals.py b/mayan/apps/mailer/tests/literals.py
index ae3b603355..e3ea1038a0 100644
--- a/mayan/apps/mailer/tests/literals.py
+++ b/mayan/apps/mailer/tests/literals.py
@@ -1,8 +1,10 @@
from __future__ import unicode_literals
-TEST_BODY_HTML = 'test body'
TEST_EMAIL_ADDRESS = 'test@example.com'
+TEST_EMAIL_BODY = 'test body'
+TEST_EMAIL_BODY_HTML = 'test body'
TEST_EMAIL_FROM_ADDRESS = 'from.test@example.com'
+TEST_EMAIL_SUBJECT = 'test subject'
TEST_RECIPIENTS_MULTIPLE_COMMA = 'test@example.com,test2@example.com'
TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT = [
'test@example.com', 'test2@example.com'
diff --git a/mayan/apps/mailer/tests/test_actions.py b/mayan/apps/mailer/tests/test_actions.py
new file mode 100644
index 0000000000..eed6d9f49a
--- /dev/null
+++ b/mayan/apps/mailer/tests/test_actions.py
@@ -0,0 +1,180 @@
+from __future__ import unicode_literals
+
+import json
+
+from django.core import mail
+
+from mayan.apps.common.tests import GenericViewTestCase
+from mayan.apps.documents.tests.mixins import DocumentTestMixin
+from mayan.apps.document_states.literals import WORKFLOW_ACTION_ON_ENTRY
+from mayan.apps.document_states.tests.mixins import WorkflowTestMixin
+from mayan.apps.document_states.tests.test_actions import ActionTestCase
+from mayan.apps.metadata.tests.mixins import MetadataTypeTestMixin
+
+from ..permissions import permission_user_mailer_use
+from ..workflow_actions import EmailAction
+
+from .literals import (
+ TEST_EMAIL_ADDRESS, TEST_EMAIL_BODY, TEST_EMAIL_FROM_ADDRESS,
+ TEST_EMAIL_SUBJECT
+)
+from .mixins import MailerTestMixin
+
+
+class EmailActionTestCase(MailerTestMixin, WorkflowTestMixin, ActionTestCase):
+ def test_email_action_literal_text(self):
+ self._create_test_user_mailer()
+
+ action = EmailAction(
+ form_data={
+ 'mailing_profile': self.test_user_mailer.pk,
+ 'recipient': TEST_EMAIL_ADDRESS,
+ 'subject': TEST_EMAIL_SUBJECT,
+ 'body': TEST_EMAIL_BODY,
+ }
+ )
+ action.execute(context={'document': self.test_document})
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS)
+ self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS])
+
+ def test_email_action_workflow_execute(self):
+ self._create_test_workflow()
+ self._create_test_workflow_state()
+ self._create_test_user_mailer()
+
+ self.test_workflow_state.actions.create(
+ action_data=json.dumps(
+ {
+ 'mailing_profile': self.test_user_mailer.pk,
+ 'recipient': TEST_EMAIL_ADDRESS,
+ 'subject': TEST_EMAIL_SUBJECT,
+ 'body': TEST_EMAIL_BODY,
+ }
+ ),
+ action_path='mayan.apps.mailer.workflow_actions.EmailAction',
+ label='test email action', when=WORKFLOW_ACTION_ON_ENTRY,
+ )
+
+ self.test_workflow_state.initial = True
+ self.test_workflow_state.save()
+ self.test_workflow.document_types.add(self.test_document_type)
+
+ self.upload_document()
+
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS)
+ self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS])
+
+
+class EmailActionTemplateTestCase(MetadataTypeTestMixin, MailerTestMixin, WorkflowTestMixin, ActionTestCase):
+ def test_email_action_recipient_template(self):
+ self._create_test_metadata_type()
+ self.test_document_type.metadata.create(metadata_type=self.test_metadata_type)
+ self.test_document.metadata.create(metadata_type=self.test_metadata_type, value=TEST_EMAIL_ADDRESS)
+
+ self._create_test_user_mailer()
+
+ action = EmailAction(
+ form_data={
+ 'mailing_profile': self.test_user_mailer.pk,
+ 'recipient': '{{{{ document.metadata_value_of.{} }}}}'.format(self.test_metadata_type.name),
+ 'subject': TEST_EMAIL_SUBJECT,
+ 'body': '',
+ }
+ )
+ action.execute(context={'document': self.test_document})
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS)
+ self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS])
+
+ def test_email_action_subject_template(self):
+ self._create_test_metadata_type()
+ self.test_document_type.metadata.create(metadata_type=self.test_metadata_type)
+ self.test_document.metadata.create(metadata_type=self.test_metadata_type, value=TEST_EMAIL_SUBJECT)
+
+ self._create_test_user_mailer()
+
+ action = EmailAction(
+ form_data={
+ 'mailing_profile': self.test_user_mailer.pk,
+ 'recipient': TEST_EMAIL_ADDRESS,
+ 'subject': '{{{{ document.metadata_value_of.{} }}}}'.format(self.test_metadata_type.name),
+ 'body': '',
+ }
+ )
+ action.execute(context={'document': self.test_document})
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS)
+ self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS])
+
+ def test_email_action_body_template(self):
+ self._create_test_metadata_type()
+ self.test_document_type.metadata.create(metadata_type=self.test_metadata_type)
+ self.test_document.metadata.create(metadata_type=self.test_metadata_type, value=TEST_EMAIL_BODY)
+
+ self._create_test_user_mailer()
+
+ action = EmailAction(
+ form_data={
+ 'mailing_profile': self.test_user_mailer.pk,
+ 'recipient': TEST_EMAIL_ADDRESS,
+ 'subject': TEST_EMAIL_SUBJECT,
+ 'body': '{{{{ document.metadata_value_of.{} }}}}'.format(self.test_metadata_type.name),
+ }
+ )
+ action.execute(context={'document': self.test_document})
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS)
+ self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS])
+ self.assertEqual(mail.outbox[0].body, TEST_EMAIL_BODY)
+
+
+class EmailActionViewTestCase(DocumentTestMixin, MailerTestMixin, WorkflowTestMixin, GenericViewTestCase):
+ auto_upload_document = False
+
+ def test_email_action_create_get_view(self):
+ self._create_test_workflow()
+ self._create_test_workflow_state()
+ self._create_test_user_mailer()
+
+ response = self.get(
+ viewname='document_states:setup_workflow_state_action_create',
+ kwargs={
+ 'pk': self.test_workflow_state.pk,
+ 'class_path': 'mayan.apps.mailer.workflow_actions.EmailAction',
+ }
+ )
+ self.assertEqual(response.status_code, 200)
+
+ self.assertEqual(self.test_workflow_state.actions.count(), 0)
+
+ def _request_email_action_create_post_view(self):
+ return self.post(
+ viewname='document_states:setup_workflow_state_action_create',
+ kwargs={
+ 'pk': self.test_workflow_state.pk,
+ 'class_path': 'mayan.apps.mailer.workflow_actions.EmailAction',
+ }, data={
+ 'when': WORKFLOW_ACTION_ON_ENTRY,
+ 'label': 'test email action',
+ 'mailing_profile': self.test_user_mailer.pk,
+ 'recipient': TEST_EMAIL_ADDRESS,
+ 'subject': TEST_EMAIL_SUBJECT,
+ 'body': TEST_EMAIL_BODY,
+ }
+ )
+
+ def test_email_action_create_post_view(self):
+ self._create_test_workflow()
+ self._create_test_workflow_state()
+ self._create_test_user_mailer()
+
+ self.grant_access(
+ obj=self.test_user_mailer, permission=permission_user_mailer_use
+ )
+
+ response = self._request_email_action_create_post_view()
+ self.assertEqual(response.status_code, 302)
+
+ self.assertEqual(self.test_workflow_state.actions.count(), 1)
diff --git a/mayan/apps/mailer/tests/test_models.py b/mayan/apps/mailer/tests/test_models.py
index 376e4ba669..02f3cab3b1 100644
--- a/mayan/apps/mailer/tests/test_models.py
+++ b/mayan/apps/mailer/tests/test_models.py
@@ -5,7 +5,7 @@ from django.core import mail
from mayan.apps.documents.tests.test_models import GenericDocumentTestCase
from .literals import (
- TEST_BODY_HTML, TEST_EMAIL_ADDRESS, TEST_EMAIL_FROM_ADDRESS,
+ TEST_EMAIL_BODY_HTML, TEST_EMAIL_ADDRESS, TEST_EMAIL_FROM_ADDRESS,
TEST_RECIPIENTS_MULTIPLE_COMMA, TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT,
TEST_RECIPIENTS_MULTIPLE_SEMICOLON,
TEST_RECIPIENTS_MULTIPLE_SEMICOLON_RESULT, TEST_RECIPIENTS_MULTIPLE_MIXED,
@@ -25,17 +25,22 @@ class ModelTestCase(MailerTestMixin, GenericDocumentTestCase):
def test_send_simple_with_html(self):
self._create_test_user_mailer()
- self.test_user_mailer.send(to=TEST_EMAIL_ADDRESS, body=TEST_BODY_HTML)
+ self.test_user_mailer.send(
+ to=TEST_EMAIL_ADDRESS, body=TEST_EMAIL_BODY_HTML
+ )
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS)
self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS])
- self.assertEqual(mail.outbox[0].alternatives[0][0], TEST_BODY_HTML)
+ self.assertEqual(
+ mail.outbox[0].alternatives[0][0], TEST_EMAIL_BODY_HTML
+ )
def test_send_attachment(self):
self._create_test_user_mailer()
self.test_user_mailer.send_document(
- to=TEST_EMAIL_ADDRESS, document=self.test_document, as_attachment=True
+ to=TEST_EMAIL_ADDRESS, document=self.test_document,
+ as_attachment=True
)
self.assertEqual(len(mail.outbox), 1)
diff --git a/mayan/apps/mailer/workflow_actions.py b/mayan/apps/mailer/workflow_actions.py
new file mode 100644
index 0000000000..c344082ac5
--- /dev/null
+++ b/mayan/apps/mailer/workflow_actions.py
@@ -0,0 +1,124 @@
+from __future__ import absolute_import, unicode_literals
+
+import logging
+
+from django.template import Template, Context
+from django.utils.translation import ugettext_lazy as _
+
+from mayan.apps.acls.models import AccessControlList
+from mayan.apps.document_states.classes import WorkflowAction
+from mayan.apps.document_states.exceptions import WorkflowStateActionError
+
+from .models import UserMailer
+from .permissions import permission_user_mailer_use
+
+__all__ = ('EmailAction',)
+logger = logging.getLogger(__name__)
+
+
+class EmailAction(WorkflowAction):
+ fields = {
+ 'mailing_profile': {
+ 'label': _('Mailing profile'),
+ 'class': 'django.forms.ModelChoiceField', 'kwargs': {
+ 'help_text': _('Mailing profile to use when sending the email.'),
+ 'queryset': UserMailer.objects.none(), 'required': True
+ }
+ },
+ 'recipient': {
+ 'label': _('Recipient'),
+ 'class': 'django.forms.CharField', 'kwargs': {
+ 'help_text': _(
+ 'Email address of the recipient. Can be multiple addresses '
+ 'separated by comma or semicolon. A template can be used '
+ 'to reference properties of the document.'
+ ),
+ 'required': True
+ }
+ },
+ 'subject': {
+ 'label': _('Subject'),
+ 'class': 'django.forms.CharField', 'kwargs': {
+ 'help_text': _(
+ 'Subject of the email. Can be a string or a template.'
+ ),
+ 'required': True
+ }
+ },
+ 'body': {
+ 'label': _('Body'),
+ 'class': 'django.forms.CharField', 'kwargs': {
+ 'help_text': _(
+ 'Body of the email to send. Can be a string or a template.'
+ ),
+ 'required': True
+ }
+ },
+ }
+ field_order = ('mailing_profile', 'recipient', 'subject', 'body')
+ label = _('Send email')
+ widgets = {
+ 'body': {
+ 'class': 'django.forms.widgets.Textarea', 'kwargs': {}
+ }
+ }
+ permission = permission_user_mailer_use
+
+ def execute(self, context):
+ try:
+ recipient = Template(self.form_data['recipient']).render(
+ context=Context(context)
+ )
+ except Exception as exception:
+ raise WorkflowStateActionError(
+ _('Recipient template error: %s') % exception
+ )
+ else:
+ logger.debug('Recipient result: %s', recipient)
+
+ try:
+ subject = Template(self.form_data['subject']).render(
+ context=Context(context)
+ )
+ except Exception as exception:
+ raise WorkflowStateActionError(
+ _('Subject template error: %s') % exception
+ )
+ else:
+ logger.debug('Subject result: %s', subject)
+
+ try:
+ body = Template(self.form_data['body']).render(
+ context=Context(context)
+ )
+ except Exception as exception:
+ raise WorkflowStateActionError(
+ _('Body template error: %s') % exception
+ )
+ else:
+ logger.debug('Body result: %s', body)
+
+ user_mailer = self.get_user_mailer()
+ user_mailer.send(
+ to=recipient, subject=subject, body=body,
+ )
+
+ def get_form_schema(self, request):
+ user = request.user
+ logger.debug('user: %s', user)
+
+ queryset = AccessControlList.objects.restrict_queryset(
+ permission=self.permission, queryset=UserMailer.objects.all(),
+ user=user
+ )
+
+ self.fields['mailing_profile']['kwargs']['queryset'] = queryset
+
+ return {
+ 'field_order': self.field_order,
+ 'fields': self.fields,
+ 'widgets': self.widgets
+ }
+
+ def get_user_mailer(self):
+ return UserMailer.objects.get(pk=self.form_data['mailing_profile'])