Commit 0ebd8c29 by muhammad-ammar Committed by Usman Khalid

Allow Instructor To Rename Cohorts And Set Cohort Assignment Method

TNL-181
parent ec6cc04b
...@@ -6,7 +6,7 @@ Instructor (2) dashboard page. ...@@ -6,7 +6,7 @@ Instructor (2) dashboard page.
from bok_choy.page_object import PageObject from bok_choy.page_object import PageObject
from .course_page import CoursePage from .course_page import CoursePage
import os import os
from bok_choy.promise import EmptyPromise from bok_choy.promise import EmptyPromise, Promise
from ...tests.helpers import select_option_by_text, get_selected_option_text, get_options from ...tests.helpers import select_option_by_text, get_selected_option_text, get_options
...@@ -101,6 +101,7 @@ class MembershipPageCohortManagementSection(PageObject): ...@@ -101,6 +101,7 @@ class MembershipPageCohortManagementSection(PageObject):
content_group_selector_css = 'select.input-cohort-group-association' content_group_selector_css = 'select.input-cohort-group-association'
no_content_group_button_css = '.cohort-management-details-association-course input.radio-no' no_content_group_button_css = '.cohort-management-details-association-course input.radio-no'
select_content_group_button_css = '.cohort-management-details-association-course input.radio-yes' select_content_group_button_css = '.cohort-management-details-association-course input.radio-yes'
assignment_type_buttons_css = '.cohort-management-assignment-type-settings input'
def is_browser_on_page(self): def is_browser_on_page(self):
return self.q(css='.cohort-management.membership-section').present return self.q(css='.cohort-management.membership-section').present
...@@ -115,7 +116,12 @@ class MembershipPageCohortManagementSection(PageObject): ...@@ -115,7 +116,12 @@ class MembershipPageCohortManagementSection(PageObject):
""" """
Returns the available options in the cohort dropdown, including the initial "Select a cohort". Returns the available options in the cohort dropdown, including the initial "Select a cohort".
""" """
return self.q(css=self._bounded_selector("#cohort-select option")) def check_func():
"""Promise Check Function"""
query = self.q(css=self._bounded_selector("#cohort-select option"))
return len(query) > 0, query
return Promise(check_func, "Waiting for cohort selector to populate").fulfill()
def _cohort_name(self, label): def _cohort_name(self, label):
""" """
...@@ -129,6 +135,41 @@ class MembershipPageCohortManagementSection(PageObject): ...@@ -129,6 +135,41 @@ class MembershipPageCohortManagementSection(PageObject):
""" """
return int(label.split(' (')[1].split(')')[0]) return int(label.split(' (')[1].split(')')[0])
def save_cohort_settings(self):
"""
Click on Save button shown after click on Settings tab or when we add a new cohort.
"""
self.q(css=self._bounded_selector("div.form-actions .action-save")).first.click()
@property
def is_assignment_settings_disabled(self):
"""
Check if assignment settings are disabled.
"""
attributes = self.q(css=self._bounded_selector('.cohort-management-assignment-type-settings')).attrs('class')
if 'is-disabled' in attributes[0].split():
return True
return False
@property
def assignment_settings_message(self):
"""
Return assignment settings disabled message in case of default cohort.
"""
query = self.q(css=self._bounded_selector('.copy-error'))
if query.present:
return query.text[0]
else:
return ''
@property
def cohort_name_in_header(self):
"""
Return cohort name as shown in cohort header.
"""
return self._cohort_name(self.q(css=self._bounded_selector(".group-header-title .title-value")).text[0])
def get_cohorts(self): def get_cohorts(self):
""" """
Returns, as a list, the names of the available cohorts in the drop-down, filtering out "Select a cohort". Returns, as a list, the names of the available cohorts in the drop-down, filtering out "Select a cohort".
...@@ -142,10 +183,6 @@ class MembershipPageCohortManagementSection(PageObject): ...@@ -142,10 +183,6 @@ class MembershipPageCohortManagementSection(PageObject):
""" """
Returns the name of the selected cohort. Returns the name of the selected cohort.
""" """
EmptyPromise(
lambda: len(self._get_cohort_options().results) > 0,
"Waiting for cohort selector to populate"
).fulfill()
return self._cohort_name( return self._cohort_name(
self._get_cohort_options().filter(lambda el: el.is_selected()).first.text[0] self._get_cohort_options().filter(lambda el: el.is_selected()).first.text[0]
) )
...@@ -162,10 +199,6 @@ class MembershipPageCohortManagementSection(PageObject): ...@@ -162,10 +199,6 @@ class MembershipPageCohortManagementSection(PageObject):
""" """
Selects the given cohort in the drop-down. Selects the given cohort in the drop-down.
""" """
EmptyPromise(
lambda: cohort_name in self.get_cohorts(),
"Waiting for cohort selector to populate"
).fulfill()
# Note: can't use Select to select by text because the count is also included in the displayed text. # Note: can't use Select to select by text because the count is also included in the displayed text.
self._get_cohort_options().filter( self._get_cohort_options().filter(
lambda el: self._cohort_name(el.text) == cohort_name lambda el: self._cohort_name(el.text) == cohort_name
...@@ -176,7 +209,25 @@ class MembershipPageCohortManagementSection(PageObject): ...@@ -176,7 +209,25 @@ class MembershipPageCohortManagementSection(PageObject):
"Waiting to confirm cohort has been selected" "Waiting to confirm cohort has been selected"
).fulfill() ).fulfill()
def add_cohort(self, cohort_name, content_group=None): def set_cohort_name(self, cohort_name):
"""
Set Cohort Name.
"""
textinput = self.q(css=self._bounded_selector("#cohort-name")).results[0]
textinput.clear()
textinput.send_keys(cohort_name)
def set_assignment_type(self, assignment_type):
"""
Set assignment type for selected cohort.
Arguments:
assignment_type (str): Should be 'random' or 'manual'
"""
css = self._bounded_selector(self.assignment_type_buttons_css)
self.q(css=css).filter(lambda el: el.get_attribute('value') == assignment_type).first.click()
def add_cohort(self, cohort_name, content_group=None, assignment_type=None):
""" """
Adds a new manual cohort with the specified name. Adds a new manual cohort with the specified name.
If a content group should also be associated, the name of the content group should be specified. If a content group should also be associated, the name of the content group should be specified.
...@@ -187,9 +238,15 @@ class MembershipPageCohortManagementSection(PageObject): ...@@ -187,9 +238,15 @@ class MembershipPageCohortManagementSection(PageObject):
create_buttons.results[len(create_buttons.results) - 1].click() create_buttons.results[len(create_buttons.results) - 1].click()
textinput = self.q(css=self._bounded_selector("#cohort-name")).results[0] textinput = self.q(css=self._bounded_selector("#cohort-name")).results[0]
textinput.send_keys(cohort_name) textinput.send_keys(cohort_name)
# Manual assignment type will be selected by default for a new cohort
# if we are not setting the assignment type explicitly
if assignment_type:
self.set_assignment_type(assignment_type)
if content_group: if content_group:
self._select_associated_content_group(content_group) self._select_associated_content_group(content_group)
self.q(css=self._bounded_selector("div.form-actions .action-save")).first.click() self.save_cohort_settings()
def get_cohort_group_setup(self): def get_cohort_group_setup(self):
""" """
...@@ -200,6 +257,12 @@ class MembershipPageCohortManagementSection(PageObject): ...@@ -200,6 +257,12 @@ class MembershipPageCohortManagementSection(PageObject):
def select_edit_settings(self): def select_edit_settings(self):
self.q(css=self._bounded_selector(".action-edit")).first.click() self.q(css=self._bounded_selector(".action-edit")).first.click()
def select_manage_settings(self):
"""
Click on Manage Students Tab under cohort management section.
"""
self.q(css=self._bounded_selector(".tab-manage_students")).first.click()
def add_students_to_selected_cohort(self, users): def add_students_to_selected_cohort(self, users):
""" """
Adds a list of users (either usernames or email addresses) to the currently selected cohort. Adds a list of users (either usernames or email addresses) to the currently selected cohort.
...@@ -245,23 +308,6 @@ class MembershipPageCohortManagementSection(PageObject): ...@@ -245,23 +308,6 @@ class MembershipPageCohortManagementSection(PageObject):
return None return None
return get_selected_option_text(self.q(css=self._bounded_selector(self.content_group_selector_css))) return get_selected_option_text(self.q(css=self._bounded_selector(self.content_group_selector_css)))
def verify_cohort_content_group_selected(self, content_group=None):
"""
Waits for the expected content_group (or none) to show as selected for
cohort associated content group.
"""
if content_group:
self.wait_for(
lambda: unicode(
self.q(css='select.input-cohort-group-association option:checked').text[0]
) == content_group,
"Cohort group has been selected."
)
else:
self.wait_for_element_visibility(
'.cohort-management-details-association-course input.radio-no:checked',
'Radio button "No content group" has been selected.')
def set_cohort_associated_content_group(self, content_group=None, select_settings=True): def set_cohort_associated_content_group(self, content_group=None, select_settings=True):
""" """
Sets the content group associated with the cohort currently being edited. Sets the content group associated with the cohort currently being edited.
...@@ -274,8 +320,7 @@ class MembershipPageCohortManagementSection(PageObject): ...@@ -274,8 +320,7 @@ class MembershipPageCohortManagementSection(PageObject):
self.q(css=self._bounded_selector(self.no_content_group_button_css)).first.click() self.q(css=self._bounded_selector(self.no_content_group_button_css)).first.click()
else: else:
self._select_associated_content_group(content_group) self._select_associated_content_group(content_group)
self.q(css=self._bounded_selector("div.form-actions .action-save")).first.click() self.save_cohort_settings()
self.verify_cohort_content_group_selected(content_group)
def _select_associated_content_group(self, content_group): def _select_associated_content_group(self, content_group):
""" """
......
...@@ -73,7 +73,7 @@ class CohortTestMixin(object): ...@@ -73,7 +73,7 @@ class CohortTestMixin(object):
Adds a cohort by name, returning its ID. Adds a cohort by name, returning its ID.
""" """
url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/' url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/'
data = json.dumps({"name": cohort_name}) data = json.dumps({"name": cohort_name, 'assignment_type': 'manual'})
response = course_fixture.session.post(url, data=data, headers=course_fixture.headers) response = course_fixture.session.post(url, data=data, headers=course_fixture.headers)
self.assertTrue(response.ok, "Failed to create cohort") self.assertTrue(response.ok, "Failed to create cohort")
return response.json()['id'] return response.json()['id']
......
...@@ -6,14 +6,17 @@ var edx = edx || {}; ...@@ -6,14 +6,17 @@ var edx = edx || {};
edx.groups = edx.groups || {}; edx.groups = edx.groups || {};
edx.groups.CohortEditorView = Backbone.View.extend({ edx.groups.CohortEditorView = Backbone.View.extend({
events : { events : {
'click .wrapper-tabs .tab': 'selectTab', 'click .wrapper-tabs .tab': 'selectTab',
'click .tab-content-settings .action-save': 'saveSettings', 'click .tab-content-settings .action-save': 'saveSettings',
'click .tab-content-settings .action-cancel': 'cancelSettings',
'submit .cohort-management-group-add-form': 'addStudents' 'submit .cohort-management-group-add-form': 'addStudents'
}, },
initialize: function(options) { initialize: function(options) {
this.template = _.template($('#cohort-editor-tpl').text()); this.template = _.template($('#cohort-editor-tpl').text());
this.groupHeaderTemplate = _.template($('#cohort-group-header-tpl').text());
this.cohorts = options.cohorts; this.cohorts = options.cohorts;
this.contentGroups = options.contentGroups; this.contentGroups = options.contentGroups;
this.context = options.context; this.context = options.context;
...@@ -26,9 +29,9 @@ var edx = edx || {}; ...@@ -26,9 +29,9 @@ var edx = edx || {};
render: function() { render: function() {
this.$el.html(this.template({ this.$el.html(this.template({
cohort: this.model, cohort: this.model
studioAdvancedSettingsUrl: this.context.studioAdvancedSettingsUrl
})); }));
this.renderGroupHeader();
this.cohortFormView = new CohortFormView({ this.cohortFormView = new CohortFormView({
model: this.model, model: this.model,
contentGroups: this.contentGroups, contentGroups: this.contentGroups,
...@@ -39,6 +42,13 @@ var edx = edx || {}; ...@@ -39,6 +42,13 @@ var edx = edx || {};
return this; return this;
}, },
renderGroupHeader: function() {
this.$('.cohort-management-group-header').html(this.groupHeaderTemplate({
cohort: this.model,
studioAdvancedSettingsUrl: this.context.studioAdvancedSettingsUrl
}));
},
selectTab: function(event) { selectTab: function(event) {
var tabElement = $(event.currentTarget), var tabElement = $(event.currentTarget),
tabName = tabElement.data('tab'); tabName = tabElement.data('tab');
...@@ -53,13 +63,20 @@ var edx = edx || {}; ...@@ -53,13 +63,20 @@ var edx = edx || {};
saveSettings: function(event) { saveSettings: function(event) {
var cohortFormView = this.cohortFormView; var cohortFormView = this.cohortFormView;
var self = this;
event.preventDefault(); event.preventDefault();
cohortFormView.saveForm() cohortFormView.saveForm()
.done(function() { .done(function() {
self.renderGroupHeader();
cohortFormView.showMessage(gettext('Saved cohort')); cohortFormView.showMessage(gettext('Saved cohort'));
}); });
}, },
cancelSettings: function(event) {
event.preventDefault();
this.render();
},
setCohort: function(cohort) { setCohort: function(cohort) {
this.model = cohort; this.model = cohort;
this.render(); this.render();
......
...@@ -35,12 +35,22 @@ var edx = edx || {}; ...@@ -35,12 +35,22 @@ var edx = edx || {};
render: function() { render: function() {
this.$el.html(this.template({ this.$el.html(this.template({
cohort: this.model, cohort: this.model,
isDefaultCohort: this.isDefault(this.model.get('name')),
contentGroups: this.contentGroups, contentGroups: this.contentGroups,
studioGroupConfigurationsUrl: this.context.studioGroupConfigurationsUrl studioGroupConfigurationsUrl: this.context.studioGroupConfigurationsUrl
})); }));
return this; return this;
}, },
isDefault: function(name) {
var cohorts = this.model.collection;
if (_.isUndefined(cohorts)) {
return false;
}
var randomModels = cohorts.where({assignment_type:'random'});
return (randomModels.length === 1) && (randomModels[0].get('name') === name);
},
onRadioButtonChange: function(event) { onRadioButtonChange: function(event) {
var target = $(event.currentTarget), var target = $(event.currentTarget),
groupsEnabled = target.val() === 'yes'; groupsEnabled = target.val() === 'yes';
...@@ -77,7 +87,11 @@ var edx = edx || {}; ...@@ -77,7 +87,11 @@ var edx = edx || {};
getUpdatedCohortName: function() { getUpdatedCohortName: function() {
var cohortName = this.$('.cohort-name').val(); var cohortName = this.$('.cohort-name').val();
return cohortName ? cohortName.trim() : this.model.get('name'); return cohortName ? cohortName.trim() : '';
},
getAssignmentType: function() {
return this.$('input[name="cohort-assignment-type"]:checked').val();
}, },
showMessage: function(message, type, details) { showMessage: function(message, type, details) {
...@@ -109,18 +123,21 @@ var edx = edx || {}; ...@@ -109,18 +123,21 @@ var edx = edx || {};
cohort = this.model, cohort = this.model,
saveOperation = $.Deferred(), saveOperation = $.Deferred(),
isUpdate = !_.isUndefined(this.model.id), isUpdate = !_.isUndefined(this.model.id),
fieldData, selectedContentGroup, errorMessages, showErrorMessage; fieldData, selectedContentGroup, selectedAssignmentType, errorMessages, showErrorMessage;
showErrorMessage = function(message, details) { showErrorMessage = function(message, details) {
self.showMessage(message, 'error', details); self.showMessage(message, 'error', details);
}; };
this.removeNotification(); this.removeNotification();
selectedContentGroup = this.getSelectedContentGroup(); selectedContentGroup = this.getSelectedContentGroup();
selectedAssignmentType = this.getAssignmentType();
fieldData = { fieldData = {
name: this.getUpdatedCohortName(), name: this.getUpdatedCohortName(),
group_id: selectedContentGroup ? selectedContentGroup.id : null, group_id: selectedContentGroup ? selectedContentGroup.id : null,
user_partition_id: selectedContentGroup ? selectedContentGroup.get('user_partition_id') : null user_partition_id: selectedContentGroup ? selectedContentGroup.get('user_partition_id') : null,
assignment_type: selectedAssignmentType
}; };
errorMessages = this.validate(fieldData); errorMessages = this.validate(fieldData);
if (errorMessages.length > 0) { if (errorMessages.length > 0) {
showErrorMessage( showErrorMessage(
isUpdate ? gettext("The cohort cannot be saved") : gettext("The cohort cannot be added"), isUpdate ? gettext("The cohort cannot be saved") : gettext("The cohort cannot be added"),
...@@ -129,7 +146,7 @@ var edx = edx || {}; ...@@ -129,7 +146,7 @@ var edx = edx || {};
saveOperation.reject(); saveOperation.reject();
} else { } else {
cohort.save( cohort.save(
fieldData, {patch: isUpdate} fieldData, {patch: isUpdate, wait: true}
).done(function(result) { ).done(function(result) {
cohort.id = result.id; cohort.id = result.id;
self.render(); // re-render to remove any now invalid error messages self.render(); // re-render to remove any now invalid error messages
......
...@@ -567,6 +567,12 @@ ...@@ -567,6 +567,12 @@
} }
} }
.cohort-management-assignment-type-settings {
&.is-disabled {
opacity: 0.50;
}
}
// cohort // cohort
.cohort-management-group-header { .cohort-management-group-header {
padding: $baseline; padding: $baseline;
......
<section class="cohort-management-settings has-tabs"> <section class="cohort-management-settings has-tabs">
<header class="cohort-management-group-header"> <header class="cohort-management-group-header"></header>
<h3 class="group-header-title" tabindex="-1">
<span class="title-value"><%- cohort.get('name') %></span>
<span class="group-count"><%-
interpolate(
ngettext('(contains %(student_count)s student)', '(contains %(student_count)s students)', cohort.get('user_count')),
{ student_count: cohort.get('user_count') },
true
)
%></span>
</h3>
<div class="cohort-management-group-setup">
<div class="setup-value">
<% if (cohort.get('assignment_type') == "none") { %>
<%- gettext("Students are added to this cohort only when you provide their email addresses or usernames on this page.") %>
<a href="http://edx.readthedocs.org/projects/edx-partner-course-staff/en/latest/cohorts/cohort_config.html#assign-students-to-cohort-groups-manually" class="incontext-help action-secondary action-help"><%= gettext("What does this mean?") %></a>
<% } else { %>
<%- gettext("Students are added to this cohort automatically.") %>
<a href="http://edx.readthedocs.org/projects/edx-partner-course-staff/en/latest/cohorts/cohorts_overview.html#all-automated-assignment" class="incontext-help action-secondary action-help"><%- gettext("What does this mean?") %></a>
<% } %>
</div>
<div class="setup-actions">
<% if (studioAdvancedSettingsUrl !== "None") { %>
<a href="<%= studioAdvancedSettingsUrl %>" class="action-secondary action-edit"><%- gettext("Edit settings in Studio") %></a>
<% } %>
</div>
</div>
</header>
<ul class="wrapper-tabs"> <ul class="wrapper-tabs">
<li class="tab tab-manage_students is-selected" data-tab="manage_students"><a href="#"><span class="sr"><%- gettext('Selected tab') %> </span><%- gettext("Manage Students") %></a></li> <li class="tab tab-manage_students is-selected" data-tab="manage_students"><a href="#"><span class="sr"><%- gettext('Selected tab') %> </span><%- gettext("Manage Students") %></a></li>
......
...@@ -6,23 +6,6 @@ ...@@ -6,23 +6,6 @@
<div class="tab-content is-visible new-cohort-form"> <div class="tab-content is-visible new-cohort-form">
<% } %> <% } %>
<div class="form-fields"> <div class="form-fields">
<%
// Don't allow renaming of existing cohorts yet as it doesn't interact well with
// the course's advanced setting for auto cohorting.
if (isNewCohort) {
%>
<div class="form-field">
<div class="cohort-management-settings-form-name field field-text">
<label for="cohort-name" class="form-label">
<%- gettext('Cohort Name') %> *
<span class="sr"><%- gettext('(Required Field)')%></span>
</label>
<input type="text" name="cohort-name" value="<%- cohort ? cohort.get('name') : '' %>" class="input cohort-name"
id="cohort-name"
placeholder="<%- gettext("Enter the name of the cohort") %>" required="required" />
</div>
</div>
<% } %>
<% <%
var foundSelected = false; var foundSelected = false;
...@@ -30,8 +13,44 @@ ...@@ -30,8 +13,44 @@
var selectedUserPartitionId = cohort.get('user_partition_id'); var selectedUserPartitionId = cohort.get('user_partition_id');
var hasSelectedContentGroup = selectedContentGroupId != null; var hasSelectedContentGroup = selectedContentGroupId != null;
var hasContentGroups = contentGroups.length > 0; var hasContentGroups = contentGroups.length > 0;
var assignment_type = cohort.get('assignment_type');
var cohort_name = cohort.get('name');
var cohort_name_value = isNewCohort ? '' : cohort_name;
var placeholder_value = isNewCohort ? gettext('Enter the name of the cohort') : '';
%> %>
<div class="form-field"> <div class="form-field">
<div class="cohort-management-settings-form-name field field-text">
<label for="cohort-name" class="form-label">
<%- gettext('Cohort Name') %> *
<span class="sr"><%- gettext('(Required Field)')%></span>
</label>
<input name="cohort-name" value="<%- cohort_name_value %>" class="input cohort-name" id="cohort-name"
placeholder="<%- placeholder_value %>" required="required" type="text">
</div>
<hr class="divider divider-lv1">
<% if (isDefaultCohort) { %>
<div class="cohort-management-assignment-type-settings field field-radio is-disabled" aria-disabled="true">
<% } else { %>
<div class="cohort-management-assignment-type-settings field field-radio">
<% } %>
<h4 class="form-label">
<%- gettext('Students in this cohort are:') %>
</h4>
<label>
<input type="radio" class="type-random" name="cohort-assignment-type" value="random" <%- assignment_type == 'random' ? 'checked="checked"' : '' %>/> <%- gettext("Randomly Assigned") %>
</label>
<label>
<input type="radio" class="type-manual" name="cohort-assignment-type" value="manual" <%- assignment_type == 'manual' || isNewCohort ? 'checked="checked"' : '' %>/> <%- gettext("Manually Assigned") %>
</label>
</div>
<% if (isDefaultCohort) { %>
<p class="copy-error">
<i class="icon fa fa-exclamation-triangle" aria-hidden="true"></i>
<%- gettext("There must be one cohort to which students can be randomly assigned.") %>
</p>
<% } %>
<hr class="divider divider-lv1">
<div class="cohort-management-details-association-course field field-radio"> <div class="cohort-management-details-association-course field field-radio">
<h4 class="form-label"> <h4 class="form-label">
<%- gettext('Associated Content Group') %> <%- gettext('Associated Content Group') %>
...@@ -119,12 +138,9 @@ ...@@ -119,12 +138,9 @@
<div class="form-actions <% if (isNewCohort) { %>new-cohort-form<% } %>"> <div class="form-actions <% if (isNewCohort) { %>new-cohort-form<% } %>">
<button class="form-submit button action-primary action-save"> <button class="form-submit button action-primary action-save">
<i class="icon fa fa-plus" aria-hidden="true"></i>
<%- gettext('Save') %> <%- gettext('Save') %>
</button> </button>
<% if (isNewCohort) { %> <a href="" class="form-cancel action-secondary action-cancel"><%- gettext('Cancel') %></a>
<a href="" class="form-cancel action-secondary action-cancel"><%- gettext('Cancel') %></a>
<% } %>
</div> </div>
</form> </form>
</div> </div>
<h3 class="group-header-title" tabindex="-1">
<span class="title-value"><%- cohort.get('name') %></span>
<span class="group-count"><%-
interpolate(
ngettext('(contains %(student_count)s student)', '(contains %(student_count)s students)', cohort.get('user_count')),
{ student_count: cohort.get('user_count') },
true
)
%></span>
</h3>
<div class="cohort-management-group-setup">
<div class="setup-value">
<% if (cohort.get('assignment_type') == "manual") { %>
<%- gettext("Students are added to this cohort only when you provide their email addresses or usernames on this page.") %>
<a href="http://edx.readthedocs.org/projects/edx-partner-course-staff/en/latest/cohorts/cohort_config.html#assign-students-to-cohort-groups-manually" class="incontext-help action-secondary action-help"><%= gettext("What does this mean?") %></a>
<% } else { %>
<%- gettext("Students are added to this cohort automatically.") %>
<a href="http://edx.readthedocs.org/projects/edx-partner-course-staff/en/latest/cohorts/cohorts_overview.html#all-automated-assignment" class="incontext-help action-secondary action-help"><%- gettext("What does this mean?") %></a>
<% } %>
</div>
<div class="setup-actions">
<% if (studioAdvancedSettingsUrl !== "None") { %>
<a href="<%= studioAdvancedSettingsUrl %>" class="action-secondary action-edit"><%- gettext("Edit settings in Studio") %></a>
<% } %>
</div>
</div>
\ No newline at end of file
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
## Include Underscore templates ## Include Underscore templates
<%block name="header_extras"> <%block name="header_extras">
% for template_name in ["cohorts", "cohort-editor", "cohort-selector", "cohort-form", "notification"]: % for template_name in ["cohorts", "cohort-editor", "cohort-group-header", "cohort-selector", "cohort-form", "notification"]:
<script type="text/template" id="${template_name}-tpl"> <script type="text/template" id="${template_name}-tpl">
<%static:include path="instructor/instructor_dashboard_2/${template_name}.underscore" /> <%static:include path="instructor/instructor_dashboard_2/${template_name}.underscore" />
</script> </script>
......
...@@ -5,6 +5,7 @@ forums, and to the cohort admin views. ...@@ -5,6 +5,7 @@ forums, and to the cohort admin views.
import logging import logging
import random import random
import json
from django.db import transaction from django.db import transaction
from django.db.models.signals import post_save, m2m_changed from django.db.models.signals import post_save, m2m_changed
...@@ -15,7 +16,7 @@ from django.utils.translation import ugettext as _ ...@@ -15,7 +16,7 @@ from django.utils.translation import ugettext as _
from courseware import courses from courseware import courses
from eventtracking import tracker from eventtracking import tracker
from student.models import get_user_by_username_or_email from student.models import get_user_by_username_or_email
from .models import CourseUserGroup, CourseUserGroupPartitionGroup from .models import CourseUserGroup, CourseCohort, CourseCohortsSettings, CourseUserGroupPartitionGroup
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -82,31 +83,6 @@ def _cohort_membership_changed(sender, **kwargs): ...@@ -82,31 +83,6 @@ def _cohort_membership_changed(sender, **kwargs):
DEFAULT_COHORT_NAME = "Default Group" DEFAULT_COHORT_NAME = "Default Group"
class CohortAssignmentType(object):
"""
The various types of rule-based cohorts
"""
# No automatic rules are applied to this cohort; users must be manually added.
NONE = "none"
# One of (possibly) multiple cohorts to which users are randomly assigned.
# Note: The 'default' cohort is included in this category iff it exists and
# there are no other random groups. (Also see Note 2 above.)
RANDOM = "random"
@staticmethod
def get(cohort, course):
"""
Returns the assignment type of the given cohort for the given course
"""
if cohort.name in course.auto_cohort_groups:
return CohortAssignmentType.RANDOM
elif len(course.auto_cohort_groups) == 0 and cohort.name == DEFAULT_COHORT_NAME:
return CohortAssignmentType.RANDOM
else:
return CohortAssignmentType.NONE
# tl;dr: global state is bad. capa reseeds random every time a problem is loaded. Even # tl;dr: global state is bad. capa reseeds random every time a problem is loaded. Even
# if and when that's fixed, it's a good idea to have a local generator to avoid any other # if and when that's fixed, it's a good idea to have a local generator to avoid any other
# code that messes with the global random module. # code that messes with the global random module.
...@@ -237,47 +213,74 @@ def get_cohort(user, course_key, assign=True): ...@@ -237,47 +213,74 @@ def get_cohort(user, course_key, assign=True):
if not assign: if not assign:
return None return None
choices = course.auto_cohort_groups cohorts = get_course_cohorts(course, assignment_type=CourseCohort.RANDOM)
if len(choices) > 0: if cohorts:
# Randomly choose one of the auto_cohort_groups, creating it if needed. cohort = local_random().choice(cohorts)
group_name = local_random().choice(choices)
else: else:
# Use the "default cohort". cohort = CourseCohort.create(
group_name = DEFAULT_COHORT_NAME cohort_name=DEFAULT_COHORT_NAME,
course_id=course_key,
assignment_type=CourseCohort.RANDOM
).course_user_group
group, __ = CourseUserGroup.objects.get_or_create( user.course_groups.add(cohort)
course_id=course_key,
group_type=CourseUserGroup.COHORT, return cohort
name=group_name
def migrate_cohort_settings(course):
"""
Migrate all the cohort settings associated with this course from modulestore to mysql.
After that we will never touch modulestore for any cohort related settings.
"""
course_id = course.location.course_key
cohort_settings, created = CourseCohortsSettings.objects.get_or_create(
course_id=course_id,
defaults={
'is_cohorted': course.is_cohorted,
'cohorted_discussions': json.dumps(list(course.cohorted_discussions)),
'always_cohort_inline_discussions': course.always_cohort_inline_discussions
}
) )
user.course_groups.add(group)
return group
# Add the new and update the existing cohorts
if created:
# Update the manual cohorts already present in CourseUserGroup
manual_cohorts = CourseUserGroup.objects.filter(
course_id=course_id,
group_type=CourseUserGroup.COHORT
).exclude(name__in=course.auto_cohort_groups)
for cohort in manual_cohorts:
CourseCohort.create(course_user_group=cohort)
for group_name in course.auto_cohort_groups:
CourseCohort.create(cohort_name=group_name, course_id=course_id, assignment_type=CourseCohort.RANDOM)
return cohort_settings
def get_course_cohorts(course):
def get_course_cohorts(course, assignment_type=None):
""" """
Get a list of all the cohorts in the given course. This will include auto cohorts, Get a list of all the cohorts in the given course. This will include auto cohorts,
regardless of whether or not the auto cohorts include any users. regardless of whether or not the auto cohorts include any users.
Arguments: Arguments:
course: the course for which cohorts should be returned course: the course for which cohorts should be returned
assignment_type: cohort assignment type
Returns: Returns:
A list of CourseUserGroup objects. Empty if there are no cohorts. Does A list of CourseUserGroup objects. Empty if there are no cohorts. Does
not check whether the course is cohorted. not check whether the course is cohorted.
""" """
# Ensure all auto cohorts are created. # Migrate cohort settings for this course
for group_name in course.auto_cohort_groups: migrate_cohort_settings(course)
CourseUserGroup.objects.get_or_create(
course_id=course.location.course_key,
group_type=CourseUserGroup.COHORT,
name=group_name
)
return list(CourseUserGroup.objects.filter( query_set = CourseUserGroup.objects.filter(
course_id=course.location.course_key, course_id=course.location.course_key,
group_type=CourseUserGroup.COHORT group_type=CourseUserGroup.COHORT
)) )
query_set = query_set.filter(cohort__assignment_type=assignment_type) if assignment_type else query_set
return list(query_set)
### Helpers for cohort management views ### Helpers for cohort management views
...@@ -297,7 +300,7 @@ def get_cohort_by_name(course_key, name): ...@@ -297,7 +300,7 @@ def get_cohort_by_name(course_key, name):
def get_cohort_by_id(course_key, cohort_id): def get_cohort_by_id(course_key, cohort_id):
""" """
Return the CourseUserGroup object for the given cohort. Raises DoesNotExist Return the CourseUserGroup object for the given cohort. Raises DoesNotExist
it isn't present. Uses the course_key for extra validation... it isn't present. Uses the course_key for extra validation.
""" """
return CourseUserGroup.objects.get( return CourseUserGroup.objects.get(
course_id=course_key, course_id=course_key,
...@@ -306,15 +309,13 @@ def get_cohort_by_id(course_key, cohort_id): ...@@ -306,15 +309,13 @@ def get_cohort_by_id(course_key, cohort_id):
) )
def add_cohort(course_key, name): def add_cohort(course_key, name, assignment_type):
""" """
Add a cohort to a course. Raises ValueError if a cohort of the same name already Add a cohort to a course. Raises ValueError if a cohort of the same name already
exists. exists.
""" """
log.debug("Adding cohort %s to %s", name, course_key) log.debug("Adding cohort %s to %s", name, course_key)
if CourseUserGroup.objects.filter(course_id=course_key, if is_cohort_exists(course_key, name):
group_type=CourseUserGroup.COHORT,
name=name).exists():
raise ValueError(_("You cannot create two cohorts with the same name")) raise ValueError(_("You cannot create two cohorts with the same name"))
try: try:
...@@ -322,11 +323,11 @@ def add_cohort(course_key, name): ...@@ -322,11 +323,11 @@ def add_cohort(course_key, name):
except Http404: except Http404:
raise ValueError("Invalid course_key") raise ValueError("Invalid course_key")
cohort = CourseUserGroup.objects.create( cohort = CourseCohort.create(
course_id=course.id, cohort_name=name, course_id=course.id,
group_type=CourseUserGroup.COHORT, assignment_type=assignment_type
name=name ).course_user_group
)
tracker.emit( tracker.emit(
"edx.cohort.creation_requested", "edx.cohort.creation_requested",
{"cohort_name": cohort.name, "cohort_id": cohort.id} {"cohort_name": cohort.name, "cohort_id": cohort.id}
...@@ -334,6 +335,13 @@ def add_cohort(course_key, name): ...@@ -334,6 +335,13 @@ def add_cohort(course_key, name):
return cohort return cohort
def is_cohort_exists(course_key, name):
"""
Check if a cohort already exists.
"""
return CourseUserGroup.objects.filter(course_id=course_key, group_type=CourseUserGroup.COHORT, name=name).exists()
def add_user_to_cohort(cohort, username_or_email): def add_user_to_cohort(cohort, username_or_email):
""" """
Look up the given user, and if successful, add them to the specified cohort. Look up the given user, and if successful, add them to the specified cohort.
...@@ -396,3 +404,37 @@ def get_group_info_for_cohort(cohort): ...@@ -396,3 +404,37 @@ def get_group_info_for_cohort(cohort):
if len(res): if len(res):
return res[0].group_id, res[0].partition_id return res[0].group_id, res[0].partition_id
return None, None return None, None
def set_assignment_type(user_group, assignment_type):
"""
Set assignment type for cohort.
"""
course_cohort = user_group.cohort
if is_default_cohort(user_group) and course_cohort.assignment_type != assignment_type:
raise ValueError(_("There must be one cohort to which students can be randomly assigned."))
course_cohort.assignment_type = assignment_type
course_cohort.save()
def get_assignment_type(user_group):
"""
Get assignment type for cohort.
"""
course_cohort = user_group.cohort
return course_cohort.assignment_type
def is_default_cohort(user_group):
"""
Check if a cohort is default.
"""
random_cohorts = CourseUserGroup.objects.filter(
course_id=user_group.course_id,
group_type=CourseUserGroup.COHORT,
cohort__assignment_type=CourseCohort.RANDOM
)
return len(random_cohorts) == 1 and random_cohorts[0].name == user_group.name
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'CourseCohort'
db.create_table('course_groups_coursecohort', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('course_user_group', self.gf('django.db.models.fields.related.OneToOneField')(related_name='cohort', unique=True, to=orm['course_groups.CourseUserGroup'])),
('assignment_type', self.gf('django.db.models.fields.CharField')(default='manual', max_length=20)),
))
db.send_create_signal('course_groups', ['CourseCohort'])
# Adding model 'CourseCohortsSettings'
db.create_table('course_groups_coursecohortssettings', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('is_cohorted', self.gf('django.db.models.fields.BooleanField')(default=False)),
('course_id', self.gf('xmodule_django.models.CourseKeyField')(unique=True, max_length=255, db_index=True)),
('cohorted_discussions', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
('always_cohort_inline_discussions', self.gf('django.db.models.fields.BooleanField')(default=True)),
))
db.send_create_signal('course_groups', ['CourseCohortsSettings'])
def backwards(self, orm):
# Deleting model 'CourseCohort'
db.delete_table('course_groups_coursecohort')
# Deleting model 'CourseCohortsSettings'
db.delete_table('course_groups_coursecohortssettings')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'course_groups.coursecohort': {
'Meta': {'object_name': 'CourseCohort'},
'assignment_type': ('django.db.models.fields.CharField', [], {'default': "'manual'", 'max_length': '20'}),
'course_user_group': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'cohort'", 'unique': 'True', 'to': "orm['course_groups.CourseUserGroup']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'course_groups.coursecohortssettings': {
'Meta': {'object_name': 'CourseCohortsSettings'},
'always_cohort_inline_discussions': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cohorted_discussions': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_cohorted': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
'course_groups.courseusergroup': {
'Meta': {'unique_together': "(('name', 'course_id'),)", 'object_name': 'CourseUserGroup'},
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
'group_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'db_index': 'True', 'related_name': "'course_groups'", 'symmetrical': 'False', 'to': "orm['auth.User']"})
},
'course_groups.courseusergrouppartitiongroup': {
'Meta': {'object_name': 'CourseUserGroupPartitionGroup'},
'course_user_group': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['course_groups.CourseUserGroup']", 'unique': 'True'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'group_id': ('django.db.models.fields.IntegerField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'partition_id': ('django.db.models.fields.IntegerField', [], {}),
'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
}
}
complete_apps = ['course_groups']
\ No newline at end of file
...@@ -36,9 +36,26 @@ class CourseUserGroup(models.Model): ...@@ -36,9 +36,26 @@ class CourseUserGroup(models.Model):
GROUP_TYPE_CHOICES = ((COHORT, 'Cohort'),) GROUP_TYPE_CHOICES = ((COHORT, 'Cohort'),)
group_type = models.CharField(max_length=20, choices=GROUP_TYPE_CHOICES) group_type = models.CharField(max_length=20, choices=GROUP_TYPE_CHOICES)
@classmethod
def create(cls, name, course_id, group_type=COHORT):
"""
Create a new course user group.
Args:
name: Name of group
course_id: course id
group_type: group type
"""
return cls.objects.get_or_create(
course_id=course_id,
group_type=group_type,
name=name
)
class CourseUserGroupPartitionGroup(models.Model): class CourseUserGroupPartitionGroup(models.Model):
""" """
Create User Partition Info.
""" """
course_user_group = models.OneToOneField(CourseUserGroup) course_user_group = models.OneToOneField(CourseUserGroup)
partition_id = models.IntegerField( partition_id = models.IntegerField(
...@@ -49,3 +66,55 @@ class CourseUserGroupPartitionGroup(models.Model): ...@@ -49,3 +66,55 @@ class CourseUserGroupPartitionGroup(models.Model):
) )
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
class CourseCohortsSettings(models.Model):
"""
This model represents cohort settings for courses.
"""
is_cohorted = models.BooleanField(default=False)
course_id = CourseKeyField(
unique=True,
max_length=255,
db_index=True,
help_text="Which course are these settings associated with?",
)
cohorted_discussions = models.TextField(null=True, blank=True) # JSON list
# pylint: disable=invalid-name
always_cohort_inline_discussions = models.BooleanField(default=True)
class CourseCohort(models.Model):
"""
This model represents cohort related info.
"""
course_user_group = models.OneToOneField(CourseUserGroup, unique=True, related_name='cohort')
RANDOM = 'random'
MANUAL = 'manual'
ASSIGNMENT_TYPE_CHOICES = ((RANDOM, 'Random'), (MANUAL, 'Manual'),)
assignment_type = models.CharField(max_length=20, choices=ASSIGNMENT_TYPE_CHOICES, default=MANUAL)
@classmethod
def create(cls, cohort_name=None, course_id=None, course_user_group=None, assignment_type=MANUAL):
"""
Create a complete(CourseUserGroup + CourseCohort) object.
Args:
cohort_name: Name of the cohort to be created
course_id: Course Id
course_user_group: CourseUserGroup
assignment_type: 'random' or 'manual'
"""
if course_user_group is None:
course_user_group, __ = CourseUserGroup.create(cohort_name, course_id)
course_cohort, __ = cls.objects.get_or_create(
course_user_group=course_user_group,
defaults={'assignment_type': assignment_type}
)
return course_cohort
""" """
Helper methods for testing cohorts. Helper methods for testing cohorts.
""" """
import factory
from factory import post_generation, Sequence from factory import post_generation, Sequence
from factory.django import DjangoModelFactory from factory.django import DjangoModelFactory
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
from ..models import CourseUserGroup from ..models import CourseUserGroup, CourseCohort
class CohortFactory(DjangoModelFactory): class CohortFactory(DjangoModelFactory):
...@@ -29,6 +30,16 @@ class CohortFactory(DjangoModelFactory): ...@@ -29,6 +30,16 @@ class CohortFactory(DjangoModelFactory):
self.users.add(*extracted) self.users.add(*extracted)
class CourseCohortFactory(DjangoModelFactory):
"""
Factory for constructing mock course cohort.
"""
FACTORY_FOR = CourseCohort
course_user_group = factory.SubFactory(CohortFactory)
assignment_type = 'manual'
def topic_name_to_id(course, name): def topic_name_to_id(course, name):
""" """
Given a discussion topic name, return an id for that name (includes Given a discussion topic name, return an id for that name (includes
......
...@@ -76,6 +76,7 @@ class TestCohortPartitionScheme(ModuleStoreTestCase): ...@@ -76,6 +76,7 @@ class TestCohortPartitionScheme(ModuleStoreTestCase):
first_cohort, second_cohort = [ first_cohort, second_cohort = [
CohortFactory(course_id=self.course_key) for _ in range(2) CohortFactory(course_id=self.course_key) for _ in range(2)
] ]
# place student 0 into first cohort # place student 0 into first cohort
add_user_to_cohort(first_cohort, self.student.username) add_user_to_cohort(first_cohort, self.student.username)
self.assert_student_in_group(None) self.assert_student_in_group(None)
......
...@@ -7,7 +7,8 @@ from django.http import Http404, HttpResponse, HttpResponseBadRequest ...@@ -7,7 +7,8 @@ from django.http import Http404, HttpResponse, HttpResponseBadRequest
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from util.json_request import expect_json, JsonResponse from util.json_request import expect_json, JsonResponse
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
import json from django.utils.translation import ugettext
import logging import logging
import re import re
...@@ -59,13 +60,14 @@ def _get_cohort_representation(cohort, course): ...@@ -59,13 +60,14 @@ def _get_cohort_representation(cohort, course):
Returns a JSON representation of a cohort. Returns a JSON representation of a cohort.
""" """
group_id, partition_id = cohorts.get_group_info_for_cohort(cohort) group_id, partition_id = cohorts.get_group_info_for_cohort(cohort)
assignment_type = cohorts.get_assignment_type(cohort)
return { return {
'name': cohort.name, 'name': cohort.name,
'id': cohort.id, 'id': cohort.id,
'user_count': cohort.users.count(), 'user_count': cohort.users.count(),
'assignment_type': cohorts.CohortAssignmentType.get(cohort, course), 'assignment_type': assignment_type,
'user_partition_id': partition_id, 'user_partition_id': partition_id,
'group_id': group_id 'group_id': group_id,
} }
...@@ -102,25 +104,30 @@ def cohort_handler(request, course_key_string, cohort_id=None): ...@@ -102,25 +104,30 @@ def cohort_handler(request, course_key_string, cohort_id=None):
cohort = cohorts.get_cohort_by_id(course_key, cohort_id) cohort = cohorts.get_cohort_by_id(course_key, cohort_id)
return JsonResponse(_get_cohort_representation(cohort, course)) return JsonResponse(_get_cohort_representation(cohort, course))
else: else:
name = request.json.get('name')
assignment_type = request.json.get('assignment_type')
if not name:
# Note: error message not translated because it is not exposed to the user (UI prevents this state).
return JsonResponse({"error": "Cohort name must be specified."}, 400)
if not assignment_type:
# Note: error message not translated because it is not exposed to the user (UI prevents this state).
return JsonResponse({"error": "Assignment type must be specified."}, 400)
# If cohort_id is specified, update the existing cohort. Otherwise, create a new cohort. # If cohort_id is specified, update the existing cohort. Otherwise, create a new cohort.
if cohort_id: if cohort_id:
cohort = cohorts.get_cohort_by_id(course_key, cohort_id) cohort = cohorts.get_cohort_by_id(course_key, cohort_id)
name = request.json.get('name')
if name != cohort.name: if name != cohort.name:
if cohorts.CohortAssignmentType.get(cohort, course) == cohorts.CohortAssignmentType.RANDOM: if cohorts.is_cohort_exists(course_key, name):
return JsonResponse( err_msg = ugettext("A cohort with the same name already exists.")
# Note: error message not translated because it is not exposed to the user (UI prevents). return JsonResponse({"error": unicode(err_msg)}, 400)
{"error": "Renaming of random cohorts is not supported at this time."}, 400
)
cohort.name = name cohort.name = name
cohort.save() cohort.save()
try:
cohorts.set_assignment_type(cohort, assignment_type)
except ValueError as err:
return JsonResponse({"error": unicode(err)}, 400)
else: else:
name = request.json.get('name')
if not name:
# Note: error message not translated because it is not exposed to the user (UI prevents this state).
return JsonResponse({"error": "In order to create a cohort, a name must be specified."}, 400)
try: try:
cohort = cohorts.add_cohort(course_key, name) cohort = cohorts.add_cohort(course_key, name, assignment_type)
except ValueError as err: except ValueError as err:
return JsonResponse({"error": unicode(err)}, 400) return JsonResponse({"error": unicode(err)}, 400)
......
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