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
from django.utils.translation import ugettext_lazy as _
log = logging.getLogger(__name__)
logger = logging.getLogger(__name__)
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.send()
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
from course_discovery.apps.course_metadata.utils import UploadToFieldNamePath
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.utils import is_email_notification_enabled
logger = logging.getLogger(__name__)
......@@ -167,10 +169,8 @@ class Course(TimeStampedModel, ChangedByMixin):
then user will be eligible for emails.
"""
users_list = get_users_with_perms(self)
emails = [
user.email for user in users_list
if not hasattr(user, 'attributes') or user.attributes.enable_email_notification
]
emails = [user.email for user in users_list if is_email_notification_enabled(user)]
return emails
@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
from guardian.shortcuts import assign_perm
import pytz
import mock
from testfixtures import LogCapture
from course_discovery.apps.core.tests.factories import UserFactory
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.tests import factories
from course_discovery.apps.publisher.tests.factories import UserAttributeFactory
......@@ -121,3 +123,60 @@ class StateChangeEmailTests(TestCase):
# add the start date again for other tests.
self.course_run.start = datetime.datetime.now(pytz.UTC)
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
from django.db import IntegrityError
from django.conf import settings
from django.contrib.sites.models import Site
from django.core import mail
from django.core.urlresolvers import reverse
from django.forms import model_to_dict
from django.test import TestCase
......@@ -15,8 +16,9 @@ from guardian.shortcuts import assign_perm
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.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.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.utils import is_email_notification_enabled
from course_discovery.apps.publisher.views import CourseRunDetailView
......@@ -930,7 +932,7 @@ class DashboardTests(TestCase):
self.assertEqual(response.status_code, 200)
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. """
self.assert_dashboard_reponse(2, 1, 2)
......@@ -996,3 +998,79 @@ class ToggleEmailNotificationTests(TestCase):
user = User.objects.get(username=self.user.username)
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
from django.views.generic.list import ListView
from django_fsm import TransitionNotAllowed
from guardian.shortcuts import get_objects_for_user
from rest_framework.generics import UpdateAPIView
from course_discovery.apps.publisher.forms import (
CourseForm, CourseRunForm, SeatForm, CustomCourseForm, CustomCourseRunForm, CustomSeatForm
)
from course_discovery.apps.publisher import mixins
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
......@@ -68,6 +70,9 @@ class CourseRunDetailView(mixins.LoginRequiredMixin, mixins.ViewPermissionMixin,
context['comment_object'] = self.object.course
return context
def patch(self, *args, **kwargs):
return UpdateCourseKeyView.as_view()(self.request, *args, **kwargs)
# pylint: disable=attribute-defined-outside-init
class CreateCourseView(mixins.LoginRequiredMixin, CreateView):
......@@ -266,3 +271,8 @@ class ToggleEmailNotification(mixins.LoginRequiredMixin, View):
user_attribute.save()
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 ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -414,6 +414,10 @@ msgstr ""
msgid "Course Run {title}-{pacing_type}-{start} state has been changed."
msgstr ""
#: apps/publisher/emails.py
msgid "Studio instance created"
msgstr ""
#: apps/publisher/forms.py
msgid "Yes"
msgstr ""
......@@ -1531,6 +1535,8 @@ msgstr ""
#: templates/publisher/email/change_state.txt
#: templates/publisher/email/comment.html
#: templates/publisher/email/comment.txt
#: templates/publisher/email/studio_instance_created.html
#: templates/publisher/email/studio_instance_created.txt
msgid "The edX team"
msgstr ""
......@@ -1554,6 +1560,21 @@ msgstr ""
msgid "View comment: "
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
msgid "Seat Form"
msgstr ""
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -20,6 +20,12 @@ msgstr ""
msgid "Preview"
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
msgid "OFF"
msgstr ""
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -520,6 +520,10 @@ msgstr ""
"Çöü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
msgid "Yes"
msgstr "Ýés Ⱡ'σяєм#"
......@@ -1788,6 +1792,8 @@ msgstr "Vïéw Çöürsé Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: templates/publisher/email/change_state.txt
#: templates/publisher/email/comment.html
#: templates/publisher/email/comment.txt
#: templates/publisher/email/studio_instance_created.html
#: templates/publisher/email/studio_instance_created.txt
msgid "The edX team"
msgstr "Thé édX téäm Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
......@@ -1813,6 +1819,26 @@ msgstr "Vïéw çömmént Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
msgid "View comment: "
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
msgid "Seat Form"
msgstr "Séät Förm Ⱡ'σяєм ιρѕυм ∂σł#"
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -21,6 +21,15 @@ msgstr ""
msgid "Preview"
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
msgid "OFF"
msgstr "ÖFF Ⱡ'σяєм#"
......
......@@ -18,9 +18,3 @@ $(document).ready(function(){
})
});
function alertTimeout(wait) {
setTimeout(function(){
$('.alert-messages').html('');
}, wait);
}
$(document).ready(function() {
$('.data-table-studio').addClass('nowrap').DataTable({
var data_table_studio = $('.data-table-studio').addClass('nowrap').DataTable({
"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() {
}
});
}
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 {
font-family: "Open Sans", Arial, Helvetica, sans-serif;
.studio-instance-success {
display: none;
p.copy-meta {
@include padding(15px, 15px, 15px, 15px);
}
}
.tabs {
@include padding(0, 0, 0, 0);
@include margin(0, 0, 0, 0);
......
......@@ -13,7 +13,7 @@
<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-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>
<div role="tabpanel" id="progress" class="tab-panel" aria-labelledby="tab-progress" aria-hidden="false" tabindex="-1">
......
......@@ -2,6 +2,9 @@
{% if studio_count > 0 %}
<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">
<table class="data-table-studio display" cellspacing="0" width="100%">
<thead>
......@@ -26,21 +29,20 @@
<tbody>
{% for course_run in studio_request_courses %}
<tr>
{% url 'publisher:publisher_course_run_detail' course_run.id as run_page_url %}
<td id="course-title-{{ course_run.title }}">
<a href="{% url 'publisher:publisher_course_run_detail' course_run.id %}">{{ course_run.title }}</a>
<a href="{{ run_page_url }}" id="course-title">{{ course_run.title }}</a>
</td>
<td>{% if course_run.course.group_institution %}{{ course_run.course.group_institution }}{% endif %}</td>
<td>
<td id="course-start">
{{ course_run.start|date:"Y-m-d" }}
</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" />
<button class="btn-inline">{% trans "Add" %}</button>
</div>
<button data-course-run-url="{{ run_page_url }}" class="btn-inline btn-add-course-key">{% trans "Add" %}</button>
</td>
</tr>
{% endfor %}
......
{% 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
pep8==1.7.0
responses==0.5.1
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