Commit de83e87f by Waheed Ahmed

Added mark as reviewed functionality for parent course.

ECOM-6347
parent a297c8e0
......@@ -425,7 +425,8 @@ class ChangeCourseStateViewTests(TestCase):
self.assertEqual(self.course_state.name, CourseStateChoices.Review)
self.assertEqual(self.course_state.owner_role, PublisherUserRole.CourseTeam)
self._assert_email_sent(course_team_user)
subject = 'Changes to {title} are ready for review'.format(title=self.course.title)
self._assert_email_sent(course_team_user, subject)
def test_change_course_state_with_course_team(self):
"""
......@@ -461,10 +462,10 @@ class ChangeCourseStateViewTests(TestCase):
self.assertEqual(self.course_state.owner_role, PublisherUserRole.MarketingReviewer)
self.assertGreater(self.course_state.owner_role_modified, old_owner_role_modified)
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._assert_email_sent(marketing_user, subject)
def _assert_email_sent(self, user, subject):
self.assertEqual(len(mail.outbox), 1)
self.assertEqual([user.email], mail.outbox[0].to)
self.assertEqual(str(mail.outbox[0].subject), subject)
......@@ -494,6 +495,37 @@ class ChangeCourseStateViewTests(TestCase):
self.assertEqual(response.data, expected)
def test_mark_as_reviewed(self):
"""
Verify that user can mark course as reviewed.
"""
self.course_state.name = CourseStateChoices.Review
self.course_state.save()
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.MarketingReviewer, user=self.user
)
course_team_user = UserFactory()
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.CourseTeam, user=course_team_user
)
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.course_state = CourseState.objects.get(course=self.course)
self.assertEqual(self.course_state.name, CourseStateChoices.Approved)
subject = 'Changes to {title} has been approved'.format(title=self.course.title)
self._assert_email_sent(course_team_user, subject)
class ChangeCourseRunStateViewTests(TestCase):
......
......@@ -8,6 +8,7 @@ from django.template.loader import get_template
from django.utils.translation import ugettext_lazy as _
from course_discovery.apps.publisher.choices import PublisherUserRole
from course_discovery.apps.publisher.utils import is_email_notification_enabled
logger = logging.getLogger(__name__)
......@@ -155,16 +156,50 @@ def send_email_for_send_for_review(course, user):
course (Object): Course object
user (Object): User object
"""
txt_template = 'publisher/email/course/send_for_review.txt'
html_template = 'publisher/email/course/send_for_review.html'
subject = _('Changes to {title} are ready for review').format(title=course.title) # pylint: disable=no-member
try:
txt_template = 'publisher/email/send_for_review.txt'
html_template = 'publisher/email/send_for_review.html'
send_course_workflow_email(course, user, subject, txt_template, html_template)
except Exception: # pylint: disable=broad-except
logger.exception('Failed to send email notifications send for review of course %s', course.id)
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
def send_email_for_mark_as_reviewed(course, user):
""" Send email when course is marked as reviewed.
Arguments:
course (Object): Course object
user (Object): User object
"""
txt_template = 'publisher/email/course/mark_as_reviewed.txt'
html_template = 'publisher/email/course/mark_as_reviewed.html'
subject = _('Changes to {title} has been approved').format(title=course.title) # pylint: disable=no-member
try:
send_course_workflow_email(course, user, subject, txt_template, html_template)
except Exception: # pylint: disable=broad-except
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):
""" Send email for course workflow state change.
Arguments:
course (Object): Course object
user (Object): User object
subject (String): Email subject
txt_template: (String): Email text template path
html_template: (String): Email html template path
"""
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
if is_email_notification_enabled(recipient_user):
partner_coordinator = course.partner_coordinator
to_addresses = [recipient_user.email]
from_address = settings.PUBLISHER_FROM_EMAIL
page_path = reverse('publisher:publisher_course_detail', kwargs={'pk': course.id})
......@@ -172,6 +207,7 @@ def send_email_for_send_for_review(course, user):
'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,
'contact_us_email': partner_coordinator.email if partner_coordinator else '',
'course_page_url': 'https://{host}{path}'.format(
host=Site.objects.get_current().domain.strip('/'), path=page_path
)
......@@ -181,12 +217,8 @@ def send_email_for_send_for_review(course, user):
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)
......@@ -23,7 +23,8 @@ 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, send_email_for_send_for_review
from course_discovery.apps.publisher.emails import (send_email_for_change_state, send_email_for_mark_as_reviewed,
send_email_for_send_for_review)
from course_discovery.apps.publisher.utils import is_email_notification_enabled
logger = logging.getLogger(__name__)
......@@ -207,6 +208,14 @@ class Course(TimeStampedModel, ChangedByMixin):
except CourseUserRole.DoesNotExist:
return None
@property
def organization_extension(self):
organization = self.organizations.all().first()
if organization:
return organization.organization_extension
return None
class CourseRun(TimeStampedModel, ChangedByMixin):
""" Publisher CourseRun model. It contains fields related to the course run intake form."""
......@@ -554,6 +563,9 @@ class CourseState(TimeStampedModel, ChangedByMixin):
elif state == CourseStateChoices.Approved:
self.approved()
if waffle.switch_is_active('enable_publisher_email_notifications'):
send_email_for_mark_as_reviewed(self.course, user)
self.save()
......
......@@ -259,3 +259,51 @@ class CourseCreatedEmailTests(TestCase):
self.assertIn('{dashboard_url}'.format(dashboard_url=reverse('publisher:publisher_dashboard')), body)
self.assertIn('Please create a Studio instance for this course', body)
self.assertIn('Thanks', body)
class SendForReviewEmailTests(TestCase):
""" Tests for the email functionality for send for review. """
def setUp(self):
super(SendForReviewEmailTests, self).setUp()
self.user = UserFactory()
self.course_state = factories.CourseStateFactory()
def test_email_with_error(self):
""" Verify that email failure log error message."""
with LogCapture(emails.logger.name) as l:
emails.send_email_for_send_for_review(self.course_state.course, self.user)
l.check(
(
emails.logger.name,
'ERROR',
'Failed to send email notifications send for review of course {}'.format(
self.course_state.course.id
)
)
)
class MarkAsReviewedEmailTests(TestCase):
""" Tests for the email functionality for mark as reviewed. """
def setUp(self):
super(MarkAsReviewedEmailTests, self).setUp()
self.user = UserFactory()
self.course_state = factories.CourseStateFactory()
def test_email_with_error(self):
""" Verify that email failure log error message."""
with LogCapture(emails.logger.name) as l:
emails.send_email_for_mark_as_reviewed(self.course_state.course, self.user)
l.check(
(
emails.logger.name,
'ERROR',
'Failed to send email notifications mark as reviewed of course {}'.format(
self.course_state.course.id
)
)
)
......@@ -672,6 +672,7 @@ class CourseRunDetailTests(TestCase):
self.user.groups.add(Group.objects.get(name=ADMIN_GROUP_NAME))
self.client.login(username=self.user.username, password=USER_PASSWORD)
self.course_run = factories.CourseRunFactory(course=self.course)
self.course_run_state = factories.CourseRunStateFactory(course_run=self.course_run)
self.organization_extension = factories.OrganizationExtensionFactory()
self.course.organizations.add(self.organization_extension.organization)
......@@ -702,6 +703,7 @@ class CourseRunDetailTests(TestCase):
available for that course-run.
"""
course_run = factories.CourseRunFactory(course=self.course)
factories.CourseRunStateFactory(course_run=course_run)
assign_perm(
OrganizationExtension.VIEW_COURSE_RUN, self.organization_extension.group, self.organization_extension
)
......@@ -888,14 +890,20 @@ class CourseRunDetailTests(TestCase):
expected_roles = []
for user_course_role in self.course.course_user_roles.all():
expected_roles.append(
{'user_course_role': user_course_role, 'heading': ROLE_WIDGET_HEADINGS.get(user_course_role.role)}
{
'course_role': user_course_role,
'heading': ROLE_WIDGET_HEADINGS.get(user_course_role.role),
'change_state_url': reverse(
'publisher:api:change_course_run_state', kwargs={'pk': self.course_run_state.id}
),
'user_list': get_internal_users()
}
)
self.assertEqual(response.context['role_widgets'], expected_roles)
self.assertEqual(list(response.context['user_list']), list(get_internal_users()))
self.assertEqual(response.context['role_widgets'], expected_roles)
def test_detail_page_role_assignment_with_non_internal_user(self):
""" Verify that user can't see change role assignment widget without permissions. """
def test_detail_page_approval_widget_with_non_internal_user(self):
""" Verify that user can see change approval widget. """
# Create a user and assign course view permission.
user = UserFactory()
......@@ -909,8 +917,8 @@ class CourseRunDetailTests(TestCase):
response = self.client.get(self.page_url)
self.assertNotIn('role_widgets', response.context)
self.assertNotIn('user_list', response.context)
self.assertIn('role_widgets', response.context)
self.assertContains(response, 'APPROVALS')
def test_details_page_with_edit_permission(self):
""" Test that user can see edit button on course run detail page. """
......@@ -1651,9 +1659,9 @@ class CourseDetailViewTests(TestCase):
# Verify that `Send for Review` button is enabled
self.assertContains(response, self.get_expected_data(CourseStateChoices.Review))
def test_course_approval_widget_with_reviewed(self):
def test_course_with_mark_as_reviewed(self):
"""
Verify that user can see approval widget on course detail page with `Reviewed`.
Verify that user can see approval widget on course detail page with `Mark as Reviewed`.
"""
factories.CourseUserRoleFactory(
course=self.course, user=self.user, role=PublisherUserRole.MarketingReviewer
......@@ -1674,7 +1682,7 @@ class CourseDetailViewTests(TestCase):
response = self.client.get(self.detail_page_url)
# Verify that content is sent for review and user can see Reviewed button.
self.assertContains(response, 'Reviewed')
self.assertContains(response, 'Mark as Reviewed')
self.assertContains(response, '<span class="icon fa fa-check" aria-hidden="true">')
self.assertContains(response, 'Send for Review')
self.assertContains(response, self.get_expected_data(CourseStateChoices.Approved))
......@@ -1689,6 +1697,37 @@ class CourseDetailViewTests(TestCase):
return expected
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
)
self.course_state.owner_role = PublisherUserRole.MarketingReviewer
self.course_state.save()
new_user = UserFactory()
factories.CourseUserRoleFactory(
course=self.course, user=new_user, role=PublisherUserRole.CourseTeam
)
# To create history objects for both `Review` and `Approved` states
self.course.course_state.name = CourseStateChoices.Review
self.course.course_state.save()
self.course.course_state.name = CourseStateChoices.Approved
self.course.course_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.detail_page_url)
# Verify that content is sent for review and user can see Reviewed button.
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)
class CourseEditViewTests(TestCase):
""" Tests for the course edit view. """
......@@ -1922,6 +1961,8 @@ class CourseRunEditViewTests(TestCase):
self.new_course = Course.objects.get(number=data['number'])
self.new_course_run = self.new_course.course_runs.first()
factories.CourseRunStateFactory(course_run=self.new_course_run)
# assert edit page is loading sucesfully.
self.edit_page_url = reverse('publisher:publisher_course_runs_edit', kwargs={'pk': self.new_course_run.id})
response = self.client.get(self.edit_page_url)
......
......@@ -43,7 +43,7 @@ ROLE_WIDGET_HEADINGS = {
STATE_BUTTONS = {
CourseStateChoices.Draft: {'text': _('Send for Review'), 'value': CourseStateChoices.Review},
CourseStateChoices.Review: {'text': _('Reviewed'), 'value': CourseStateChoices.Approved}
CourseStateChoices.Review: {'text': _('Mark as Reviewed'), 'value': CourseStateChoices.Approved}
}
......@@ -128,8 +128,12 @@ class CourseRunDetailView(mixins.LoginRequiredMixin, mixins.PublisherPermissionM
for course_role in course_roles:
role_widgets.append(
{
'user_course_role': course_role,
'heading': ROLE_WIDGET_HEADINGS.get(course_role.role)
'course_role': course_role,
'heading': ROLE_WIDGET_HEADINGS.get(course_role.role),
'change_state_url': reverse(
'publisher:api:change_course_run_state', kwargs={'pk': self.object.course_run_state.id}
),
'user_list': get_internal_users()
}
)
......@@ -145,14 +149,11 @@ class CourseRunDetailView(mixins.LoginRequiredMixin, mixins.PublisherPermissionM
context['post_back_url'] = reverse('publisher:publisher_course_run_detail', kwargs={'pk': course_run.id})
context['can_edit'] = mixins.check_course_organization_permission(
self.request.user, course_run.course, OrganizationExtension.EDIT_COURSE_RUN
user, course_run.course, OrganizationExtension.EDIT_COURSE_RUN
)
# Show role assignment widgets if user is an internal user.
if is_internal_user(user):
course_roles = course_run.course.course_user_roles.exclude(role=PublisherUserRole.CourseTeam)
context['role_widgets'] = self.get_role_widgets_data(course_roles)
context['user_list'] = get_internal_users()
course_roles = course_run.course.course_user_roles.exclude(role=PublisherUserRole.CourseTeam)
context['role_widgets'] = self.get_role_widgets_data(course_roles)
context['breadcrumbs'] = make_bread_crumbs(
[
......@@ -167,7 +168,7 @@ class CourseRunDetailView(mixins.LoginRequiredMixin, mixins.PublisherPermissionM
]
)
context['can_view_all_tabs'] = mixins.check_roles_access(self.request.user)
context['can_view_all_tabs'] = mixins.check_roles_access(user)
context['publisher_hide_features_for_pilot'] = waffle.switch_is_active('publisher_hide_features_for_pilot')
context['publisher_comment_widget_feature'] = waffle.switch_is_active('publisher_comment_widget_feature')
context['publisher_approval_widget_feature'] = waffle.switch_is_active('publisher_approval_widget_feature')
......@@ -395,9 +396,18 @@ class CourseDetailView(mixins.LoginRequiredMixin, mixins.PublisherPermissionMixi
for course_role in self.object.course_user_roles.order_by('role'):
role_widget = {
'course_role': course_role,
'heading': ROLE_WIDGET_HEADINGS.get(course_role.role)
'heading': ROLE_WIDGET_HEADINGS.get(course_role.role),
'change_state_url': reverse(
'publisher:api:change_course_state', kwargs={'pk': course_state.id}
)
}
if is_internal_user(user):
role_widget['user_list'] = get_internal_users()
if course_role.role == PublisherUserRole.CourseTeam:
role_widget['user_list'] = self.object.organization_extension.group.user_set.all()
if course_state.owner_role == course_role.role:
role_widget['ownership'] = timezone.now() - course_state.owner_role_modified
if user == course_role.user:
......@@ -407,8 +417,18 @@ class CourseDetailView(mixins.LoginRequiredMixin, mixins.PublisherPermissionMixi
role_widget['button_disabled'] = True
if course_role.role in [PublisherUserRole.CourseTeam, PublisherUserRole.MarketingReviewer]:
if course_state.owner_role != course_role.role and course_state.name == CourseStateChoices.Review:
role_widget['sent_for_review'] = course_state.modified
if course_state.owner_role != course_role.role:
if course_state.name != CourseStateChoices.Draft:
history_records = course_state.history.filter(
name=CourseStateChoices.Review
).order_by('-modified')
role_widget['sent_for_review'] = history_records.first().modified
if course_state.name == CourseStateChoices.Approved:
history_records = course_state.history.filter(
name=CourseStateChoices.Approved
).order_by('-modified')
role_widget['reviewed'] = history_records.first().modified
role_widgets.append(role_widget)
......
......@@ -498,6 +498,11 @@ msgstr ""
msgid "Changes to {title} are ready for review"
msgstr ""
#: apps/publisher/emails.py
#, python-brace-format
msgid "Changes to {title} has been approved"
msgstr ""
#: apps/publisher/forms.py
msgid "Remove Image"
msgstr ""
......@@ -747,13 +752,12 @@ msgstr ""
msgid "PUBLISHER"
msgstr ""
#: apps/publisher/views.py
#: templates/publisher/course_detail/_approval_widget.html
#: apps/publisher/views.py templates/publisher/_approval_widget.html
msgid "Send for Review"
msgstr ""
#: apps/publisher/views.py
msgid "Reviewed"
msgid "Mark as Reviewed"
msgstr ""
#: apps/publisher/views.py
......@@ -949,6 +953,26 @@ msgstr ""
msgid "Add Staff Member"
msgstr ""
#: templates/publisher/_approval_widget.html
msgid "APPROVALS"
msgstr ""
#: templates/publisher/_approval_widget.html
msgid "Reviewed"
msgstr ""
#: templates/publisher/_approval_widget.html
msgid "day in ownership"
msgstr ""
#: templates/publisher/_approval_widget.html
msgid "change owner"
msgstr ""
#: templates/publisher/_approval_widget.html
msgid "CHANGE"
msgstr ""
#: templates/publisher/_footer.html
msgid "Terms of Service"
msgstr ""
......@@ -1715,16 +1739,8 @@ msgstr ""
msgid "Course Level"
msgstr ""
#: templates/publisher/course_detail/_approval_widget.html
msgid "APPROVALS"
msgstr ""
#: templates/publisher/course_detail/_approval_widget.html
msgid "day in ownership"
msgstr ""
#: templates/publisher/course_detail/_widgets.html
#: templates/publisher/course_run_detail/_approval_widget.html
#: templates/publisher/course_run_detail/_widgets.html
msgid "EDIT"
msgstr ""
......@@ -1888,14 +1904,6 @@ msgstr ""
msgid "Course Length (Weeks)"
msgstr ""
#: templates/publisher/course_run_detail/_approval_widget.html
msgid "change owner"
msgstr ""
#: templates/publisher/course_run_detail/_approval_widget.html
msgid "CHANGE"
msgstr ""
#: templates/publisher/course_run_detail/_cat.html
#: templates/publisher/course_run_detail/_drupal.html
msgid "Course ID"
......@@ -2296,30 +2304,28 @@ msgstr ""
msgid "View comment: "
msgstr ""
#. Translators: partner_coordinator_name is a member name.
#: templates/publisher/email/course_created.html
#: templates/publisher/email/course/mark_as_reviewed.html
#: templates/publisher/email/course/mark_as_reviewed.txt
#: templates/publisher/email/course/send_for_review.html
#: templates/publisher/email/course/send_for_review.txt
#, python-format
msgid "Dear %(partner_coordinator_name)s,"
msgid "Dear %(recipient_name)s,"
msgstr ""
#: templates/publisher/email/course_created.html
#: templates/publisher/email/course/mark_as_reviewed.html
#, python-format
msgid ""
"%(course_team_name)s created the "
"%(link_start)s%(dashboard_url)s%(link_middle)s %(course_title)s %(link_end)s"
" course in Publisher on %(date)s at %(time)s."
msgstr ""
#: templates/publisher/email/course_created.html
#: templates/publisher/email/course_created.txt
msgid "Please create a Studio instance for this course."
"Changes to %(link_start)s%(course_page_url)s%(link_middle)s %(course_name)s "
"%(link_end)s has been approved."
msgstr ""
#. Translators: It's closing of mail.
#: templates/publisher/email/course/mark_as_reviewed.html
#: templates/publisher/email/course/mark_as_reviewed.txt
#: templates/publisher/email/course/send_for_review.html
#: templates/publisher/email/course/send_for_review.txt
#: 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
......@@ -2327,26 +2333,30 @@ msgstr ""
msgid "Thanks,"
msgstr ""
#: templates/publisher/email/course_created.txt
#: templates/publisher/email/studio_instance_needed.html
#: templates/publisher/email/studio_instance_needed.txt
msgid "Dear"
#: templates/publisher/email/course/mark_as_reviewed.html
#: templates/publisher/email/course/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/course_created.txt
#: templates/publisher/email/course/mark_as_reviewed.txt
#, python-format
msgid ""
"%(course_team_name)s created the %(course_title)s : %(dashboard_url)s course"
" in Publisher on %(date)s at %(time)s."
msgid "Changes to %(course_name)s has been approved. %(course_page_url)s"
msgstr ""
#: templates/publisher/email/send_for_review.html
#: templates/publisher/email/send_for_review.txt
#: templates/publisher/email/course/mark_as_reviewed.txt
#: templates/publisher/email/course/send_for_review.txt
#: templates/publisher/email/studio_instance_created.txt
#, python-format
msgid "Dear %(recipient_name)s,"
msgid ""
"Note: This email address is unable to receive replies. For questions or "
"comments, contact %(contact_us_email)s."
msgstr ""
#: templates/publisher/email/send_for_review.html
#: templates/publisher/email/course/send_for_review.html
#, python-format
msgid ""
"New changes to %(link_start)s%(course_page_url)s%(link_middle)s "
......@@ -2355,28 +2365,44 @@ msgid ""
"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
#: templates/publisher/email/course/send_for_review.txt
#, python-format
msgid ""
"<p>Note: This email address is unable to receive replies. For questions or "
"comments, contact %(contact_us_email)s.</p>"
"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 ""
#. Translators: partner_coordinator_name is a member name.
#: templates/publisher/email/course_created.html
#, python-format
msgid "Dear %(partner_coordinator_name)s,"
msgstr ""
#: templates/publisher/email/send_for_review.txt
#: templates/publisher/email/course_created.html
#, 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."
"%(course_team_name)s created the "
"%(link_start)s%(dashboard_url)s%(link_middle)s %(course_title)s %(link_end)s"
" course in Publisher on %(date)s at %(time)s."
msgstr ""
#: templates/publisher/email/send_for_review.txt
#: templates/publisher/email/studio_instance_created.txt
#: templates/publisher/email/course_created.html
#: templates/publisher/email/course_created.txt
msgid "Please create a Studio instance for this course."
msgstr ""
#: templates/publisher/email/course_created.txt
#: templates/publisher/email/studio_instance_needed.html
#: templates/publisher/email/studio_instance_needed.txt
msgid "Dear"
msgstr ""
#: templates/publisher/email/course_created.txt
#, python-format
msgid ""
"Note: This email address is unable to receive replies. For questions or "
"comments, contact %(contact_us_email)s."
"%(course_team_name)s created the %(course_title)s : %(dashboard_url)s course"
" in Publisher on %(date)s at %(time)s."
msgstr ""
#. Translators: course_team_name is course team member name.
......
......@@ -401,6 +401,7 @@
}
.btn-change-state {
@include padding(2px, 10px, 3px, 10px);
margin-top: 15px;
}
......
......@@ -10,15 +10,23 @@
<div class="layout-1q3q layout-reversed">
<div class="layout-col layout-col-a">
{% if role_widget.state_button %}
<button class="btn btn-neutral btn-change-state" data-change-state-url="{% url 'publisher:api:change_course_state' object.course_state.id %}" data-state-name="{{ role_widget.state_button.value }}"{% if role_widget.button_disabled %} disabled{% endif %} type="button">
<button class="btn btn-neutral btn-change-state" data-change-state-url="{{ role_widget.change_state_url }}" data-state-name="{{ role_widget.state_button.value }}"{% if role_widget.button_disabled %} disabled{% endif %} type="button">
{{ role_widget.state_button.text }}
</button>
{% elif role_widget.sent_for_review %}
<span class="state-status">
<span class="icon fa fa-check" aria-hidden="true"></span>
{% trans "Send for Review" %}<br>
{{ role_widget.sent_for_review|date:'m/d/y H:i a' }}
</span>
{% else %}
{% if role_widget.sent_for_review %}
<span class="state-status">
<span class="icon fa fa-check" aria-hidden="true"></span>
{% trans "Send for Review" %}<br>
{{ role_widget.sent_for_review|date:'m/d/y H:i a' }}
</span>
{% elif role_widget.reviewed %}
<span class="state-status">
<span class="icon fa fa-check" aria-hidden="true"></span>
{% trans "Reviewed" %}<br>
{{ role_widget.reviewed|date:'m/d/y H:i a' }}
</span>
{% endif %}
{% endif %}
</div>
<div class="layout-col layout-col-b">
......@@ -28,12 +36,37 @@
{% if role_widget.ownership %}
<span>{{ role_widget.ownership.days }} {% trans "day in ownership" %}</span>
{% endif %}
<div class="field-readonly 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 class="role-assignment-container">
<div id="userRoleContainer-{{ role_widget.course_role.role }}">
<span id="userFullName-{{ role_widget.course_role.role }}" class="field-readonly 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 %}
</span>
<a class="change-role-assignment" data-role="{{ role_widget.course_role.role }}" href="#">
{% trans "change owner" %}
</a>
</div>
<div class="change-role-container" id="changeRoleContainer-{{ role_widget.course_role.role }}">
<select class="select-users-by-role" id="selectUsers-{{ role_widget.course_role.role }}">
<option value="-----------">-----------</option>
{% for user in role_widget.user_list %}
<option value="{{ user.id }}">
{% if user.full_name %}
{{ user.full_name }}
{% else %}
{{ user.username }}
{% endif %}
</option>
{% endfor %}
</select>
<input type="hidden" id="roleName" value="{{ role_widget.course_role.role }}">
<button type="button" class="btn-neutral btn-change-assignment" data-role="{{ role_widget.course_role.role }}" data-api-endpoint="{% url 'publisher:api:course_role_assignments' role_widget.course_role.id %}">
{% trans "CHANGE" %}
</button>
</div>
</div>
</div>
</div>
......
......@@ -132,6 +132,7 @@
{% endblock %}
{% block extra_js %}
<script src="{% static 'bower_components/google-diff-match-patch/diff_match_patch.js' %}"></script>
<script src="{% static 'js/publisher/views/course_detail.js' %}"></script>
<script src="{% static 'js/publisher/publisher.js' %}"></script>
<script src="{% static 'js/publisher/comparing-objects.js' %}"></script>
<script src="{% static 'js/publisher/jquery-dateFormat.min.js' %}"></script>
......
......@@ -43,6 +43,6 @@
{% include 'publisher/_history_widget.html' %}
</div>
<div class="approval-widget {% if not publisher_approval_widget_feature %}hidden{% endif %}">
{% include 'publisher/course_detail/_approval_widget.html' %}
{% include 'publisher/_approval_widget.html' %}
</div>
</div>
......@@ -68,7 +68,7 @@
<aside id="right-panel" class="layout-col layout-col-a layout-col-a-custom">
<div id="approval-widget" class="approval-widget {% if not publisher_approval_widget_feature %}hidden{% endif %}">
{% include 'publisher/course_run_detail/_approval_widget.html' %}
{% include 'publisher/course_run_detail/_widgets.html' %}
</div>
<div id="comments-widget" class="comment-container {% if not publisher_comment_widget_feature %}hidden{% endif %}">
{% include 'comments/add_auth_comments.html' %}
......
{% load i18n %}
<div class="approval-widget">
{% if can_edit %}
<a href="{% url 'publisher:publisher_course_runs_edit' pk=object.id %}" class="btn btn-neutral btn-courserun-edit">
{% trans "EDIT" %}
</a>
<div class="clearfix"></div>
{% endif %}
{% for role_widget in role_widgets %}
<div class="role-widget">
<span class="role-heading">
<strong>{{ role_widget.heading }}</strong>
</span>
<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">
{% 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" %}
</a>
</div>
<div class="change-role-container" id="changeRoleContainer-{{ role_widget.user_course_role.role }}">
<select class="select-users-by-role" id="selectUsers-{{ role_widget.user_course_role.role }}">
{% for user in user_list %}
<option {% if role_widget.user_course_role.user == user%}selected="selected"{% endif %} value="{{ user.id }}">
{{ user.full_name }}
</option>
{% endfor %}
</select>
<input type="hidden" id="roleName" value="{{ role_widget.user_course_role.role }}">
<button type="button" class="btn-neutral btn-change-assignment" data-role="{{ role_widget.user_course_role.role }}" data-api-endpoint="{% url 'publisher:api:course_role_assignments' role_widget.user_course_role.id %}">
{% trans "CHANGE" %}
</button>
</div>
</div>
</div>
<hr>
{% endfor %}
<div class="actions">
<form action="{% url 'publisher:publisher_change_state' pk=object.id %}" method="post"> {% csrf_token %}
<button type="submit" value="{{ object.change_state_button.value }}" class="btn-brand btn-small btn-states" name="state">{{ object.change_state_button.text }}</button>
</form>
</div>
</div>
{% load i18n %}
<div class="course-widgets">
{% if can_edit %}
<a href="{% url 'publisher:publisher_course_runs_edit' pk=object.id %}" class="btn btn-neutral btn-courserun-edit">
{% trans "EDIT" %}
</a>
<div class="clearfix"></div>
{% endif %}
<div class="approval-widget {% if not publisher_approval_widget_feature %}hidden{% endif %}">
{% include 'publisher/_approval_widget.html' %}
</div>
</div>
{% 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 }}{{ course_page_url }}{{ link_middle }} {{ course_name }} {{ link_end }} has been approved.
{% 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 approved. {{ course_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