Commit 15bd7d7f by Waheed Ahmed

Email funtionality for `Send for Review`

ECOM-6047
parent 09a629fd
......@@ -384,12 +384,14 @@ class ChangeCourseStateViewTests(TestCase):
self.user = UserFactory()
self.user.groups.add(Group.objects.get(name=INTERNAL_USER_GROUP_NAME))
course = self.course_state.course
course.image = make_image_file('test_banner.jpg')
course.save()
self.course = self.course_state.course
self.course.image = make_image_file('test_banner.jpg')
self.course.save()
self.organization_extension = factories.OrganizationExtensionFactory()
course.organizations.add(self.organization_extension.organization)
self.course.organizations.add(self.organization_extension.organization)
factories.UserAttributeFactory(user=self.user, enable_email_notification=True)
toggle_switch('enable_publisher_email_notifications', True)
self.change_state_url = reverse('publisher:api:change_course_state', kwargs={'pk': self.course_state.id})
......@@ -402,11 +404,12 @@ class ChangeCourseStateViewTests(TestCase):
"""
self.assertNotEqual(self.course_state.name, CourseStateChoices.Review)
factories.CourseUserRoleFactory(
course=self.course_state.course, role=PublisherUserRole.MarketingReviewer, user=self.user
course=self.course, role=PublisherUserRole.MarketingReviewer, user=self.user
)
course_team_user = UserFactory()
factories.CourseUserRoleFactory(
course=self.course_state.course, role=PublisherUserRole.CourseTeam, user=UserFactory()
course=self.course, role=PublisherUserRole.CourseTeam, user=course_team_user
)
response = self.client.patch(
......@@ -417,11 +420,13 @@ class ChangeCourseStateViewTests(TestCase):
self.assertEqual(response.status_code, 200)
self.course_state = CourseState.objects.get(course=self.course_state.course)
self.course_state = CourseState.objects.get(course=self.course)
self.assertEqual(self.course_state.name, CourseStateChoices.Review)
self.assertEqual(self.course_state.owner_role, PublisherUserRole.CourseTeam)
self._assert_email_sent(course_team_user)
def test_change_course_state_with_course_team(self):
"""
Verify that course team admin can change course workflow state
......@@ -432,7 +437,12 @@ class ChangeCourseStateViewTests(TestCase):
self.assertNotEqual(self.course_state.name, CourseStateChoices.Review)
factories.CourseUserRoleFactory(
course=self.course_state.course, role=PublisherUserRole.CourseTeam, user=self.user
course=self.course, role=PublisherUserRole.CourseTeam, user=self.user
)
marketing_user = UserFactory()
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.MarketingReviewer, user=marketing_user
)
response = self.client.patch(
......@@ -443,11 +453,24 @@ class ChangeCourseStateViewTests(TestCase):
self.assertEqual(response.status_code, 200)
self.course_state = CourseState.objects.get(course=self.course_state.course)
self.course_state = CourseState.objects.get(course=self.course)
self.assertEqual(self.course_state.name, CourseStateChoices.Review)
self.assertEqual(self.course_state.owner_role, PublisherUserRole.MarketingReviewer)
self._assert_email_sent(marketing_user)
def _assert_email_sent(self, user):
subject = 'Changes to {title} are ready for review'.format(title=self.course.title)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual([user.email], mail.outbox[0].to)
self.assertEqual(str(mail.outbox[0].subject), subject)
body = mail.outbox[0].body.strip()
object_path = reverse('publisher:publisher_course_detail', kwargs={'pk': self.course.id})
page_url = 'https://{host}{path}'.format(host=Site.objects.get_current().domain.strip('/'), path=object_path)
self.assertIn(page_url, body)
def test_change_course_state_with_error(self):
"""
Verify that user cannot change course workflow state directly from `Draft` to `Approved`.
......
......@@ -146,3 +146,47 @@ def send_email_for_course_creation(course, course_run):
logger.exception(
'Failed to send email notifications for creation of course [%s]', course_run.course.id
)
def send_email_for_send_for_review(course, user):
""" Send email when course is submitted for review.
Arguments:
course (Object): Course object
user (Object): User object
"""
try:
txt_template = 'publisher/email/send_for_review.txt'
html_template = 'publisher/email/send_for_review.html'
recipient_user = course.marketing_reviewer
user_role = course.course_user_roles.get(user=user)
if user_role.role == PublisherUserRole.MarketingReviewer:
recipient_user = course.course_team_admin
to_addresses = [recipient_user.email]
from_address = settings.PUBLISHER_FROM_EMAIL
page_path = reverse('publisher:publisher_course_detail', kwargs={'pk': course.id})
context = {
'recipient_name': recipient_user.full_name or recipient_user.username if recipient_user else '',
'sender_name': user.full_name or user.username,
'course_name': course.title,
'course_page_url': 'https://{host}{path}'.format(
host=Site.objects.get_current().domain.strip('/'), path=page_path
)
}
template = get_template(txt_template)
plain_content = template.render(context)
template = get_template(html_template)
html_content = template.render(context)
subject = _('Changes to {title} are ready for review').format(title=course.title) # pylint: disable=no-member
email_msg = EmailMultiAlternatives(
subject, plain_content, from_address, to_addresses
)
email_msg.attach_alternative(html_content, 'text/html')
email_msg.send()
except Exception: # pylint: disable=broad-except
logger.exception('Failed to send email notifications send for review of course %s', course.id)
......@@ -22,7 +22,7 @@ from course_discovery.apps.course_metadata.models import LevelType, Organization
from course_discovery.apps.course_metadata.utils import UploadToFieldNamePath
from course_discovery.apps.ietf_language_tags.models import LanguageTag
from course_discovery.apps.publisher.choices import CourseRunStateChoices, CourseStateChoices, PublisherUserRole
from course_discovery.apps.publisher.emails import send_email_for_change_state
from course_discovery.apps.publisher.emails import send_email_for_change_state, send_email_for_send_for_review
from course_discovery.apps.publisher.utils import is_email_notification_enabled
logger = logging.getLogger(__name__)
......@@ -199,6 +199,13 @@ class Course(TimeStampedModel, ChangedByMixin):
organization = self.organizations.all().first()
return organization.partner if organization else None
@property
def marketing_reviewer(self):
try:
return self.course_user_roles.get(role=PublisherUserRole.MarketingReviewer).user
except CourseUserRole.DoesNotExist:
return None
class CourseRun(TimeStampedModel, ChangedByMixin):
""" Publisher CourseRun model. It contains fields related to the course run intake form."""
......@@ -535,6 +542,10 @@ class CourseState(TimeStampedModel, ChangedByMixin):
elif user_role.role == PublisherUserRole.CourseTeam:
self.owner_role = PublisherUserRole.MarketingReviewer
self.review()
if waffle.switch_is_active('enable_publisher_email_notifications'):
send_email_for_send_for_review(self.course, user)
elif state == CourseStateChoices.Approved:
self.approved()
......
......@@ -246,6 +246,16 @@ class CourseTests(TestCase):
""" Verify that the partner property returns organization partner if exist. """
self.assertEqual(self.course.partner, self.org_extension_1.organization.partner)
def test_marketing_reviewer(self):
""" Verify that the marketing_reviewer property returns user if exist. """
self.assertIsNone(self.course2.marketing_reviewer)
factories.CourseUserRoleFactory(
course=self.course2, user=self.user1, role=PublisherUserRole.MarketingReviewer
)
self.assertEqual(self.user1, self.course2.marketing_reviewer)
class SeatTests(TestCase):
""" Tests for the publisher `Seat` model. """
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-13 14:02+0500\n"
"POT-Creation-Date: 2017-02-13 14:47+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -493,6 +493,11 @@ msgstr ""
msgid "New Studio instance request for {title}"
msgstr ""
#: apps/publisher/emails.py
#, python-brace-format
msgid "Changes to {title} are ready for review"
msgstr ""
#: apps/publisher/forms.py
msgid "Remove Image"
msgstr ""
......@@ -1181,7 +1186,6 @@ msgid "Save Draft"
msgstr ""
#: templates/publisher/add_update_course_form.html
#: templates/publisher/course_detail.html
msgid "Course Form"
msgstr ""
......@@ -1641,6 +1645,10 @@ msgstr ""
msgid "Courses"
msgstr ""
#: templates/publisher/course_detail.html
msgid "Course Detail"
msgstr ""
#: templates/publisher/course_detail.html templates/publisher/courses.html
#: templates/publisher/dashboard/_in_progress.html
msgid "Institution"
......@@ -2333,6 +2341,8 @@ msgstr ""
#. Translators: It's closing of mail.
#: templates/publisher/email/course_created.html
#: templates/publisher/email/course_created.txt
#: templates/publisher/email/send_for_review.html
#: templates/publisher/email/send_for_review.txt
#: templates/publisher/email/studio_instance_created.html
#: templates/publisher/email/studio_instance_created.txt
#: templates/publisher/email/studio_instance_needed.html
......@@ -2353,6 +2363,45 @@ msgid ""
" in Publisher on %(date)s at %(time)s."
msgstr ""
#: templates/publisher/email/send_for_review.html
#: templates/publisher/email/send_for_review.txt
#, python-format
msgid "Dear %(recipient_name)s,"
msgstr ""
#: templates/publisher/email/send_for_review.html
#, python-format
msgid ""
"New changes to %(link_start)s%(course_page_url)s%(link_middle)s "
"%(course_name)s %(link_end)s are ready for your review. "
"%(link_start)s%(course_page_url)s%(link_middle)s View this course in "
"Publisher %(link_end)s to approve or decline the changes."
msgstr ""
#: templates/publisher/email/send_for_review.html
#: templates/publisher/email/studio_instance_created.html
#, python-format
msgid ""
"<p>Note: This email address is unable to receive replies. For questions or "
"comments, contact %(contact_us_email)s.</p>"
msgstr ""
#: templates/publisher/email/send_for_review.txt
#, python-format
msgid ""
"New changes to %(course_name)s are ready for your review. "
"%(course_page_url)s View this course in Publisher to approve or decline the "
"changes."
msgstr ""
#: templates/publisher/email/send_for_review.txt
#: templates/publisher/email/studio_instance_created.txt
#, python-format
msgid ""
"Note: This email address is unable to receive replies. For questions or "
"comments, contact %(contact_us_email)s."
msgstr ""
#. Translators: course_team_name is course team member name.
#: templates/publisher/email/studio_instance_created.html
#: templates/publisher/email/studio_instance_created.txt
......@@ -2416,13 +2465,6 @@ msgid ""
"build your course. We recommend that you use the list early!"
msgstr ""
#: templates/publisher/email/studio_instance_created.html
#, python-format
msgid ""
"<p>Note: This email address is unable to receive replies. For questions or "
"comments, contact %(contact_us_email)s.</p>"
msgstr ""
#: templates/publisher/email/studio_instance_created.txt
#, python-format
msgid ""
......@@ -2430,13 +2472,6 @@ msgid ""
"%(course_run_page_url)s. You can now edit this course in Studio."
msgstr ""
#: templates/publisher/email/studio_instance_created.txt
#, python-format
msgid ""
"Note: This email address is unable to receive replies. For questions or "
"comments, contact %(contact_us_email)s."
msgstr ""
#: templates/publisher/email/studio_instance_needed.html
#: templates/publisher/email/studio_instance_needed.txt
msgid "Please create a Studio instance for the following course."
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-13 14:02+0500\n"
"POT-Creation-Date: 2017-02-13 14:47+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-13 14:02+0500\n"
"POT-Creation-Date: 2017-02-13 14:47+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -611,6 +611,13 @@ msgstr ""
"Néw Stüdïö ïnstänçé réqüést för {title} Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєт#"
#: apps/publisher/emails.py
#, python-brace-format
msgid "Changes to {title} are ready for review"
msgstr ""
"Çhängés tö {title} äré réädý för révïéw Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєт#"
#: apps/publisher/forms.py
msgid "Remove Image"
msgstr "Rémövé Ìmägé Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
......@@ -1372,7 +1379,6 @@ msgid "Save Draft"
msgstr "Sävé Dräft Ⱡ'σяєм ιρѕυм ∂σłσ#"
#: templates/publisher/add_update_course_form.html
#: templates/publisher/course_detail.html
msgid "Course Form"
msgstr "Çöürsé Förm Ⱡ'σяєм ιρѕυм ∂σłσя #"
......@@ -1982,6 +1988,10 @@ msgstr "Däshßöärd Ⱡ'σяєм ιρѕυм ∂σł#"
msgid "Courses"
msgstr "Çöürsés Ⱡ'σяєм ιρѕυм #"
#: templates/publisher/course_detail.html
msgid "Course Detail"
msgstr "Çöürsé Détäïl Ⱡ'σяєм ιρѕυм ∂σłσя ѕι#"
#: templates/publisher/course_detail.html templates/publisher/courses.html
#: templates/publisher/dashboard/_in_progress.html
msgid "Institution"
......@@ -2736,6 +2746,8 @@ msgstr ""
#. Translators: It's closing of mail.
#: templates/publisher/email/course_created.html
#: templates/publisher/email/course_created.txt
#: templates/publisher/email/send_for_review.html
#: templates/publisher/email/send_for_review.txt
#: templates/publisher/email/studio_instance_created.html
#: templates/publisher/email/studio_instance_created.txt
#: templates/publisher/email/studio_instance_needed.html
......@@ -2759,6 +2771,62 @@ msgstr ""
" ïn Püßlïshér ön %(date)s ät %(time)s. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυя α#"
#: templates/publisher/email/send_for_review.html
#: templates/publisher/email/send_for_review.txt
#, python-format
msgid "Dear %(recipient_name)s,"
msgstr "Déär %(recipient_name)s, Ⱡ'σяєм ιρѕυм ∂σł#"
#: templates/publisher/email/send_for_review.html
#, python-format
msgid ""
"New changes to %(link_start)s%(course_page_url)s%(link_middle)s "
"%(course_name)s %(link_end)s are ready for your review. "
"%(link_start)s%(course_page_url)s%(link_middle)s View this course in "
"Publisher %(link_end)s to approve or decline the changes."
msgstr ""
"Néw çhängés tö %(link_start)s%(course_page_url)s%(link_middle)s "
"%(course_name)s %(link_end)s äré réädý för ýöür révïéw. "
"%(link_start)s%(course_page_url)s%(link_middle)s Vïéw thïs çöürsé ïn "
"Püßlïshér %(link_end)s tö äpprövé ör déçlïné thé çhängés. Ⱡ'σяєм ιρѕυм ∂σłσя"
" ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє∂ ∂σ єιυѕмσ∂ тємρσя ιη¢ι∂ι∂υηт υт"
" łαвσяє єт ∂σłσяє мαgηα αłιqυα. υт єηιм α∂ мιηιм νєηιαм, qυιѕ ησѕтяυ∂ "
"єχєя¢ιтαтιση υłłαм¢σ łαвσяιѕ ηιѕι υт αłιqυιρ єχ єα ¢σммσ∂σ ¢σηѕєqυαт. ∂υιѕ "
"αυтє ιяυяє ∂σłσя ιη яєρяєнєη∂єяιт ιη νσłυρтαтє νєłιт єѕѕє ¢ιłłυм ∂σłσяє єυ "
"ƒυgιαт ηυłłα ραяιαтυя. єχ¢єρтєυя ѕιηт σ¢¢αє¢αт ¢υρι∂αтαт ηση ρяσι∂єηт, ѕυηт "
"ιη ¢υłρα qυι σƒƒι¢ια ∂єѕєяυηт мσłłιт αηιм ι∂ єѕт łαв#"
#: templates/publisher/email/send_for_review.html
#: templates/publisher/email/studio_instance_created.html
#, python-format
msgid ""
"<p>Note: This email address is unable to receive replies. For questions or "
"comments, contact %(contact_us_email)s.</p>"
msgstr ""
"<p>Nöté: Thïs émäïl äddréss ïs ünäßlé tö réçéïvé réplïés. För qüéstïöns ör "
"çömménts, çöntäçt %(contact_us_email)s.</p> Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт#"
#: templates/publisher/email/send_for_review.txt
#, python-format
msgid ""
"New changes to %(course_name)s are ready for your review. "
"%(course_page_url)s View this course in Publisher to approve or decline the "
"changes."
msgstr ""
"Néw çhängés tö %(course_name)s äré réädý för ýöür révïéw. "
"%(course_page_url)s Vïéw thïs çöürsé ïn Püßlïshér tö äpprövé ör déçlïné thé "
"çhängés. Ⱡ'σяєм ιρѕυм ∂σł#"
#: templates/publisher/email/send_for_review.txt
#: templates/publisher/email/studio_instance_created.txt
#, python-format
msgid ""
"Note: This email address is unable to receive replies. For questions or "
"comments, contact %(contact_us_email)s."
msgstr ""
"Nöté: Thïs émäïl äddréss ïs ünäßlé tö réçéïvé réplïés. För qüéstïöns ör "
"çömménts, çöntäçt %(contact_us_email)s. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢#"
#. Translators: course_team_name is course team member name.
#: templates/publisher/email/studio_instance_created.html
#: templates/publisher/email/studio_instance_created.txt
......@@ -2850,15 +2918,6 @@ msgstr ""
"ησѕтяυ∂ єχєя¢ιтαтιση υłłαм¢σ łαвσяιѕ ηιѕι υт αłιqυιρ єχ єα ¢σммσ∂σ "
"¢σηѕєqυαт. ∂υιѕ αυтє ιяυяє ∂σłσя ιη яєρяєнєη∂єяιт ιη νσłυρтαтє νєł#"
#: templates/publisher/email/studio_instance_created.html
#, python-format
msgid ""
"<p>Note: This email address is unable to receive replies. For questions or "
"comments, contact %(contact_us_email)s.</p>"
msgstr ""
"<p>Nöté: Thïs émäïl äddréss ïs ünäßlé tö réçéïvé réplïés. För qüéstïöns ör "
"çömménts, çöntäçt %(contact_us_email)s.</p> Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт#"
#: templates/publisher/email/studio_instance_created.txt
#, python-format
msgid ""
......@@ -2869,15 +2928,6 @@ msgstr ""
"%(course_run_page_url)s. Ýöü çän nöw édït thïs çöürsé ïn Stüdïö. Ⱡ'σяєм "
"ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢т#"
#: templates/publisher/email/studio_instance_created.txt
#, python-format
msgid ""
"Note: This email address is unable to receive replies. For questions or "
"comments, contact %(contact_us_email)s."
msgstr ""
"Nöté: Thïs émäïl äddréss ïs ünäßlé tö réçéïvé réplïés. För qüéstïöns ör "
"çömménts, çöntäçt %(contact_us_email)s. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢#"
#: templates/publisher/email/studio_instance_needed.html
#: templates/publisher/email/studio_instance_needed.txt
msgid "Please create a Studio instance for the following course."
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-13 14:02+0500\n"
"POT-Creation-Date: 2017-02-13 14:47+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......
......@@ -2,7 +2,7 @@
{% load i18n %}
{% load staticfiles %}
{% block title %}
{% trans "Course Form" %}
{% trans "Course Detail" %}
{% endblock title %}
{% block page_content %}
......
......@@ -29,7 +29,11 @@
<span>{{ role_widget.ownership.days }} {% trans "day in ownership" %}</span>
{% endif %}
<div class="field-readonly user-full-name">
{{ role_widget.course_role.user.full_name }}
{% if role_widget.course_role.user.full_name %}
{{ role_widget.course_role.user.full_name }}
{% else %}
{{ role_widget.course_role.user.username }}
{% endif %}
</div>
</div>
</div>
......
......@@ -15,7 +15,11 @@
<div class="role-assignment-container">
<div id="userRoleContainer-{{ role_widget.user_course_role.role }}">
<span id="userFullName-{{ role_widget.user_course_role.role }}" class="field-readonly user-full-name">
{{ role_widget.user_course_role.user.full_name }}
{% if role_widget.user_course_role.user.full_name %}
{{ role_widget.user_course_role.user.full_name }}
{% else %}
{{ role_widget.user_course_role.user.username }}
{% endif %}
</span>
<a class="change-role-assignment" data-role="{{ role_widget.user_course_role.role }}" href="#">
{% trans "change owner" %}
......
{% extends "publisher/email/email_base.html" %}
{% load i18n %}
{% block body %}
<!-- Message Body -->
<p>
{% blocktrans trimmed %}
Dear {{ recipient_name }},
{% endblocktrans %}
<p>
{% blocktrans with link_start='<a href="' link_middle='">' link_end='</a>' trimmed %}
New changes to {{ link_start }}{{ course_page_url }}{{ link_middle }} {{ course_name }} {{ link_end }} are ready for your review. {{ link_start }}{{ course_page_url }}{{ link_middle }} View this course in Publisher {{ link_end }} to approve or decline the changes.
{% endblocktrans %}
</p>
{% comment %}Translators: It's closing of mail.{% endcomment %}
{% trans "Thanks," %}<br>
{{ sender_name }}
{% blocktrans trimmed %}
<p>Note: This email address is unable to receive replies. For questions or comments, contact {{ contact_us_email }}.</p>
{% endblocktrans %}
<!-- End Message Body -->
{% endblock body %}
{% load i18n %}
{% blocktrans trimmed %}
Dear {{ recipient_name }},
{% endblocktrans %}
{% blocktrans trimmed %}
New changes to {{ course_name }} are ready for your review. {{ course_page_url }} View this course in Publisher to approve or decline the changes.
{% endblocktrans %}
{% trans "Thanks," %}
{{ sender_name }}
{% blocktrans trimmed %}
Note: This email address is unable to receive replies. For questions or comments, contact {{ contact_us_email }}.
{% endblocktrans %}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment