Commit 759512a8 by tasawernawaz Committed by Tasawer Nawaz

Preview widget added for Publisher role

ECOM-6077
parent 9ba4792f
"""Publisher API Serializers""" """Publisher API Serializers"""
import re
import waffle import waffle
from django.apps import apps from django.apps import apps
from django.db import transaction from django.db import transaction
...@@ -11,7 +12,9 @@ from rest_framework import serializers ...@@ -11,7 +12,9 @@ from rest_framework import serializers
from course_discovery.apps.core.models import User from course_discovery.apps.core.models import User
from course_discovery.apps.publisher.choices import PublisherUserRole 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.emails import (
send_email_for_studio_instance_created, send_email_preview_accepted, send_email_preview_page_is_available
)
from course_discovery.apps.publisher.models import CourseRun, CourseRunState, CourseState, CourseUserRole from course_discovery.apps.publisher.models import CourseRun, CourseRunState, CourseState, CourseUserRole
...@@ -45,38 +48,59 @@ class GroupUserSerializer(serializers.ModelSerializer): ...@@ -45,38 +48,59 @@ class GroupUserSerializer(serializers.ModelSerializer):
return obj.get_full_name() or obj.username return obj.get_full_name() or obj.username
class UpdateCourseKeySerializer(serializers.ModelSerializer): class CourseRunSerializer(serializers.ModelSerializer):
""" """
Serializer for the `CourseRun` model to update 'lms_course_id'. Serializer for the `CourseRun` model.
""" """
class Meta: class Meta:
model = CourseRun model = CourseRun
fields = ('lms_course_id', 'changed_by',) fields = ('lms_course_id', 'changed_by', 'preview_url',)
def validate(self, data):
validated_values = super(UpdateCourseKeySerializer, self).validate(data)
lms_course_id = validated_values.get('lms_course_id')
def validate_lms_course_id(self, value):
try: try:
CourseKey.from_string(lms_course_id) CourseKey.from_string(value)
except InvalidKeyError: except InvalidKeyError:
# pylint: disable=no-member # pylint: disable=no-member
raise serializers.ValidationError( raise serializers.ValidationError(
{'lms_course_id': _('Invalid course key "{lms_course_id}"').format(lms_course_id=lms_course_id)} {'lms_course_id': _('Invalid course key "{lms_course_id}"').format(lms_course_id=value)}
) )
request = self.context.get('request') return value
if request:
validated_values.update({'changed_by': request.user}) def validate_preview_url(self, value):
if not re.match(r'https?://(?:www)?(?:[\w-]{2,255}(?:\.\w{2,6}){1,2})(?:/[\w&%?#-]{1,300})?', value):
# pylint: disable=no-member
raise serializers.ValidationError(
{'preview_url': _('Invalid URL format "{preview_url}"').format(preview_url=value)}
)
return value
def validate(self, data):
validated_values = super(CourseRunSerializer, self).validate(data)
if validated_values.get('lms_course_id'):
request = self.context.get('request')
if request:
validated_values.update({'changed_by': request.user})
return validated_values return validated_values
def update(self, instance, validated_data): def update(self, instance, validated_data):
instance = super(UpdateCourseKeySerializer, self).update(instance, validated_data) instance = super(CourseRunSerializer, self).update(instance, validated_data)
preview_url = validated_data.get('preview_url')
lms_course_id = validated_data.get('lms_course_id')
if preview_url:
instance.course_run_state.change_role(PublisherUserRole.CourseTeam)
if waffle.switch_is_active('enable_publisher_email_notifications'): if waffle.switch_is_active('enable_publisher_email_notifications'):
send_email_for_studio_instance_created(instance) if preview_url:
send_email_preview_page_is_available(instance)
elif lms_course_id:
send_email_for_studio_instance_created(instance)
return instance return instance
......
...@@ -6,9 +6,9 @@ from course_discovery.apps.core.tests.factories import UserFactory ...@@ -6,9 +6,9 @@ from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.core.tests.helpers import make_image_file from course_discovery.apps.core.tests.helpers import make_image_file
from course_discovery.apps.course_metadata.tests.factories import PersonFactory from course_discovery.apps.course_metadata.tests.factories import PersonFactory
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.api.serializers import (CourseRevisionSerializer, CourseRunStateSerializer, from course_discovery.apps.publisher.api.serializers import (CourseRevisionSerializer, CourseRunSerializer,
CourseStateSerializer, CourseUserRoleSerializer, CourseRunStateSerializer, CourseStateSerializer,
GroupUserSerializer, UpdateCourseKeySerializer) CourseUserRoleSerializer, GroupUserSerializer)
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.models import CourseState, Seat from course_discovery.apps.publisher.models import CourseState, Seat
from course_discovery.apps.publisher.tests.factories import (CourseFactory, CourseRunFactory, CourseRunStateFactory, from course_discovery.apps.publisher.tests.factories import (CourseFactory, CourseRunFactory, CourseRunStateFactory,
...@@ -60,35 +60,64 @@ class GroupUserSerializerTests(TestCase): ...@@ -60,35 +60,64 @@ class GroupUserSerializerTests(TestCase):
self.assertDictEqual(serializer.data, expected) self.assertDictEqual(serializer.data, expected)
class UpdateCourseKeySerializerTests(TestCase): class CourseRunSerializerTests(TestCase):
serializer_class = UpdateCourseKeySerializer serializer_class = CourseRunSerializer
def setUp(self): def setUp(self):
super(UpdateCourseKeySerializerTests, self).setUp() super(CourseRunSerializerTests, self).setUp()
self.course_run = CourseRunFactory() self.course_run = CourseRunFactory()
self.course_run.lms_course_id = 'course-v1:edX+DemoX+Demo_Course'
self.request = RequestFactory() self.request = RequestFactory()
self.user = UserFactory() self.user = UserFactory()
self.request.user = self.user self.request.user = self.user
self.course_state = CourseRunStateFactory(course_run=self.course_run, owner_role=PublisherUserRole.Publisher)
def get_expected_data(self): def get_expected_data(self):
return { return {
'lms_course_id': self.course_run.lms_course_id, 'lms_course_id': self.course_run.lms_course_id,
'changed_by': self.user 'changed_by': self.user,
'preview_url': self.course_run.preview_url
} }
def test_validation(self): def test_validate_lms_course_id(self):
self.course_run.lms_course_id = 'course-v1:edxTest+TC101+2016_Q1' """ Verify that serializer raises error if 'lms_course_id' has invalid format. """
self.course_run.lms_course_id = 'invalid-course-id'
self.course_run.save() # pylint: disable=no-member self.course_run.save() # pylint: disable=no-member
serializer = self.serializer_class(self.course_run, context={'request': self.request}) serializer = self.serializer_class(self.course_run)
expected = serializer.validate(serializer.data) with self.assertRaises(ValidationError):
self.assertEqual(self.get_expected_data(), expected) serializer.validate_lms_course_id(self.course_run.lms_course_id)
def test_validation_error(self): def test_validate_preview_url(self):
self.course_run.lms_course_id = 'invalid-course-id' """ Verify that serializer raises error if 'preview_url' has invalid format. """
self.course_run.preview_url = 'invalid-preview-url'
self.course_run.save() # pylint: disable=no-member self.course_run.save() # pylint: disable=no-member
serializer = self.serializer_class(self.course_run) serializer = self.serializer_class(self.course_run)
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
serializer.validate(serializer.data) serializer.validate_preview_url(self.course_run.preview_url)
def test_serializer_with_valid_data(self):
""" Verify that serializer validate course_run object. """
serializer = self.serializer_class(self.course_run, context={'request': self.request})
self.assertEqual(self.get_expected_data(), serializer.validate(serializer.data))
def test_update_preview_url(self):
""" Verify that course 'owner_role' will be changed to course_team after updating
course run with preview url.
"""
serializer = self.serializer_class(self.course_run)
serializer.update(self.course_run, serializer.data)
self.assertEqual(self.course_state.owner_role, PublisherUserRole.CourseTeam)
def test_update_lms_course_id(self):
""" Verify that 'changed_by' also updated after updating course_run's lms_course_id."""
self.course_run.preview_url = None
self.course_run.save()
serializer = self.serializer_class(self.course_run, context={'request': self.request})
serializer.update(self.course_run, serializer.validate(serializer.data))
self.assertEqual(self.course_run.lms_course_id, serializer.data['lms_course_id'])
self.assertEqual(self.course_run.changed_by, self.user)
class CourseRevisionSerializerTests(TestCase): class CourseRevisionSerializerTests(TestCase):
......
...@@ -180,16 +180,16 @@ class OrganizationGroupUserViewTests(TestCase): ...@@ -180,16 +180,16 @@ class OrganizationGroupUserViewTests(TestCase):
expected_results = [ expected_results = [
{ {
"id": self.org_user1.id,
"full_name": self.org_user1.full_name
},
{
"id": self.org_user2.id, "id": self.org_user2.id,
"full_name": self.org_user2.username "full_name": self.org_user2.username
},
{
"id": self.org_user1.id,
"full_name": self.org_user1.full_name
} }
] ]
self.assertEqual(json.loads(response.content.decode("utf-8"))["results"], expected_results) self.assertListEqual(json.loads(response.content.decode("utf-8"))["results"], expected_results)
def test_get_organization_not_found(self): def test_get_organization_not_found(self):
""" Verify that view returns status=404 if organization is not found """ Verify that view returns status=404 if organization is not found
...@@ -205,10 +205,10 @@ class OrganizationGroupUserViewTests(TestCase): ...@@ -205,10 +205,10 @@ class OrganizationGroupUserViewTests(TestCase):
) )
class UpdateCourseKeyViewTests(TestCase): class UpdateCourseRunViewTests(TestCase):
def setUp(self): def setUp(self):
super(UpdateCourseKeyViewTests, self).setUp() super(UpdateCourseRunViewTests, self).setUp()
self.course_run = factories.CourseRunFactory() self.course_run = factories.CourseRunFactory()
self.user = UserFactory() self.user = UserFactory()
self.user.groups.add(Group.objects.get(name=INTERNAL_USER_GROUP_NAME)) self.user.groups.add(Group.objects.get(name=INTERNAL_USER_GROUP_NAME))
...@@ -216,8 +216,8 @@ class UpdateCourseKeyViewTests(TestCase): ...@@ -216,8 +216,8 @@ class UpdateCourseKeyViewTests(TestCase):
self.organization_extension = factories.OrganizationExtensionFactory() self.organization_extension = factories.OrganizationExtensionFactory()
self.course_run.course.organizations.add(self.organization_extension.organization) self.course_run.course.organizations.add(self.organization_extension.organization)
self.update_course_key_url = reverse( self.update_course_run_url = reverse(
'publisher:api:update_course_key', kwargs={'pk': self.course_run.id} 'publisher:api:update_course_run', kwargs={'pk': self.course_run.id}
) )
factories.CourseUserRoleFactory( factories.CourseUserRoleFactory(
...@@ -236,7 +236,7 @@ class UpdateCourseKeyViewTests(TestCase): ...@@ -236,7 +236,7 @@ class UpdateCourseKeyViewTests(TestCase):
""" """
invalid_course_id = 'invalid-course-key' invalid_course_id = 'invalid-course-key'
response = self.client.patch( response = self.client.patch(
self.update_course_key_url, self.update_course_run_url,
data=json.dumps({'lms_course_id': invalid_course_id}), data=json.dumps({'lms_course_id': invalid_course_id}),
content_type=JSON_CONTENT_TYPE content_type=JSON_CONTENT_TYPE
) )
...@@ -244,7 +244,7 @@ class UpdateCourseKeyViewTests(TestCase): ...@@ -244,7 +244,7 @@ class UpdateCourseKeyViewTests(TestCase):
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
self.assertEqual( self.assertEqual(
response.data.get('lms_course_id'), response.data.get('lms_course_id'),
['Invalid course key "{lms_course_id}"'.format(lms_course_id=invalid_course_id)] ({'lms_course_id': 'Invalid course key "{lms_course_id}"'.format(lms_course_id=invalid_course_id)})
) )
def test_update_course_key_without_permission(self): def test_update_course_key_without_permission(self):
...@@ -253,7 +253,7 @@ class UpdateCourseKeyViewTests(TestCase): ...@@ -253,7 +253,7 @@ class UpdateCourseKeyViewTests(TestCase):
""" """
self.user.groups.remove(Group.objects.get(name=INTERNAL_USER_GROUP_NAME)) self.user.groups.remove(Group.objects.get(name=INTERNAL_USER_GROUP_NAME))
response = self.client.patch( response = self.client.patch(
self.update_course_key_url, self.update_course_run_url,
data=json.dumps({'lms_course_id': 'course-v1:edxTest+TC12+2050Q1'}), data=json.dumps({'lms_course_id': 'course-v1:edxTest+TC12+2050Q1'}),
content_type=JSON_CONTENT_TYPE content_type=JSON_CONTENT_TYPE
) )
...@@ -271,7 +271,7 @@ class UpdateCourseKeyViewTests(TestCase): ...@@ -271,7 +271,7 @@ class UpdateCourseKeyViewTests(TestCase):
factories.CourseRunFactory(lms_course_id=lms_course_id) factories.CourseRunFactory(lms_course_id=lms_course_id)
response = self.client.patch( response = self.client.patch(
self.update_course_key_url, self.update_course_run_url,
data=json.dumps({'lms_course_id': lms_course_id}), data=json.dumps({'lms_course_id': lms_course_id}),
content_type=JSON_CONTENT_TYPE content_type=JSON_CONTENT_TYPE
) )
...@@ -290,7 +290,7 @@ class UpdateCourseKeyViewTests(TestCase): ...@@ -290,7 +290,7 @@ class UpdateCourseKeyViewTests(TestCase):
lms_course_id = 'course-v1:edxTest+TC12+2050Q1' lms_course_id = 'course-v1:edxTest+TC12+2050Q1'
response = self.client.patch( response = self.client.patch(
self.update_course_key_url, self.update_course_run_url,
data=json.dumps({'lms_course_id': lms_course_id}), data=json.dumps({'lms_course_id': lms_course_id}),
content_type=JSON_CONTENT_TYPE content_type=JSON_CONTENT_TYPE
) )
...@@ -326,6 +326,30 @@ class UpdateCourseKeyViewTests(TestCase): ...@@ -326,6 +326,30 @@ class UpdateCourseKeyViewTests(TestCase):
page_url = 'https://{host}{path}'.format(host=Site.objects.get_current().domain.strip('/'), path=object_path) page_url = 'https://{host}{path}'.format(host=Site.objects.get_current().domain.strip('/'), path=object_path)
self.assertIn(page_url, body) self.assertIn(page_url, body)
def test_update_preview_url(self):
"""Verify the user can update course preview url."""
preview_url = 'https://example.com/abc/course'
factories.CourseRunStateFactory.create(course_run=self.course_run, owner_role=PublisherUserRole.Publisher)
response = self._make_request(preview_url)
self.assertEqual(response.status_code, 200)
course_run = CourseRun.objects.get(id=self.course_run.id)
self.assertEqual(course_run.preview_url, preview_url)
def test_update_with_invalid_preview_url(self):
"""Verify the user can't update course preview url if url has invalid format."""
preview_url = 'invalid_url_format'
response = self._make_request(preview_url)
self.assertEqual(response.status_code, 400)
def _make_request(self, preview_url):
""" Helper method to make request. """
return self.client.patch(
self.update_course_run_url,
data=json.dumps({'preview_url': preview_url}),
content_type=JSON_CONTENT_TYPE
)
class CourseRevisionDetailViewTests(TestCase): class CourseRevisionDetailViewTests(TestCase):
......
...@@ -3,7 +3,7 @@ from django.conf.urls import url ...@@ -3,7 +3,7 @@ from django.conf.urls import url
from course_discovery.apps.publisher.api.views import ( from course_discovery.apps.publisher.api.views import (
ChangeCourseRunStateView, ChangeCourseStateView, CourseRevisionDetailView, CourseRoleAssignmentView, ChangeCourseRunStateView, ChangeCourseStateView, CourseRevisionDetailView, CourseRoleAssignmentView,
OrganizationGroupUserView, UpdateCourseKeyView OrganizationGroupUserView, UpdateCourseRunView
) )
urlpatterns = [ urlpatterns = [
...@@ -11,7 +11,7 @@ urlpatterns = [ ...@@ -11,7 +11,7 @@ urlpatterns = [
url(r'^admins/organizations/(?P<pk>\d+)/users/$', OrganizationGroupUserView.as_view(), url(r'^admins/organizations/(?P<pk>\d+)/users/$', OrganizationGroupUserView.as_view(),
name='organization_group_users'), name='organization_group_users'),
url(r'^course_state/(?P<pk>\d+)/$', ChangeCourseStateView.as_view(), name='change_course_state'), url(r'^course_state/(?P<pk>\d+)/$', ChangeCourseStateView.as_view(), name='change_course_state'),
url(r'^course_runs/(?P<pk>\d+)/$', UpdateCourseKeyView.as_view(), name='update_course_key'), url(r'^course_runs/(?P<pk>\d+)/$', UpdateCourseRunView.as_view(), name='update_course_run'),
url(r'^course_revisions/(?P<history_id>\d+)/$', CourseRevisionDetailView.as_view(), name='course_revisions'), url(r'^course_revisions/(?P<history_id>\d+)/$', CourseRevisionDetailView.as_view(), name='course_revisions'),
url(r'^course_run_state/(?P<pk>\d+)/$', ChangeCourseRunStateView.as_view(), name='change_course_run_state'), url(r'^course_run_state/(?P<pk>\d+)/$', ChangeCourseRunStateView.as_view(), name='change_course_run_state'),
] ]
...@@ -2,16 +2,13 @@ from rest_framework.generics import ListAPIView, RetrieveAPIView, UpdateAPIView, ...@@ -2,16 +2,13 @@ from rest_framework.generics import ListAPIView, RetrieveAPIView, UpdateAPIView,
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from course_discovery.apps.core.models import User from course_discovery.apps.core.models import User
from course_discovery.apps.publisher.api.permissions import ( from course_discovery.apps.publisher.api.permissions import (CanViewAssociatedCourse, InternalUserPermission,
CanViewAssociatedCourse, InternalUserPermission, PublisherUserPermission PublisherUserPermission)
) from course_discovery.apps.publisher.api.serializers import (CourseRevisionSerializer, CourseRunSerializer,
from course_discovery.apps.publisher.api.serializers import ( CourseRunStateSerializer, CourseStateSerializer,
CourseRevisionSerializer, CourseRunStateSerializer, CourseStateSerializer, CourseUserRoleSerializer, CourseUserRoleSerializer, GroupUserSerializer)
GroupUserSerializer, UpdateCourseKeySerializer from course_discovery.apps.publisher.models import (Course, CourseRun, CourseRunState, CourseState, CourseUserRole,
) OrganizationExtension)
from course_discovery.apps.publisher.models import (
Course, CourseRun, CourseRunState, CourseState, CourseUserRole, OrganizationExtension
)
class CourseRoleAssignmentView(UpdateAPIView): class CourseRoleAssignmentView(UpdateAPIView):
...@@ -30,10 +27,10 @@ class OrganizationGroupUserView(ListAPIView): ...@@ -30,10 +27,10 @@ class OrganizationGroupUserView(ListAPIView):
return queryset return queryset
class UpdateCourseKeyView(UpdateAPIView): class UpdateCourseRunView(UpdateAPIView):
permission_classes = (IsAuthenticated, InternalUserPermission,) permission_classes = (IsAuthenticated, InternalUserPermission,)
queryset = CourseRun.objects.all() queryset = CourseRun.objects.all()
serializer_class = UpdateCourseKeySerializer serializer_class = CourseRunSerializer
class CourseRevisionDetailView(RetrieveAPIView): class CourseRevisionDetailView(RetrieveAPIView):
......
...@@ -365,3 +365,47 @@ def send_email_preview_accepted(course_run): ...@@ -365,3 +365,47 @@ def send_email_preview_accepted(course_run):
email_msg.send() email_msg.send()
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
logger.exception('Failed to send email notifications for preview approved of course-run %s', course_run.id) logger.exception('Failed to send email notifications for preview approved of course-run %s', course_run.id)
def send_email_preview_page_is_available(course_run):
""" Send email for course preview available to course team.
Arguments:
course_run (Object): CourseRun object
"""
txt_template = 'publisher/email/course_run/preview_available.txt'
html_template = 'publisher/email/course_run/preview_available.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} is available').format(run_name=run_name) # pylint: disable=no-member
course_team_user = course_run.course.course_team_admin
try:
if is_email_notification_enabled(course_team_user):
to_addresses = [course_team_user.email]
from_address = settings.PUBLISHER_FROM_EMAIL
project_coordinator = course_run.course.project_coordinator
page_path = reverse('publisher:publisher_course_run_detail', kwargs={'pk': course_run.id})
context = {
'course_name': run_name,
'contact_us_email': project_coordinator.email if project_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 available of course-run %s', course_run.id)
...@@ -709,6 +709,11 @@ class CourseRunState(TimeStampedModel, ChangedByMixin): ...@@ -709,6 +709,11 @@ class CourseRunState(TimeStampedModel, ChangedByMixin):
self.save() self.save()
def change_role(self, role):
self.owner_role = role
self.owner_role_modified = timezone.now()
self.save()
@property @property
def is_preview_accepted(self): def is_preview_accepted(self):
return self.preview_accepted return self.preview_accepted
......
...@@ -534,3 +534,40 @@ class CourseRunPreviewEmailTests(TestCase): ...@@ -534,3 +534,40 @@ class CourseRunPreviewEmailTests(TestCase):
) )
) )
) )
def test_preview_available_email(self):
"""
Verify that preview available email functionality works fine.
"""
emails.send_email_preview_page_is_available(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} is available'.format(
run_name=run_name
)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual([self.course.course_team_admin.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('is available for review.', body)
def test_preview_available_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_page_is_available(self.run_state.course_run)
l.check(
(
emails.logger.name,
'ERROR',
'Failed to send email notifications for preview available of course-run {}'.format(
self.run_state.course_run.id
)
)
)
...@@ -1198,6 +1198,36 @@ class CourseRunDetailTests(TestCase): ...@@ -1198,6 +1198,36 @@ class CourseRunDetailTests(TestCase):
) )
self.assertContains(response, 'Approved') self.assertContains(response, 'Approved')
def test_course_preview(self):
"""Verify that publisher user can see preview widget."""
factories.CourseUserRoleFactory(course=self.course, user=self.user, role=PublisherUserRole.Publisher)
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)
preview_api_url = reverse('publisher:api:update_course_run', args=[self.course_run.id])
response = self.client.get(self.page_url)
self.assertContains(response, 'COURSE PREVIEW')
self.assertContains(
response,
'<button data-url="{url}" class="btn btn-neutral btn-edit-preview-url">'.format(url=preview_api_url)
)
# verify with out preview_url
self.course_run.preview_url = None
self.course_run.save()
response = self.client.get(self.page_url)
self.assertContains(response, 'COURSE PREVIEW')
self.assertContains(
response,
'<button data-url="{url}" class="btn btn-neutral btn-save-preview-url">'.format(url=preview_api_url)
)
self.assertContains(response, '<input id="id-review-url" type="text">')
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
@ddt.ddt @ddt.ddt
......
...@@ -764,7 +764,9 @@ def get_course_role_widgets_data(user, course, state_object, change_state_url): ...@@ -764,7 +764,9 @@ def get_course_role_widgets_data(user, course, state_object, change_state_url):
if history_record: if history_record:
role_widget['reviewed'] = history_record.modified role_widget['reviewed'] = history_record.modified
elif state_object.name != CourseStateChoices.Draft: elif ((state_object.name != CourseStateChoices.Draft and course_role.role != state_object.owner_role) or
state_object.name == CourseRunStateChoices.Approved):
history_record = state_object.history.filter( history_record = state_object.history.filter(
name=CourseStateChoices.Review name=CourseStateChoices.Review
).order_by('-modified').first() ).order_by('-modified').first()
......
...@@ -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-23 12:44+0500\n" "POT-Creation-Date: 2017-02-23 17:25+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
...@@ -431,6 +431,11 @@ msgstr "" ...@@ -431,6 +431,11 @@ msgstr ""
#: apps/publisher/api/serializers.py #: apps/publisher/api/serializers.py
#, python-brace-format #, python-brace-format
msgid "Invalid URL format \"{preview_url}\""
msgstr ""
#: apps/publisher/api/serializers.py
#, python-brace-format
msgid "Cannot switch from state `{state}` to `{target_state}`" msgid "Cannot switch from state `{state}` to `{target_state}`"
msgstr "" msgstr ""
...@@ -507,6 +512,11 @@ msgstr "" ...@@ -507,6 +512,11 @@ msgstr ""
msgid "Preview for {run_name} has been approved" msgid "Preview for {run_name} has been approved"
msgstr "" msgstr ""
#: apps/publisher/emails.py
#, python-brace-format
msgid "Preview for {run_name} is available"
msgstr ""
#: apps/publisher/forms.py #: apps/publisher/forms.py
msgid "Remove Image" msgid "Remove Image"
msgstr "" msgstr ""
...@@ -879,7 +889,9 @@ msgstr "" ...@@ -879,7 +889,9 @@ msgstr ""
msgid "Edit Comment" msgid "Edit Comment"
msgstr "" msgstr ""
#: templates/comments/edit_comment.html templates/publisher/seat_form.html #: templates/comments/edit_comment.html
#: templates/publisher/course_run_detail/_widgets.html
#: templates/publisher/seat_form.html
msgid "Save" msgid "Save"
msgstr "" msgstr ""
...@@ -2171,6 +2183,15 @@ msgid "Accept" ...@@ -2171,6 +2183,15 @@ msgid "Accept"
msgstr "" msgstr ""
#: templates/publisher/course_run_detail/_widgets.html #: templates/publisher/course_run_detail/_widgets.html
msgid "Submitted for review"
msgstr ""
#: templates/publisher/course_run_detail/_widgets.html
#: templates/publisher/courses.html
msgid "Edit"
msgstr ""
#: templates/publisher/course_run_detail/_widgets.html
msgid "COURSE PREVIEW" msgid "COURSE PREVIEW"
msgstr "" msgstr ""
...@@ -2238,10 +2259,6 @@ msgstr "" ...@@ -2238,10 +2259,6 @@ msgstr ""
msgid "Runs" msgid "Runs"
msgstr "" msgstr ""
#: templates/publisher/courses.html
msgid "Edit"
msgstr ""
#: templates/publisher/dashboard.html #: templates/publisher/dashboard.html
msgid "Course runs" msgid "Course runs"
msgstr "" msgstr ""
...@@ -2344,6 +2361,8 @@ msgstr "" ...@@ -2344,6 +2361,8 @@ msgstr ""
#: templates/publisher/email/course_created.txt #: templates/publisher/email/course_created.txt
#: templates/publisher/email/course_run/preview_accepted.html #: templates/publisher/email/course_run/preview_accepted.html
#: templates/publisher/email/course_run/preview_accepted.txt #: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/preview_available.html
#: templates/publisher/email/course_run/preview_available.txt
msgid "The edX team" msgid "The edX team"
msgstr "" msgstr ""
...@@ -2397,6 +2416,8 @@ msgstr "" ...@@ -2397,6 +2416,8 @@ msgstr ""
#: templates/publisher/email/course_run/mark_as_reviewed.txt #: templates/publisher/email/course_run/mark_as_reviewed.txt
#: templates/publisher/email/course_run/preview_accepted.html #: templates/publisher/email/course_run/preview_accepted.html
#: templates/publisher/email/course_run/preview_accepted.txt #: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/preview_available.html
#: templates/publisher/email/course_run/preview_available.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
...@@ -2410,6 +2431,7 @@ msgstr "" ...@@ -2410,6 +2431,7 @@ msgstr ""
#: 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/mark_as_reviewed.html
#: templates/publisher/email/course_run/preview_accepted.html #: templates/publisher/email/course_run/preview_accepted.html
#: templates/publisher/email/course_run/preview_available.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
...@@ -2427,6 +2449,7 @@ msgstr "" ...@@ -2427,6 +2449,7 @@ msgstr ""
#: 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/mark_as_reviewed.txt
#: templates/publisher/email/course_run/preview_accepted.txt #: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/preview_available.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
...@@ -2498,6 +2521,8 @@ msgstr "" ...@@ -2498,6 +2521,8 @@ msgstr ""
#: templates/publisher/email/course_run/preview_accepted.html #: templates/publisher/email/course_run/preview_accepted.html
#: templates/publisher/email/course_run/preview_accepted.txt #: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/preview_available.html
#: templates/publisher/email/course_run/preview_available.txt
msgid "Dear member," msgid "Dear member,"
msgstr "" msgstr ""
...@@ -2514,6 +2539,18 @@ msgid "" ...@@ -2514,6 +2539,18 @@ msgid ""
"Preview for %(course_name)s has beed approved by course team. %(page_url)s" "Preview for %(course_name)s has beed approved by course team. %(page_url)s"
msgstr "" msgstr ""
#: templates/publisher/email/course_run/preview_available.html
#, python-format
msgid ""
"Preview for %(link_start)s%(page_url)s%(link_middle)s %(course_name)s "
"%(link_end)s is available for review."
msgstr ""
#: templates/publisher/email/course_run/preview_available.txt
#, python-format
msgid "Preview for %(course_name)s is available for review. %(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-23 12:44+0500\n" "POT-Creation-Date: 2017-02-23 17:25+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"
...@@ -36,6 +36,14 @@ msgstr "" ...@@ -36,6 +36,14 @@ msgstr ""
msgid "File must be smaller than 1 megabyte in size." msgid "File must be smaller than 1 megabyte in size."
msgstr "" msgstr ""
#: static/js/publisher/publisher.js
msgid "Please enter a valid URL."
msgstr ""
#: static/js/publisher/publisher.js
msgid "Save"
msgstr ""
#: static/js/publisher/views/dashboard.js #: static/js/publisher/views/dashboard.js
msgid "" msgid ""
"You have successfully created a studio instance ({studioLinkTag}) for " "You have successfully created a studio instance ({studioLinkTag}) for "
......
...@@ -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-23 12:44+0500\n" "POT-Creation-Date: 2017-02-23 17:25+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
...@@ -541,6 +541,11 @@ msgstr "Ìnvälïd çöürsé kéý \"{lms_course_id}\" Ⱡ'σяєм ιρѕυм ...@@ -541,6 +541,11 @@ msgstr "Ìnvälïd çöürsé kéý \"{lms_course_id}\" Ⱡ'σяєм ιρѕυм
#: apps/publisher/api/serializers.py #: apps/publisher/api/serializers.py
#, python-brace-format #, python-brace-format
msgid "Invalid URL format \"{preview_url}\""
msgstr "Ìnvälïd ÛRL förmät \"{preview_url}\" Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢ση#"
#: apps/publisher/api/serializers.py
#, python-brace-format
msgid "Cannot switch from state `{state}` to `{target_state}`" msgid "Cannot switch from state `{state}` to `{target_state}`"
msgstr "" msgstr ""
"Çännöt swïtçh fröm stäté `{state}` tö `{target_state}` Ⱡ'σяєм ιρѕυм ∂σłσя " "Çännöt swïtçh fröm stäté `{state}` tö `{target_state}` Ⱡ'σяєм ιρѕυм ∂σłσя "
...@@ -630,6 +635,12 @@ msgstr "" ...@@ -630,6 +635,12 @@ msgstr ""
"Prévïéw för {run_name} häs ßéén äpprövéd Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " "Prévïéw för {run_name} häs ßéén äpprövéd Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тє#" "¢σηѕє¢тє#"
#: apps/publisher/emails.py
#, python-brace-format
msgid "Preview for {run_name} is available"
msgstr ""
"Prévïéw för {run_name} ïs äväïläßlé Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#"
#: 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é Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
...@@ -1037,7 +1048,9 @@ msgstr "Çöürsé Rün Förm Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α#" ...@@ -1037,7 +1048,9 @@ msgstr "Çöürsé Rün Förm Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α#"
msgid "Edit Comment" msgid "Edit Comment"
msgstr "Édït Çömmént Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#" msgstr "Édït Çömmént Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
#: templates/comments/edit_comment.html templates/publisher/seat_form.html #: templates/comments/edit_comment.html
#: templates/publisher/course_run_detail/_widgets.html
#: templates/publisher/seat_form.html
msgid "Save" msgid "Save"
msgstr "Sävé Ⱡ'σяєм ι#" msgstr "Sävé Ⱡ'σяєм ι#"
...@@ -2555,6 +2568,15 @@ msgid "Accept" ...@@ -2555,6 +2568,15 @@ msgid "Accept"
msgstr "Àççépt Ⱡ'σяєм ιρѕυ#" msgstr "Àççépt Ⱡ'σяєм ιρѕυ#"
#: templates/publisher/course_run_detail/_widgets.html #: templates/publisher/course_run_detail/_widgets.html
msgid "Submitted for review"
msgstr "Süßmïttéd för révïéw Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #"
#: templates/publisher/course_run_detail/_widgets.html
#: templates/publisher/courses.html
msgid "Edit"
msgstr "Édït Ⱡ'σяєм ι#"
#: templates/publisher/course_run_detail/_widgets.html
msgid "COURSE PREVIEW" msgid "COURSE PREVIEW"
msgstr "ÇÖÛRSÉ PRÉVÌÉW Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#" msgstr "ÇÖÛRSÉ PRÉVÌÉW Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#"
...@@ -2629,10 +2651,6 @@ msgstr "" ...@@ -2629,10 +2651,6 @@ msgstr ""
msgid "Runs" msgid "Runs"
msgstr "Rüns Ⱡ'σяєм ι#" msgstr "Rüns Ⱡ'σяєм ι#"
#: templates/publisher/courses.html
msgid "Edit"
msgstr "Édït Ⱡ'σяєм ι#"
#: templates/publisher/dashboard.html #: templates/publisher/dashboard.html
msgid "Course runs" msgid "Course runs"
msgstr "Çöürsé rüns Ⱡ'σяєм ιρѕυм ∂σłσя #" msgstr "Çöürsé rüns Ⱡ'σяєм ιρѕυм ∂σłσя #"
...@@ -2754,6 +2772,8 @@ msgstr "Vïéw Çöürsé Ⱡ'σяєм ιρѕυм ∂σłσя #" ...@@ -2754,6 +2772,8 @@ msgstr "Vïéw Çöürsé Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: templates/publisher/email/course_created.txt #: templates/publisher/email/course_created.txt
#: templates/publisher/email/course_run/preview_accepted.html #: templates/publisher/email/course_run/preview_accepted.html
#: templates/publisher/email/course_run/preview_accepted.txt #: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/preview_available.html
#: templates/publisher/email/course_run/preview_available.txt
msgid "The edX team" msgid "The edX team"
msgstr "Thé édX téäm Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#" msgstr "Thé édX téäm Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
...@@ -2811,6 +2831,8 @@ msgstr "" ...@@ -2811,6 +2831,8 @@ msgstr ""
#: templates/publisher/email/course_run/mark_as_reviewed.txt #: templates/publisher/email/course_run/mark_as_reviewed.txt
#: templates/publisher/email/course_run/preview_accepted.html #: templates/publisher/email/course_run/preview_accepted.html
#: templates/publisher/email/course_run/preview_accepted.txt #: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/preview_available.html
#: templates/publisher/email/course_run/preview_available.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
...@@ -2824,6 +2846,7 @@ msgstr "Thänks, Ⱡ'σяєм ιρѕυм #" ...@@ -2824,6 +2846,7 @@ msgstr "Thänks, Ⱡ'σяєм ιρѕυм #"
#: 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/mark_as_reviewed.html
#: templates/publisher/email/course_run/preview_accepted.html #: templates/publisher/email/course_run/preview_accepted.html
#: templates/publisher/email/course_run/preview_available.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
...@@ -2845,6 +2868,7 @@ msgstr "" ...@@ -2845,6 +2868,7 @@ msgstr ""
#: 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/mark_as_reviewed.txt
#: templates/publisher/email/course_run/preview_accepted.txt #: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/preview_available.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
...@@ -2945,6 +2969,8 @@ msgstr "" ...@@ -2945,6 +2969,8 @@ msgstr ""
#: templates/publisher/email/course_run/preview_accepted.html #: templates/publisher/email/course_run/preview_accepted.html
#: templates/publisher/email/course_run/preview_accepted.txt #: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/preview_available.html
#: templates/publisher/email/course_run/preview_available.txt
msgid "Dear member," msgid "Dear member,"
msgstr "Déär mémßér, Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#" msgstr "Déär mémßér, Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
...@@ -2966,6 +2992,23 @@ msgstr "" ...@@ -2966,6 +2992,23 @@ msgstr ""
"Prévïéw för %(course_name)s häs ßééd äpprövéd ßý çöürsé téäm. %(page_url)s " "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/preview_available.html
#, python-format
msgid ""
"Preview for %(link_start)s%(page_url)s%(link_middle)s %(course_name)s "
"%(link_end)s is available for review."
msgstr ""
"Prévïéw för %(link_start)s%(page_url)s%(link_middle)s %(course_name)s "
"%(link_end)s ïs äväïläßlé för révïéw. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυя α#"
#: templates/publisher/email/course_run/preview_available.txt
#, python-format
msgid "Preview for %(course_name)s is available for review. %(page_url)s"
msgstr ""
"Prévïéw för %(course_name)s ïs äväïläßlé för révïéw. %(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-23 12:44+0500\n" "POT-Creation-Date: 2017-02-23 17:25+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
...@@ -39,6 +39,14 @@ msgstr "" ...@@ -39,6 +39,14 @@ msgstr ""
"Fïlé müst ßé smällér thän 1 mégäßýté ïn sïzé. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " "Fïlé müst ßé smällér thän 1 mégäßýté ïn sïzé. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυя #" "¢σηѕє¢тєтυя #"
#: static/js/publisher/publisher.js
msgid "Please enter a valid URL."
msgstr "Pléäsé éntér ä välïd ÛRL. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#"
#: static/js/publisher/publisher.js
msgid "Save"
msgstr "Sävé Ⱡ'σяєм ι#"
#: static/js/publisher/views/dashboard.js #: static/js/publisher/views/dashboard.js
msgid "" msgid ""
"You have successfully created a studio instance ({studioLinkTag}) for " "You have successfully created a studio instance ({studioLinkTag}) for "
......
...@@ -328,3 +328,40 @@ function clearModalError($modal) { ...@@ -328,3 +328,40 @@ function clearModalError($modal) {
$('#modal-errors').html(''); $('#modal-errors').html('');
$('#modal-errors').hide(); $('#modal-errors').hide();
} }
$(document).on('click', '.btn-save-preview-url', function (e) {
preview_url = $('#id-review-url').val();
if (!preview_url) {
showInvalidPreviewUrlError();
return
}
$.ajax({
type: "PATCH",
url: $(this).data('url'),
data: JSON.stringify({'preview_url': preview_url}),
contentType: "application/json",
success: function (response) {
location.reload();
},
error: function (response) {
showInvalidPreviewUrlError();
}
});
});
function showInvalidPreviewUrlError() {
$('#id-review-url').addClass('has-error');
$('.error-message').html(gettext("Please enter a valid URL."));
}
$(document).on('click', '.btn-edit-preview-url', function (e) {
var $previewUrl = $('.preview-url'),
currentUrl = $previewUrl.find('a').attr('href'),
html = '<input id="id-review-url" type="text" value='+ currentUrl +'>';
$(this).addClass('btn-save-preview-url').removeClass('btn-edit-preview-url');
$(this).text(gettext("Save"));
$('.preview-status').remove();
$previewUrl.html(html);
$('#id-review-url').focus();
});
...@@ -339,8 +339,8 @@ ...@@ -339,8 +339,8 @@
.approval-widget, .course-widgets { .approval-widget, .course-widgets {
.btn-course-edit, .btn-courserun-edit, .btn-change-state, .btn-preview { .btn-course-edit, .btn-courserun-edit, .btn-change-state, .btn-preview, .btn-save-preview-url, .btn-edit-preview-url {
@include padding(2px, 20px, 3px, 20px); @include padding(2px, 20px, 3px, 20px);
@include float(right); @include float(right);
font-weight: 400; font-weight: 400;
...@@ -605,3 +605,22 @@ ...@@ -605,3 +605,22 @@
background-color: #F2F2F2; background-color: #F2F2F2;
padding: 20px; padding: 20px;
} }
.preview-container {
#id-review-url {
width: 360px;
}
.btn-save-preview-url {
margin-top: 25px;
}
.preview-status {
@include float(right);
font-size: 12px;
}
.has-error{
border-color: red;
}
.error-message {
color: red;
}
}
...@@ -14,7 +14,8 @@ ...@@ -14,7 +14,8 @@
<div class="preview-container"> <div class="preview-container">
<div class="layout-1q3q layout-reversed"> <div class="layout-1q3q layout-reversed">
<div class="layout-col layout-col-a"> <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 %} {% if object.preview_url %}
{% if 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"> <button class="btn btn-neutral btn-preview btn-preview-decline" type="button">
{% trans "Decline" %} {% trans "Decline" %}
</button> </button>
...@@ -27,18 +28,29 @@ ...@@ -27,18 +28,29 @@
{% trans "Approved" %}<br> {% trans "Approved" %}<br>
{{ preview_accepted_date|date:'m/d/y H:i a' }} {{ preview_accepted_date|date:'m/d/y H:i a' }}
</span> </span>
{% elif object.course.publisher == request.user %}
<span class="preview-status">{% trans "Submitted for review" %}</span>
<button data-url="{% url 'publisher:api:update_course_run' object.id %}" class="btn btn-neutral btn-edit-preview-url">{% trans "Edit" %}</button>
{% endif %} {% endif %}
{% elif object.course.publisher == request.user %}
<button data-url="{% url 'publisher:api:update_course_run' object.id %}" class="btn btn-neutral btn-save-preview-url">{% trans "Save" %}</button>
{% endif %}
</div> </div>
<div class="layout-col layout-col-b"> <div class="layout-col layout-col-b">
<span class="preview-heading"> <span class="preview-heading">
<strong>{% trans "COURSE PREVIEW" %}</strong> <strong>{% trans "COURSE PREVIEW" %}</strong>
</span> </span>
<div> <div class="preview-url">
<span class="preview-url-heading">{% trans "Preview URL" %} - </span>
{% if object.preview_url %} {% if object.preview_url %}
<span class="preview-url-heading">{% trans "Preview URL" %} - </span>
<a href="{{ object.preview_url }}" target="_blank">{% trans "View course preview live" %}</a> <a href="{{ object.preview_url }}" target="_blank">{% trans "View course preview live" %}</a>
{% else %} {% else %}
{% trans "Not available" %} {% if object.course.publisher == request.user %}
<input id="id-review-url" type="text">
<span class="error-message"></span>
{% else %}
<span>{% trans "Not available" %}</span>
{% endif %}
{% endif %} {% endif %}
</div> </div>
</div> </div>
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
</td> </td>
<td class="form-group"> <td class="form-group">
<input type="text" class="field-input input-text small" aria-labelledby="course-title-{{ course_run.title }} column-title" /> <input type="text" class="field-input input-text small" aria-labelledby="course-title-{{ course_run.title }} column-title" />
<button data-update-course-key-url="{% url 'publisher:api:update_course_key' course_run.id %}" class="btn-inline btn-add-course-key">{% trans "Add" %}</button> <button data-update-course-key-url="{% url 'publisher:api:update_course_run' course_run.id %}" class="btn-inline btn-add-course-key">{% trans "Add" %}</button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
......
{% 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 }} is available for review.
{% 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 }} is available for review. {{ 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