Commit 2f956826 by Waheed Ahmed

Course run mark as reviewed.

ECOM-6248
parent 0098e790
...@@ -155,13 +155,14 @@ class CourseStateSerializerTests(TestCase): ...@@ -155,13 +155,14 @@ class CourseStateSerializerTests(TestCase):
self.user = UserFactory() self.user = UserFactory()
self.request.user = self.user self.request.user = self.user
CourseUserRoleFactory(
course=self.course_state.course, role=PublisherUserRole.CourseTeam, user=self.user
)
def test_update(self): def test_update(self):
""" """
Verify that we can update course workflow state with serializer. Verify that we can update course workflow state with serializer.
""" """
CourseUserRoleFactory(
course=self.course_state.course, role=PublisherUserRole.CourseTeam, user=self.user
)
course = self.course_state.course course = self.course_state.course
course.image = make_image_file('test_banner.jpg') course.image = make_image_file('test_banner.jpg')
course.save() course.save()
......
...@@ -481,6 +481,9 @@ class ChangeCourseStateViewTests(TestCase): ...@@ -481,6 +481,9 @@ class ChangeCourseStateViewTests(TestCase):
""" """
Verify that user cannot change course workflow state directly from `Draft` to `Approved`. Verify that user cannot change course workflow state directly from `Draft` to `Approved`.
""" """
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.CourseTeam, user=self.user
)
response = self.client.patch( response = self.client.patch(
self.change_state_url, self.change_state_url,
data=json.dumps({'name': CourseStateChoices.Approved}), data=json.dumps({'name': CourseStateChoices.Approved}),
...@@ -576,11 +579,12 @@ class ChangeCourseRunStateViewTests(TestCase): ...@@ -576,11 +579,12 @@ class ChangeCourseRunStateViewTests(TestCase):
self.assertEqual(response.data, expected) self.assertEqual(response.data, expected)
def test_mark_as_reviewed(self): def test_send_for_review(self):
""" """
Verify that user can mark course-run as reviewed. Verify that user can change course-run workflow state and owner role will be changed to `CourseTeam`.
""" """
self.run_state.name = CourseRunStateChoices.Draft self.run_state.name = CourseRunStateChoices.Draft
self.run_state.owner_role = PublisherUserRole.MarketingReviewer
self.run_state.save() self.run_state.save()
self._assign_role(self.course_run.course, PublisherUserRole.MarketingReviewer, self.user) self._assign_role(self.course_run.course, PublisherUserRole.MarketingReviewer, self.user)
...@@ -599,6 +603,7 @@ class ChangeCourseRunStateViewTests(TestCase): ...@@ -599,6 +603,7 @@ class ChangeCourseRunStateViewTests(TestCase):
course_run_state = CourseRunState.objects.get(course_run=self.course_run) course_run_state = CourseRunState.objects.get(course_run=self.course_run)
self.assertEqual(course_run_state.name, CourseRunStateChoices.Review) self.assertEqual(course_run_state.name, CourseRunStateChoices.Review)
self.assertEqual(course_run_state.owner_role, PublisherUserRole.CourseTeam)
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
...@@ -607,3 +612,31 @@ class ChangeCourseRunStateViewTests(TestCase): ...@@ -607,3 +612,31 @@ class ChangeCourseRunStateViewTests(TestCase):
factories.CourseUserRoleFactory( factories.CourseUserRoleFactory(
course=course, role=role, user=user course=course, role=role, user=user
) )
def test_mark_as_reviewed(self):
"""
Verify that user can change course-run workflow state and owner role will be changed to `Publisher`.
"""
self.run_state.name = CourseRunStateChoices.Review
self.run_state.owner_role = PublisherUserRole.CourseTeam
self.run_state.save()
self._assign_role(self.course_run.course, PublisherUserRole.CourseTeam, self.user)
self._assign_role(self.course_run.course, PublisherUserRole.MarketingReviewer, UserFactory())
self._assign_role(self.course_run.course, PublisherUserRole.Publisher, UserFactory())
response = self.client.patch(
self.change_state_url,
data=json.dumps({'name': CourseStateChoices.Approved}),
content_type=JSON_CONTENT_TYPE
)
self.assertEqual(response.status_code, 200)
self.run_state = CourseRunState.objects.get(course_run=self.course_run)
self.assertEqual(self.run_state.name, CourseRunStateChoices.Approved)
self.assertEqual(self.run_state.owner_role, PublisherUserRole.Publisher)
self.assertEqual(len(mail.outbox), 2)
...@@ -185,7 +185,7 @@ def send_email_for_mark_as_reviewed(course, user): ...@@ -185,7 +185,7 @@ def send_email_for_mark_as_reviewed(course, user):
logger.exception('Failed to send email notifications mark as reviewed of course %s', course.id) logger.exception('Failed to send email notifications mark as reviewed of course %s', course.id)
def send_course_workflow_email(course, user, subject, txt_template, html_template, page_path): def send_course_workflow_email(course, user, subject, txt_template, html_template, page_path, course_name=None):
""" Send email for course workflow state change. """ Send email for course workflow state change.
Arguments: Arguments:
...@@ -194,6 +194,8 @@ def send_course_workflow_email(course, user, subject, txt_template, html_templat ...@@ -194,6 +194,8 @@ def send_course_workflow_email(course, user, subject, txt_template, html_templat
subject (String): Email subject subject (String): Email subject
txt_template: (String): Email text template path txt_template: (String): Email text template path
html_template: (String): Email html template path html_template: (String): Email html template path
page_path: (URL): Detail page url
course_name: (String): Custom course name, by default None
""" """
recipient_user = course.marketing_reviewer recipient_user = course.marketing_reviewer
user_role = course.course_user_roles.get(user=user) user_role = course.course_user_roles.get(user=user)
...@@ -207,7 +209,7 @@ def send_course_workflow_email(course, user, subject, txt_template, html_templat ...@@ -207,7 +209,7 @@ def send_course_workflow_email(course, user, subject, txt_template, html_templat
context = { context = {
'recipient_name': recipient_user.full_name or recipient_user.username if recipient_user else '', 'recipient_name': recipient_user.full_name or recipient_user.username if recipient_user else '',
'sender_name': user.full_name or user.username, 'sender_name': user.full_name or user.username,
'course_name': course.title, 'course_name': course_name if course_name else course.title,
'contact_us_email': partner_coordinator.email if partner_coordinator else '', 'contact_us_email': partner_coordinator.email if partner_coordinator else '',
'page_url': 'https://{host}{path}'.format( 'page_url': 'https://{host}{path}'.format(
host=Site.objects.get_current().domain.strip('/'), path=page_path host=Site.objects.get_current().domain.strip('/'), path=page_path
...@@ -241,3 +243,80 @@ def send_email_for_send_for_review_course_run(course_run, user): ...@@ -241,3 +243,80 @@ def send_email_for_send_for_review_course_run(course_run, user):
send_course_workflow_email(course_run.course, user, subject, txt_template, html_template, page_path) send_course_workflow_email(course_run.course, user, subject, txt_template, html_template, page_path)
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
logger.exception('Failed to send email notifications send for review of course-run %s', course_run.id) logger.exception('Failed to send email notifications send for review of course-run %s', course_run.id)
def send_email_for_mark_as_reviewed_course_run(course_run, user):
""" Send email when course-run is marked as reviewed.
Arguments:
course-run (Object): CourseRun object
user (Object): User object
"""
txt_template = 'publisher/email/course_run/mark_as_reviewed.txt'
html_template = 'publisher/email/course_run/mark_as_reviewed.html'
run_name = '{pacing_type}: {start_date}'.format(
pacing_type=course_run.get_pacing_type_display(),
start_date=course_run.start.strftime("%B %d, %Y")
)
subject = _('Changes to {run_name} has been marked as reviewed').format(run_name=run_name) # pylint: disable=no-member
try:
page_path = reverse('publisher:publisher_course_run_detail', kwargs={'pk': course_run.id})
send_course_workflow_email(
course_run.course,
user,
subject,
txt_template,
html_template,
page_path,
course_name=run_name
)
except Exception: # pylint: disable=broad-except
logger.exception('Failed to send email notifications for mark as reviewed of course-run %s', course_run.id)
def send_email_to_publisher(course_run, user):
""" Send email to publisher when course-run is marked as reviewed.
Arguments:
course_run (Object): CourseRun object
user (Object): User object
"""
txt_template = 'publisher/email/course_run/mark_as_reviewed.txt'
html_template = 'publisher/email/course_run/mark_as_reviewed.html'
run_name = '{pacing_type}: {start_date}'.format(
pacing_type=course_run.get_pacing_type_display(),
start_date=course_run.start.strftime("%B %d, %Y")
)
subject = _('Changes to {run_name} has been marked as reviewed').format(run_name=run_name) # pylint: disable=no-member
recipient_user = course_run.course.publisher
try:
if is_email_notification_enabled(recipient_user):
partner_coordinator = course_run.course.partner_coordinator
to_addresses = [recipient_user.email]
from_address = settings.PUBLISHER_FROM_EMAIL
page_path = reverse('publisher:publisher_course_run_detail', kwargs={'pk': course_run.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': run_name,
'contact_us_email': partner_coordinator.email if partner_coordinator else '',
'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)
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 for mark as reviewed of course-run %s', course_run.id)
...@@ -22,11 +22,8 @@ from course_discovery.apps.course_metadata.choices import CourseRunPacing ...@@ -22,11 +22,8 @@ from course_discovery.apps.course_metadata.choices import CourseRunPacing
from course_discovery.apps.course_metadata.models import LevelType, Organization, Person, Subject from course_discovery.apps.course_metadata.models import LevelType, Organization, Person, Subject
from course_discovery.apps.course_metadata.utils import UploadToFieldNamePath from course_discovery.apps.course_metadata.utils import UploadToFieldNamePath
from course_discovery.apps.ietf_language_tags.models import LanguageTag from course_discovery.apps.ietf_language_tags.models import LanguageTag
from course_discovery.apps.publisher import emails
from course_discovery.apps.publisher.choices import CourseRunStateChoices, CourseStateChoices, PublisherUserRole from course_discovery.apps.publisher.choices import CourseRunStateChoices, CourseStateChoices, PublisherUserRole
from course_discovery.apps.publisher.emails import (
send_email_for_change_state, send_email_for_mark_as_reviewed,
send_email_for_send_for_review, send_email_for_send_for_review_course_run
)
from course_discovery.apps.publisher.utils import is_email_notification_enabled from course_discovery.apps.publisher.utils import is_email_notification_enabled
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -161,9 +158,9 @@ class Course(TimeStampedModel, ChangedByMixin): ...@@ -161,9 +158,9 @@ class Course(TimeStampedModel, ChangedByMixin):
""" """
users_list_roles = [obj.user for obj in self.course_user_roles.all()] users_list_roles = [obj.user for obj in self.course_user_roles.all()]
emails = [user.email for user in users_list_roles if is_email_notification_enabled(user)] user_emails = [user.email for user in users_list_roles if is_email_notification_enabled(user)]
return emails return user_emails
@property @property
def keywords_data(self): def keywords_data(self):
...@@ -218,6 +215,13 @@ class Course(TimeStampedModel, ChangedByMixin): ...@@ -218,6 +215,13 @@ class Course(TimeStampedModel, ChangedByMixin):
return None return None
@property
def publisher(self):
try:
return self.course_user_roles.get(role=PublisherUserRole.Publisher).user
except CourseUserRole.DoesNotExist:
return None
class CourseRun(TimeStampedModel, ChangedByMixin): class CourseRun(TimeStampedModel, ChangedByMixin):
""" Publisher CourseRun model. It contains fields related to the course run intake form.""" """ Publisher CourseRun model. It contains fields related to the course run intake form."""
...@@ -318,7 +322,7 @@ class CourseRun(TimeStampedModel, ChangedByMixin): ...@@ -318,7 +322,7 @@ class CourseRun(TimeStampedModel, ChangedByMixin):
self.state.save() self.state.save()
if waffle.switch_is_active('enable_publisher_email_notifications'): if waffle.switch_is_active('enable_publisher_email_notifications'):
send_email_for_change_state(self) emails.send_email_for_change_state(self)
@property @property
def current_state(self): def current_state(self):
...@@ -603,13 +607,16 @@ class CourseState(TimeStampedModel, ChangedByMixin): ...@@ -603,13 +607,16 @@ class CourseState(TimeStampedModel, ChangedByMixin):
self.review() self.review()
if waffle.switch_is_active('enable_publisher_email_notifications'): if waffle.switch_is_active('enable_publisher_email_notifications'):
send_email_for_send_for_review(self.course, user) emails.send_email_for_send_for_review(self.course, user)
elif state == CourseStateChoices.Approved: elif state == CourseStateChoices.Approved:
user_role = self.course.course_user_roles.get(user=user)
self.approved_by_role = user_role.role
self.owner_role_modified = timezone.now()
self.approved() self.approved()
if waffle.switch_is_active('enable_publisher_email_notifications'): if waffle.switch_is_active('enable_publisher_email_notifications'):
send_email_for_mark_as_reviewed(self.course, user) emails.send_email_for_mark_as_reviewed(self.course, user)
self.save() self.save()
...@@ -683,10 +690,19 @@ class CourseRunState(TimeStampedModel, ChangedByMixin): ...@@ -683,10 +690,19 @@ class CourseRunState(TimeStampedModel, ChangedByMixin):
self.review() self.review()
if waffle.switch_is_active('enable_publisher_email_notifications'): if waffle.switch_is_active('enable_publisher_email_notifications'):
send_email_for_send_for_review_course_run(self.course_run, user) emails.send_email_for_send_for_review_course_run(self.course_run, user)
elif state == CourseRunStateChoices.Approved: elif state == CourseRunStateChoices.Approved:
user_role = self.course_run.course.course_user_roles.get(user=user)
self.approved_by_role = user_role.role
self.owner_role = PublisherUserRole.Publisher
self.owner_role_modified = timezone.now()
self.approved() self.approved()
if waffle.switch_is_active('enable_publisher_email_notifications'):
emails.send_email_for_mark_as_reviewed_course_run(self.course_run, user)
emails.send_email_to_publisher(self.course_run, user)
elif state == CourseRunStateChoices.Published: elif state == CourseRunStateChoices.Published:
self.published() self.published()
......
...@@ -62,13 +62,13 @@ class StateChangeEmailTests(TestCase): ...@@ -62,13 +62,13 @@ class StateChangeEmailTests(TestCase):
toggle_switch('enable_publisher_email_notifications', True) toggle_switch('enable_publisher_email_notifications', True)
@mock.patch('course_discovery.apps.publisher.models.send_email_for_change_state') @mock.patch('course_discovery.apps.publisher.emails.send_email_for_change_state')
def test_email_with_enable_waffle_switch(self, send_email_for_change_state): def test_email_with_enable_waffle_switch(self, send_email_for_change_state):
""" Verify that send_email_for_state called with enable waffle switch.. """ """ Verify that send_email_for_state called with enable waffle switch.. """
self.course_run.change_state(target=State.DRAFT) self.course_run.change_state(target=State.DRAFT)
send_email_for_change_state.assert_called_once_with(self.course_run) send_email_for_change_state.assert_called_once_with(self.course_run)
@mock.patch('course_discovery.apps.publisher.models.send_email_for_change_state') @mock.patch('course_discovery.apps.publisher.emails.send_email_for_change_state')
def test_email_with_waffle_switch_disabled(self, send_email_for_change_state): def test_email_with_waffle_switch_disabled(self, send_email_for_change_state):
""" Verify that send_email_for_state not called with disable waffle switch.. """ """ Verify that send_email_for_state not called with disable waffle switch.. """
toggle_switch('enable_publisher_email_notifications', False) toggle_switch('enable_publisher_email_notifications', False)
...@@ -309,11 +309,11 @@ class CourseMarkAsReviewedEmailTests(TestCase): ...@@ -309,11 +309,11 @@ class CourseMarkAsReviewedEmailTests(TestCase):
) )
class CourseRunMarkAsReviewedEmailTests(TestCase): class CourseRunSendForReviewEmailTests(TestCase):
""" Tests for the email functionality for mark as reviewed. """ """ Tests for the email functionality for send for review. """
def setUp(self): def setUp(self):
super(CourseRunMarkAsReviewedEmailTests, self).setUp() super(CourseRunSendForReviewEmailTests, self).setUp()
self.user = UserFactory() self.user = UserFactory()
self.user_2 = UserFactory() self.user_2 = UserFactory()
self.user_3 = UserFactory() self.user_3 = UserFactory()
...@@ -376,3 +376,98 @@ class CourseRunMarkAsReviewedEmailTests(TestCase): ...@@ -376,3 +376,98 @@ class CourseRunMarkAsReviewedEmailTests(TestCase):
page_url = 'https://{host}{path}'.format(host=Site.objects.get_current().domain.strip('/'), path=page_path) page_url = 'https://{host}{path}'.format(host=Site.objects.get_current().domain.strip('/'), path=page_path)
self.assertIn(page_url, body) self.assertIn(page_url, body)
self.assertIn('are ready for your review.', body) self.assertIn('are ready for your review.', body)
class CourseRunMarkAsReviewedEmailTests(TestCase):
""" Tests email functionality of mark as reviewed. """
def setUp(self):
super(CourseRunMarkAsReviewedEmailTests, self).setUp()
self.user = UserFactory()
self.user_2 = UserFactory()
self.user_3 = UserFactory()
self.seat = factories.SeatFactory()
self.course_run = self.seat.course_run
self.course = self.course_run.course
# add user in course-user-role table
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.CourseTeam, user=self.user_2
)
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.Publisher, user=self.user_3
)
self.course_run_state = factories.CourseRunStateFactory(course_run=self.course_run)
toggle_switch('enable_publisher_email_notifications', True)
def test_email_sent_by_marketing_reviewer(self):
""" Verify that email works successfully."""
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.MarketingReviewer, user=self.user
)
emails.send_email_for_mark_as_reviewed_course_run(self.course_run_state.course_run, self.user)
self.assert_email_sent(self.user_2)
def test_email_sent_by_course_team(self):
""" Verify that email works successfully."""
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.MarketingReviewer, user=self.user
)
emails.send_email_for_mark_as_reviewed_course_run(self.course_run_state.course_run, self.user_2)
self.assert_email_sent(self.user)
def test_email_mark_as_reviewed_with_error(self):
""" Verify that email failure log error message."""
with LogCapture(emails.logger.name) as l:
emails.send_email_for_mark_as_reviewed_course_run(self.course_run, self.user)
l.check(
(
emails.logger.name,
'ERROR',
'Failed to send email notifications for mark as reviewed of course-run {}'.format(
self.course_run.id
)
)
)
def test_email_sent_to_publisher(self):
""" Verify that email works successfully."""
emails.send_email_to_publisher(self.course_run_state.course_run, self.user)
self.assert_email_sent(self.user_3)
def test_email_to_publisher_with_error(self):
""" Verify that email failure log error message."""
with mock.patch('django.core.mail.message.EmailMessage.send', side_effect=TypeError):
with LogCapture(emails.logger.name) as l:
emails.send_email_to_publisher(self.course_run, self.user_3)
l.check(
(
emails.logger.name,
'ERROR',
'Failed to send email notifications for mark as reviewed of course-run {}'.format(
self.course_run.id
)
)
)
def assert_email_sent(self, to_email):
""" Verify the email data for tests cases."""
run_name = '{pacing_type}: {start_date}'.format(
pacing_type=self.course_run.get_pacing_type_display(),
start_date=self.course_run.start.strftime("%B %d, %Y")
)
subject = 'Changes to {run_name} has been marked as reviewed'.format(
run_name=run_name
)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(to_email.email, mail.outbox[0].to[0])
self.assertEqual(str(mail.outbox[0].subject), subject)
body = mail.outbox[0].body.strip()
page_path = reverse('publisher:publisher_course_run_detail', kwargs={'pk': self.course_run.id})
page_url = 'https://{host}{path}'.format(host=Site.objects.get_current().domain.strip('/'), path=page_path)
self.assertIn(page_url, body)
self.assertIn('has been marked as reviewed.', body)
...@@ -314,6 +314,16 @@ class CourseTests(TestCase): ...@@ -314,6 +314,16 @@ class CourseTests(TestCase):
self.assertEqual(self.user1, self.course2.marketing_reviewer) self.assertEqual(self.user1, self.course2.marketing_reviewer)
def test_publisher(self):
""" Verify that the publisher property returns user if exist. """
self.assertIsNone(self.course2.publisher)
factories.CourseUserRoleFactory(
course=self.course2, user=self.user1, role=PublisherUserRole.Publisher
)
self.assertEqual(self.user1, self.course2.publisher)
class SeatTests(TestCase): class SeatTests(TestCase):
""" Tests for the publisher `Seat` model. """ """ Tests for the publisher `Seat` model. """
......
...@@ -1031,9 +1031,6 @@ class CourseRunDetailTests(TestCase): ...@@ -1031,9 +1031,6 @@ class CourseRunDetailTests(TestCase):
factories.CourseUserRoleFactory( factories.CourseUserRoleFactory(
course=self.course, user=self.user, role=PublisherUserRole.CourseTeam course=self.course, user=self.user, role=PublisherUserRole.CourseTeam
) )
self.course_state.owner_role = PublisherUserRole.MarketingReviewer
self.course_state.name = CourseStateChoices.Approved
self.course_state.save()
new_user = UserFactory() new_user = UserFactory()
factories.CourseUserRoleFactory( factories.CourseUserRoleFactory(
...@@ -1063,8 +1060,6 @@ class CourseRunDetailTests(TestCase): ...@@ -1063,8 +1060,6 @@ class CourseRunDetailTests(TestCase):
factories.CourseUserRoleFactory( factories.CourseUserRoleFactory(
course=self.course, user=self.user, role=PublisherUserRole.MarketingReviewer course=self.course, user=self.user, role=PublisherUserRole.MarketingReviewer
) )
self.course_state.owner_role = PublisherUserRole.MarketingReviewer
self.course_state.save()
new_user = UserFactory() new_user = UserFactory()
factories.CourseUserRoleFactory( factories.CourseUserRoleFactory(
...@@ -1119,6 +1114,58 @@ class CourseRunDetailTests(TestCase): ...@@ -1119,6 +1114,58 @@ class CourseRunDetailTests(TestCase):
response = self.client.get(self.page_url) response = self.client.get(self.page_url)
self.assertContains(response, '<div class="parent-course-approval">') self.assertContains(response, '<div class="parent-course-approval">')
def test_course_run_mark_as_reviewed(self):
"""
Verify that user can see mark as reviewed button on course detail page.
"""
toggle_switch('publisher_approval_widget_feature', True)
self.course_run_state.name = CourseRunStateChoices.Review
self.course_run_state.save()
factories.CourseUserRoleFactory(
course=self.course, user=self.user, role=PublisherUserRole.CourseTeam
)
factories.CourseUserRoleFactory(
course=self.course, user=UserFactory(), role=PublisherUserRole.MarketingReviewer
)
response = self.client.get(self.page_url)
# Verify that content is sent for review and user can see `Mark as Reviewed` button.
self.assertContains(response, 'Send for Review')
self.assertContains(response, '<span class="icon fa fa-check" aria-hidden="true">', count=1)
self.assertContains(response, 'Mark as Reviewed')
self.assertContains(response, self.get_expected_data(CourseRunStateChoices.Approved))
def test_course_with_reviewed(self):
"""
Verify that user can see approval widget on course detail page with `Reviewed`.
"""
factories.CourseUserRoleFactory(
course=self.course, user=self.user, role=PublisherUserRole.MarketingReviewer
)
factories.CourseUserRoleFactory(
course=self.course, user=UserFactory(), role=PublisherUserRole.CourseTeam
)
# To create history objects for both `Review` and `Approved` states
self.course_run_state.name = CourseStateChoices.Review
self.course_run_state.save()
self.course_run_state.name = CourseStateChoices.Approved
self.course_run_state.owner_role = PublisherUserRole.Publisher
self.course_run_state.approved_by_role = PublisherUserRole.CourseTeam
self.course_run_state.save()
self.user.groups.add(self.organization_extension.group)
assign_perm(OrganizationExtension.VIEW_COURSE, self.organization_extension.group, self.organization_extension)
response = self.client.get(self.page_url)
# Verify that content is marked as reviewed and user can see Reviewed status.
self.assertNotContains(response, 'Mark as Reviewed')
self.assertContains(response, 'Reviewed', count=1)
self.assertContains(response, '<span class="icon fa fa-check" aria-hidden="true">', count=2)
self.assertContains(response, 'Send for Review', count=1)
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
@ddt.ddt @ddt.ddt
...@@ -1686,16 +1733,13 @@ class CourseDetailViewTests(TestCase): ...@@ -1686,16 +1733,13 @@ class CourseDetailViewTests(TestCase):
course=self.course, user=self.user, role=PublisherUserRole.MarketingReviewer course=self.course, user=self.user, role=PublisherUserRole.MarketingReviewer
) )
self.course_state.owner_role = PublisherUserRole.MarketingReviewer self.course_state.owner_role = PublisherUserRole.MarketingReviewer
self.course_state.name = CourseStateChoices.Review
self.course_state.save() self.course_state.save()
new_user = UserFactory()
factories.CourseUserRoleFactory( factories.CourseUserRoleFactory(
course=self.course, user=new_user, role=PublisherUserRole.CourseTeam course=self.course, user=UserFactory(), role=PublisherUserRole.CourseTeam
) )
self.course.course_state.name = CourseStateChoices.Review
self.course.course_state.save()
self.user.groups.add(self.organization_extension.group) self.user.groups.add(self.organization_extension.group)
assign_perm(OrganizationExtension.VIEW_COURSE, self.organization_extension.group, self.organization_extension) assign_perm(OrganizationExtension.VIEW_COURSE, self.organization_extension.group, self.organization_extension)
response = self.client.get(self.detail_page_url) response = self.client.get(self.detail_page_url)
...@@ -1723,25 +1767,24 @@ class CourseDetailViewTests(TestCase): ...@@ -1723,25 +1767,24 @@ class CourseDetailViewTests(TestCase):
factories.CourseUserRoleFactory( factories.CourseUserRoleFactory(
course=self.course, user=self.user, role=PublisherUserRole.MarketingReviewer course=self.course, user=self.user, role=PublisherUserRole.MarketingReviewer
) )
self.course_state.owner_role = PublisherUserRole.MarketingReviewer
self.course_state.save()
new_user = UserFactory()
factories.CourseUserRoleFactory( factories.CourseUserRoleFactory(
course=self.course, user=new_user, role=PublisherUserRole.CourseTeam course=self.course, user=UserFactory(), role=PublisherUserRole.CourseTeam
) )
# To create history objects for both `Review` and `Approved` states # To create history objects for both `Review` and `Approved` states
self.course.course_state.name = CourseStateChoices.Review self.course_state.name = CourseStateChoices.Review
self.course.course_state.save() self.course_state.save()
self.course.course_state.name = CourseStateChoices.Approved self.course_state.name = CourseStateChoices.Approved
self.course.course_state.save() self.course_state.owner_role = PublisherUserRole.Publisher
self.course_state.approved_by_role = PublisherUserRole.CourseTeam
self.course_state.save()
self.user.groups.add(self.organization_extension.group) self.user.groups.add(self.organization_extension.group)
assign_perm(OrganizationExtension.VIEW_COURSE, self.organization_extension.group, self.organization_extension) assign_perm(OrganizationExtension.VIEW_COURSE, self.organization_extension.group, self.organization_extension)
response = self.client.get(self.detail_page_url) response = self.client.get(self.detail_page_url)
# Verify that content is sent for review and user can see Reviewed button. # Verify that content is marked as reviewed and user can see Reviewed status.
self.assertNotContains(response, 'Mark as Reviewed') self.assertNotContains(response, 'Mark as Reviewed')
self.assertContains(response, 'Reviewed', count=1) self.assertContains(response, 'Reviewed', count=1)
self.assertContains(response, '<span class="icon fa fa-check" aria-hidden="true">', count=2) self.assertContains(response, '<span class="icon fa fa-check" aria-hidden="true">', count=2)
......
...@@ -749,18 +749,19 @@ def get_course_role_widgets_data(user, course, state_object, change_state_url): ...@@ -749,18 +749,19 @@ def get_course_role_widgets_data(user, course, state_object, change_state_url):
role_widget['button_disabled'] = True role_widget['button_disabled'] = True
if course_role.role in [PublisherUserRole.CourseTeam, PublisherUserRole.MarketingReviewer]: if course_role.role in [PublisherUserRole.CourseTeam, PublisherUserRole.MarketingReviewer]:
if state_object.owner_role != course_role.role: if state_object.name == CourseStateChoices.Approved and course_role.role == state_object.approved_by_role:
if state_object.name != CourseStateChoices.Draft: history_record = state_object.history.filter(
history_records = state_object.history.filter(
name=CourseStateChoices.Review
).order_by('-modified')
role_widget['sent_for_review'] = history_records.first() and history_records.first().modified
if state_object.name == CourseStateChoices.Approved:
history_records = state_object.history.filter(
name=CourseStateChoices.Approved name=CourseStateChoices.Approved
).order_by('-modified') ).order_by('-modified').first()
role_widget['reviewed'] = history_records.first().modified if history_record:
role_widget['reviewed'] = history_record.modified
elif state_object.name != CourseStateChoices.Draft and course_role.role != state_object.owner_role:
history_record = state_object.history.filter(
name=CourseStateChoices.Review
).order_by('-modified').first()
if history_record:
role_widget['sent_for_review'] = history_record.modified
role_widgets.append(role_widget) role_widgets.append(role_widget)
......
...@@ -7,14 +7,14 @@ msgid "" ...@@ -7,14 +7,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-20 17:52+0500\n" "POT-Creation-Date: 2017-02-21 13:08+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: apps/api/filters.py #: apps/api/filters.py
#, python-brace-format #, python-brace-format
...@@ -495,6 +495,11 @@ msgstr "" ...@@ -495,6 +495,11 @@ msgstr ""
msgid "Changes to {title} has been approved" msgid "Changes to {title} has been approved"
msgstr "" msgstr ""
#: apps/publisher/emails.py
#, python-brace-format
msgid "Changes to {run_name} has been marked as reviewed"
msgstr ""
#: apps/publisher/forms.py #: apps/publisher/forms.py
msgid "Remove Image" msgid "Remove Image"
msgstr "" msgstr ""
...@@ -2308,6 +2313,8 @@ msgstr "" ...@@ -2308,6 +2313,8 @@ msgstr ""
#: templates/publisher/email/course/mark_as_reviewed.txt #: templates/publisher/email/course/mark_as_reviewed.txt
#: templates/publisher/email/course/send_for_review.html #: templates/publisher/email/course/send_for_review.html
#: templates/publisher/email/course/send_for_review.txt #: templates/publisher/email/course/send_for_review.txt
#: templates/publisher/email/course_run/mark_as_reviewed.html
#: templates/publisher/email/course_run/mark_as_reviewed.txt
#: templates/publisher/email/course_run/send_for_review.html #: templates/publisher/email/course_run/send_for_review.html
#: templates/publisher/email/course_run/send_for_review.txt #: templates/publisher/email/course_run/send_for_review.txt
#, python-format #, python-format
...@@ -2328,6 +2335,8 @@ msgstr "" ...@@ -2328,6 +2335,8 @@ msgstr ""
#: templates/publisher/email/course/send_for_review.txt #: templates/publisher/email/course/send_for_review.txt
#: templates/publisher/email/course_created.html #: templates/publisher/email/course_created.html
#: templates/publisher/email/course_created.txt #: templates/publisher/email/course_created.txt
#: templates/publisher/email/course_run/mark_as_reviewed.html
#: templates/publisher/email/course_run/mark_as_reviewed.txt
#: templates/publisher/email/course_run/send_for_review.html #: templates/publisher/email/course_run/send_for_review.html
#: templates/publisher/email/course_run/send_for_review.txt #: templates/publisher/email/course_run/send_for_review.txt
#: templates/publisher/email/studio_instance_created.html #: templates/publisher/email/studio_instance_created.html
...@@ -2339,6 +2348,7 @@ msgstr "" ...@@ -2339,6 +2348,7 @@ msgstr ""
#: templates/publisher/email/course/mark_as_reviewed.html #: templates/publisher/email/course/mark_as_reviewed.html
#: templates/publisher/email/course/send_for_review.html #: templates/publisher/email/course/send_for_review.html
#: templates/publisher/email/course_run/mark_as_reviewed.html
#: templates/publisher/email/course_run/send_for_review.html #: templates/publisher/email/course_run/send_for_review.html
#: templates/publisher/email/studio_instance_created.html #: templates/publisher/email/studio_instance_created.html
#, python-format #, python-format
...@@ -2354,6 +2364,7 @@ msgstr "" ...@@ -2354,6 +2364,7 @@ msgstr ""
#: templates/publisher/email/course/mark_as_reviewed.txt #: templates/publisher/email/course/mark_as_reviewed.txt
#: templates/publisher/email/course/send_for_review.txt #: templates/publisher/email/course/send_for_review.txt
#: templates/publisher/email/course_run/mark_as_reviewed.txt
#: templates/publisher/email/course_run/send_for_review.txt #: templates/publisher/email/course_run/send_for_review.txt
#: templates/publisher/email/studio_instance_created.txt #: templates/publisher/email/studio_instance_created.txt
#, python-format #, python-format
...@@ -2411,6 +2422,18 @@ msgid "" ...@@ -2411,6 +2422,18 @@ msgid ""
" in Publisher on %(date)s at %(time)s." " in Publisher on %(date)s at %(time)s."
msgstr "" msgstr ""
#: templates/publisher/email/course_run/mark_as_reviewed.html
#, python-format
msgid ""
"Changes to %(link_start)s%(page_url)s%(link_middle)s %(course_name)s "
"%(link_end)s has been marked as reviewed."
msgstr ""
#: templates/publisher/email/course_run/mark_as_reviewed.txt
#, python-format
msgid "Changes to %(course_name)s has been marked as reviewed. %(page_url)s"
msgstr ""
#: templates/publisher/email/course_run/send_for_review.html #: templates/publisher/email/course_run/send_for_review.html
#, python-format #, python-format
msgid "" msgid ""
......
...@@ -7,14 +7,14 @@ msgid "" ...@@ -7,14 +7,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-20 17:52+0500\n" "POT-Creation-Date: 2017-02-21 13:08+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: static/js/catalogs-change-form.js #: static/js/catalogs-change-form.js
msgid "Preview" msgid "Preview"
......
...@@ -7,14 +7,14 @@ msgid "" ...@@ -7,14 +7,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-20 17:52+0500\n" "POT-Creation-Date: 2017-02-21 13:08+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: apps/api/filters.py #: apps/api/filters.py
...@@ -614,6 +614,13 @@ msgid "Changes to {title} has been approved" ...@@ -614,6 +614,13 @@ msgid "Changes to {title} has been approved"
msgstr "" msgstr ""
"Çhängés tö {title} häs ßéén äpprövéd Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тє#" "Çhängés tö {title} häs ßéén äpprövéd Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тє#"
#: apps/publisher/emails.py
#, python-brace-format
msgid "Changes to {run_name} has been marked as reviewed"
msgstr ""
"Çhängés tö {run_name} häs ßéén märkéd äs révïéwéd Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт "
"αмєт, ¢σηѕє¢тєтυя #"
#: apps/publisher/forms.py #: apps/publisher/forms.py
msgid "Remove Image" msgid "Remove Image"
msgstr "Rémövé Ìmägé Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#" msgstr "Rémövé Ìmägé Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
...@@ -2711,6 +2718,8 @@ msgstr "Vïéw çömmént: Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#" ...@@ -2711,6 +2718,8 @@ msgstr "Vïéw çömmént: Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#"
#: templates/publisher/email/course/mark_as_reviewed.txt #: templates/publisher/email/course/mark_as_reviewed.txt
#: templates/publisher/email/course/send_for_review.html #: templates/publisher/email/course/send_for_review.html
#: templates/publisher/email/course/send_for_review.txt #: templates/publisher/email/course/send_for_review.txt
#: templates/publisher/email/course_run/mark_as_reviewed.html
#: templates/publisher/email/course_run/mark_as_reviewed.txt
#: templates/publisher/email/course_run/send_for_review.html #: templates/publisher/email/course_run/send_for_review.html
#: templates/publisher/email/course_run/send_for_review.txt #: templates/publisher/email/course_run/send_for_review.txt
#, python-format #, python-format
...@@ -2733,6 +2742,8 @@ msgstr "" ...@@ -2733,6 +2742,8 @@ msgstr ""
#: templates/publisher/email/course/send_for_review.txt #: templates/publisher/email/course/send_for_review.txt
#: templates/publisher/email/course_created.html #: templates/publisher/email/course_created.html
#: templates/publisher/email/course_created.txt #: templates/publisher/email/course_created.txt
#: templates/publisher/email/course_run/mark_as_reviewed.html
#: templates/publisher/email/course_run/mark_as_reviewed.txt
#: templates/publisher/email/course_run/send_for_review.html #: templates/publisher/email/course_run/send_for_review.html
#: templates/publisher/email/course_run/send_for_review.txt #: templates/publisher/email/course_run/send_for_review.txt
#: templates/publisher/email/studio_instance_created.html #: templates/publisher/email/studio_instance_created.html
...@@ -2744,6 +2755,7 @@ msgstr "Thänks, Ⱡ'σяєм ιρѕυм #" ...@@ -2744,6 +2755,7 @@ msgstr "Thänks, Ⱡ'σяєм ιρѕυм #"
#: templates/publisher/email/course/mark_as_reviewed.html #: templates/publisher/email/course/mark_as_reviewed.html
#: templates/publisher/email/course/send_for_review.html #: templates/publisher/email/course/send_for_review.html
#: templates/publisher/email/course_run/mark_as_reviewed.html
#: templates/publisher/email/course_run/send_for_review.html #: templates/publisher/email/course_run/send_for_review.html
#: templates/publisher/email/studio_instance_created.html #: templates/publisher/email/studio_instance_created.html
#, python-format #, python-format
...@@ -2763,6 +2775,7 @@ msgstr "" ...@@ -2763,6 +2775,7 @@ msgstr ""
#: templates/publisher/email/course/mark_as_reviewed.txt #: templates/publisher/email/course/mark_as_reviewed.txt
#: templates/publisher/email/course/send_for_review.txt #: templates/publisher/email/course/send_for_review.txt
#: templates/publisher/email/course_run/mark_as_reviewed.txt
#: templates/publisher/email/course_run/send_for_review.txt #: templates/publisher/email/course_run/send_for_review.txt
#: templates/publisher/email/studio_instance_created.txt #: templates/publisher/email/studio_instance_created.txt
#, python-format #, python-format
...@@ -2844,6 +2857,23 @@ msgstr "" ...@@ -2844,6 +2857,23 @@ msgstr ""
" ïn Püßlïshér ön %(date)s ät %(time)s. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " " ïn Püßlïshér ön %(date)s ät %(time)s. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυя α#" "¢σηѕє¢тєтυя α#"
#: templates/publisher/email/course_run/mark_as_reviewed.html
#, python-format
msgid ""
"Changes to %(link_start)s%(page_url)s%(link_middle)s %(course_name)s "
"%(link_end)s has been marked as reviewed."
msgstr ""
"Çhängés tö %(link_start)s%(page_url)s%(link_middle)s %(course_name)s "
"%(link_end)s häs ßéén märkéd äs révïéwéd. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυя α#"
#: templates/publisher/email/course_run/mark_as_reviewed.txt
#, python-format
msgid "Changes to %(course_name)s has been marked as reviewed. %(page_url)s"
msgstr ""
"Çhängés tö %(course_name)s häs ßéén märkéd äs révïéwéd. %(page_url)s Ⱡ'σяєм "
"ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: templates/publisher/email/course_run/send_for_review.html #: templates/publisher/email/course_run/send_for_review.html
#, python-format #, python-format
msgid "" msgid ""
......
...@@ -7,14 +7,14 @@ msgid "" ...@@ -7,14 +7,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-20 17:52+0500\n" "POT-Creation-Date: 2017-02-21 13:08+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/js/catalogs-change-form.js #: static/js/catalogs-change-form.js
......
{% 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 %}
Changes to {{ link_start }}{{ page_url }}{{ link_middle }} {{ course_name }} {{ link_end }} has been marked as reviewed.
{% 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 %}
Changes to {{ course_name }} has been marked as reviewed. {{ page_url }}
{% 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