Commit 698eb202 by Awais Qureshi Committed by GitHub

Merge pull request #435 from edx/awais786/ecom-6178-save-course-key

Implement tabs design for the dashboard page. Adding sass.
parents 720727c2 d5a436ab
...@@ -8,7 +8,7 @@ from django.template.loader import get_template ...@@ -8,7 +8,7 @@ from django.template.loader import get_template
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
log = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def send_email_for_change_state(course_run): def send_email_for_change_state(course_run):
...@@ -48,4 +48,40 @@ def send_email_for_change_state(course_run): ...@@ -48,4 +48,40 @@ def send_email_for_change_state(course_run):
email_msg.attach_alternative(html_content, 'text/html') email_msg.attach_alternative(html_content, 'text/html')
email_msg.send() email_msg.send()
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
log.exception('Failed to send email notifications for change state of course-run %s', course_run.id) logger.exception('Failed to send email notifications for change state of course-run %s', course_run.id)
def send_email_for_studio_instance_created(course_run):
""" Send an email to course team on studio instance creation.
Arguments:
course_run (CourseRun): CourseRun object
"""
try:
object_path = reverse('publisher:publisher_course_run_detail', kwargs={'pk': course_run.id})
subject = _('Studio instance created')
to_addresses = course_run.course.get_group_users_emails()
from_address = settings.PUBLISHER_FROM_EMAIL
context = {
'course_run': course_run,
'course_run_page_url': 'https://{host}{path}'.format(
host=Site.objects.get_current().domain.strip('/'), path=object_path
)
}
txt_template_path = 'publisher/email/studio_instance_created.txt'
html_template_path = 'publisher/email/studio_instance_created.html'
txt_template = get_template(txt_template_path)
plain_content = txt_template.render(context)
html_template = get_template(html_template_path)
html_content = html_template.render(context)
email_msg = EmailMultiAlternatives(
subject, plain_content, from_address, to=[settings.PUBLISHER_FROM_EMAIL], 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 course_run [%s]', course_run.id)
...@@ -20,6 +20,8 @@ from course_discovery.apps.course_metadata.models import LevelType, Subject, Per ...@@ -20,6 +20,8 @@ from course_discovery.apps.course_metadata.models import LevelType, Subject, Per
from course_discovery.apps.course_metadata.utils import UploadToFieldNamePath from course_discovery.apps.course_metadata.utils import UploadToFieldNamePath
from course_discovery.apps.ietf_language_tags.models import LanguageTag from course_discovery.apps.ietf_language_tags.models import LanguageTag
from course_discovery.apps.publisher.emails import send_email_for_change_state from course_discovery.apps.publisher.emails import send_email_for_change_state
from course_discovery.apps.publisher.utils import is_email_notification_enabled
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -167,10 +169,8 @@ class Course(TimeStampedModel, ChangedByMixin): ...@@ -167,10 +169,8 @@ class Course(TimeStampedModel, ChangedByMixin):
then user will be eligible for emails. then user will be eligible for emails.
""" """
users_list = get_users_with_perms(self) users_list = get_users_with_perms(self)
emails = [ emails = [user.email for user in users_list if is_email_notification_enabled(user)]
user.email for user in users_list
if not hasattr(user, 'attributes') or user.attributes.enable_email_notification
]
return emails return emails
@property @property
......
"""Publisher Serializers"""
import waffle
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from rest_framework import serializers
from course_discovery.apps.publisher.emails import send_email_for_studio_instance_created
from course_discovery.apps.publisher.models import CourseRun
class UpdateCourseKeySerializer(serializers.ModelSerializer):
"""Serializer for the `CourseRun` model to update 'lms_course_id'. """
class Meta:
model = CourseRun
fields = ('lms_course_id', 'changed_by',)
def validate(self, data):
validated_values = super(UpdateCourseKeySerializer, self).validate(data)
lms_course_id = validated_values.get('lms_course_id')
try:
CourseKey.from_string(lms_course_id)
except InvalidKeyError:
raise serializers.ValidationError('Invalid course key [{}]'.format(lms_course_id))
request = self.context.get('request')
if request:
validated_values.update({'changed_by': request.user})
return validated_values
def update(self, instance, validated_data):
instance = super(UpdateCourseKeySerializer, self).update(instance, validated_data)
if waffle.switch_is_active('enable_publisher_email_notifications'):
send_email_for_studio_instance_created(instance)
return instance
...@@ -9,9 +9,11 @@ from django.core import mail ...@@ -9,9 +9,11 @@ from django.core import mail
from guardian.shortcuts import assign_perm from guardian.shortcuts import assign_perm
import pytz import pytz
import mock import mock
from testfixtures import LogCapture
from course_discovery.apps.core.tests.factories import UserFactory from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.course_metadata.tests import toggle_switch from course_discovery.apps.course_metadata.tests import toggle_switch
from course_discovery.apps.publisher import emails
from course_discovery.apps.publisher.models import State, Course from course_discovery.apps.publisher.models import State, Course
from course_discovery.apps.publisher.tests import factories from course_discovery.apps.publisher.tests import factories
from course_discovery.apps.publisher.tests.factories import UserAttributeFactory from course_discovery.apps.publisher.tests.factories import UserAttributeFactory
...@@ -121,3 +123,60 @@ class StateChangeEmailTests(TestCase): ...@@ -121,3 +123,60 @@ class StateChangeEmailTests(TestCase):
# add the start date again for other tests. # add the start date again for other tests.
self.course_run.start = datetime.datetime.now(pytz.UTC) self.course_run.start = datetime.datetime.now(pytz.UTC)
self.course_run.save() self.course_run.save()
class StudioInstanceCreatedEmailTests(TestCase):
""" Tests for the email functionality for studio instance created. """
def setUp(self):
super(StudioInstanceCreatedEmailTests, self).setUp()
self.user = UserFactory()
self.group = factories.GroupFactory()
self.user.groups.add(self.group)
self.course_run = factories.CourseRunFactory()
assign_perm(Course.VIEW_PERMISSION, self.group, self.course_run.course)
UserAttributeFactory(user=self.user, enable_email_notification=True)
toggle_switch('enable_publisher_email_notifications', True)
@mock.patch('django.core.mail.message.EmailMessage.send', mock.Mock(side_effect=TypeError))
def test_email_with_error(self):
""" Verify that emails for studio instance created."""
with LogCapture(emails.logger.name) as l:
emails.send_email_for_studio_instance_created(self.course_run)
l.check(
(
emails.logger.name,
'ERROR',
'Failed to send email notifications for course_run [{}]'.format(self.course_run.id)
)
)
def test_email_sent_successfully(self):
""" Verify that emails sent successfully for studio instance created."""
emails.send_email_for_studio_instance_created(self.course_run)
# assert email sent
self.assert_email_sent(
reverse('publisher:publisher_course_run_detail', kwargs={'pk': self.course_run.id}),
'Studio instance created',
'Studio instance created for the following course run'
)
def assert_email_sent(self, object_path, subject, expected_body):
""" DRY method to assert sent email data"""
self.assertEqual(len(mail.outbox), 1)
self.assertEqual([settings.PUBLISHER_FROM_EMAIL], mail.outbox[0].to)
self.assertEqual([self.user.email], mail.outbox[0].bcc)
self.assertEqual(str(mail.outbox[0].subject), subject)
body = mail.outbox[0].body.strip()
self.assertIn(expected_body, body)
page_url = 'https://{host}{path}'.format(host=Site.objects.get_current().domain.strip('/'), path=object_path)
self.assertIn(page_url, body)
"""Tests Publisher Serializers."""
from unittest import TestCase
from django.test import RequestFactory
from rest_framework.exceptions import ValidationError
from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.publisher.serializers import UpdateCourseKeySerializer
from course_discovery.apps.publisher.tests.factories import CourseRunFactory
class UpdateCourseKeySerializerTests(TestCase):
serializer_class = UpdateCourseKeySerializer
def setUp(self):
super(UpdateCourseKeySerializerTests, self).setUp()
self.course_run = CourseRunFactory()
self.request = RequestFactory()
self.user = UserFactory()
self.request.user = self.user
def get_expected_data(self):
return {
'lms_course_id': self.course_run.lms_course_id,
'changed_by': self.user
}
def test_validation(self):
self.course_run.lms_course_id = 'course-v1:edxTest+TC101+2016_Q1'
self.course_run.save() # pylint: disable=no-member
serializer = self.serializer_class(self.course_run)
serializer.context['request'] = self.request
expected = serializer.validate(serializer.data)
self.assertEqual(self.get_expected_data(), expected)
def test_validation_error(self):
self.course_run.lms_course_id = 'wrong-course-id'
self.course_run.save() # pylint: disable=no-member
serializer = self.serializer_class(self.course_run)
with self.assertRaises(ValidationError):
serializer.validate(serializer.data)
...@@ -7,6 +7,7 @@ from mock import patch ...@@ -7,6 +7,7 @@ from mock import patch
from django.db import IntegrityError from django.db import IntegrityError
from django.conf import settings from django.conf import settings
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core import mail
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.forms import model_to_dict from django.forms import model_to_dict
from django.test import TestCase from django.test import TestCase
...@@ -15,8 +16,9 @@ from guardian.shortcuts import assign_perm ...@@ -15,8 +16,9 @@ from guardian.shortcuts import assign_perm
from course_discovery.apps.core.models import User from course_discovery.apps.core.models import User
from course_discovery.apps.core.tests.factories import UserFactory, USER_PASSWORD from course_discovery.apps.core.tests.factories import UserFactory, USER_PASSWORD
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 import toggle_switch
from course_discovery.apps.publisher.models import Course, CourseRun, Seat, State from course_discovery.apps.publisher.models import Course, CourseRun, Seat, State
from course_discovery.apps.publisher.tests import factories from course_discovery.apps.publisher.tests import factories, JSON_CONTENT_TYPE
from course_discovery.apps.publisher.tests.utils import create_non_staff_user_and_login from course_discovery.apps.publisher.tests.utils import create_non_staff_user_and_login
from course_discovery.apps.publisher.utils import is_email_notification_enabled from course_discovery.apps.publisher.utils import is_email_notification_enabled
from course_discovery.apps.publisher.views import CourseRunDetailView from course_discovery.apps.publisher.views import CourseRunDetailView
...@@ -930,7 +932,7 @@ class DashboardTests(TestCase): ...@@ -930,7 +932,7 @@ class DashboardTests(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_different_course_runs_counts(self): def test_different_course_runs_counts(self):
""" Verify that user can access published and un-published and """ Verify that user can access published, un-published and
studio requests course runs. """ studio requests course runs. """
self.assert_dashboard_reponse(2, 1, 2) self.assert_dashboard_reponse(2, 1, 2)
...@@ -996,3 +998,79 @@ class ToggleEmailNotificationTests(TestCase): ...@@ -996,3 +998,79 @@ class ToggleEmailNotificationTests(TestCase):
user = User.objects.get(username=self.user.username) user = User.objects.get(username=self.user.username)
self.assertEqual(is_email_notification_enabled(user), is_enabled) self.assertEqual(is_email_notification_enabled(user), is_enabled)
class UpdateCourseKeyViewTests(TestCase):
""" Tests for `UpdateCourseKeyView` """
def setUp(self):
super(UpdateCourseKeyViewTests, self).setUp()
self.course_run = factories.CourseRunFactory()
self.user = UserFactory(is_staff=True, is_superuser=True)
self.group = factories.GroupFactory()
self.user.groups.add(self.group)
assign_perm(Course.VIEW_PERMISSION, self.group, self.course_run.course)
self.client.login(username=self.user.username, password=USER_PASSWORD)
self.update_course_key_url = reverse(
'publisher:publisher_course_run_detail', kwargs={'pk': self.course_run.id}
)
factories.UserAttributeFactory(user=self.user, enable_email_notification=True)
toggle_switch('enable_publisher_email_notifications', True)
def test_update_course_key_with_errors(self):
""" Test that api returns error with invalid course key."""
invalid_course_id = 'invalid-course-key'
response = self.client.patch(
self.update_course_key_url,
data=json.dumps({'lms_course_id': invalid_course_id}),
content_type=JSON_CONTENT_TYPE
)
self.assertEqual(response.status_code, 400)
self.assertEqual(
response.data.get('non_field_errors'), ['Invalid course key [{}]'.format(invalid_course_id)]
)
def test_update_course_key(self):
""" Test that user can update `lms_course_id` for a course run."""
# Verify that `lms_course_id` and `changed_by` are None
self.assert_course_key_and_changed_by()
lms_course_id = 'course-v1:edxTest+TC12+2050Q1'
response = self.client.patch(
self.update_course_key_url,
data=json.dumps({'lms_course_id': lms_course_id}),
content_type=JSON_CONTENT_TYPE
)
self.assertEqual(response.status_code, 200)
# Verify that `lms_course_id` and `changed_by` are not None
self.assert_course_key_and_changed_by(lms_course_id=lms_course_id, changed_by=self.user)
# assert email sent
self.assert_email_sent(
reverse('publisher:publisher_course_run_detail', kwargs={'pk': self.course_run.id}),
'Studio instance created',
'Studio instance created for the following course run'
)
def assert_course_key_and_changed_by(self, lms_course_id=None, changed_by=None):
self.course_run = CourseRun.objects.get(id=self.course_run.id)
self.assertEqual(self.course_run.lms_course_id, lms_course_id)
self.assertEqual(self.course_run.changed_by, changed_by)
def assert_email_sent(self, object_path, subject, expected_body):
""" DRY method to assert sent email data"""
self.assertEqual(len(mail.outbox), 1)
self.assertEqual([settings.PUBLISHER_FROM_EMAIL], mail.outbox[0].to)
self.assertEqual([self.user.email], mail.outbox[0].bcc)
self.assertEqual(str(mail.outbox[0].subject), subject)
body = mail.outbox[0].body.strip()
self.assertIn(expected_body, body)
page_url = 'https://{host}{path}'.format(host=Site.objects.get_current().domain.strip('/'), path=object_path)
self.assertIn(page_url, body)
...@@ -16,12 +16,14 @@ from django.views.generic.edit import CreateView, UpdateView ...@@ -16,12 +16,14 @@ from django.views.generic.edit import CreateView, UpdateView
from django.views.generic.list import ListView from django.views.generic.list import ListView
from django_fsm import TransitionNotAllowed from django_fsm import TransitionNotAllowed
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from rest_framework.generics import UpdateAPIView
from course_discovery.apps.publisher.forms import ( from course_discovery.apps.publisher.forms import (
CourseForm, CourseRunForm, SeatForm, CustomCourseForm, CustomCourseRunForm, CustomSeatForm CourseForm, CourseRunForm, SeatForm, CustomCourseForm, CustomCourseRunForm, CustomSeatForm
) )
from course_discovery.apps.publisher import mixins from course_discovery.apps.publisher import mixins
from course_discovery.apps.publisher.models import Course, CourseRun, Seat, State, UserAttributes from course_discovery.apps.publisher.models import Course, CourseRun, Seat, State, UserAttributes
from course_discovery.apps.publisher.serializers import UpdateCourseKeySerializer
from course_discovery.apps.publisher.wrappers import CourseRunWrapper from course_discovery.apps.publisher.wrappers import CourseRunWrapper
...@@ -68,6 +70,9 @@ class CourseRunDetailView(mixins.LoginRequiredMixin, mixins.ViewPermissionMixin, ...@@ -68,6 +70,9 @@ class CourseRunDetailView(mixins.LoginRequiredMixin, mixins.ViewPermissionMixin,
context['comment_object'] = self.object.course context['comment_object'] = self.object.course
return context return context
def patch(self, *args, **kwargs):
return UpdateCourseKeyView.as_view()(self.request, *args, **kwargs)
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
class CreateCourseView(mixins.LoginRequiredMixin, CreateView): class CreateCourseView(mixins.LoginRequiredMixin, CreateView):
...@@ -266,3 +271,8 @@ class ToggleEmailNotification(mixins.LoginRequiredMixin, View): ...@@ -266,3 +271,8 @@ class ToggleEmailNotification(mixins.LoginRequiredMixin, View):
user_attribute.save() user_attribute.save()
return JsonResponse({'is_enabled': is_enabled}) return JsonResponse({'is_enabled': is_enabled})
class UpdateCourseKeyView(mixins.LoginRequiredMixin, mixins.ViewPermissionMixin, UpdateAPIView):
queryset = CourseRun.objects.all()
serializer_class = UpdateCourseKeySerializer
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ 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: 2016-11-11 10:35+0500\n" "POT-Creation-Date: 2016-11-11 17:27+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"
...@@ -414,6 +414,10 @@ msgstr "" ...@@ -414,6 +414,10 @@ msgstr ""
msgid "Course Run {title}-{pacing_type}-{start} state has been changed." msgid "Course Run {title}-{pacing_type}-{start} state has been changed."
msgstr "" msgstr ""
#: apps/publisher/emails.py
msgid "Studio instance created"
msgstr ""
#: apps/publisher/forms.py #: apps/publisher/forms.py
msgid "Yes" msgid "Yes"
msgstr "" msgstr ""
...@@ -1531,6 +1535,8 @@ msgstr "" ...@@ -1531,6 +1535,8 @@ msgstr ""
#: templates/publisher/email/change_state.txt #: templates/publisher/email/change_state.txt
#: templates/publisher/email/comment.html #: templates/publisher/email/comment.html
#: templates/publisher/email/comment.txt #: templates/publisher/email/comment.txt
#: templates/publisher/email/studio_instance_created.html
#: templates/publisher/email/studio_instance_created.txt
msgid "The edX team" msgid "The edX team"
msgstr "" msgstr ""
...@@ -1554,6 +1560,21 @@ msgstr "" ...@@ -1554,6 +1560,21 @@ msgstr ""
msgid "View comment: " msgid "View comment: "
msgstr "" msgstr ""
#: templates/publisher/email/studio_instance_created.html
#, python-format
msgid ""
"Studio instance created for the following course run: "
"%(link_start)s%(course_run_page_url)s%(link_middle)scourse run "
"link%(link_end)s."
msgstr ""
#: templates/publisher/email/studio_instance_created.txt
#, python-format
msgid ""
"Studio instance created for the following course run: "
"%(course_run_page_url)s"
msgstr ""
#: templates/publisher/seat_form.html #: templates/publisher/seat_form.html
msgid "Seat Form" msgid "Seat Form"
msgstr "" msgstr ""
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ 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: 2016-11-11 10:35+0500\n" "POT-Creation-Date: 2016-11-11 17:27+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"
...@@ -20,6 +20,12 @@ msgstr "" ...@@ -20,6 +20,12 @@ msgstr ""
msgid "Preview" msgid "Preview"
msgstr "" msgstr ""
#: static/js/publisher/views/dashboard.js
msgid ""
"You have successfully created a studio instance ({studioLinkTag}) for "
"{courseRunDetail} with a start date of {startDate}"
msgstr ""
#: static/js/publisher/views/navbar.js #: static/js/publisher/views/navbar.js
msgid "OFF" msgid "OFF"
msgstr "" msgstr ""
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ 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: 2016-11-11 10:35+0500\n" "POT-Creation-Date: 2016-11-11 17:27+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"
...@@ -520,6 +520,10 @@ msgstr "" ...@@ -520,6 +520,10 @@ msgstr ""
"Çöürsé Rün {title}-{pacing_type}-{start} stäté häs ßéén çhängéd. Ⱡ'σяєм " "Çöürsé Rün {title}-{pacing_type}-{start} stäté häs ßéén çhängéd. Ⱡ'σяєм "
"ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#" "ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: apps/publisher/emails.py
msgid "Studio instance created"
msgstr "Stüdïö ïnstänçé çréätéd Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σ#"
#: apps/publisher/forms.py #: apps/publisher/forms.py
msgid "Yes" msgid "Yes"
msgstr "Ýés Ⱡ'σяєм#" msgstr "Ýés Ⱡ'σяєм#"
...@@ -1788,6 +1792,8 @@ msgstr "Vïéw Çöürsé Ⱡ'σяєм ιρѕυм ∂σłσя #" ...@@ -1788,6 +1792,8 @@ msgstr "Vïéw Çöürsé Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: templates/publisher/email/change_state.txt #: templates/publisher/email/change_state.txt
#: templates/publisher/email/comment.html #: templates/publisher/email/comment.html
#: templates/publisher/email/comment.txt #: templates/publisher/email/comment.txt
#: templates/publisher/email/studio_instance_created.html
#: templates/publisher/email/studio_instance_created.txt
msgid "The edX team" msgid "The edX team"
msgstr "Thé édX téäm Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#" msgstr "Thé édX téäm Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
...@@ -1813,6 +1819,26 @@ msgstr "Vïéw çömmént Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#" ...@@ -1813,6 +1819,26 @@ msgstr "Vïéw çömmént Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
msgid "View comment: " msgid "View comment: "
msgstr "Vïéw çömmént: Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#" msgstr "Vïéw çömmént: Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#"
#: templates/publisher/email/studio_instance_created.html
#, python-format
msgid ""
"Studio instance created for the following course run: "
"%(link_start)s%(course_run_page_url)s%(link_middle)scourse run "
"link%(link_end)s."
msgstr ""
"Stüdïö ïnstänçé çréätéd för thé föllöwïng çöürsé rün: "
"%(link_start)s%(course_run_page_url)s%(link_middle)sçöürsé rün "
"lïnk%(link_end)s. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тє#"
#: templates/publisher/email/studio_instance_created.txt
#, python-format
msgid ""
"Studio instance created for the following course run: "
"%(course_run_page_url)s"
msgstr ""
"Stüdïö ïnstänçé çréätéd för thé föllöwïng çöürsé rün: "
"%(course_run_page_url)s Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: templates/publisher/seat_form.html #: templates/publisher/seat_form.html
msgid "Seat Form" msgid "Seat Form"
msgstr "Séät Förm Ⱡ'σяєм ιρѕυм ∂σł#" msgstr "Séät Förm Ⱡ'σяєм ιρѕυм ∂σł#"
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ 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: 2016-11-11 10:35+0500\n" "POT-Creation-Date: 2016-11-11 17:27+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"
...@@ -21,6 +21,15 @@ msgstr "" ...@@ -21,6 +21,15 @@ msgstr ""
msgid "Preview" msgid "Preview"
msgstr "Prévïéw Ⱡ'σяєм ιρѕυм #" msgstr "Prévïéw Ⱡ'σяєм ιρѕυм #"
#: static/js/publisher/views/dashboard.js
msgid ""
"You have successfully created a studio instance ({studioLinkTag}) for "
"{courseRunDetail} with a start date of {startDate}"
msgstr ""
"Ýöü hävé süççéssfüllý çréätéd ä stüdïö ïnstänçé ({studioLinkTag}) för "
"{courseRunDetail} wïth ä stärt däté öf {startDate} Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт "
"αмєт, ¢σηѕє¢#"
#: static/js/publisher/views/navbar.js #: static/js/publisher/views/navbar.js
msgid "OFF" msgid "OFF"
msgstr "ÖFF Ⱡ'σяєм#" msgstr "ÖFF Ⱡ'σяєм#"
......
...@@ -18,9 +18,3 @@ $(document).ready(function(){ ...@@ -18,9 +18,3 @@ $(document).ready(function(){
}) })
}); });
function alertTimeout(wait) {
setTimeout(function(){
$('.alert-messages').html('');
}, wait);
}
$(document).ready(function() { $(document).ready(function() {
$('.data-table-studio').addClass('nowrap').DataTable({ var data_table_studio = $('.data-table-studio').addClass('nowrap').DataTable({
"autoWidth": false "autoWidth": false
}); });
data_table_studio.on('click', '.btn-add-course-key', function(e){
var $courseRunParentTag = $(this).parent().parent(),
courseKeyInput = $courseRunParentTag.find('input');
if (!courseKeyInput.val().trim()){
courseKeyInput.focus();
return;
}
var courseRunPageURL = $(this).data('courseRunUrl'),
courseKeyValue = courseKeyInput.val().trim(),
courseTitleTag = $courseRunParentTag.find("#course-title").html().trim(),
startDateTag = $courseRunParentTag.find("#course-start").html().trim(),
headers = {
'X-CSRFToken': Cookies.get('course_discovery_csrftoken')
},
$studioInstanceSuccess = $(".studio-instance-success"),
successMessage = interpolateString(
gettext("You have successfully created a studio instance ({studioLinkTag}) for {courseRunDetail} with a start date of {startDate}"),
{
"studioLinkTag": "<a href=''>"+ courseKeyValue +"</a>",
"courseRunDetail": courseTitleTag,
"startDate": startDateTag
}
);
e.preventDefault();
$.ajax({
url: courseRunPageURL,
type: "PATCH",
data: JSON.stringify({lms_course_id: courseKeyValue}),
contentType: "application/json",
headers: headers,
success: function (response) {
data_table_studio.row($courseRunParentTag).remove().draw();
$("#studio-count").html(data_table_studio.rows().count());
$studioInstanceSuccess.find(".copy-meta").html(successMessage);
$studioInstanceSuccess.css("display", "block");
}
});
});
}); });
...@@ -15,3 +15,18 @@ function addDatePicker() { ...@@ -15,3 +15,18 @@ function addDatePicker() {
} }
}); });
} }
function interpolateString(formatString, parameters) {
return formatString.replace(/{\w+}/g,
function(parameter) {
var parameterName = parameter.slice(1,-1);
return String(parameters[parameterName]);
});
}
function alertTimeout(wait, elementName) {
var element = elementName || ".alert-messages";
setTimeout(function(){
$(element).hide();
}, wait);
}
.publisher-container { .publisher-container {
font-family: "Open Sans", Arial, Helvetica, sans-serif; font-family: "Open Sans", Arial, Helvetica, sans-serif;
.studio-instance-success {
display: none;
p.copy-meta {
@include padding(15px, 15px, 15px, 15px);
}
}
.tabs { .tabs {
@include padding(0, 0, 0, 0); @include padding(0, 0, 0, 0);
@include margin(0, 0, 0, 0); @include margin(0, 0, 0, 0);
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<ul role="tablist" class="tabs"> <ul role="tablist" class="tabs">
<li role="tab" id="tab-progress" class="tab" aria-selected="true" aria-expanded="false" aria-controls="progress" tabindex="0"><span>0</span>{% trans "IN PROGRESS" %}</li> <li role="tab" id="tab-progress" class="tab" aria-selected="true" aria-expanded="false" aria-controls="progress" tabindex="0"><span>0</span>{% trans "IN PROGRESS" %}</li>
<li role="tab" id="tab-preview" class="tab" aria-selected="false" aria-expanded="false" aria-controls="preview" tabindex="-1"><span>0</span>{% trans "PREVIEW READY" %}</li> <li role="tab" id="tab-preview" class="tab" aria-selected="false" aria-expanded="false" aria-controls="preview" tabindex="-1"><span>0</span>{% trans "PREVIEW READY" %}</li>
<li role="tab" id="tab-studio" class="tab" aria-selected="false" aria-expanded="true" aria-controls="studio" tabindex="-1" data-studio-count="{{ studio_count }}"><span>{{ studio_count }}</span>{% trans "STUDIO REQUEST" %}</li> <li role="tab" id="tab-studio" class="tab" aria-selected="false" aria-expanded="true" aria-controls="studio" tabindex="-1" data-studio-count="{{ studio_count }}"><span id="studio-count">{{ studio_count }}</span>{% trans "STUDIO REQUEST" %}</li>
</ul> </ul>
<div role="tabpanel" id="progress" class="tab-panel" aria-labelledby="tab-progress" aria-hidden="false" tabindex="-1"> <div role="tabpanel" id="progress" class="tab-panel" aria-labelledby="tab-progress" aria-hidden="false" tabindex="-1">
......
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
{% if studio_count > 0 %} {% if studio_count > 0 %}
<p>{% trans "The list below are the courses that need a studio instance to start development." %}</p> <p>{% trans "The list below are the courses that need a studio instance to start development." %}</p>
<div class="studio-instance-success depth depth-0">
<p class="copy-meta"></p>
</div>
<div class="table-view"> <div class="table-view">
<table class="data-table-studio display" cellspacing="0" width="100%"> <table class="data-table-studio display" cellspacing="0" width="100%">
<thead> <thead>
...@@ -26,22 +29,21 @@ ...@@ -26,22 +29,21 @@
<tbody> <tbody>
{% for course_run in studio_request_courses %} {% for course_run in studio_request_courses %}
<tr> <tr>
<td id="course-title-{{ course_run.title }}"> {% url 'publisher:publisher_course_run_detail' course_run.id as run_page_url %}
<a href="{% url 'publisher:publisher_course_run_detail' course_run.id %}">{{ course_run.title }}</a> <td id="course-title-{{ course_run.title }}">
</td> <a href="{{ run_page_url }}" id="course-title">{{ course_run.title }}</a>
<td>{% if course_run.course.group_institution %}{{ course_run.course.group_institution }}{% endif %}</td> </td>
<td> <td>{% if course_run.course.group_institution %}{{ course_run.course.group_institution }}{% endif %}</td>
{{ course_run.start|date:"Y-m-d" }} <td id="course-start">
</td> {{ course_run.start|date:"Y-m-d" }}
<td> </td>
{{ course_run.number }} <td>
</td> {{ course_run.number }}
<td> </td>
<div 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 class="btn-inline">{% trans "Add" %}</button> <button data-course-run-url="{{ run_page_url }}" class="btn-inline btn-add-course-key">{% trans "Add" %}</button>
</div> </td>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
......
{% extends "publisher/email/email_base.html" %}
{% load i18n %}
{% block body %}
<!-- Message Body -->
<p>
{% blocktrans with link_start='<a href="' link_middle='">' link_end='</a>' trimmed %}
Studio instance created for the following course run: {{ link_start }}{{ course_run_page_url }}{{ link_middle }}course run link{{ link_end }}.
{% endblocktrans %}
</p>
<p>{% trans "The edX team" %}</p>
<!-- End Message Body -->
{% endblock body %}
{% load i18n %}
{% blocktrans trimmed %}
Studio instance created for the following course run: {{ course_run_page_url }}
{% endblocktrans %}
{% trans "The edX team" %}
...@@ -14,3 +14,4 @@ nose-ignore-docstring==0.2 ...@@ -14,3 +14,4 @@ nose-ignore-docstring==0.2
pep8==1.7.0 pep8==1.7.0
responses==0.5.1 responses==0.5.1
selenium==2.53.6 selenium==2.53.6
testfixtures==4.13.1
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