Commit 7479639f by Waheed Ahmed

Preview widget approval.

ECOM-7168
parent 5cd58ec7
"""Publisher API Serializers"""
import waffle
from django.apps import apps
from django.db import transaction
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django_fsm import TransitionNotAllowed
from opaque_keys import InvalidKeyError
......@@ -8,7 +10,8 @@ from opaque_keys.edx.keys import CourseKey
from rest_framework import serializers
from course_discovery.apps.core.models import User
from course_discovery.apps.publisher.emails import send_email_for_studio_instance_created
from course_discovery.apps.publisher.choices import PublisherUserRole
from course_discovery.apps.publisher.emails import send_email_for_studio_instance_created, send_email_preview_accepted
from course_discovery.apps.publisher.models import CourseRun, CourseRunState, CourseState, CourseUserRole
......@@ -144,26 +147,39 @@ class CourseRunStateSerializer(serializers.ModelSerializer):
class Meta:
model = CourseRunState
fields = ('name', 'approved_by_role', 'owner_role', 'course_run',)
fields = ('name', 'approved_by_role', 'owner_role', 'course_run', 'preview_accepted',)
extra_kwargs = {
'course_run': {'read_only': True},
'approved_by_role': {'read_only': True},
'owner_role': {'read_only': True}
}
@transaction.atomic
def update(self, instance, validated_data):
state = validated_data.get('name')
request = self.context.get('request')
try:
instance.change_state(state=state, user=request.user)
except TransitionNotAllowed:
# pylint: disable=no-member
raise serializers.ValidationError(
{
'name': _('Cannot switch from state `{state}` to `{target_state}`').format(
state=instance.name, target_state=state
)
}
)
state = validated_data.get('name')
preview_accepted = validated_data.get('preview_accepted')
if state:
try:
instance.change_state(state=state, user=request.user)
except TransitionNotAllowed:
# pylint: disable=no-member
raise serializers.ValidationError(
{
'name': _('Cannot switch from state `{state}` to `{target_state}`').format(
state=instance.name, target_state=state
)
}
)
elif preview_accepted:
instance.preview_accepted = True
instance.owner_role = PublisherUserRole.Publisher
instance.owner_role_modified = timezone.now()
instance.save()
if waffle.switch_is_active('enable_publisher_email_notifications'):
send_email_preview_accepted(instance.course_run)
return instance
......@@ -10,7 +10,7 @@ from course_discovery.apps.publisher.api.serializers import (CourseRevisionSeria
CourseStateSerializer, CourseUserRoleSerializer,
GroupUserSerializer, UpdateCourseKeySerializer)
from course_discovery.apps.publisher.choices import CourseRunStateChoices, CourseStateChoices, PublisherUserRole
from course_discovery.apps.publisher.models import CourseRunState, CourseState, Seat
from course_discovery.apps.publisher.models import CourseState, Seat
from course_discovery.apps.publisher.tests.factories import (CourseFactory, CourseRunFactory, CourseRunStateFactory,
CourseStateFactory, CourseUserRoleFactory,
OrganizationExtensionFactory, SeatFactory)
......@@ -210,7 +210,7 @@ class CourseRunStateSerializerTests(TestCase):
def test_update(self):
"""
Verify that we can update course-run workflow state with serializer.
Verify that we can update course-run workflow state name and preview_accepted with serializer.
"""
CourseUserRoleFactory(
course=self.course_run.course, role=PublisherUserRole.CourseTeam, user=self.user
......@@ -221,9 +221,13 @@ class CourseRunStateSerializerTests(TestCase):
data = {'name': CourseRunStateChoices.Review}
serializer.update(self.run_state, data)
self.run_state = CourseRunState.objects.get(course_run=self.course_run)
self.assertEqual(self.run_state.name, CourseRunStateChoices.Review)
self.assertFalse(self.run_state.preview_accepted)
serializer.update(self.run_state, {'preview_accepted': True})
self.assertTrue(self.run_state.preview_accepted)
def test_update_with_error(self):
"""
Verify that serializer raises `ValidationError` with wrong transition.
......
......@@ -640,3 +640,35 @@ class ChangeCourseRunStateViewTests(TestCase):
self.assertEqual(self.run_state.owner_role, PublisherUserRole.Publisher)
self.assertEqual(len(mail.outbox), 2)
def test_preview_accepted(self):
"""
Verify that user can accept preview for course run and owner role will be changed to `Publisher`.
"""
course = self.course_run.course
self.run_state.name = CourseRunStateChoices.Approved
self.run_state.owner_role = PublisherUserRole.CourseTeam
self.run_state.save()
self._assign_role(course, PublisherUserRole.CourseTeam, self.user)
self._assign_role(course, PublisherUserRole.PartnerCoordinator, UserFactory())
self._assign_role(course, PublisherUserRole.Publisher, UserFactory())
self.assertFalse(self.run_state.preview_accepted)
response = self.client.patch(
self.change_state_url,
data=json.dumps({'preview_accepted': True}),
content_type=JSON_CONTENT_TYPE
)
self.assertEqual(response.status_code, 200)
self.run_state = CourseRunState.objects.get(course_run=self.course_run)
self.assertTrue(self.run_state.preview_accepted)
self.assertEqual(self.run_state.owner_role, PublisherUserRole.Publisher)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual([course.publisher.email, course.partner_coordinator.email], mail.outbox[0].bcc)
......@@ -320,3 +320,48 @@ def send_email_to_publisher(course_run, user):
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)
def send_email_preview_accepted(course_run):
""" Send email for preview approved to publisher and partner coordinator.
Arguments:
course_run (Object): CourseRun object
"""
txt_template = 'publisher/email/course_run/preview_accepted.txt'
html_template = 'publisher/email/course_run/preview_accepted.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 = _('Preview for {run_name} has been approved').format(run_name=run_name) # pylint: disable=no-member
publisher_user = course_run.course.publisher
try:
if is_email_notification_enabled(publisher_user):
partner_coordinator = course_run.course.partner_coordinator
to_addresses = [publisher_user.email]
if is_email_notification_enabled(partner_coordinator):
to_addresses.append(partner_coordinator.email)
from_address = settings.PUBLISHER_FROM_EMAIL
page_path = reverse('publisher:publisher_course_run_detail', kwargs={'pk': course_run.id})
context = {
'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=[from_address], bcc=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 preview approved of course-run %s', course_run.id)
# -*- coding: utf-8 -*-
# Generated by Django 1.9.12 on 2017-02-21 11:50
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('publisher', '0036_auto_20170216_0946'),
]
operations = [
migrations.AddField(
model_name='courserunstate',
name='preview_accepted',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='historicalcourserunstate',
name='preview_accepted',
field=models.BooleanField(default=False),
),
]
......@@ -633,6 +633,7 @@ class CourseRunState(TimeStampedModel, ChangedByMixin):
owner_role = models.CharField(max_length=63, choices=PublisherUserRole.choices)
course_run = models.OneToOneField(CourseRun, related_name='course_run_state')
owner_role_modified = models.DateTimeField(auto_now_add=True, null=True, blank=True)
preview_accepted = models.BooleanField(default=False)
history = HistoricalRecords()
......@@ -708,6 +709,10 @@ class CourseRunState(TimeStampedModel, ChangedByMixin):
self.save()
@property
def is_preview_accepted(self):
return self.preview_accepted
class PublisherUser(User):
""" Publisher User Proxy Model. """
......
......@@ -471,3 +471,66 @@ class CourseRunMarkAsReviewedEmailTests(TestCase):
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)
class CourseRunPreviewEmailTests(TestCase):
"""
Tests email functionality of course preview.
"""
def setUp(self):
super(CourseRunPreviewEmailTests, self).setUp()
self.user = UserFactory()
self.run_state = factories.CourseRunStateFactory()
self.course = self.run_state.course_run.course
# add users in CourseUserRole table
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.CourseTeam, user=self.user
)
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.Publisher, user=UserFactory()
)
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.PartnerCoordinator, user=UserFactory()
)
toggle_switch('enable_publisher_email_notifications', True)
def test_preview_accepted_email(self):
"""
Verify that preview accepted email functionality works fine.
"""
emails.send_email_preview_accepted(self.run_state.course_run)
run_name = '{pacing_type}: {start_date}'.format(
pacing_type=self.run_state.course_run.get_pacing_type_display(),
start_date=self.run_state.course_run.start.strftime("%B %d, %Y")
)
subject = 'Preview for {run_name} has been approved'.format(
run_name=run_name
)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual([self.course.publisher.email, self.course.partner_coordinator.email], mail.outbox[0].bcc)
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.run_state.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 beed approved by course team.', body)
def test_preview_accepted_email_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_preview_accepted(self.run_state.course_run)
l.check(
(
emails.logger.name,
'ERROR',
'Failed to send email notifications for preview approved of course-run {}'.format(
self.run_state.course_run.id
)
)
)
......@@ -657,3 +657,12 @@ class CourseRunStateTests(TestCase):
self.course_run.transcript_languages = []
self.course_run.save()
self.assertFalse(self.course_run_state.can_send_for_review())
def test_preview_accepted(self):
"""
Verify that property is_preview_accepted return Boolean.
"""
self.assertFalse(self.course_run_state.is_preview_accepted)
self.course_run_state.preview_accepted = True
self.course_run_state.save()
self.assertTrue(self.course_run_state.is_preview_accepted)
......@@ -1166,6 +1166,38 @@ class CourseRunDetailTests(TestCase):
self.assertContains(response, '<span class="icon fa fa-check" aria-hidden="true">', count=2)
self.assertContains(response, 'Send for Review', count=1)
def test_preview_widget(self):
"""
Verify that user can see preview widget on course detail page.
"""
factories.CourseUserRoleFactory(
course=self.course, user=self.user, role=PublisherUserRole.CourseTeam
)
self.course_run_state.name = CourseStateChoices.Approved
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)
self.assertContains(response, 'COURSE PREVIEW')
self.assertContains(response, '<button class="btn btn-neutral btn-preview btn-preview-accept" type="button">')
self.assertContains(response, '<button class="btn btn-neutral btn-preview btn-preview-decline" type="button">')
self.course_run_state.preview_accepted = True
self.course_run_state.owner_role = PublisherUserRole.Publisher
self.course_run_state.save()
response = self.client.get(self.page_url)
self.assertNotContains(
response, '<button class="btn btn-neutral btn-preview btn-preview-accept" type="button">'
)
self.assertNotContains(
response, '<button class="btn btn-neutral btn-preview btn-preview-decline" type="button">'
)
self.assertContains(response, 'Approved')
# pylint: disable=attribute-defined-outside-init
@ddt.ddt
......
......@@ -38,7 +38,7 @@ ROLE_WIDGET_HEADINGS = {
PublisherUserRole.PartnerCoordinator: _('PARTNER COORDINATOR'),
PublisherUserRole.MarketingReviewer: _('MARKETING'),
PublisherUserRole.Publisher: _('PUBLISHER'),
PublisherUserRole.CourseTeam: _('Course Team')
PublisherUserRole.CourseTeam: _('COURSE TEAM')
}
STATE_BUTTONS = {
......@@ -137,6 +137,11 @@ class CourseRunDetailView(mixins.LoginRequiredMixin, mixins.PublisherPermissionM
context['role_widgets'] = get_course_role_widgets_data(
user, course_run.course, course_run.course_run_state, 'publisher:api:change_course_run_state'
)
course_run_state = course_run.course_run_state
if course_run_state.preview_accepted:
history_object = course_run_state.history.filter(preview_accepted=True).order_by('-modified').first()
if history_object:
context['preview_accepted_date'] = history_object.modified
context['breadcrumbs'] = make_bread_crumbs(
[
......@@ -756,7 +761,7 @@ def get_course_role_widgets_data(user, course, state_object, change_state_url):
if history_record:
role_widget['reviewed'] = history_record.modified
elif state_object.name != CourseStateChoices.Draft and course_role.role != state_object.owner_role:
elif state_object.name != CourseStateChoices.Draft:
history_record = state_object.history.filter(
name=CourseStateChoices.Review
).order_by('-modified').first()
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-21 13:08+0500\n"
"POT-Creation-Date: 2017-02-22 12:44+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"
......@@ -450,7 +450,7 @@ msgstr ""
msgid "Publisher"
msgstr ""
#: apps/publisher/choices.py apps/publisher/views.py
#: apps/publisher/choices.py
msgid "Course Team"
msgstr ""
......@@ -462,7 +462,9 @@ msgstr ""
msgid "Review"
msgstr ""
#: apps/publisher/choices.py templates/publisher/dashboard/_preview_ready.html
#: apps/publisher/choices.py
#: templates/publisher/course_run_detail/_widgets.html
#: templates/publisher/dashboard/_preview_ready.html
msgid "Approved"
msgstr ""
......@@ -500,6 +502,11 @@ msgstr ""
msgid "Changes to {run_name} has been marked as reviewed"
msgstr ""
#: apps/publisher/emails.py
#, python-brace-format
msgid "Preview for {run_name} has been approved"
msgstr ""
#: apps/publisher/forms.py
msgid "Remove Image"
msgstr ""
......@@ -573,6 +580,7 @@ msgid "instructor"
msgstr ""
#: apps/publisher/forms.py
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "Yes"
msgstr ""
......@@ -749,6 +757,10 @@ msgstr ""
msgid "PUBLISHER"
msgstr ""
#: apps/publisher/views.py
msgid "COURSE TEAM"
msgstr ""
#: apps/publisher/views.py templates/publisher/_approval_widget.html
msgid "Send for Review"
msgstr ""
......@@ -875,6 +887,7 @@ msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/add_update_course_form.html
#: templates/publisher/course_edit_form.html
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "Cancel"
msgstr ""
......@@ -2018,6 +2031,28 @@ msgstr ""
msgid "Seats"
msgstr ""
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "Publish Course Run"
msgstr ""
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid ""
"Before this course run can be published, the Studio instance for the course "
"run must have the following information."
msgstr ""
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "The course run start and end date."
msgstr ""
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "The course card image."
msgstr ""
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "Names, titles, and signature images for all certificate signatories."
msgstr ""
#: templates/publisher/course_run_detail/_salesforce.html
msgid "Course"
msgstr ""
......@@ -2127,6 +2162,31 @@ msgstr ""
msgid "Pacing Type"
msgstr ""
#: templates/publisher/course_run_detail/_widgets.html
msgid "Decline"
msgstr ""
#: templates/publisher/course_run_detail/_widgets.html
msgid "Accept"
msgstr ""
#: templates/publisher/course_run_detail/_widgets.html
msgid "COURSE PREVIEW"
msgstr ""
#: templates/publisher/course_run_detail/_widgets.html
#: templates/publisher/dashboard/_preview_ready.html
msgid "Preview URL"
msgstr ""
#: templates/publisher/course_run_detail/_widgets.html
msgid "View course preview live"
msgstr ""
#: templates/publisher/course_run_detail/_widgets.html
msgid "Not available"
msgstr ""
#: templates/publisher/course_runs_list.html
msgid "Course Run List"
msgstr ""
......@@ -2224,10 +2284,6 @@ msgid ""
"message when the course run has been published."
msgstr ""
#: templates/publisher/dashboard/_preview_ready.html
msgid "Preview URL"
msgstr ""
#: templates/publisher/dashboard/_published.html
msgid "Looks like you haven't published any course yet"
msgstr ""
......@@ -2286,6 +2342,8 @@ msgstr ""
#: templates/publisher/email/comment.txt
#: templates/publisher/email/course_created.html
#: templates/publisher/email/course_created.txt
#: templates/publisher/email/course_run/preview_accepted.html
#: templates/publisher/email/course_run/preview_accepted.txt
msgid "The edX team"
msgstr ""
......@@ -2337,6 +2395,8 @@ msgstr ""
#: 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/preview_accepted.html
#: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/send_for_review.html
#: templates/publisher/email/course_run/send_for_review.txt
#: templates/publisher/email/studio_instance_created.html
......@@ -2349,6 +2409,7 @@ msgstr ""
#: templates/publisher/email/course/mark_as_reviewed.html
#: templates/publisher/email/course/send_for_review.html
#: templates/publisher/email/course_run/mark_as_reviewed.html
#: templates/publisher/email/course_run/preview_accepted.html
#: templates/publisher/email/course_run/send_for_review.html
#: templates/publisher/email/studio_instance_created.html
#, python-format
......@@ -2365,6 +2426,7 @@ msgstr ""
#: templates/publisher/email/course/mark_as_reviewed.txt
#: templates/publisher/email/course/send_for_review.txt
#: templates/publisher/email/course_run/mark_as_reviewed.txt
#: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/send_for_review.txt
#: templates/publisher/email/studio_instance_created.txt
#, python-format
......@@ -2434,6 +2496,24 @@ msgstr ""
msgid "Changes to %(course_name)s has been marked as reviewed. %(page_url)s"
msgstr ""
#: templates/publisher/email/course_run/preview_accepted.html
#: templates/publisher/email/course_run/preview_accepted.txt
msgid "Dear member,"
msgstr ""
#: templates/publisher/email/course_run/preview_accepted.html
#, python-format
msgid ""
"Preview for %(link_start)s%(page_url)s%(link_middle)s %(course_name)s "
"%(link_end)s has beed approved by course team."
msgstr ""
#: templates/publisher/email/course_run/preview_accepted.txt
#, python-format
msgid ""
"Preview for %(course_name)s has beed approved by course team. %(page_url)s"
msgstr ""
#: templates/publisher/email/course_run/send_for_review.html
#, python-format
msgid ""
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-21 13:08+0500\n"
"POT-Creation-Date: 2017-02-22 12:44+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-21 13:08+0500\n"
"POT-Creation-Date: 2017-02-22 12:44+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"
......@@ -562,7 +562,7 @@ msgstr "Märkétïng Révïéwér Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α
msgid "Publisher"
msgstr "Püßlïshér Ⱡ'σяєм ιρѕυм ∂σł#"
#: apps/publisher/choices.py apps/publisher/views.py
#: apps/publisher/choices.py
msgid "Course Team"
msgstr "Çöürsé Téäm Ⱡ'σяєм ιρѕυм ∂σłσя #"
......@@ -574,7 +574,9 @@ msgstr "Dräft Ⱡ'σяєм ιρѕ#"
msgid "Review"
msgstr "Révïéw Ⱡ'σяєм ιρѕυ#"
#: apps/publisher/choices.py templates/publisher/dashboard/_preview_ready.html
#: apps/publisher/choices.py
#: templates/publisher/course_run_detail/_widgets.html
#: templates/publisher/dashboard/_preview_ready.html
msgid "Approved"
msgstr "Àpprövéd Ⱡ'σяєм ιρѕυм ∂#"
......@@ -621,6 +623,13 @@ msgstr ""
"Çhängés tö {run_name} häs ßéén märkéd äs révïéwéd Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт "
"αмєт, ¢σηѕє¢тєтυя #"
#: apps/publisher/emails.py
#, python-brace-format
msgid "Preview for {run_name} has been approved"
msgstr ""
"Prévïéw för {run_name} häs ßéén äpprövéd Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тє#"
#: apps/publisher/forms.py
msgid "Remove Image"
msgstr "Rémövé Ìmägé Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
......@@ -694,6 +703,7 @@ msgid "instructor"
msgstr "ïnstrüçtör Ⱡ'σяєм ιρѕυм ∂σłσ#"
#: apps/publisher/forms.py
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "Yes"
msgstr "Ýés Ⱡ'σяєм#"
......@@ -884,6 +894,10 @@ msgstr "MÀRKÉTÌNG Ⱡ'σяєм ιρѕυм ∂σł#"
msgid "PUBLISHER"
msgstr "PÛBLÌSHÉR Ⱡ'σяєм ιρѕυм ∂σł#"
#: apps/publisher/views.py
msgid "COURSE TEAM"
msgstr "ÇÖÛRSÉ TÉÀM Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: apps/publisher/views.py templates/publisher/_approval_widget.html
msgid "Send for Review"
msgstr "Sénd för Révïéw Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α#"
......@@ -1031,6 +1045,7 @@ msgstr "Sävé Ⱡ'σяєм ι#"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/add_update_course_form.html
#: templates/publisher/course_edit_form.html
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "Cancel"
msgstr "Çänçél Ⱡ'σяєм ιρѕυ#"
......@@ -2393,6 +2408,33 @@ msgstr "Énröllménts Ⱡ'σяєм ιρѕυм ∂σłσя #"
msgid "Seats"
msgstr "Séäts Ⱡ'σяєм ιρѕ#"
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "Publish Course Run"
msgstr "Püßlïsh Çöürsé Rün Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт#"
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid ""
"Before this course run can be published, the Studio instance for the course "
"run must have the following information."
msgstr ""
"Béföré thïs çöürsé rün çän ßé püßlïshéd, thé Stüdïö ïnstänçé för thé çöürsé "
"rün müst hävé thé föllöwïng ïnförmätïön. Ⱡ'σяєм ιρѕυм ∂σ#"
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "The course run start and end date."
msgstr ""
"Thé çöürsé rün stärt änd énd däté. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєт#"
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "The course card image."
msgstr "Thé çöürsé çärd ïmägé. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢#"
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "Names, titles, and signature images for all certificate signatories."
msgstr ""
"Nämés, tïtlés, änd sïgnätüré ïmägés för äll çértïfïçäté sïgnätörïés. Ⱡ'σяєм "
"ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя #"
#: templates/publisher/course_run_detail/_salesforce.html
msgid "Course"
msgstr "Çöürsé Ⱡ'σяєм ιρѕυ#"
......@@ -2504,6 +2546,31 @@ msgstr "Énröllmént Énd Däté Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α
msgid "Pacing Type"
msgstr "Päçïng Týpé Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: templates/publisher/course_run_detail/_widgets.html
msgid "Decline"
msgstr "Déçlïné Ⱡ'σяєм ιρѕυм #"
#: templates/publisher/course_run_detail/_widgets.html
msgid "Accept"
msgstr "Àççépt Ⱡ'σяєм ιρѕυ#"
#: templates/publisher/course_run_detail/_widgets.html
msgid "COURSE PREVIEW"
msgstr "ÇÖÛRSÉ PRÉVÌÉW Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#"
#: templates/publisher/course_run_detail/_widgets.html
#: templates/publisher/dashboard/_preview_ready.html
msgid "Preview URL"
msgstr "Prévïéw ÛRL Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: templates/publisher/course_run_detail/_widgets.html
msgid "View course preview live"
msgstr "Vïéw çöürsé prévïéw lïvé Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢ση#"
#: templates/publisher/course_run_detail/_widgets.html
msgid "Not available"
msgstr "Nöt äväïläßlé Ⱡ'σяєм ιρѕυм ∂σłσя ѕι#"
#: templates/publisher/course_runs_list.html
msgid "Course Run List"
msgstr "Çöürsé Rün Lïst Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α#"
......@@ -2617,10 +2684,6 @@ msgstr ""
" ∂σłσяє мαgηα αłιqυα. υт єηιм α∂ мιηιм νєηιαм, qυιѕ ησѕтяυ∂ єχєя¢ιтαтιση "
"υłłαм¢σ łαвσяιѕ ηιѕι υт αłιqυιρ єχ єα ¢σммσ∂σ ¢σηѕєqυαт. ∂υιѕ αυтє#"
#: templates/publisher/dashboard/_preview_ready.html
msgid "Preview URL"
msgstr "Prévïéw ÛRL Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: templates/publisher/dashboard/_published.html
msgid "Looks like you haven't published any course yet"
msgstr ""
......@@ -2689,6 +2752,8 @@ msgstr "Vïéw Çöürsé Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: templates/publisher/email/comment.txt
#: templates/publisher/email/course_created.html
#: templates/publisher/email/course_created.txt
#: templates/publisher/email/course_run/preview_accepted.html
#: templates/publisher/email/course_run/preview_accepted.txt
msgid "The edX team"
msgstr "Thé édX téäm Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
......@@ -2744,6 +2809,8 @@ msgstr ""
#: 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/preview_accepted.html
#: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/send_for_review.html
#: templates/publisher/email/course_run/send_for_review.txt
#: templates/publisher/email/studio_instance_created.html
......@@ -2756,6 +2823,7 @@ msgstr "Thänks, Ⱡ'σяєм ιρѕυм #"
#: templates/publisher/email/course/mark_as_reviewed.html
#: templates/publisher/email/course/send_for_review.html
#: templates/publisher/email/course_run/mark_as_reviewed.html
#: templates/publisher/email/course_run/preview_accepted.html
#: templates/publisher/email/course_run/send_for_review.html
#: templates/publisher/email/studio_instance_created.html
#, python-format
......@@ -2776,6 +2844,7 @@ msgstr ""
#: templates/publisher/email/course/mark_as_reviewed.txt
#: templates/publisher/email/course/send_for_review.txt
#: templates/publisher/email/course_run/mark_as_reviewed.txt
#: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/send_for_review.txt
#: templates/publisher/email/studio_instance_created.txt
#, python-format
......@@ -2874,6 +2943,29 @@ 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/preview_accepted.html
#: templates/publisher/email/course_run/preview_accepted.txt
msgid "Dear member,"
msgstr "Déär mémßér, Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
#: templates/publisher/email/course_run/preview_accepted.html
#, python-format
msgid ""
"Preview for %(link_start)s%(page_url)s%(link_middle)s %(course_name)s "
"%(link_end)s has beed approved by course team."
msgstr ""
"Prévïéw för %(link_start)s%(page_url)s%(link_middle)s %(course_name)s "
"%(link_end)s häs ßééd äpprövéd ßý çöürsé téäm. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυя α#"
#: templates/publisher/email/course_run/preview_accepted.txt
#, python-format
msgid ""
"Preview for %(course_name)s has beed approved by course team. %(page_url)s"
msgstr ""
"Prévïéw för %(course_name)s häs ßééd äpprövéd ßý çöürsé téäm. %(page_url)s "
"Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: templates/publisher/email/course_run/send_for_review.html
#, python-format
msgid ""
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-21 13:08+0500\n"
"POT-Creation-Date: 2017-02-22 12:44+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"
......
......@@ -49,13 +49,13 @@ $(document).ready(function(){
$('body').addClass('stopScroll');
});
$(document).click(function(e){
var modal = $('#addInstructorModal');
var modal = $('.modal');
if (event.target == modal[0]) {
closeModal(e, modal);
}
});
$('.closeModal').click(function (e) {
closeModal(e, $('#addInstructorModal'));
closeModal(e, $('.modal'));
});
$('#add-instructor-btn').click(function (e) {
......@@ -147,6 +147,28 @@ $(document).ready(function(){
}
});
});
$('.btn-preview-accept').click(function(e){
$('#acceptPreviewModal').show();
$('body').addClass('stopScroll');
});
$('.btn-accept').click(function (e) {
$.ajax({
type: "PATCH",
url: $(this).data('url'),
data: JSON.stringify({preview_accepted: true}),
contentType: "application/json",
success: function (response) {
location.reload();
},
error: function (response) {
addModalError(gettext("Something went wrong!"));
console.log(response);
}
});
});
});
$(document).on('change', '#id_organization', function (e) {
......
......@@ -43,3 +43,11 @@
border-color: #169bd5;
border-radius: 5px;
}
.btn-accept {
@include padding-right(30px);
@include padding-left(30px);
background-color: #169BD5;
border-color: #169BD5;
border-radius: 5px;
}
......@@ -340,7 +340,7 @@
.approval-widget, .course-widgets {
.btn-course-edit, .btn-courserun-edit, .btn-change-state {
.btn-course-edit, .btn-courserun-edit, .btn-change-state, .btn-preview {
@include padding(2px, 20px, 3px, 20px);
@include float(right);
font-weight: 400;
......@@ -360,11 +360,12 @@
}
}
.role-heading {
.role-heading, .preview-heading {
color: #999999;
}
.role-assignment-container {
.role-assignment-container, .preview-container {
@include margin-right(5px);
.state-status {
@include float(right);
......@@ -400,11 +401,15 @@
}
}
.btn-change-state {
.btn-change-state, .btn-preview {
@include padding(2px, 10px, 3px, 10px);
margin-top: 15px;
}
.btn-preview-accept {
@include margin-right(10px);
}
}//approval-widget (END)
......
......@@ -7,7 +7,7 @@
{% if is_course_run and not object.course.course_state.is_approved %}
<div class="parent-course-approval">
{% url 'publisher:publisher_course_detail' object.id as course_url %}
{% url 'publisher:publisher_course_detail' object.course.id as course_url %}
{% with link_start='<a href="' link_middle='">' link_end='</a>' %}
{% blocktrans trimmed %}
The {{ link_start }}{{ course_url }}{{ link_middle }} parent course {{ link_end }} for this course run has changes that must be approved before you can submit this course run for approval. You can save changes to this course run.
......
{% load i18n %}
<div id="acceptPreviewModal" class="modal">
<div class="modal-content">
<h2 class="hd-2 emphasized accept-preview-heading">{% trans "Publish Course Run" %}</h2>
<div id="modal-errors" class="alert-messages"></div>
<p>
{% trans "Before this course run can be published, the Studio instance for the course run must have the following information." %}
</p>
<ul>
<li>{% trans "The course run start and end date." %}</li>
<li>{% trans "The course card image." %}</li>
<li>{% trans "Names, titles, and signature images for all certificate signatories." %}</li>
</ul>
<div class="actions">
<a class="btn-cancel closeModal" href="#">{% trans "Cancel" %}</a>
<button class="btn-brand btn-base btn-accept" type="button" data-url="{% url 'publisher:api:change_course_run_state' object.course_run_state.id %}">{% trans "Yes" %}</button>
</div>
</div>
</div>
......@@ -8,6 +8,45 @@
<div class="clearfix"></div>
{% endif %}
<div class="approval-widget {% if not publisher_approval_widget_feature %}hidden{% endif %}">
{% include 'publisher/_approval_widget.html' %}
{% include 'publisher/_approval_widget.html' %}
<div class="preview-widget">
<div class="preview-container">
<div class="layout-1q3q layout-reversed">
<div class="layout-col layout-col-a">
{% if object.preview_url and object.course.course_team_admin == request.user and not object.course_run_state.is_preview_accepted %}
<button class="btn btn-neutral btn-preview btn-preview-decline" type="button">
{% trans "Decline" %}
</button>
<button class="btn btn-neutral btn-preview btn-preview-accept" type="button">
{% trans "Accept" %}
</button>
{% elif preview_accepted_date %}
<span class="state-status">
<span class="icon fa fa-check" aria-hidden="true"></span>
{% trans "Approved" %}<br>
{{ preview_accepted_date|date:'m/d/y H:i a' }}
</span>
{% endif %}
</div>
<div class="layout-col layout-col-b">
<span class="preview-heading">
<strong>{% trans "COURSE PREVIEW" %}</strong>
</span>
<div>
<span class="preview-url-heading">{% trans "Preview URL" %} - </span>
{% if object.preview_url %}
<a href="{{ object.preview_url }}" target="_blank">{% trans "View course preview live" %}</a>
{% else %}
{% trans "Not available" %}
{% endif %}
</div>
</div>
</div>
</div>
</div>
<hr>
{% include 'publisher/course_run_detail/_preview_accept_popup.html' %}
</div>
</div>
{% extends "publisher/email/email_base.html" %}
{% load i18n %}
{% block body %}
<!-- Message Body -->
<p>
{% blocktrans trimmed %}
Dear member,
{% endblocktrans %}
<p>
{% blocktrans with link_start='<a href="' link_middle='">' link_end='</a>' trimmed %}
Preview for {{ link_start }}{{ page_url }}{{ link_middle }} {{ course_name }} {{ link_end }} has beed approved by course team.
{% endblocktrans %}
</p>
{% comment %}Translators: It's closing of mail.{% endcomment %}
{% trans "Thanks," %}<br>
{% trans "The edX team" %}
{% 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 member,
{% endblocktrans %}
{% blocktrans trimmed %}
Preview for {{ course_name }} has beed approved by course team. {{ page_url }}
{% endblocktrans %}
{% trans "Thanks," %}
{% trans "The edX team" %}
{% 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