');
+ $('.new-course-save').addClass('is-disabled');
+ });
};
var cancelNewCourse = function (e) {
@@ -78,91 +76,20 @@ require(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape"],
$cancelButton.bind('click', cancelNewCourse);
CancelOnEscape($cancelButton);
- // Check that a course (org, number, run) doesn't use any special characters
- var validateCourseItemEncoding = function (item) {
- var required = validateRequiredField(item);
- if (required) {
- return required;
- }
- if ($('.allow-unicode-course-id').val() === 'True'){
- if (/\s/g.test(item)) {
- return gettext('Please do not use any spaces in this field.');
- }
- }
- else{
- if (item !== encodeURIComponent(item)) {
- return gettext('Please do not use any spaces or special characters in this field.');
- }
- }
- return '';
- };
-
- // Ensure that org/course_num/run < 65 chars.
- var validateTotalCourseItemsLength = function () {
- var totalLength = _.reduce(
- ['.new-course-org', '.new-course-number', '.new-course-run'],
- function (sum, ele) {
- return sum + $(ele).val().length;
- }, 0
- );
- if (totalLength > 65) {
- $('.wrap-error').addClass('is-shown');
- $('#course_creation_error').html('
' + gettext('The combined length of the organization, course number, and course run fields cannot be more than 65 characters.') + '
');
- $('.new-course-save').addClass('is-disabled');
- }
- else {
- $('.wrap-error').removeClass('is-shown');
- }
- };
-
- // Handle validation asynchronously
- _.each(
- ['.new-course-org', '.new-course-number', '.new-course-run'],
- function (ele) {
- var $ele = $(ele);
- $ele.on('keyup', function (event) {
- // Don't bother showing "required field" error when
- // the user tabs into a new field; this is distracting
- // and unnecessary
- if (event.keyCode === 9) {
- return;
- }
- var error = validateCourseItemEncoding($ele.val());
- setNewCourseFieldInErr($ele.parent('li'), error);
- validateTotalCourseItemsLength();
- });
- }
- );
- var $name = $('.new-course-name');
- $name.on('keyup', function () {
- var error = validateRequiredField($name.val());
- setNewCourseFieldInErr($name.parent('li'), error);
- validateTotalCourseItemsLength();
- });
+ CreateCourseUtils.configureHandlers();
};
- var validateRequiredField = function (msg) {
- return msg.length === 0 ? gettext('Required field.') : '';
- };
-
- var setNewCourseFieldInErr = function (el, msg) {
- if(msg) {
- el.addClass('error');
- el.children('span.tip-error').addClass('is-showing').removeClass('is-hiding').text(msg);
- $('.new-course-save').addClass('is-disabled');
- }
- else {
- el.removeClass('error');
- el.children('span.tip-error').addClass('is-hiding').removeClass('is-showing');
- // One "error" div is always present, but hidden or shown
- if($('.error').length === 1) {
- $('.new-course-save').removeClass('is-disabled');
- }
- }
- };
-
-
- domReady(function () {
+ var onReady = function () {
$('.new-course-button').bind('click', addNewCourse);
- });
+ $('.dismiss-button').bind('click', ViewUtils.deleteNotificationHandler(function () {
+ ViewUtils.reload();
+ }));
+ $('.action-reload').bind('click', ViewUtils.reload);
+ };
+
+ domReady(onReady);
+
+ return {
+ onReady: onReady
+ };
});
diff --git a/cms/static/js/spec/views/pages/course_outline_spec.js b/cms/static/js/spec/views/pages/course_outline_spec.js
index 352c201d9a..a13537051d 100644
--- a/cms/static/js/spec/views/pages/course_outline_spec.js
+++ b/cms/static/js/spec/views/pages/course_outline_spec.js
@@ -7,7 +7,8 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
getItemsOfType, getItemHeaders, verifyItemsExpanded, expandItemsAndVerifyState, collapseItemsAndVerifyState,
createMockCourseJSON, createMockSectionJSON, createMockSubsectionJSON, verifyTypePublishable,
mockCourseJSON, mockEmptyCourseJSON, mockSingleSectionCourseJSON, createMockVerticalJSON,
- mockOutlinePage = readFixtures('mock/mock-course-outline-page.underscore');
+ mockOutlinePage = readFixtures('mock/mock-course-outline-page.underscore'),
+ mockRerunNotification = readFixtures('mock/mock-course-rerun-notification.underscore');
createMockCourseJSON = function(options, children) {
return $.extend(true, {}, {
@@ -243,6 +244,18 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
});
});
+ describe("Rerun notification", function () {
+ it("can be dismissed", function () {
+ appendSetFixtures(mockRerunNotification);
+ createCourseOutlinePage(this, mockEmptyCourseJSON);
+ expect($('.wrapper-alert-announcement')).not.toHaveClass('is-hidden');
+ $('.dismiss-button').click();
+ create_sinon.expectJsonRequest(requests, 'DELETE', 'dummy_dismiss_url');
+ create_sinon.respondToDelete(requests);
+ expect($('.wrapper-alert-announcement')).toHaveClass('is-hidden');
+ });
+ });
+
describe("Button bar", function() {
it('can add a section', function() {
createCourseOutlinePage(this, mockEmptyCourseJSON);
diff --git a/cms/static/js/spec/views/pages/course_rerun_spec.js b/cms/static/js/spec/views/pages/course_rerun_spec.js
new file mode 100644
index 0000000000..5a9eb0b87b
--- /dev/null
+++ b/cms/static/js/spec/views/pages/course_rerun_spec.js
@@ -0,0 +1,205 @@
+define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers", "js/views/course_rerun",
+ "js/views/utils/create_course_utils", "js/views/utils/view_utils", "jquery.simulate"],
+ function ($, create_sinon, view_helpers, CourseRerunUtils, CreateCourseUtilsFactory, ViewUtils) {
+ describe("Create course rerun page", function () {
+ var selectors = {
+ org: '.rerun-course-org',
+ number: '.rerun-course-number',
+ run: '.rerun-course-run',
+ name: '.rerun-course-name',
+ tipError: 'span.tip-error',
+ save: '.rerun-course-save',
+ cancel: '.rerun-course-cancel',
+ errorWrapper: '.wrapper-error',
+ errorMessage: '#course_rerun_error',
+ error: '.error',
+ allowUnicode: '.allow-unicode-course-id'
+ },
+ classes = {
+ shown: 'is-shown',
+ showing: 'is-showing',
+ hiding: 'is-hidden',
+ hidden: 'is-hidden',
+ error: 'error',
+ disabled: 'is-disabled',
+ processing: 'is-processing'
+ },
+ mockCreateCourseRerunHTML = readFixtures('mock/mock-create-course-rerun.underscore');
+
+ var CreateCourseUtils = CreateCourseUtilsFactory(selectors, classes);
+
+ var fillInFields = function (org, number, run, name) {
+ $(selectors.org).val(org);
+ $(selectors.number).val(number);
+ $(selectors.run).val(run);
+ $(selectors.name).val(name);
+ };
+
+ beforeEach(function () {
+ view_helpers.installMockAnalytics();
+ window.source_course_key = 'test_course_key';
+ appendSetFixtures(mockCreateCourseRerunHTML);
+ CourseRerunUtils.onReady();
+ });
+
+ afterEach(function () {
+ view_helpers.removeMockAnalytics();
+ delete window.source_course_key;
+ });
+
+ describe("Field validation", function () {
+ it("returns a message for an empty string", function () {
+ var message = CreateCourseUtils.validateRequiredField('');
+ expect(message).not.toBe('');
+ });
+
+ it("does not return a message for a non empty string", function () {
+ var message = CreateCourseUtils.validateRequiredField('edX');
+ expect(message).toBe('');
+ });
+ });
+
+ describe("Error messages", function () {
+ var setErrorMessage = function(selector, message) {
+ var element = $(selector).parent();
+ CreateCourseUtils.setNewCourseFieldInErr(element, message);
+ return element;
+ };
+
+ var type = function (input, value) {
+ input.val(value);
+ input.simulate("keyup", { keyCode: $.simulate.keyCode.SPACE });
+ };
+
+ it("shows an error message", function () {
+ var element = setErrorMessage(selectors.org, 'error message');
+ expect(element).toHaveClass(classes.error);
+ expect(element.children(selectors.tipError)).not.toHaveClass(classes.hidden);
+ expect(element.children(selectors.tipError)).toContainText('error message');
+ });
+
+ it("hides an error message", function () {
+ var element = setErrorMessage(selectors.org, '');
+ expect(element).not.toHaveClass(classes.error);
+ expect(element.children(selectors.tipError)).toHaveClass(classes.hidden);
+ });
+
+ it("disables the save button", function () {
+ setErrorMessage(selectors.org, 'error message');
+ expect($(selectors.save)).toHaveClass(classes.disabled);
+ });
+
+ it("enables the save button when all errors are removed", function () {
+ setErrorMessage(selectors.org, 'error message 1');
+ setErrorMessage(selectors.number, 'error message 2');
+ expect($(selectors.save)).toHaveClass(classes.disabled);
+ setErrorMessage(selectors.org, '');
+ setErrorMessage(selectors.number, '');
+ expect($(selectors.save)).not.toHaveClass(classes.disabled);
+ });
+
+ it("does not enable the save button when errors remain", function () {
+ setErrorMessage(selectors.org, 'error message 1');
+ setErrorMessage(selectors.number, 'error message 2');
+ expect($(selectors.save)).toHaveClass(classes.disabled);
+ setErrorMessage(selectors.org, '');
+ expect($(selectors.save)).toHaveClass(classes.disabled);
+ });
+
+ it("shows an error message when non URL characters are entered", function () {
+ var input = $(selectors.org);
+ expect(input.parent()).not.toHaveClass(classes.error);
+ type(input, "%");
+ expect(input.parent()).toHaveClass(classes.error);
+ });
+
+ it("does not show an error message when tabbing into a field", function () {
+ var input = $(selectors.number);
+ input.val('');
+ expect(input.parent()).not.toHaveClass(classes.error);
+ input.simulate("keyup", { keyCode: $.simulate.keyCode.TAB });
+ expect(input.parent()).not.toHaveClass(classes.error);
+ });
+
+ it("shows an error message when a required field is empty", function () {
+ var input = $(selectors.org);
+ input.val('');
+ expect(input.parent()).not.toHaveClass(classes.error);
+ input.simulate("keyup", { keyCode: $.simulate.keyCode.ENTER });
+ expect(input.parent()).toHaveClass(classes.error);
+ });
+
+ it("shows an error message when spaces are entered and unicode is allowed", function () {
+ var input = $(selectors.org);
+ $(selectors.allowUnicode).val('True');
+ expect(input.parent()).not.toHaveClass(classes.error);
+ type(input, ' ');
+ expect(input.parent()).toHaveClass(classes.error);
+ });
+
+ it("shows an error message when total length exceeds 65 characters", function () {
+ expect($(selectors.errorWrapper)).not.toHaveClass(classes.shown);
+ type($(selectors.org), 'ThisIsAVeryLongNameThatWillExceedTheSixtyFiveCharacterLimit');
+ type($(selectors.number), 'ThisIsAVeryLongNameThatWillExceedTheSixtyFiveCharacterLimit');
+ type($(selectors.run), 'ThisIsAVeryLongNameThatWillExceedTheSixtyFiveCharacterLimit');
+ expect($(selectors.errorWrapper)).toHaveClass(classes.shown);
+ });
+
+ describe("Name field", function () {
+ it("does not show an error message when non URL characters are entered", function () {
+ var input = $(selectors.name);
+ expect(input.parent()).not.toHaveClass(classes.error);
+ type(input, "%");
+ expect(input.parent()).not.toHaveClass(classes.error);
+ });
+ });
+ });
+
+ it("saves course reruns", function () {
+ var requests = create_sinon.requests(this);
+ var redirectSpy = spyOn(ViewUtils, 'redirect')
+ fillInFields('DemoX', 'DM101', '2014', 'Demo course');
+ $(selectors.save).click();
+ create_sinon.expectJsonRequest(requests, 'POST', '/course/', {
+ source_course_key: 'test_course_key',
+ org: 'DemoX',
+ number: 'DM101',
+ run: '2014',
+ display_name: 'Demo course'
+ });
+ expect($(selectors.save)).toHaveClass(classes.disabled);
+ expect($(selectors.save)).toHaveClass(classes.processing);
+ expect($(selectors.cancel)).toHaveClass(classes.hidden);
+ create_sinon.respondWithJson(requests, {
+ url: 'dummy_test_url'
+ });
+ expect(redirectSpy).toHaveBeenCalledWith('dummy_test_url');
+ });
+
+ it("displays an error when saving fails", function () {
+ var requests = create_sinon.requests(this);
+ fillInFields('DemoX', 'DM101', '2014', 'Demo course');
+ $(selectors.save).click();
+ create_sinon.respondWithJson(requests, {
+ ErrMsg: 'error message'
+ });
+ expect($(selectors.errorWrapper)).not.toHaveClass(classes.hidden);
+ expect($(selectors.errorWrapper)).toContainText('error message');
+ expect($(selectors.save)).not.toHaveClass(classes.processing);
+ expect($(selectors.cancel)).not.toHaveClass(classes.hidden);
+ });
+
+ it("does not save if there are validation errors", function () {
+ var requests = create_sinon.requests(this);
+ fillInFields('DemoX', 'DM101', '', 'Demo course');
+ $(selectors.save).click();
+ expect(requests.length).toBe(0);
+ });
+
+ it("can be canceled", function () {
+ var redirectSpy = spyOn(ViewUtils, 'redirect');
+ $(selectors.cancel).click();
+ expect(redirectSpy).toHaveBeenCalledWith('/course/');
+ });
+ });
+ });
diff --git a/cms/static/js/spec/views/pages/index_spec.js b/cms/static/js/spec/views/pages/index_spec.js
new file mode 100644
index 0000000000..fab5b9653e
--- /dev/null
+++ b/cms/static/js/spec/views/pages/index_spec.js
@@ -0,0 +1,65 @@
+define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers", "js/index",
+ "js/views/utils/view_utils"],
+ function ($, create_sinon, view_helpers, IndexUtils, ViewUtils) {
+ describe("Course listing page", function () {
+ var mockIndexPageHTML = readFixtures('mock/mock-index-page.underscore'), fillInFields;
+
+ var fillInFields = function (org, number, run, name) {
+ $('.new-course-org').val(org);
+ $('.new-course-number').val(number);
+ $('.new-course-run').val(run);
+ $('.new-course-name').val(name);
+ };
+
+ beforeEach(function () {
+ view_helpers.installMockAnalytics();
+ appendSetFixtures(mockIndexPageHTML);
+ IndexUtils.onReady();
+ });
+
+ afterEach(function () {
+ view_helpers.removeMockAnalytics();
+ delete window.source_course_key;
+ });
+
+ it("can dismiss notifications", function () {
+ var requests = create_sinon.requests(this);
+ var reloadSpy = spyOn(ViewUtils, 'reload');
+ $('.dismiss-button').click();
+ create_sinon.expectJsonRequest(requests, 'DELETE', 'dummy_dismiss_url');
+ create_sinon.respondToDelete(requests);
+ expect(reloadSpy).toHaveBeenCalled();
+ });
+
+ it("saves new courses", function () {
+ var requests = create_sinon.requests(this);
+ var redirectSpy = spyOn(ViewUtils, 'redirect');
+ $('.new-course-button').click()
+ fillInFields('DemoX', 'DM101', '2014', 'Demo course');
+ $('.new-course-save').click();
+ create_sinon.expectJsonRequest(requests, 'POST', '/course/', {
+ org: 'DemoX',
+ number: 'DM101',
+ run: '2014',
+ display_name: 'Demo course'
+ });
+ create_sinon.respondWithJson(requests, {
+ url: 'dummy_test_url'
+ });
+ expect(redirectSpy).toHaveBeenCalledWith('dummy_test_url');
+ });
+
+ it("displays an error when saving fails", function () {
+ var requests = create_sinon.requests(this);
+ $('.new-course-button').click();
+ fillInFields('DemoX', 'DM101', '2014', 'Demo course');
+ $('.new-course-save').click();
+ create_sinon.respondWithJson(requests, {
+ ErrMsg: 'error message'
+ });
+ expect($('.wrap-error')).toHaveClass('is-shown');
+ expect($('#course_creation_error')).toContainText('error message');
+ expect($('.new-course-save')).toHaveClass('is-disabled');
+ });
+ });
+ });
diff --git a/cms/static/js/views/course_rerun.js b/cms/static/js/views/course_rerun.js
new file mode 100644
index 0000000000..bfa6370d1b
--- /dev/null
+++ b/cms/static/js/views/course_rerun.js
@@ -0,0 +1,87 @@
+define(["domReady", "jquery", "underscore", "js/views/utils/create_course_utils", "js/views/utils/view_utils"],
+ function (domReady, $, _, CreateCourseUtilsFactory, ViewUtils) {
+ var CreateCourseUtils = CreateCourseUtilsFactory({
+ name: '.rerun-course-name',
+ org: '.rerun-course-org',
+ number: '.rerun-course-number',
+ run: '.rerun-course-run',
+ save: '.rerun-course-save',
+ errorWrapper: '.wrapper-error',
+ errorMessage: '#course_rerun_error',
+ tipError: 'span.tip-error',
+ error: '.error',
+ allowUnicode: '.allow-unicode-course-id'
+ }, {
+ shown: 'is-shown',
+ showing: 'is-showing',
+ hiding: 'is-hidden',
+ disabled: 'is-disabled',
+ error: 'error'
+ });
+
+ var saveRerunCourse = function (e) {
+ e.preventDefault();
+
+ if (CreateCourseUtils.hasInvalidRequiredFields()) {
+ return;
+ }
+
+ var $newCourseForm = $(this).closest('#rerun-course-form');
+ var display_name = $newCourseForm.find('.rerun-course-name').val();
+ var org = $newCourseForm.find('.rerun-course-org').val();
+ var number = $newCourseForm.find('.rerun-course-number').val();
+ var run = $newCourseForm.find('.rerun-course-run').val();
+
+ course_info = {
+ source_course_key: source_course_key,
+ org: org,
+ number: number,
+ display_name: display_name,
+ run: run
+ };
+
+ analytics.track('Reran a Course', course_info);
+ CreateCourseUtils.createCourse(course_info, function (errorMessage) {
+ $('.wrapper-error').addClass('is-shown').removeClass('is-hidden');
+ $('#course_rerun_error').html('
' + errorMessage + '
');
+ $('.rerun-course-save').addClass('is-disabled').removeClass('is-processing').html(gettext('Create Re-run'));
+ $('.action-cancel').removeClass('is-hidden');
+ });
+
+ // Go into creating re-run state
+ $('.rerun-course-save').addClass('is-disabled').addClass('is-processing').html(
+ '' + gettext('Processing Re-run Request')
+ );
+ $('.action-cancel').addClass('is-hidden');
+ };
+
+ var cancelRerunCourse = function (e) {
+ e.preventDefault();
+ // Clear out existing fields and errors
+ $('.rerun-course-run').val('');
+ $('#course_rerun_error').html('');
+ $('wrapper-error').removeClass('is-shown').addClass('is-hidden');
+ $('.rerun-course-save').off('click');
+ ViewUtils.redirect('/course/');
+ };
+
+ var onReady = function () {
+ var $cancelButton = $('.rerun-course-cancel');
+ var $courseRun = $('.rerun-course-run');
+ $courseRun.focus().select();
+ $('.rerun-course-save').on('click', saveRerunCourse);
+ $cancelButton.bind('click', cancelRerunCourse);
+ $('.cancel-button').bind('click', cancelRerunCourse);
+
+ CreateCourseUtils.configureHandlers();
+ };
+
+ domReady(onReady);
+
+ // Return these functions so that they can be tested
+ return {
+ saveRerunCourse: saveRerunCourse,
+ cancelRerunCourse: cancelRerunCourse,
+ onReady: onReady
+ };
+ });
diff --git a/cms/static/js/views/overview.js b/cms/static/js/views/overview.js
index f33f0c908f..f83d46ee9e 100644
--- a/cms/static/js/views/overview.js
+++ b/cms/static/js/views/overview.js
@@ -1,7 +1,7 @@
define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/feedback_notification",
- "js/utils/cancel_on_escape", "js/utils/date_utils", "js/utils/module"],
+ "js/utils/cancel_on_escape", "js/utils/date_utils", "js/utils/module", "js/views/utils/view_utils"],
function (domReady, $, ui, _, gettext, NotificationView, CancelOnEscape,
- DateUtils, ModuleUtils) {
+ DateUtils, ModuleUtils, ViewUtils) {
var modalSelector = '.edit-section-publish-settings';
@@ -222,6 +222,10 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
$('.toggle-button-sections').bind('click', toggleSections);
$('.expand-collapse').bind('click', toggleSubmodules);
+ $('.dismiss-button').bind('click', ViewUtils.deleteNotificationHandler(function () {
+ $('.wrapper-alert-announcement').remove();
+ }));
+
var $body = $('body');
$body.on('click', '.section-published-date .edit-release-date', editSectionPublishDate);
$body.on('click', '.edit-section-publish-settings .action-save', saveSetSectionScheduleDate);
diff --git a/cms/static/js/views/pages/course_outline.js b/cms/static/js/views/pages/course_outline.js
index a227642945..a812eb7b02 100644
--- a/cms/static/js/views/pages/course_outline.js
+++ b/cms/static/js/views/pages/course_outline.js
@@ -2,8 +2,8 @@
* This page is used to show the user an outline of the course.
*/
define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views/utils/xblock_utils",
- "js/views/course_outline"],
- function ($, _, gettext, BasePage, XBlockViewUtils, CourseOutlineView) {
+ "js/views/course_outline", "js/views/utils/view_utils"],
+ function ($, _, gettext, BasePage, XBlockViewUtils, CourseOutlineView, ViewUtils) {
var expandedLocators, CourseOutlinePage;
CourseOutlinePage = BasePage.extend({
@@ -25,6 +25,9 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
self.outlineView.handleAddEvent(event);
});
this.model.on('change', this.setCollapseExpandVisibility, this);
+ $('.dismiss-button').bind('click', ViewUtils.deleteNotificationHandler(function () {
+ $('.wrapper-alert-announcement').removeClass('is-shown').addClass('is-hidden')
+ }));
},
setCollapseExpandVisibility: function() {
diff --git a/cms/static/js/views/utils/create_course_utils.js b/cms/static/js/views/utils/create_course_utils.js
new file mode 100644
index 0000000000..2c0c3493ac
--- /dev/null
+++ b/cms/static/js/views/utils/create_course_utils.js
@@ -0,0 +1,151 @@
+/**
+ * Provides utilities for validating courses during creation, for both new courses and reruns.
+ */
+define(["jquery", "underscore", "gettext", "js/views/utils/view_utils"],
+ function ($, _, gettext, ViewUtils) {
+ return function (selectors, classes) {
+ var validateRequiredField, validateCourseItemEncoding, validateTotalCourseItemsLength, setNewCourseFieldInErr,
+ hasInvalidRequiredFields, createCourse, validateFilledFields, configureHandlers;
+
+ validateRequiredField = function (msg) {
+ return msg.length === 0 ? gettext('Required field.') : '';
+ };
+
+ // Check that a course (org, number, run) doesn't use any special characters
+ validateCourseItemEncoding = function (item) {
+ var required = validateRequiredField(item);
+ if (required) {
+ return required;
+ }
+ if ($(selectors.allowUnicode).val() === 'True') {
+ if (/\s/g.test(item)) {
+ return gettext('Please do not use any spaces in this field.');
+ }
+ }
+ else {
+ if (item !== encodeURIComponent(item)) {
+ return gettext('Please do not use any spaces or special characters in this field.');
+ }
+ }
+ return '';
+ };
+
+ // Ensure that org/course_num/run < 65 chars.
+ validateTotalCourseItemsLength = function () {
+ var totalLength = _.reduce(
+ [selectors.org, selectors.number, selectors.run],
+ function (sum, ele) {
+ return sum + $(ele).val().length;
+ }, 0
+ );
+ if (totalLength > 65) {
+ $(selectors.errorWrapper).addClass(classes.shown).removeClass(classes.hiding);
+ $(selectors.errorMessage).html('
' + gettext('The combined length of the organization, course number, and course run fields cannot be more than 65 characters.') + '
+ ${_("You are creating a re-run from:")}
+ ${source_course_key.org} ${source_course_key.course} ${source_course_key.run}
+ ${display_name}
+
+
+
+
+
+
+
+
+
+
+
+ ${_("Provide identifying information for this re-run of the course. The original course is not affected in any way by a re-run.")}
+ ${_("Note: Together, the organization, course number, and course run must uniquely identify this new course instance.")}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+%block>
diff --git a/cms/templates/course_outline.html b/cms/templates/course_outline.html
index ee22386600..193d239d37 100644
--- a/cms/templates/course_outline.html
+++ b/cms/templates/course_outline.html
@@ -36,6 +36,31 @@ from contentstore.utils import reverse_usage_url
% endfor
%block>
+<%block name="page_alert">
+ %if notification_dismiss_url is not None:
+
+
+
+
+
+
${_("This course was created as a re-run. Some manual configuration is needed.")}
+
+
${_("Be sure to review and reset all dates (the Course Start Date has been changed to a future date); set up the course team; review course updates and other assets for dated material; and seed the discussions and wiki.")}