Merge remote-tracking branch 'upstream/master' into proversity/add-recover-password-endpoint [ci skip]

This commit is contained in:
Jose Antonio Gonzlez
2017-11-28 08:08:25 -06:00
17 changed files with 565 additions and 159 deletions

View File

@@ -1188,11 +1188,17 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
xblock_info.update({
'hide_after_due': xblock.hide_after_due,
})
elif xblock.category == 'chapter':
elif xblock.category in ('chapter', 'course'):
if xblock.category == 'chapter':
xblock_info.update({
'highlights': xblock.highlights,
})
elif xblock.category == 'course':
xblock_info.update({
'highlights_enabled_for_messaging': course.highlights_enabled_for_messaging,
})
xblock_info.update({
'highlights': xblock.highlights,
'highlights_enabled': highlights_setting.is_enabled(),
'highlights_enabled_for_messaging': course.highlights_enabled_for_messaging,
'highlights_preview_only': not COURSE_UPDATE_WAFFLE_FLAG.is_enabled(course.id),
'highlights_doc_url': HelpUrlExpert.the_one().url_for_token('content_highlights'),
})

View File

@@ -2577,8 +2577,10 @@ class TestXBlockInfo(ItemTest):
self.store.update_item(self.course, None)
chapter = self.store.get_item(self.chapter.location)
with highlights_setting.override():
xblock_info = create_xblock_info(chapter)
self.assertTrue(xblock_info['highlights_enabled'])
chapter_xblock_info = create_xblock_info(chapter)
course_xblock_info = create_xblock_info(self.course)
self.assertTrue(chapter_xblock_info['highlights_enabled'])
self.assertTrue(course_xblock_info['highlights_enabled_for_messaging'])
def validate_course_xblock_info(self, xblock_info, has_child_info=True, course_outline=False):
"""
@@ -2588,6 +2590,7 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['id'], unicode(self.course.location))
self.assertEqual(xblock_info['display_name'], self.course.display_name)
self.assertTrue(xblock_info['published'])
self.assertFalse(xblock_info['highlights_enabled_for_messaging'])
# Finally, validate the entire response for consistency
self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info, course_outline=course_outline)
@@ -2608,7 +2611,6 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['format'], None)
self.assertEqual(xblock_info['highlights'], self.chapter.highlights)
self.assertFalse(xblock_info['highlights_enabled'])
self.assertFalse(xblock_info['highlights_enabled_for_messaging'])
# Finally, validate the entire response for consistency
self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info)

View File

@@ -165,6 +165,7 @@ function(Backbone, _, str, ModuleUtils) {
*/
highlights: [],
highlights_enabled: false,
highlights_enabled_for_messaging: false,
highlights_preview_only: true,
highlights_doc_url: ''
},

View File

@@ -32,7 +32,9 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
children: []
},
user_partitions: [],
user_partition_info: {}
user_partition_info: {},
highlights_enabled: true,
highlights_enabled_for_messaging: false
}, options, {child_info: {children: children}});
};
@@ -262,7 +264,8 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
'due-date-editor', 'grading-editor', 'publish-editor',
'staff-lock-editor', 'unit-access-editor', 'content-visibility-editor',
'settings-modal-tabs', 'timed-examination-preference-editor', 'access-editor',
'show-correctness-editor', 'highlights-editor'
'show-correctness-editor', 'highlights-editor', 'highlights-enable-editor',
'course-highlights-enable'
]);
appendSetFixtures(mockOutlinePage);
mockCourseJSON = createMockCourseJSON({}, [
@@ -529,20 +532,17 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
});
});
describe('Section Highlights', function() {
var createCourse, createCourseWithHighlights, createCourseWithHighlightsDisabled, mockHighlightValues,
highlightsLink, highlightInputs, openHighlights, saveHighlights, setHighlights,
expectHighlightLinkNumberToBe, expectHighlightsToBe, expectServerHandshakeWithHighlights,
expectHighlightsToUpdate,
maxNumHighlights = 5;
describe('Content Highlights', function() {
var createCourse, createCourseWithHighlights, createCourseWithHighlightsDisabled,
clickSaveOnModal, clickCancelOnModal;
beforeEach(function() {
setSelfPaced();
});
createCourse = function(sectionOptions) {
createCourse = function(sectionOptions, courseOptions) {
createCourseOutlinePage(this,
createMockCourseJSON({}, [
createMockCourseJSON(courseOptions, [
createMockSectionJSON(sectionOptions)
])
);
@@ -553,142 +553,256 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
};
createCourseWithHighlightsDisabled = function() {
createCourse({highlights_enabled: false});
var highlightsDisabled = {highlights_enabled: false};
createCourse(highlightsDisabled, highlightsDisabled);
};
mockHighlightValues = function(numberOfHighlights) {
var highlights = [],
i;
for (i = 0; i < numberOfHighlights; i++) {
highlights.push('Highlight' + (i + 1));
}
return highlights;
};
highlightsLink = function() {
return outlinePage.$('.section-status >> .highlights-button');
};
highlightInputs = function() {
return $('.highlight-input-text');
};
openHighlights = function() {
highlightsLink().click();
};
saveHighlights = function() {
clickSaveOnModal = function() {
$('.wrapper-modal-window .action-save').click();
};
setHighlights = function(highlights) {
var i;
for (i = 0; i < highlights.length; i++) {
$(highlightInputs()[i]).val(highlights[i]);
}
for (i = highlights.length; i < maxNumHighlights; i++) {
$(highlightInputs()[i]).val('');
}
clickCancelOnModal = function() {
$('.wrapper-modal-window .action-cancel').click();
};
expectHighlightLinkNumberToBe = function(expectedNumber) {
var link = highlightsLink();
expect(link).toContainText('Section Highlights');
expect(link.find('.number-highlights')).toHaveHtml(expectedNumber);
};
describe('Course Highlights Setting', function() {
var highlightsSetting, expectHighlightsEnabledToBe, expectServerHandshake, openHighlightsSettings;
expectHighlightsToBe = function(expectedHighlights) {
var highlights = highlightInputs(),
i;
highlightsSetting = function() {
return $('.course-highlights-setting');
};
expect(highlights).toHaveLength(maxNumHighlights);
for (i = 0; i < expectedHighlights.length; i++) {
expect(highlights[i]).toHaveValue(expectedHighlights[i]);
}
for (i = expectedHighlights.length; i < maxNumHighlights; i++) {
expect(highlights[i]).toHaveValue('');
expect(highlights[i]).toHaveAttr('placeholder', 'A highlight to look forward to this week.');
}
};
expectServerHandshakeWithHighlights = function(highlights) {
// POST to update section
AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/mock-section', {
publish: 'republish',
metadata: {
highlights: highlights
expectHighlightsEnabledToBe = function(expectedEnabled) {
if (expectedEnabled) {
expect('.status-highlights-enabled-value.button').not.toExist();
expect('.status-highlights-enabled-value.text').toExist();
} else {
expect('.status-highlights-enabled-value.button').toExist();
expect('.status-highlights-enabled-value.text').not.toExist();
}
}
expectServerHandshake = function() {
// POST to update course
AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/mock-course', {
publish: 'republish',
metadata: {
highlights_enabled_for_messaging: true
}
});
AjaxHelpers.respondWithJson(requests, {});
// GET updated course
AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/outline/mock-course');
AjaxHelpers.respondWithJson(
requests, createMockCourseJSON({highlights_enabled_for_messaging: true})
);
};
openHighlightsSettings = function() {
$('button.status-highlights-enabled-value').click();
};
it('does not display settings when disabled', function() {
createCourseWithHighlightsDisabled();
expect(highlightsSetting()).not.toExist();
});
AjaxHelpers.respondWithJson(requests, {});
// GET updated section
AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/outline/mock-section');
AjaxHelpers.respondWithJson(requests, createMockSectionJSON({highlights: highlights}));
};
it('displays settings when enabled', function() {
createCourseWithHighlights([]);
expect(highlightsSetting()).toExist();
});
expectHighlightsToUpdate = function(originalHighlights, updatedHighlights) {
createCourseWithHighlights(originalHighlights);
it('displays settings as not enabled for messaging', function() {
createCourse();
expectHighlightsEnabledToBe(false);
});
openHighlights();
setHighlights(updatedHighlights);
saveHighlights();
it('displays settings as enabled for messaging', function() {
createCourse({}, {highlights_enabled_for_messaging: true});
expectHighlightsEnabledToBe(true);
});
expectServerHandshakeWithHighlights(updatedHighlights);
expectHighlightLinkNumberToBe(updatedHighlights.length);
it('changes settings when enabled for messaging', function() {
createCourse();
openHighlightsSettings();
clickSaveOnModal();
expectServerHandshake();
expectHighlightsEnabledToBe(true);
});
openHighlights();
expectHighlightsToBe(updatedHighlights);
};
it('does not display a link when highlights is disabled', function() {
createCourseWithHighlightsDisabled();
expect(highlightsLink()).toHaveLength(0);
it('does not change settings when enabling is cancelled', function() {
createCourse();
openHighlightsSettings();
clickCancelOnModal();
expectHighlightsEnabledToBe(false);
});
});
it('displays link when no highlights exist', function() {
createCourseWithHighlights([]);
expectHighlightLinkNumberToBe(0);
});
it('displays link when highlights exist', function() {
var highlights = mockHighlightValues(2);
createCourseWithHighlights(highlights);
expectHighlightLinkNumberToBe(2);
});
describe('Section Highlights', function() {
var mockHighlightValues, highlightsLink, highlightInputs, openHighlights, saveHighlights,
cancelHighlights, setHighlights, expectHighlightLinkNumberToBe, expectHighlightsToBe,
expectServerHandshakeWithHighlights, expectHighlightsToUpdate,
maxNumHighlights = 5;
it('can view when no highlights exist', function() {
createCourseWithHighlights([]);
openHighlights();
expectHighlightsToBe([]);
});
mockHighlightValues = function(numberOfHighlights) {
var highlights = [],
i;
for (i = 0; i < numberOfHighlights; i++) {
highlights.push('Highlight' + (i + 1));
}
return highlights;
};
it('can view existing highlights', function() {
var highlights = mockHighlightValues(2);
createCourseWithHighlights(highlights);
openHighlights();
expectHighlightsToBe(highlights);
});
highlightsLink = function() {
return outlinePage.$('.section-status >> .highlights-button');
};
it('can add highlights', function() {
expectHighlightsToUpdate(
mockHighlightValues(0),
mockHighlightValues(1)
);
});
highlightInputs = function() {
return $('.highlight-input-text');
};
it('can remove highlights', function() {
expectHighlightsToUpdate(
mockHighlightValues(5),
mockHighlightValues(3)
);
});
openHighlights = function() {
highlightsLink().click();
};
it('can edit highlights', function() {
var originalHighlights = mockHighlightValues(3),
editedHighlights = originalHighlights;
editedHighlights[2] = 'A New Value';
expectHighlightsToUpdate(originalHighlights, editedHighlights);
saveHighlights = function() {
clickSaveOnModal();
};
cancelHighlights = function() {
clickCancelOnModal();
};
setHighlights = function(highlights) {
var i;
for (i = 0; i < highlights.length; i++) {
$(highlightInputs()[i]).val(highlights[i]);
}
for (i = highlights.length; i < maxNumHighlights; i++) {
$(highlightInputs()[i]).val('');
}
};
expectHighlightLinkNumberToBe = function(expectedNumber) {
var link = highlightsLink();
expect(link).toContainText('Section Highlights');
expect(link.find('.number-highlights')).toHaveHtml(expectedNumber);
};
expectHighlightsToBe = function(expectedHighlights) {
var highlights = highlightInputs(),
i;
expect(highlights).toHaveLength(maxNumHighlights);
for (i = 0; i < expectedHighlights.length; i++) {
expect(highlights[i]).toHaveValue(expectedHighlights[i]);
}
for (i = expectedHighlights.length; i < maxNumHighlights; i++) {
expect(highlights[i]).toHaveValue('');
expect(highlights[i]).toHaveAttr(
'placeholder',
'A highlight to look forward to this week.'
);
}
};
expectServerHandshakeWithHighlights = function(highlights) {
// POST to update section
AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/mock-section', {
publish: 'republish',
metadata: {
highlights: highlights
}
});
AjaxHelpers.respondWithJson(requests, {});
// GET updated section
AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/outline/mock-section');
AjaxHelpers.respondWithJson(requests, createMockSectionJSON({highlights: highlights}));
};
expectHighlightsToUpdate = function(originalHighlights, updatedHighlights) {
createCourseWithHighlights(originalHighlights);
openHighlights();
setHighlights(updatedHighlights);
saveHighlights();
expectServerHandshakeWithHighlights(updatedHighlights);
expectHighlightLinkNumberToBe(updatedHighlights.length);
openHighlights();
expectHighlightsToBe(updatedHighlights);
};
it('does not display link when disabled', function() {
createCourseWithHighlightsDisabled();
expect(highlightsLink()).not.toExist();
});
it('displays link when no highlights exist', function() {
createCourseWithHighlights([]);
expectHighlightLinkNumberToBe(0);
});
it('displays link when highlights exist', function() {
var highlights = mockHighlightValues(2);
createCourseWithHighlights(highlights);
expectHighlightLinkNumberToBe(2);
});
it('can view when no highlights exist', function() {
createCourseWithHighlights([]);
openHighlights();
expectHighlightsToBe([]);
});
it('can view existing highlights', function() {
var highlights = mockHighlightValues(2);
createCourseWithHighlights(highlights);
openHighlights();
expectHighlightsToBe(highlights);
});
it('does not save highlights when cancelled', function() {
var originalHighlights = mockHighlightValues(2),
editedHighlights = originalHighlights;
editedHighlights[1] = 'A New Value';
createCourseWithHighlights(originalHighlights);
openHighlights();
setHighlights(editedHighlights);
cancelHighlights();
AjaxHelpers.expectNoRequests(requests);
openHighlights();
expectHighlightsToBe(originalHighlights);
});
it('can add highlights', function() {
expectHighlightsToUpdate(
mockHighlightValues(0),
mockHighlightValues(1)
);
});
it('can remove highlights', function() {
expectHighlightsToUpdate(
mockHighlightValues(5),
mockHighlightValues(3)
);
});
it('can edit highlights', function() {
var originalHighlights = mockHighlightValues(3),
editedHighlights = originalHighlights;
editedHighlights[2] = 'A New Value';
expectHighlightsToUpdate(originalHighlights, editedHighlights);
});
});
});

View File

@@ -0,0 +1,54 @@
define([
'jquery', 'underscore', 'backbone', 'js/views/utils/xblock_utils', 'js/utils/templates',
'js/views/modals/course_outline_modals', 'edx-ui-toolkit/js/utils/html-utils'],
function(
$, _, Backbone, XBlockViewUtils, TemplateUtils, CourseOutlineModalsFactory, HtmlUtils
) {
'use strict';
var CourseHighlightsEnableView = Backbone.View.extend({
events: {
'click button.status-highlights-enabled-value': 'handleEnableButtonPress',
'keypress button.status-highlights-enabled-value': 'handleEnableButtonPress'
},
initialize: function() {
this.template = TemplateUtils.loadTemplate('course-highlights-enable');
},
handleEnableButtonPress: function(event) {
if (event.type === 'click' || event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this.highlightsEnableXBlock();
}
},
highlightsEnableXBlock: function() {
var modal = CourseOutlineModalsFactory.getModal('highlights_enable', this.model, {
onSave: this.refresh.bind(this),
xblockType: XBlockViewUtils.getXBlockType(
this.model.get('category')
)
});
if (modal) {
window.analytics.track('edx.bi.highlights_enable.modal_open');
modal.show();
}
},
refresh: function() {
this.model.fetch({
success: this.render.bind(this)
});
},
render: function() {
var html = this.template(this.model.attributes);
HtmlUtils.setHtml(this.$el, HtmlUtils.HTML(html));
return this;
}
});
return CourseHighlightsEnableView;
}
);

View File

@@ -17,7 +17,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
AbstractEditor, BaseDateEditor,
ReleaseDateEditor, DueDateEditor, GradingEditor, PublishEditor, AbstractVisibilityEditor,
StaffLockEditor, UnitAccessEditor, ContentVisibilityEditor, TimedExaminationPreferenceEditor,
AccessEditor, ShowCorrectnessEditor, HighlightsEditor;
AccessEditor, ShowCorrectnessEditor, HighlightsEditor, HighlightsEnableXBlockModal, HighlightsEnableEditor;
CourseOutlineXBlockModal = BaseModal.extend({
events: _.extend({}, BaseModal.prototype.events, {
@@ -242,7 +242,11 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
callAnalytics: function(event) {
event.preventDefault();
window.analytics.track('edx.bi.highlights.' + event.target.innerText.toLowerCase());
this.save(event);
if (event.target.className.indexOf('save') !== -1) {
this.save(event);
} else {
this.hide();
}
},
addActionButtons: function() {
@@ -251,6 +255,44 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
}
});
HighlightsEnableXBlockModal = CourseOutlineXBlockModal.extend({
events: _.extend({}, CourseOutlineXBlockModal.prototype.events, {
'click .action-save': 'callAnalytics',
'click .action-cancel': 'callAnalytics'
}),
initialize: function() {
CourseOutlineXBlockModal.prototype.initialize.call(this);
if (this.options.xblockType) {
this.options.modalName = 'highlights-enable-' + this.options.xblockType;
}
},
getTitle: function() {
return gettext('Enable Weekly Course Highlight Messages');
},
getIntroductionMessage: function() {
return '';
},
callAnalytics: function(event) {
event.preventDefault();
window.analytics.track('edx.bi.highlights_enable.' + event.target.innerText.toLowerCase());
if (event.target.className.indexOf('save') !== -1) {
this.save(event);
} else {
this.hide();
}
},
addActionButtons: function() {
this.addActionButton('save', gettext('Enable'), true);
this.addActionButton('cancel', gettext('Not yet'));
}
});
AbstractEditor = BaseView.extend({
tagName: 'section',
templateName: null,
@@ -933,6 +975,42 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
}
});
HighlightsEnableEditor = AbstractEditor.extend({
templateName: 'highlights-enable-editor',
className: 'edit-enable-highlights',
currentValue: function() {
return true;
},
hasChanges: function() {
return this.model.get('highlights_enabled_for_messaging') !== this.currentValue();
},
getRequestData: function() {
if (this.hasChanges()) {
return {
publish: 'republish',
metadata: {
highlights_enabled_for_messaging: this.currentValue()
}
};
} else {
return {};
}
},
getContext: function() {
return $.extend(
{},
AbstractEditor.prototype.getContext.call(this),
{
highlights_enabled: this.model.get('highlights_enabled_for_messaging'),
highlights_doc_url: this.model.get('highlights_doc_url')
}
);
}
});
return {
getModal: function(type, xblockInfo, options) {
if (type === 'edit') {
@@ -941,6 +1019,8 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
return this.getPublishModal(xblockInfo, options);
} else if (type === 'highlights') {
return this.getHighlightsModal(xblockInfo, options);
} else if (type === 'highlights_enable') {
return this.getHighlightsEnableModal(xblockInfo, options);
} else {
return null;
}
@@ -1018,6 +1098,13 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
editors: [HighlightsEditor],
model: xblockInfo
}, options));
},
getHighlightsEnableModal: function(xblockInfo, options) {
return new HighlightsEnableXBlockModal($.extend({
editors: [HighlightsEnableEditor],
model: xblockInfo
}, options));
}
};
});

View File

@@ -1,10 +1,14 @@
/**
* 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', 'common/js/components/utils/view_utils', 'common/js/components/views/feedback_alert',
'common/js/components/views/feedback_notification'],
function($, _, gettext, BasePage, XBlockViewUtils, CourseOutlineView, ViewUtils, AlertView, NoteView) {
define([
'jquery', 'underscore', 'gettext', 'js/views/pages/base_page', 'js/views/utils/xblock_utils',
'js/views/course_outline', 'common/js/components/utils/view_utils', 'common/js/components/views/feedback_alert',
'common/js/components/views/feedback_notification', 'js/views/course_highlights_enable'],
function($, _, gettext, BasePage, XBlockViewUtils, CourseOutlineView, ViewUtils, AlertView, NoteView,
CourseHighlightsEnableView
) {
'use strict';
var expandedLocators, CourseOutlinePage;
CourseOutlinePage = BasePage.extend({
@@ -65,6 +69,15 @@ define(['jquery', 'underscore', 'gettext', 'js/views/pages/base_page', 'js/views
this.expandedLocators.addAll(this.initialState.expanded_locators);
}
/* globals course */
if (this.model.get('highlights_enabled') && course.get('self_paced')) {
this.highlightsEnableView = new CourseHighlightsEnableView({
el: this.$('.status-highlights-enabled'),
model: this.model
});
this.highlightsEnableView.render();
}
this.outlineView = new CourseOutlineView({
el: this.$('.outline'),
model: this.model,

View File

@@ -184,11 +184,13 @@
margin-bottom: $baseline;
.status-release,
.status-pacing {
.status-pacing,
.status-highlights-enabled {
@extend %t-copy-base;
display: inline-block;
color: $color-copy-base;
margin-right: $baseline;
// STATE: hover
&:hover {
@@ -198,10 +200,17 @@
}
}
.status-highlights-enabled {
margin-left: $baseline * 1.6;
}
.status-release-label,
.status-release-value,
.status-pacing-label,
.status-pacing-value,
.status-highlights-enabled-label,
.status-highlights-enabled-value,
.status-highlights-enabled-info,
.status-actions {
display: inline-block;
vertical-align: middle;
@@ -209,13 +218,28 @@
}
.status-release-label,
.status-pacing-label {
.status-pacing-label,
.status-highlights-enabled-label {
margin-right: ($baseline/4);
}
.status-release-value,
.status-pacing-value {
.status-pacing-value,
.status-highlights-enabled-value {
@extend %t-strong;
font-size: smaller;
}
.status-highlights-enabled-info {
font-size: smaller;
margin-left: $baseline / 2;
}
.status-highlights-enabled-value.button {
@extend %btn-primary-blue;
@extend %sizing;
padding: 5px 8px;
margin-top: 2px;
}
.status-actions {

View File

@@ -26,7 +26,7 @@ from openedx.core.djangolib.markup import HTML, Text
<%block name="header_extras">
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
% for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', 'due-date-editor', 'release-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'unit-access-editor', 'content-visibility-editor', 'verification-access-editor', 'timed-examination-preference-editor', 'access-editor', 'settings-modal-tabs', 'show-correctness-editor', 'highlights-editor']:
% for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', 'due-date-editor', 'release-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'unit-access-editor', 'content-visibility-editor', 'verification-access-editor', 'timed-examination-preference-editor', 'access-editor', 'settings-modal-tabs', 'show-correctness-editor', 'highlights-editor', 'highlights-enable-editor', 'course-highlights-enable']:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
@@ -152,7 +152,8 @@ from openedx.core.djangolib.markup import HTML, Text
<article class="content-primary" role="main">
<div class="course-status">
<div class="status-release">
<h2 class="status-release-label">${_("Course Start Date:")}</h2>
<h2 class="status-release-label">${_("Course Start Date")}</h2>
<br>
<p class="status-release-value">${course_release_date}</p>
<ul class="status-actions">
@@ -166,14 +167,16 @@ from openedx.core.djangolib.markup import HTML, Text
</div>
% if SelfPacedConfiguration.current().enabled:
<div class="status-pacing">
<h2 class=status-pacing-label>${_("Course Pacing:")}</h2>
<h2 class=status-pacing-label>${_("Course Pacing")}</h2>
<br>
% if context_course.self_paced:
<p class="status-pacing-value">${_("Self-Paced")}</p>
% else:
<p class="status-pacing-value">${_("Instructor-Paced")}</p>
% endif
</div>
% endif
% endif
<div class="status-highlights-enabled"></div>
</div>
<div class="wrapper-dnd"
% if getattr(context_course, 'language'):

View File

@@ -0,0 +1,12 @@
<div class="course-highlights-setting">
<h2 id="highlights-enabled-label" class="status-highlights-enabled-label">
<%- gettext('Weekly Highlight Emails') %>
</h2>
<br>
<% if (highlights_enabled_for_messaging) { %>
<span class="status-highlights-enabled-value text"><%- gettext('Enabled') %></span>
<% } else { %>
<button class="status-highlights-enabled-value button" aria-labelledby="highlights-enabled-label"><%- gettext('Enable Now') %></button>
<% } %>
<a class="status-highlights-enabled-info" href="<%- highlights_doc_url %>">Learn more</a>
</div>

View File

@@ -19,7 +19,10 @@
'read our {linkStart}documentation{linkEnd}.'
),
{
linkStart: edx.HtmlUtils.HTML('<a href="' + highlights_doc_url + '">'),
linkStart: edx.HtmlUtils.interpolateHtml(
edx.HtmlUtils.HTML('<a href={highlightsDocUrl}">'),
{highlightsDocUrl: highlights_doc_url}
),
linkEnd: edx.HtmlUtils.HTML('</a>')
}
) %>

View File

@@ -0,0 +1,24 @@
<p>
<%- gettext(
'When you enable weekly course highlight messages, learners ' +
'automatically receive weekly email messages for each section that ' +
'has highlights. You cannot disable highlights after you start ' +
'sending them.'
) %>
</p>
<p>
<% // xss-lint: disable=underscore-not-escaped %>
<%= edx.HtmlUtils.interpolateHtml(
gettext(
'Are you sure you want to enable weekly course highlight messages? '
+ '{linkStart}Learn more.{linkEnd}'
),
{
linkStart: edx.HtmlUtils.interpolateHtml(
edx.HtmlUtils.HTML('<a href="{highlightsDocUrl}" target="_blank">'),
{highlightsDocUrl: xblockInfo.attributes.highlights_doc_url}
),
linkEnd: edx.HtmlUtils.HTML('</a>')
}
) %>
</p>

View File

@@ -37,6 +37,9 @@
<div class="wrapper-content wrapper">
<section class="content">
<article class="content-primary" role="main">
<div class=course-status"">
<div class="status-highlights-enabled"></div>
</div>
<div class="wrapper-dnd">
<article class="outline outline-course" data-locator="mock-course" data-course-key="slashes:MockCourse">
<div class="no-content add-xblock-component">

View File

@@ -5,6 +5,7 @@ Acceptance tests for studio related to the outline page.
import itertools
import json
from datetime import datetime, timedelta
from unittest import skip
from nose.plugins.attrib import attr
from pytz import UTC
@@ -115,6 +116,7 @@ class CourseOutlineDragAndDropTest(CourseOutlineTest):
expected_ordering
)
@skip("Fails in Firefox 45 but passes in Chrome")
def test_drop_unit_in_collapsed_subsection(self):
"""
Drag vertical "1.1.2" from subsection "1.1" into collapsed subsection "1.2" which already

View File

@@ -1,3 +1,9 @@
"""
Contains methods for accessing weekly course highlights. Weekly highlights is a
schedule experience built on the Schedules app.
"""
import logging
from courseware.module_render import get_module_for_descriptor
from courseware.model_data import FieldDataCache
from openedx.core.djangoapps.schedules.config import COURSE_UPDATE_WAFFLE_FLAG
@@ -6,6 +12,8 @@ from request_cache import get_request_or_stub
from xmodule.modulestore.django import modulestore
log = logging.getLogger(__name__)
def course_has_highlights(course_key):
"""
@@ -13,35 +21,63 @@ def course_has_highlights(course_key):
This ignores access checks, since highlights may be lurking in currently
inaccessible content.
"""
if not COURSE_UPDATE_WAFFLE_FLAG.is_enabled(course_key):
try:
course = _get_course_with_highlights(course_key)
except CourseUpdateDoesNotExist:
return False
course = modulestore().get_course(course_key, depth=1)
return any(
section.highlights
for section in course.get_children()
if not section.hide_from_toc
)
else:
highlights_are_available = any(
section.highlights
for section in course.get_children()
if not section.hide_from_toc
)
if not highlights_are_available:
log.error(
"Course team enabled highlights and provided no highlights."
)
return highlights_are_available
def get_week_highlights(user, course_key, week_num):
"""
Get highlights (list of unicode strings) for a given week.
week_num starts at 1.
Raises CourseUpdateDoesNotExist if highlights do not exist for
the requested week_num.
Raises:
CourseUpdateDoesNotExist: if highlights do not exist for
the requested week_num.
"""
course_descriptor = _get_course_with_highlights(course_key)
course_module = _get_course_module(course_descriptor, user)
sections_with_highlights = _get_sections_with_highlights(course_module)
highlights = _get_highlights_for_week(
sections_with_highlights,
week_num,
course_key,
)
return highlights
def _get_course_with_highlights(course_key):
# pylint: disable=missing-docstring
if not COURSE_UPDATE_WAFFLE_FLAG.is_enabled(course_key):
raise CourseUpdateDoesNotExist(
"%s does not have Course Updates enabled.",
course_key
"%s Course Update Messages waffle flag is disabled.",
course_key,
)
course_descriptor = _get_course_descriptor(course_key)
course_module = _get_course_module(course_descriptor, user)
sections_with_highlights = _get_sections_with_highlights(course_module)
highlights = _get_highlights_for_week(sections_with_highlights, week_num, course_key)
return highlights
if not course_descriptor.highlights_enabled_for_messaging:
raise CourseUpdateDoesNotExist(
"%s Course Update Messages are disabled.",
course_key,
)
return course_descriptor
def _get_course_descriptor(course_key):

View File

@@ -21,7 +21,9 @@ class TestContentHighlights(ModuleStoreTestCase):
self._setup_user()
def _setup_course(self):
self.course = CourseFactory.create()
self.course = CourseFactory.create(
highlights_enabled_for_messaging=True
)
self.course_key = self.course.id
def _setup_user(self):
@@ -66,6 +68,23 @@ class TestContentHighlights(ModuleStoreTestCase):
highlights,
)
@override_waffle_flag(COURSE_UPDATE_WAFFLE_FLAG, True)
def test_highlights_disabled_for_messaging(self):
highlights = [u'A test highlight.']
with self.store.bulk_operations(self.course_key):
self._create_chapter(highlights=highlights)
self.course.highlights_enabled_for_messaging = False
self.store.update_item(self.course, self.user.id)
self.assertFalse(course_has_highlights(self.course_key))
with self.assertRaises(CourseUpdateDoesNotExist):
get_week_highlights(
self.user,
self.course_key,
week_num=1,
)
@override_waffle_flag(COURSE_UPDATE_WAFFLE_FLAG, True)
def test_course_with_no_highlights(self):
with self.store.bulk_operations(self.course_key):

View File

@@ -47,7 +47,7 @@ edx-lint==0.4.3
astroid==1.3.8
edx-django-oauth2-provider==1.2.5
edx-django-sites-extensions==2.3.0
edx-enterprise==0.53.16
edx-enterprise==0.53.18
edx-oauth2-provider==1.2.2
edx-opaque-keys==0.4.0
edx-organizations==0.4.8
@@ -204,3 +204,6 @@ py2neo==3.1.2
# Support for plugins
web-fragments==0.2.2
xblock==1.0.0
# Redis version
redis==2.10.6