Commit c23c0b99 by Albert St. Aubin Committed by cahrens

EDUCATOR-141: Moved the Discussions Mgt to its own tab and updated tests

parent 8951ac8c
......@@ -47,6 +47,15 @@ class InstructorDashboardPage(CoursePage):
cohort_management_section.wait_for_page()
return cohort_management_section
def select_discussion_management(self):
"""
Selects the Discussion tab and returns the DiscussionmanagementSection
"""
self.q(css='[data-section="discussions_management"').first.click()
discussion_management_section = DiscussionManagementSection(self.browser)
discussion_management_section.wait_for_page()
return discussion_management_section
def select_data_download(self):
"""
Selects the data download tab and returns a DataDownloadPage.
......@@ -666,54 +675,63 @@ class CohortManagementSection(PageObject):
self.q(css=self._bounded_selector('.cohorts-state')).first.click()
self.wait_for_ajax()
def toggles_showing_of_discussion_topics(self):
def cohort_management_controls_visible(self):
"""
Shows the discussion topics.
Return the visibility status of cohort management controls(cohort selector section etc).
"""
self.q(css=self._bounded_selector(".toggle-cohort-management-discussions")).first.click()
self.wait_for_element_visibility("#cohort-discussions-management", "Waiting for discussions to appear")
return (self.q(css=self._bounded_selector('.cohort-management-nav')).visible and
self.q(css=self._bounded_selector('.wrapper-cohort-supplemental')).visible)
def discussion_topics_visible(self):
"""
Returns the visibility status of cohort discussion controls.
"""
EmptyPromise(
lambda: self.q(css=self._bounded_selector('.cohort-discussions-nav')).results != 0,
"Waiting for discussion section to show"
).fulfill()
return (self.q(css=self._bounded_selector('.cohort-course-wide-discussions-nav')).visible and
self.q(css=self._bounded_selector('.cohort-inline-discussions-nav')).visible)
class DiscussionManagementSection(PageObject):
def select_discussion_topic(self, key):
url = None
discussion_form_selectors = {
'course-wide': '.cohort-course-wide-discussions-form',
'inline': '.cohort-inline-discussions-form'
}
def is_browser_on_page(self):
return self.q(css=self.discussion_form_selectors['course-wide']).present
def _bounded_selector(self, selector):
"""
Selects discussion topic checkbox by clicking on it.
Return `selector`, but limited to the divided discussion management context.
"""
self.q(css=self._bounded_selector(".check-discussion-subcategory-%s" % key)).first.click()
return '.discussions-management {}'.format(selector)
def select_always_inline_discussion(self):
def is_save_button_disabled(self, key):
"""
Selects the always_cohort_inline_discussions radio button.
Returns the status for form's save button, enabled or disabled.
"""
self.q(css=self._bounded_selector(".check-all-inline-discussions")).first.click()
save_button_css = '%s %s' % (self.discussion_form_selectors[key], '.action-save')
disabled = self.q(css=self._bounded_selector(save_button_css)).attrs('disabled')
return disabled[0] == 'true'
def always_inline_discussion_selected(self):
def discussion_topics_visible(self):
"""
Returns true if always_cohort_inline_discussions radio button is selected.
Returns the visibility status of divide discussion controls.
"""
return len(self.q(css=self._bounded_selector(".check-all-inline-discussions:checked"))) > 0
return (self.q(css=self._bounded_selector('.course-wide-discussions-nav')).visible and
self.q(css=self._bounded_selector('.inline-discussions-nav')).visible)
def cohort_some_inline_discussion_selected(self):
def divided_discussion_heading_is_visible(self, key):
"""
Returns true if some_cohort_inline_discussions radio button is selected.
Returns the text of discussion topic headings if it exists, otherwise return False.
"""
return len(self.q(css=self._bounded_selector(".check-cohort-inline-discussions:checked"))) > 0
form_heading_css = '%s %s' % (self.discussion_form_selectors[key], '.subsection-title')
discussion_heading = self.q(css=self._bounded_selector(form_heading_css))
def select_cohort_some_inline_discussion(self):
if len(discussion_heading) == 0:
return False
return discussion_heading.first.text[0]
def select_always_inline_discussion(self):
"""
Selects the cohort_some_inline_discussions radio button.
Selects the always_divide_inline_discussions radio button.
"""
self.q(css=self._bounded_selector(".check-cohort-inline-discussions")).first.click()
self.q(css=self._bounded_selector(".check-all-inline-discussions")).first.click()
def inline_discussion_topics_disabled(self):
"""
......@@ -722,35 +740,45 @@ class CohortManagementSection(PageObject):
inline_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-inline'))
return all(topic.get_attribute('disabled') == 'true' for topic in inline_topics)
def is_save_button_disabled(self, key):
def save_discussion_topics(self, key):
"""
Returns the status for form's save button, enabled or disabled.
Saves the discussion topics.
"""
save_button_css = '%s %s' % (self.discussion_form_selectors[key], '.action-save')
disabled = self.q(css=self._bounded_selector(save_button_css)).attrs('disabled')
return disabled[0] == 'true'
self.q(css=self._bounded_selector(save_button_css)).first.click()
def is_category_selected(self):
def always_inline_discussion_selected(self):
"""
Returns the status for category checkboxes.
Returns true if always_divide_inline_discussions radio button is selected.
"""
return self.q(css=self._bounded_selector('.check-discussion-category:checked')).is_present()
return len(self.q(css=self._bounded_selector(".check-all-inline-discussions:checked"))) > 0
def get_cohorted_topics_count(self, key):
def divide_some_inline_discussion_selected(self):
"""
Returns the count for cohorted topics.
Returns true if divide_some_inline_discussions radio button is selected.
"""
cohorted_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-%s:checked' % key))
return len(cohorted_topics.results)
return len(self.q(css=self._bounded_selector(".check-cohort-inline-discussions:checked"))) > 0
def save_discussion_topics(self, key):
def select_divide_some_inline_discussion(self):
"""
Saves the discussion topics.
Selects the divide_some_inline_discussions radio button.
"""
save_button_css = '%s %s' % (self.discussion_form_selectors[key], '.action-save')
self.q(css=self._bounded_selector(save_button_css)).first.click()
self.q(css=self._bounded_selector(".check-cohort-inline-discussions")).first.click()
def get_divided_topics_count(self, key):
"""
Returns the count for divided topics.
"""
divided_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-%s:checked' % key))
return len(divided_topics.results)
def get_cohort_discussions_message(self, key, msg_type="confirmation"):
def select_discussion_topic(self, key):
"""
Selects discussion topic checkbox by clicking on it.
"""
self.q(css=self._bounded_selector(".check-discussion-subcategory-%s" % key)).first.click()
def get_divide_discussions_message(self, key, msg_type="confirmation"):
"""
Returns the message related to modifying discussion topics.
"""
......@@ -767,23 +795,11 @@ class CohortManagementSection(PageObject):
return ''
return message_title.first.text[0]
def cohort_discussion_heading_is_visible(self, key):
"""
Returns the visibility of discussion topic headings.
"""
form_heading_css = '%s %s' % (self.discussion_form_selectors[key], '.subsection-title')
discussion_heading = self.q(css=self._bounded_selector(form_heading_css))
if len(discussion_heading) == 0:
return False
return discussion_heading.first.text[0]
def cohort_management_controls_visible(self):
def is_category_selected(self):
"""
Return the visibility status of cohort management controls(cohort selector section etc).
Returns the status for category checkboxes.
"""
return (self.q(css=self._bounded_selector('.cohort-management-nav')).visible and
self.q(css=self._bounded_selector('.wrapper-cohort-supplemental')).visible)
return self.q(css=self._bounded_selector('.check-discussion-category:checked')).is_present()
class MembershipPageAutoEnrollSection(PageObject):
......
......@@ -76,11 +76,16 @@ class CohortTestMixin(object):
def enable_cohorting(self, course_fixture):
"""
enables cohorting for the current course fixture.
enables cohorts and always_divide_inline_discussions for the current course fixture.
"""
url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/settings' # pylint: disable=protected-access
data = json.dumps({'always_cohort_inline_discussions': True})
discussions_url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/discussions/settings' # pylint: disable=protected-access
data = json.dumps({'is_cohorted': True})
discussions_data = json.dumps({'always_divide_inline_discussions': True})
response = course_fixture.session.patch(url, data=data, headers=course_fixture.headers)
course_fixture.session.patch(discussions_url, data=discussions_data, headers=course_fixture.headers)
def disable_cohorting(self, course_fixture):
"""
......
......@@ -26,7 +26,7 @@ from courseware.tests.factories import InstructorFactory
from courseware.tabs import get_course_tab_list
from openedx.core.djangoapps.course_groups import cohorts
from openedx.core.djangoapps.course_groups.cohorts import set_course_cohorted
from openedx.core.djangoapps.course_groups.tests.helpers import config_course_cohorts, topic_name_to_id, CohortFactory
from openedx.core.djangoapps.course_groups.tests.helpers import config_course_cohorts, config_course_discussions, topic_name_to_id, CohortFactory
from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentFactory
from openedx.core.djangoapps.content.course_structures.models import CourseStructure
from openedx.core.djangoapps.util.testing import ContentGroupTestCase
......@@ -478,7 +478,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
}
)
def test_inline_with_always_cohort_inline_discussion_flag(self):
def test_inline_with_always_divide_inline_discussion_flag(self):
self.create_discussion("Chapter", "Discussion")
set_discussion_division_settings(self.course.id, enable_cohorts=True, always_divide_inline_discussions=True)
......@@ -502,7 +502,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
}
)
def test_inline_without_always_cohort_inline_discussion_flag(self):
def test_inline_without_always_divide_inline_discussion_flag(self):
self.create_discussion("Chapter", "Discussion")
set_discussion_division_settings(self.course.id, enable_cohorts=True)
......@@ -1301,15 +1301,16 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
)
# not cohorted
config_course_cohorts(course, is_cohorted=False, discussion_topics=["General", "Feedback"])
config_course_cohorts(course, is_cohorted=False)
config_course_discussions(course, discussion_topics=["General", "Feedback"])
self.assertFalse(
utils.is_commentable_divided(course.id, to_id("General")),
"Course isn't cohorted"
)
# cohorted, but top level topics aren't
config_course_cohorts(course, is_cohorted=True, discussion_topics=["General", "Feedback"])
config_course_cohorts(course, is_cohorted=True)
config_course_discussions(course, discussion_topics=["General", "Feedback"])
self.assertTrue(cohorts.is_course_cohorted(course.id))
self.assertFalse(
......@@ -1320,10 +1321,9 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
# cohorted, including "Feedback" top-level topics aren't
config_course_cohorts(
course,
is_cohorted=True,
discussion_topics=["General", "Feedback"],
divided_discussions=["Feedback"]
is_cohorted=True
)
config_course_discussions(course, discussion_topics=["General", "Feedback"], divided_discussions=["Feedback"])
self.assertTrue(cohorts.is_course_cohorted(course.id))
self.assertFalse(
......@@ -1345,42 +1345,52 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
config_course_cohorts(
course,
is_cohorted=True,
)
config_course_discussions(
course,
discussion_topics=["General", "Feedback"],
divided_discussions=["Feedback", "random_inline"]
)
self.assertFalse(
utils.is_commentable_divided(course.id, to_id("random")),
"By default, Non-top-level discussions are not cohorted in a cohorted courses."
)
# if always_cohort_inline_discussions is set to False, non-top-level discussion are always
# non cohorted unless they are explicitly set in cohorted_discussions
# if always_divide_inline_discussions is set to False, non-top-level discussion are always
# not divided unless they are explicitly set in divided_discussions
config_course_cohorts(
course,
is_cohorted=True,
)
config_course_discussions(
course,
discussion_topics=["General", "Feedback"],
divided_discussions=["Feedback", "random_inline"],
always_divide_inline_discussions=False
)
self.assertFalse(
utils.is_commentable_divided(course.id, to_id("random")),
"Non-top-level discussion is not cohorted if always_cohort_inline_discussions is False."
"Non-top-level discussion is not cohorted if always_divide_inline_discussions is False."
)
self.assertTrue(
utils.is_commentable_divided(course.id, to_id("random_inline")),
"If always_cohort_inline_discussions set to False, Non-top-level discussion is "
"If always_divide_inline_discussions set to False, Non-top-level discussion is "
"cohorted if explicitly set in cohorted_discussions."
)
self.assertTrue(
utils.is_commentable_divided(course.id, to_id("Feedback")),
"If always_cohort_inline_discussions set to False, top-level discussion are not affected."
"If always_divide_inline_discussions set to False, top-level discussion are not affected."
)
def test_is_commentable_divided_team(self):
course = modulestore().get_course(self.toy_course_key)
self.assertFalse(cohorts.is_course_cohorted(course.id))
config_course_cohorts(course, is_cohorted=True, always_divide_inline_discussions=True)
config_course_cohorts(course, is_cohorted=True)
config_course_discussions(course, always_divide_inline_discussions=True)
team = CourseTeamFactory(course_id=course.id)
# Verify that team discussions are not cohorted, but other discussions are
......
......@@ -125,6 +125,7 @@ def instructor_dashboard_2(request, course_id):
_section_course_info(course, access),
_section_membership(course, access, is_white_label),
_section_cohort_management(course, access),
_section_discussions_management(course, access),
_section_student_admin(course, access),
_section_data_download(course, access),
]
......@@ -513,7 +514,6 @@ def _section_cohort_management(course, access):
),
'cohorts_url': reverse('cohorts', kwargs={'course_key_string': unicode(course_key)}),
'upload_cohorts_csv_url': reverse('add_users_to_cohorts', kwargs={'course_id': unicode(course_key)}),
'discussion_topics_url': reverse('cohort_discussion_topics', kwargs={'course_key_string': unicode(course_key)}),
'verified_track_cohorting_url': reverse(
'verified_track_cohorting', kwargs={'course_key_string': unicode(course_key)}
),
......@@ -521,6 +521,21 @@ def _section_cohort_management(course, access):
return section_data
def _section_discussions_management(course, access):
""" Provide data for the corresponding discussion management section """
course_key = course.id
section_data = {
'section_key': 'discussions_management',
'section_display_name': _('Discussions'),
'discussion_topics_url': reverse('discussion_topics', kwargs={'course_key_string': unicode(course_key)}),
'course_discussion_settings': reverse(
'course_discussions_settings',
kwargs={'course_key_string': unicode(course_key)}
),
}
return section_data
def _is_small_course(course_key):
""" Compares against MAX_ENROLLMENT_INSTR_BUTTONS to determine if course enrollment is considered small. """
is_small_course = False
......
......@@ -1759,6 +1759,7 @@ REQUIRE_JS_PATH_OVERRIDES = {
'js/student_profile/views/learner_profile_factory': 'js/student_profile/views/learner_profile_factory.js',
'js/courseware/courseware_factory': 'js/courseware/courseware_factory.js',
'js/groups/views/cohorts_dashboard_factory': 'js/groups/views/cohorts_dashboard_factory.js',
'js/groups/discussions_management/discussions_dashboard_factory': 'js/discussions_management/views/discussions_dashboard_factory.js',
'draggabilly': 'js/vendor/draggabilly.js',
'hls': 'common/js/vendor/hls.js'
}
......
(function(define) {
'use strict';
define(['backbone'], function(Backbone) {
var DiscussionTopicsSettingsModel = Backbone.Model.extend({
var CourseDiscussionTopicDetailsModel = Backbone.Model.extend({
defaults: {
course_wide_discussions: {},
inline_discussions: {}
}
});
return DiscussionTopicsSettingsModel;
return CourseDiscussionTopicDetailsModel;
});
}).call(this, define || RequireJS.define);
(function(define) {
'use strict';
define(['backbone'], function(Backbone) {
var CourseDiscussionsSettingsModel = Backbone.Model.extend({
idAttribute: 'id',
defaults: {
divided_inline_discussions: [],
divided_course_wide_discussions: [],
always_divide_inline_discussions: false
}
});
return CourseDiscussionsSettingsModel;
});
}).call(this, define || RequireJS.define);
(function(define) {
'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext',
'js/discussions_management/views/divided_discussions_inline',
'js/discussions_management/views/divided_discussions_course_wide',
'edx-ui-toolkit/js/utils/html-utils'
],
function($, _, Backbone, gettext, InlineDiscussionsView, CourseWideDiscussionsView, HtmlUtils) {
var DiscussionsView = Backbone.View.extend({
initialize: function(options) {
this.template = HtmlUtils.template($('#discussions-tpl').text());
this.context = options.context;
this.discussionSettings = options.discussionSettings;
},
render: function() {
HtmlUtils.setHtml(this.$el, this.template({}));
this.showDiscussionTopics();
return this;
},
getSectionCss: function(section) {
return ".instructor-nav .nav-item [data-section='" + section + "']";
},
showDiscussionTopics: function() {
var dividedDiscussionsElement = this.$('.discussions-nav');
if (!this.CourseWideDiscussionsView) {
this.CourseWideDiscussionsView = new CourseWideDiscussionsView({
el: dividedDiscussionsElement,
model: this.context.courseDiscussionTopicDetailsModel,
discussionSettings: this.discussionSettings
}).render();
}
if (!this.InlineDiscussionsView) {
this.InlineDiscussionsView = new InlineDiscussionsView({
el: dividedDiscussionsElement,
model: this.context.courseDiscussionTopicDetailsModel,
discussionSettings: this.discussionSettings
}).render();
}
}
});
return DiscussionsView;
});
}).call(this, define || RequireJS.define);
(function(define) {
'use strict';
define('js/discussions_management/views/discussions_dashboard_factory',
['jquery', 'js/discussions_management/views/discussions',
'js/discussions_management/models/course_discussions_detail',
'js/discussions_management/models/course_discussions_settings'],
function($, DiscussionsView, CourseDiscussionTopicDetailsModel, CourseDiscussionsSettingsModel) {
return function() {
var courseDiscussionSettings = new CourseDiscussionsSettingsModel(),
discussionTopicsSettings = new CourseDiscussionTopicDetailsModel(),
$discussionsManagementElement = $('.discussions-management'),
discussionsView;
courseDiscussionSettings.url = $discussionsManagementElement.data('course-discussion-settings-url');
discussionTopicsSettings.url = $discussionsManagementElement.data('discussion-topics-url');
discussionsView = new DiscussionsView({
el: $discussionsManagementElement,
discussionSettings: courseDiscussionSettings,
context: {
courseDiscussionTopicDetailsModel: discussionTopicsSettings
}
});
courseDiscussionSettings.fetch().done(function() {
discussionTopicsSettings.fetch().done(function() {
discussionsView.render();
});
});
};
});
}).call(this, define || RequireJS.define);
(function(define) {
'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/models/notification', 'js/views/notification'],
function($, _, Backbone) {
var CohortDiscussionConfigurationView = Backbone.View.extend({
function($, _, Backbone, gettext) {
/* global NotificationModel, NotificationView */
var DividedDiscussionConfigurationView = Backbone.View.extend({
/**
* Add/Remove the disabled attribute on given element.
......@@ -14,53 +15,53 @@
},
/**
* Returns the cohorted discussions list.
* Returns the divided discussions list.
* @param {string} selector - To select the discussion elements whose ids to return.
* @returns {Array} - Cohorted discussions.
* @returns {Array} - Divided discussions.
*/
getCohortedDiscussions: function(selector) {
getDividedDiscussions: function(selector) {
var self = this,
cohortedDiscussions = [];
dividedDiscussions = [];
_.each(self.$(selector), function(topic) {
cohortedDiscussions.push($(topic).data('id'));
dividedDiscussions.push($(topic).data('id'));
});
return cohortedDiscussions;
return dividedDiscussions;
},
/**
* Save the cohortSettings' changed attributes to the server via PATCH method.
* Save the discussionSettings' changed attributes to the server via PATCH method.
* It shows the error message(s) if any.
* @param {object} $element - Messages would be shown before this element.
* @param {object} fieldData - Data to update on the server.
*/
saveForm: function($element, fieldData) {
var self = this,
cohortSettingsModel = this.cohortSettings,
discussionSettingsModel = this.discussionSettings,
saveOperation = $.Deferred(),
showErrorMessage;
showErrorMessage = function(message, $element) {
showErrorMessage = function(message) {
self.showMessage(message, $element, 'error');
};
this.removeNotification();
cohortSettingsModel.save(
discussionSettingsModel.save(
fieldData, {patch: true, wait: true}
).done(function() {
saveOperation.resolve();
}).fail(function(result) {
var errorMessage = null;
var errorMessage = null,
jsonResponse;
try {
var jsonResponse = JSON.parse(result.responseText);
jsonResponse = JSON.parse(result.responseText);
errorMessage = jsonResponse.error;
} catch (e) {
// Ignore the exception and show the default error message instead.
}
if (!errorMessage) {
errorMessage = gettext("We've encountered an error. Refresh your browser and then try again.");
errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); // eslint-disable-line max-len
}
showErrorMessage(errorMessage, $element);
showErrorMessage(errorMessage);
saveOperation.reject();
});
return saveOperation.promise();
......@@ -92,6 +93,6 @@
}
});
return CohortDiscussionConfigurationView;
return DividedDiscussionConfigurationView;
});
}).call(this, define || RequireJS.define);
(function(define) {
'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/groups/views/cohort_discussions',
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/discussions_management/views/divided_discussions',
'edx-ui-toolkit/js/utils/html-utils'],
function($, _, Backbone, gettext, CohortDiscussionConfigurationView, HtmlUtils) {
var CourseWideDiscussionsView = CohortDiscussionConfigurationView.extend({
function($, _, Backbone, gettext, DividedDiscussionConfigurationView, HtmlUtils) {
var CourseWideDiscussionsView = DividedDiscussionConfigurationView.extend({
events: {
'change .check-discussion-subcategory-course-wide': 'discussionCategoryStateChanged',
'click .cohort-course-wide-discussions-form .action-save': 'saveCourseWideDiscussionsForm'
},
initialize: function(options) {
this.template = HtmlUtils.template($('#cohort-discussions-course-wide-tpl').text());
this.cohortSettings = options.cohortSettings;
this.template = HtmlUtils.template($('#divided-discussions-course-wide-tpl').text());
this.discussionSettings = options.discussionSettings;
},
render: function() {
HtmlUtils.setHtml(this.$('.cohort-course-wide-discussions-nav'), this.template({
HtmlUtils.setHtml(this.$('.course-wide-discussions-nav'), this.template({
courseWideTopicsHtml: this.getCourseWideDiscussionsHtml(
this.model.get('course_wide_discussions')
)
......@@ -56,25 +56,27 @@
},
/**
* Sends the cohorted_course_wide_discussions to the server and renders the view.
* Sends the courseWideDividedDiscussions to the server and renders the view.
*/
saveCourseWideDiscussionsForm: function(event) {
event.preventDefault();
var self = this,
courseWideCohortedDiscussions = self.getCohortedDiscussions(
courseWideDividedDiscussions = self.getDividedDiscussions(
'.check-discussion-subcategory-course-wide:checked'
),
fieldData = {cohorted_course_wide_discussions: courseWideCohortedDiscussions};
fieldData = {divided_course_wide_discussions: courseWideDividedDiscussions};
event.preventDefault();
self.saveForm(self.$('.course-wide-discussion-topics'), fieldData)
.done(function() {
self.model.fetch()
.done(function() {
self.render();
self.showMessage(gettext('Your changes have been saved.'), self.$('.course-wide-discussion-topics'));
self.showMessage(gettext('Your changes have been saved.'),
self.$('.course-wide-discussion-topics')
);
}).fail(function() {
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again.");
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); // eslint-disable-line max-len
self.showMessage(errorMessage, self.$('.course-wide-discussion-topics'), 'error');
});
});
......
(function(define) {
'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/groups/views/cohort_discussions',
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/discussions_management/views/divided_discussions',
'edx-ui-toolkit/js/utils/html-utils', 'js/vendor/jquery.qubit'],
function($, _, Backbone, gettext, CohortDiscussionConfigurationView, HtmlUtils) {
var InlineDiscussionsView = CohortDiscussionConfigurationView.extend({
function($, _, Backbone, gettext, DividedDiscussionConfigurationView, HtmlUtils) {
var InlineDiscussionsView = DividedDiscussionConfigurationView.extend({
events: {
'change .check-discussion-category': 'setSaveButton',
'change .check-discussion-subcategory-inline': 'setSaveButton',
......@@ -13,17 +13,19 @@
},
initialize: function(options) {
this.template = HtmlUtils.template($('#cohort-discussions-inline-tpl').text());
this.cohortSettings = options.cohortSettings;
this.template = HtmlUtils.template($('#divided-discussions-inline-tpl').text());
this.discussionSettings = options.discussionSettings;
},
render: function() {
var alwaysCohortInlineDiscussions = this.cohortSettings.get('always_cohort_inline_discussions'),
inline_discussions = this.model.get('inline_discussions');
var inlineDiscussions = this.model.get('inline_discussions'),
alwaysDivideInlineDiscussions = this.discussionSettings.get(
'always_divide_inline_discussions'
);
HtmlUtils.setHtml(this.$('.cohort-inline-discussions-nav'), this.template({
inlineDiscussionTopicsHtml: this.getInlineDiscussionsHtml(inline_discussions),
alwaysCohortInlineDiscussions: alwaysCohortInlineDiscussions
HtmlUtils.setHtml(this.$('.inline-discussions-nav'), this.template({
inlineDiscussionTopicsHtml: this.getInlineDiscussionsHtml(inlineDiscussions),
alwaysDivideInlineDiscussions: alwaysDivideInlineDiscussions
}));
// Provides the semantics for a nested list of tri-state checkboxes.
......@@ -32,7 +34,7 @@
// based on the checked values of any checkboxes in child elements of the DOM.
this.$('ul.inline-topics').qubit();
this.setElementsEnabled(alwaysCohortInlineDiscussions, true);
this.setElementsEnabled(alwaysDivideInlineDiscussions, true);
},
/**
......@@ -99,45 +101,48 @@
*
* Enable/Disable the category and sub-category checkboxes.
* Enable/Disable the save button.
* @param {bool} enable_checkboxes - The flag to enable/disable the checkboxes.
* @param {bool} enable_save_button - The flag to enable/disable the save button.
* @param {bool} enableCheckboxes - The flag to enable/disable the checkboxes.
* @param {bool} enableSaveButton - The flag to enable/disable the save button.
*/
setElementsEnabled: function(enable_checkboxes, enable_save_button) {
this.setDisabled(this.$('.check-discussion-category'), enable_checkboxes);
this.setDisabled(this.$('.check-discussion-subcategory-inline'), enable_checkboxes);
this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), enable_save_button);
setElementsEnabled: function(enableCheckboxes, enableSaveButton) {
this.setDisabled(this.$('.check-discussion-category'), enableCheckboxes);
this.setDisabled(this.$('.check-discussion-subcategory-inline'), enableCheckboxes);
this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), enableSaveButton);
},
/**
* Enables the save button for inline discussions.
*/
setSaveButton: function(event) {
setSaveButton: function() {
this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), false);
},
/**
* Sends the cohorted_inline_discussions to the server and renders the view.
* Sends the dividedInlineDiscussions to the server and renders the view.
*/
saveInlineDiscussionsForm: function(event) {
event.preventDefault();
var self = this,
cohortedInlineDiscussions = self.getCohortedDiscussions(
dividedInlineDiscussions = self.getDividedDiscussions(
'.check-discussion-subcategory-inline:checked'
),
fieldData = {
cohorted_inline_discussions: cohortedInlineDiscussions,
always_cohort_inline_discussions: self.$('.check-all-inline-discussions').prop('checked')
divided_inline_discussions: dividedInlineDiscussions,
always_divide_inline_discussions: self.$(
'.check-all-inline-discussions'
).prop('checked')
};
event.preventDefault();
self.saveForm(self.$('.inline-discussion-topics'), fieldData)
.done(function() {
self.model.fetch()
.done(function() {
self.render();
self.showMessage(gettext('Your changes have been saved.'), self.$('.inline-discussion-topics'));
self.showMessage(gettext('Your changes have been saved.'),
self.$('.inline-discussion-topics'));
}).fail(function() {
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again.");
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); // eslint-disable-line max-len
self.showMessage(errorMessage, self.$('.inline-discussion-topics'), 'error');
});
});
......
......@@ -4,10 +4,7 @@
var CourseCohortSettingsModel = Backbone.Model.extend({
idAttribute: 'id',
defaults: {
is_cohorted: false,
cohorted_inline_discussions: [],
cohorted_course_wide_discussions: [],
always_cohort_inline_discussions: false
is_cohorted: false
}
});
return CourseCohortSettingsModel;
......
......@@ -4,13 +4,11 @@
'js/groups/models/verified_track_settings',
'js/groups/views/cohort_editor', 'js/groups/views/cohort_form',
'js/groups/views/course_cohort_settings_notification',
'js/groups/views/cohort_discussions_inline', 'js/groups/views/cohort_discussions_course_wide',
'js/groups/views/verified_track_settings_notification',
'edx-ui-toolkit/js/utils/html-utils',
'js/views/file_uploader', 'js/models/notification', 'js/views/notification', 'string_utils'],
function($, _, Backbone, gettext, CohortModel, VerifiedTrackSettingsModel, CohortEditorView, CohortFormView,
CourseCohortSettingsNotificationView, InlineDiscussionsView, CourseWideDiscussionsView,
VerifiedTrackSettingsNotificationView, HtmlUtils) {
CourseCohortSettingsNotificationView, VerifiedTrackSettingsNotificationView, HtmlUtils) {
var hiddenClass = 'is-hidden',
disabledClass = 'is-disabled',
enableCohortsSelector = '.cohorts-state';
......@@ -25,7 +23,6 @@
'click .cohort-management-add-form .action-cancel': 'cancelAddCohortForm',
'click .link-cross-reference': 'showSection',
'click .toggle-cohort-management-secondary': 'showCsvUpload',
'click .toggle-cohort-management-discussions': 'showDiscussionTopics'
},
initialize: function(options) {
......@@ -306,27 +303,6 @@
}).render();
}
},
showDiscussionTopics: function(event) {
event.preventDefault();
$(event.currentTarget).addClass(hiddenClass);
var cohortDiscussionsElement = this.$('.cohort-discussions-nav').removeClass(hiddenClass);
if (!this.CourseWideDiscussionsView) {
this.CourseWideDiscussionsView = new CourseWideDiscussionsView({
el: cohortDiscussionsElement,
model: this.context.discussionTopicsSettingsModel,
cohortSettings: this.cohortSettings
}).render();
}
if (!this.InlineDiscussionsView) {
this.InlineDiscussionsView = new InlineDiscussionsView({
el: cohortDiscussionsElement,
model: this.context.discussionTopicsSettingsModel,
cohortSettings: this.cohortSettings
}).render();
}
},
getSectionCss: function(section) {
return ".instructor-nav .nav-item [data-section='" + section + "']";
......
(function(define, undefined) {
'use strict';
define(['jquery', 'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/course_cohort_settings',
'js/groups/models/cohort_discussions', 'js/groups/models/content_group'],
function($, CohortsView, CohortCollection, CourseCohortSettingsModel, DiscussionTopicsSettingsModel, ContentGroupModel) {
'js/groups/models/content_group'],
function($, CohortsView, CohortCollection, CourseCohortSettingsModel, ContentGroupModel) {
return function(contentGroups, studioGroupConfigurationsUrl) {
var contentGroupModels = $.map(contentGroups, function(group) {
return new ContentGroupModel({
......@@ -14,33 +14,27 @@
var cohorts = new CohortCollection(),
courseCohortSettings = new CourseCohortSettingsModel(),
discussionTopicsSettings = new DiscussionTopicsSettingsModel();
$cohortManagementElement = $('.cohort-management');
var cohortManagementElement = $('.cohort-management');
cohorts.url = cohortManagementElement.data('cohorts_url');
courseCohortSettings.url = cohortManagementElement.data('course_cohort_settings_url');
discussionTopicsSettings.url = cohortManagementElement.data('discussion-topics-url');
cohorts.url = $cohortManagementElement.data('cohorts_url');
courseCohortSettings.url = $cohortManagementElement.data('course_cohort_settings_url');
var cohortsView = new CohortsView({
el: cohortManagementElement,
el: $cohortManagementElement,
model: cohorts,
contentGroups: contentGroupModels,
cohortSettings: courseCohortSettings,
context: {
discussionTopicsSettingsModel: discussionTopicsSettings,
uploadCohortsCsvUrl: cohortManagementElement.data('upload_cohorts_csv_url'),
verifiedTrackCohortingUrl: cohortManagementElement.data('verified_track_cohorting_url'),
uploadCohortsCsvUrl: $cohortManagementElement.data('upload_cohorts_csv_url'),
verifiedTrackCohortingUrl: $cohortManagementElement.data('verified_track_cohorting_url'),
studioGroupConfigurationsUrl: studioGroupConfigurationsUrl,
isCcxEnabled: cohortManagementElement.data('is_ccx_enabled')
isCcxEnabled: $cohortManagementElement.data('is_ccx_enabled')
}
});
cohorts.fetch().done(function() {
courseCohortSettings.fetch().done(function() {
discussionTopicsSettings.fetch().done(function() {
cohortsView.render();
});
cohortsView.render();
});
});
};
......
(function() {
'use strict';
function DiscussionsManagement($section) {
this.$section = $section;
this.$section.data('wrapper', this);
}
DiscussionsManagement.prototype.onClickTitle = function() {};
window.InstructorDashboard.sections.DiscussionsManagement = DiscussionsManagement;
}).call(this);
......@@ -188,6 +188,9 @@ such that the value can be defined later than this assignment (file load order).
constructor: window.InstructorDashboard.sections.CohortManagement,
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#cohort_management')
}, {
constructor: window.InstructorDashboard.sections.DiscussionsManagement,
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#discussions_management')
}, {
constructor: window.InstructorDashboard.sections.Certificates,
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#certificates')
}, {
......
......@@ -28,6 +28,7 @@
'js/edxnotes/views/page_factory',
'js/financial-assistance/financial_assistance_form_factory',
'js/groups/views/cohorts_dashboard_factory',
'js/discussions_management/views/discussions_dashboard_factory',
'js/header_factory',
'js/learner_dashboard/program_details_factory',
'js/learner_dashboard/program_list_factory',
......
......@@ -731,6 +731,7 @@
'js/spec/edxnotes/views/visibility_decorator_spec.js',
'js/spec/financial-assistance/financial_assistance_form_view_spec.js',
'js/spec/groups/views/cohorts_spec.js',
'js/spec/groups/views/discussions_spec.js',
'js/spec/instructor_dashboard/certificates_bulk_exception_spec.js',
'js/spec/instructor_dashboard/certificates_exception_spec.js',
'js/spec/instructor_dashboard/certificates_invalidation_spec.js',
......
......@@ -1181,49 +1181,6 @@
}
}
// cohort discussions interface.
.cohort-discussions-nav {
.cohort-course-wide-discussions-form {
.form-actions {
padding-top: ($baseline/2);
}
}
.category-title,
.topic-name,
.all-inline-discussions,
.always_cohort_inline_discussions,
.cohort_inline_discussions {
padding-left: ($baseline/2);
}
.always_cohort_inline_discussions,
.cohort_inline_discussions {
padding-top: ($baseline/2);
}
.category-item,
.subcategory-item {
padding-top: ($baseline/2);
}
.cohorted-text {
color: $uxpl-blue-base;
}
.discussions-wrapper {
@extend %ui-no-list;
padding: 0 ($baseline/2);
.subcategories {
padding: 0 ($baseline*1.5);
}
}
}
.wrapper-tabs { // This applies to the tab-like interface that toggles between the student management and the group settings
@extend %ui-no-list;
@extend %ui-depth1;
......@@ -1280,6 +1237,77 @@
}
}
// view - discussions management
// --------------------
.instructor-dashboard-wrapper-2 section.idash-section#discussions_management {
.form-submit {
@include idashbutton($uxpl-blue-base);
@include font-size(14);
@include line-height(14);
margin-right: ($baseline/2);
margin-bottom: 0;
text-shadow: none;
}
.discussions-management-supplemental {
@extend %t-copy-sub1;
margin-top: $baseline;
padding: ($baseline/2) $baseline;
background: $gray-l6;
border-radius: ($baseline/10);
}
// cohort discussions interface.
.discussions-nav {
.cohort-course-wide-discussions-form {
.form-actions {
padding-top: ($baseline/2);
}
}
.category-title,
.topic-name,
.all-inline-discussions,
.always_divide_inline_discussions,
.divide_inline_discussions {
padding-left: ($baseline/2);
}
.always_divide_inline_discussions,
.divide_inline_discussions {
padding-top: ($baseline/2);
}
.category-item,
.subcategory-item {
padding-top: ($baseline/2);
}
.divided-discussion-text{
color: $uxpl-blue-base;
}
.discussions-wrapper {
@extend %ui-no-list;
padding: 0 ($baseline/2);
.subcategories {
padding: 0 ($baseline*1.5);
}
}
}
.wrapper-tabs {
@extend %ui-no-list;
@extend %ui-depth1;
position: relative;
top: 1px;
padding: 0 $baseline;
}
}
// view - student admin
// --------------------
......
......@@ -3,7 +3,7 @@
<label>
<input data-id="<%- id %>" class="check-discussion-subcategory-<%- type %>" type="checkbox" <%- is_divided ? 'checked="checked"' : '' %> />
<span class="topic-name"><%- name %></span>
<span class="cohorted-text <%- is_divided ? '' : 'hidden'%>">- <%- gettext('Cohorted') %></span>
<span class="divided-discussion-text <%- is_divided ? '' : 'hidden'%>">- <%- gettext('Divided') %></span>
</label>
</div>
</li>
......@@ -13,7 +13,6 @@ from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_
data-cohorts_url="${section_data['cohorts_url']}"
data-upload_cohorts_csv_url="${section_data['upload_cohorts_csv_url']}"
data-course_cohort_settings_url="${section_data['course_cohort_settings_url']}"
data-discussion-topics-url="${section_data['discussion_topics_url']}"
data-verified_track_cohorting_url="${section_data['verified_track_cohorting_url']}"
data-is_ccx_enabled="${'true' if section_data['ccx_is_enabled'] else 'false'}"
>
......
......@@ -49,15 +49,5 @@
%>
</p>
</div>
<hr class="divider divider-lv1" />
<!-- Discussion Topics. -->
<button class="toggle-cohort-management-discussions" data-href="#cohort-discussions-management"><%- gettext('Specify whether discussion topics are divided by cohort') %></button>
<div class="cohort-discussions-nav is-hidden" id="cohort-discussions-management" tabindex="-1">
<div class="cohort-course-wide-discussions-nav"></div>
<div class="cohort-inline-discussions-nav"></div>
</div>
</div>
<% } %>
<!-- Discussion Topics. -->
<div class="discussions-nav" id="discussions-management" tabindex="-1">
<div class="course-wide-discussions-nav"></div>
<div class="inline-discussions-nav"></div>
</div>
<%page expression_filter="h" args="section_data"/>
<%namespace name='static' file='../../static_content.html'/>
<%!
from django.utils.translation import ugettext as _
from openedx.core.djangolib.js_utils import js_escaped_string, dump_js_escaped_json
from courseware.courses import get_studio_url
from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_user_partition
%>
<div class="discussions-management"
data-discussion-topics-url="${section_data['discussion_topics_url']}"
data-course-discussion-settings-url="${section_data['course_discussion_settings']}"
>
</div>
<%block name="js_extra">
<%static:require_module module_name="js/discussions_management/views/discussions_dashboard_factory" class_name="DiscussionsFactory">
DiscussionsFactory();
</%static:require_module>
</%block>
<h3 class="hd hd-3 subsection-title"><%- gettext('Specify whether discussion topics are divided by cohort') %></h3>
<form action="" method="post" id="cohort-course-wide-discussions-form" class="cohort-course-wide-discussions-form">
<div class="wrapper cohort-management-supplemental">
<div class="wrapper discussions-management-supplemental">
<div class="form-fields">
<div class="form-field">
<div class="course-wide-discussion-topics">
......
<hr class="divider divider-lv1" />
<form action="" method="post" id="cohort-inline-discussions-form" class="cohort-inline-discussions-form">
<div class="wrapper cohort-management-supplemental">
<div class="wrapper discussions-management-supplemental">
<div class="form-fields">
<div class="form-field">
<div class="inline-discussion-topics">
<h4 class="hd hd-4 subsection-title"><%- gettext('Content-Specific Discussion Topics') %></h4>
<p><%- gettext('Specify whether content-specific discussion topics are divided by cohort.') %></p>
<div class="always_cohort_inline_discussions">
<div class="always_divide_inline_discussions">
<label>
<input type="radio" name="inline" class="check-all-inline-discussions" <%- alwaysCohortInlineDiscussions ? 'checked="checked"' : '' %>/>
<input type="radio" name="inline" class="check-all-inline-discussions" <%- alwaysDivideInlineDiscussions ? 'checked="checked"' : '' %>/>
<span class="all-inline-discussions"><%- gettext('Always cohort content-specific discussion topics') %></span>
</label>
</div>
<div class="cohort_inline_discussions">
<div class="divide_inline_discussions">
<label>
<input type="radio" name="inline" class="check-cohort-inline-discussions" <%- alwaysCohortInlineDiscussions ? '' : 'checked="checked"' %>/>
<input type="radio" name="inline" class="check-cohort-inline-discussions" <%- alwaysDivideInlineDiscussions ? '' : 'checked="checked"' %>/>
<span class="all-inline-discussions"><%- gettext('Cohort selected content-specific discussion topics') %></span>
</label>
</div>
......
......@@ -81,7 +81,7 @@ from openedx.core.djangolib.markup import HTML
## Include Underscore templates
<%block name="header_extras">
% for template_name in ["cohorts", "enrollment-code-lookup-links", "cohort-editor", "cohort-group-header", "cohort-selector", "cohort-form", "notification", "cohort-state", "cohort-discussions-inline", "cohort-discussions-course-wide", "cohort-discussions-category", "cohort-discussions-subcategory", "certificate-white-list", "certificate-white-list-editor", "certificate-bulk-white-list", "certificate-invalidation"]:
% for template_name in ["cohorts", "discussions", "enrollment-code-lookup-links", "cohort-editor", "cohort-group-header", "cohort-selector", "cohort-form", "notification", "cohort-state", "divided-discussions-inline", "divided-discussions-course-wide", "cohort-discussions-category", "cohort-discussions-subcategory", "certificate-white-list", "certificate-white-list-editor", "certificate-bulk-white-list", "certificate-invalidation"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="instructor/instructor_dashboard_2/${template_name}.underscore" />
</script>
......
......@@ -504,6 +504,15 @@ urlpatterns += (
include(COURSE_URLS)
),
# Discussions Management
url(
r'^courses/{}/discussions/settings$'.format(
settings.COURSE_KEY_PATTERN,
),
'openedx.core.djangoapps.course_groups.views.course_discussions_settings_handler',
name='course_discussions_settings',
),
# Cohorts management
url(
r'^courses/{}/cohorts/settings$'.format(
......@@ -548,11 +557,11 @@ urlpatterns += (
name='debug_cohort_mgmt',
),
url(
r'^courses/{}/cohorts/topics$'.format(
r'^courses/{}/discussion/topics$'.format(
settings.COURSE_KEY_PATTERN,
),
'openedx.core.djangoapps.course_groups.views.cohort_discussion_topics',
name='cohort_discussion_topics',
'openedx.core.djangoapps.course_groups.views.discussion_topics',
name='discussion_topics',
),
url(
r'^courses/{}/verified_track_content/settings'.format(
......
......@@ -121,6 +121,16 @@ def is_course_cohorted(course_key):
return _get_course_cohort_settings(course_key).is_cohorted
def get_course_cohort_id(course_key):
"""
Given a course key, return the int id for the cohort settings.
Raises:
Http404 if the course doesn't exist.
"""
return _get_course_cohort_settings(course_key).id
def set_course_cohorted(course_key, cohorted):
"""
Given a course course and a boolean, sets whether or not the course is cohorted.
......
......@@ -136,42 +136,71 @@ def config_course_cohorts_legacy(
# pylint: disable=dangerous-default-value
def config_course_discussions(
course,
discussion_topics={},
divided_discussions=[],
always_divide_inline_discussions=False
):
"""
Set discussions and configure divided discussions for a course.
Arguments:
course: CourseDescriptor
discussion_topics (Dict): Discussion topic names. Picks ids and
sort_keys automatically.
divided_discussions: Discussion topics to divide. Converts the
list to use the same ids as discussion topic names.
always_divide_inline_discussions (bool): Whether inline discussions
should be divided by default.
Returns:
Nothing -- modifies course in place.
"""
def to_id(name):
"""Convert name to id."""
return topic_name_to_id(course, name)
set_course_discussion_settings(
course.id,
divided_discussions=[to_id(name) for name in divided_discussions],
always_divide_inline_discussions=always_divide_inline_discussions,
division_scheme=CourseDiscussionSettings.COHORT,
)
course.discussion_topics = dict((name, {"sort_key": "A", "id": to_id(name)})
for name in discussion_topics)
try:
# Not implemented for XMLModulestore, which is used by test_cohorts.
modulestore().update_item(course, ModuleStoreEnum.UserID.test)
except NotImplementedError:
pass
# pylint: disable=dangerous-default-value
def config_course_cohorts(
course,
is_cohorted,
auto_cohorts=[],
manual_cohorts=[],
discussion_topics=[],
divided_discussions=[],
always_divide_inline_discussions=False
):
"""
Set discussions and configure cohorts for a course.
Set and configure cohorts for a course.
Arguments:
course: CourseDescriptor
is_cohorted (bool): Is the course cohorted?
auto_cohorts (list): Names of auto cohorts to create.
manual_cohorts (list): Names of manual cohorts to create.
discussion_topics (list): Discussion topic names. Picks ids and
sort_keys automatically.
divided_discussions: Discussion topics to divide. Converts the
list to use the same ids as discussion topic names.
always_divide_inline_discussions (bool): Whether inline discussions
should be divided by default.
Returns:
Nothing -- modifies course in place.
"""
def to_id(name):
"""Convert name to id."""
return topic_name_to_id(course, name)
set_course_cohorted(course.id, is_cohorted)
set_course_discussion_settings(
course.id,
divided_discussions=[to_id(name) for name in divided_discussions],
always_divide_inline_discussions=always_divide_inline_discussions,
division_scheme=CourseDiscussionSettings.COHORT,
)
......@@ -183,8 +212,6 @@ def config_course_cohorts(
cohort = CohortFactory(course_id=course.id, name=cohort_name)
CourseCohortFactory(course_user_group=cohort, assignment_type=CourseCohort.MANUAL)
course.discussion_topics = dict((name, {"sort_key": "A", "id": to_id(name)})
for name in discussion_topics)
try:
# Not implemented for XMLModulestore, which is used by test_cohorts.
modulestore().update_item(course, ModuleStoreEnum.UserID.test)
......
......@@ -64,20 +64,29 @@ def unlink_cohort_partition_group(cohort):
# pylint: disable=invalid-name
def _get_course_cohort_settings_representation(course, is_cohorted, course_discussion_settings):
def _get_course_cohort_settings_representation(cohort_id, is_cohorted):
"""
Returns a JSON representation of a course cohort settings.
"""
return {
'id': cohort_id,
'is_cohorted': is_cohorted,
}
def _get_course_discussion_settings_representation(course, course_discussion_settings):
"""
Returns a JSON representation of a course discussion settings.
"""
divided_course_wide_discussions, divided_inline_discussions = get_divided_discussions(
course, course_discussion_settings
)
return {
'id': course_discussion_settings.id,
'is_cohorted': is_cohorted,
'cohorted_inline_discussions': divided_inline_discussions,
'cohorted_course_wide_discussions': divided_course_wide_discussions,
'always_cohort_inline_discussions': course_discussion_settings.always_divide_inline_discussions,
'divided_inline_discussions': divided_inline_discussions,
'divided_course_wide_discussions': divided_course_wide_discussions,
'always_divide_inline_discussions': course_discussion_settings.always_divide_inline_discussions,
}
......@@ -121,18 +130,17 @@ def get_divided_discussions(course, discussion_settings):
@ensure_csrf_cookie
@expect_json
@login_required
def course_cohort_settings_handler(request, course_key_string):
def course_discussions_settings_handler(request, course_key_string):
"""
The restful handler for cohort setting requests. Requires JSON.
The restful handler for divided discussion setting requests. Requires JSON.
This will raise 404 if user is not staff.
GET
Returns the JSON representation of cohort settings for the course.
Returns the JSON representation of divided discussion settings for the course.
PATCH
Updates the cohort settings for the course. Returns the JSON representation of updated settings.
Updates the divided discussion settings for the course. Returns the JSON representation of updated settings.
"""
course_key = CourseKey.from_string(course_key_string)
course = get_course_with_access(request.user, 'staff', course_key)
is_cohorted = cohorts.is_course_cohorted(course_key)
discussion_settings = get_course_discussion_settings(course_key)
if request.method == 'PATCH':
......@@ -142,40 +150,72 @@ def course_cohort_settings_handler(request, course_key_string):
settings_to_change = {}
if 'is_cohorted' in request.json:
settings_to_change['is_cohorted'] = request.json.get('is_cohorted')
if 'cohorted_course_wide_discussions' in request.json or 'cohorted_inline_discussions' in request.json:
if 'divided_course_wide_discussions' in request.json or 'divided_inline_discussions' in request.json:
divided_course_wide_discussions = request.json.get(
'cohorted_course_wide_discussions', divided_course_wide_discussions
'divided_course_wide_discussions', divided_course_wide_discussions
)
divided_inline_discussions = request.json.get(
'cohorted_inline_discussions', divided_inline_discussions
'divided_inline_discussions', divided_inline_discussions
)
settings_to_change['divided_discussions'] = divided_course_wide_discussions + divided_inline_discussions
if 'always_cohort_inline_discussions' in request.json:
if 'always_divide_inline_discussions' in request.json:
settings_to_change['always_divide_inline_discussions'] = request.json.get(
'always_cohort_inline_discussions'
'always_divide_inline_discussions'
)
if not settings_to_change:
return JsonResponse({"error": unicode("Bad Request")}, 400)
try:
if 'is_cohorted' in settings_to_change:
is_cohorted = settings_to_change['is_cohorted']
cohorts.set_course_cohorted(course_key, is_cohorted)
del settings_to_change['is_cohorted']
settings_to_change['division_scheme'] = CourseDiscussionSettings.COHORT if is_cohorted \
else CourseDiscussionSettings.NONE
if settings_to_change:
discussion_settings = set_course_discussion_settings(course_key, **settings_to_change)
except ValueError as err:
# Note: error message not translated because it is not exposed to the user (UI prevents this state).
return JsonResponse({"error": unicode(err)}, 400)
return JsonResponse(_get_course_discussion_settings_representation(
course,
discussion_settings
))
@require_http_methods(("GET", "PATCH"))
@ensure_csrf_cookie
@expect_json
@login_required
def course_cohort_settings_handler(request, course_key_string):
"""
The restful handler for cohort setting requests. Requires JSON.
This will raise 404 if user is not staff.
GET
Returns the JSON representation of cohort settings for the course.
PATCH
Updates the cohort settings for the course. Returns the JSON representation of updated settings.
"""
course_key = CourseKey.from_string(course_key_string)
# Although this course data is not used this method will return 404 is user is not staff
course = get_course_with_access(request.user, 'staff', course_key)
if request.method == 'PATCH':
if 'is_cohorted' not in request.json:
return JsonResponse({"error": unicode("Bad Request")}, 400)
is_cohorted = request.json.get('is_cohorted')
try:
cohorts.set_course_cohorted(course_key, is_cohorted)
scheme = CourseDiscussionSettings.COHORT if is_cohorted else CourseDiscussionSettings.NONE
scheme_settings = {'division_scheme': scheme}
set_course_discussion_settings(course_key, **scheme_settings)
except ValueError as err:
# Note: error message not translated because it is not exposed to the user (UI prevents this state).
return JsonResponse({"error": unicode(err)}, 400)
return JsonResponse(_get_course_cohort_settings_representation(course, is_cohorted, discussion_settings))
return JsonResponse(_get_course_cohort_settings_representation(
cohorts.get_course_cohort_id(course_key),
cohorts.is_course_cohorted(course_key)
))
@require_http_methods(("GET", "PUT", "POST", "PATCH"))
......@@ -428,9 +468,9 @@ def debug_cohort_mgmt(request, course_key_string):
@expect_json
@login_required
def cohort_discussion_topics(request, course_key_string):
def discussion_topics(request, course_key_string):
"""
The handler for cohort discussion categories requests.
The handler for divided discussion categories requests.
This will raise 404 if user is not staff.
Returns the JSON representation of discussion topics w.r.t categories for the course.
......
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