Commit dbae1e39 by cahrens

Add ability to set visibility by enrollment track.

TNL-6744
parent 073826ca
......@@ -8,8 +8,8 @@ from util.db import generate_int_id, MYSQL_MAX_INT
from django.utils.translation import ugettext as _
from contentstore.utils import reverse_usage_url
from xmodule.partitions.partitions import UserPartition
from xmodule.partitions.partitions_service import get_all_partitions_for_course, MINIMUM_STATIC_PARTITION_ID
from xmodule.partitions.partitions import UserPartition, MINIMUM_STATIC_PARTITION_ID
from xmodule.partitions.partitions_service import get_all_partitions_for_course
from xmodule.split_test_module import get_split_user_partitions
from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_user_partition
......@@ -18,11 +18,11 @@ MINIMUM_GROUP_ID = MINIMUM_STATIC_PARTITION_ID
RANDOM_SCHEME = "random"
COHORT_SCHEME = "cohort"
# Note: the following content group configuration strings are not
# translated since they are not visible to users.
CONTENT_GROUP_CONFIGURATION_DESCRIPTION = 'The groups in this configuration can be mapped to cohort groups in the LMS.'
CONTENT_GROUP_CONFIGURATION_DESCRIPTION = _(
'The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.'
)
CONTENT_GROUP_CONFIGURATION_NAME = 'Content Group Configuration'
CONTENT_GROUP_CONFIGURATION_NAME = _('Content Groups')
log = logging.getLogger(__name__)
......
......@@ -510,7 +510,7 @@ class GetUserPartitionInfoTest(ModuleStoreTestCase):
self.assertEqual(len(groups), 3)
self.assertEqual(groups[2], {
"id": 3,
"name": "Deleted group",
"name": "Deleted Group",
"selected": True,
"deleted": True
})
......
......@@ -401,7 +401,7 @@ def get_user_partition_info(xblock, schemes=None, course=None):
for gid in missing_group_ids:
groups.append({
"id": gid,
"name": _("Deleted group"),
"name": _("Deleted Group"),
"selected": True,
"deleted": True,
})
......@@ -429,30 +429,45 @@ def get_visibility_partition_info(xblock):
Returns: dict
"""
user_partitions = get_user_partition_info(xblock, schemes=["verification", "cohort"])
cohort_partitions = []
verification_partitions = []
has_selected_groups = False
selected_verified_partition_id = None
selectable_partitions = []
# We wish to display enrollment partitions before cohort partitions.
enrollment_user_partitions = get_user_partition_info(xblock, schemes=["enrollment_track"])
# Pre-process the partitions to make it easier to display the UI
for p in user_partitions:
has_selected = any(g["selected"] for g in p["groups"])
has_selected_groups = has_selected_groups or has_selected
# For enrollment partitions, we only show them if there is a selected group or
# or if the number of groups > 1.
for partition in enrollment_user_partitions:
if len(partition["groups"]) > 1 or any(group["selected"] for group in partition["groups"]):
selectable_partitions.append(partition)
if p["scheme"] == "cohort":
cohort_partitions.append(p)
elif p["scheme"] == "verification":
verification_partitions.append(p)
if has_selected:
selected_verified_partition_id = p["id"]
# Now add the cohort user partitions.
selectable_partitions = selectable_partitions + get_user_partition_info(xblock, schemes=["cohort"])
# Find the first partition with a selected group. That will be the one initially enabled in the dialog
# (if the course has only been added in Studio, only one partition should have a selected group).
selected_partition_index = -1
# At the same time, build up all the selected groups as they are displayed in the dialog title.
selected_groups_label = ''
for index, partition in enumerate(selectable_partitions):
for group in partition["groups"]:
if group["selected"]:
if len(selected_groups_label) == 0:
selected_groups_label = group['name']
else:
# Translators: This is building up a list of groups. It is marked for translation because of the
# comma, which is used as a separator between each group.
selected_groups_label = _('{previous_groups}, {current_group}').format(
previous_groups=selected_groups_label,
current_group=group['name']
)
if selected_partition_index == -1:
selected_partition_index = index
return {
"user_partitions": user_partitions,
"cohort_partitions": cohort_partitions,
"verification_partitions": verification_partitions,
"has_selected_groups": has_selected_groups,
"selected_verified_partition_id": selected_verified_partition_id,
"selectable_partitions": selectable_partitions,
"selected_partition_index": selected_partition_index,
"selected_groups_label": selected_groups_label,
}
......
......@@ -8,7 +8,7 @@ import ddt
from mock import patch
from contentstore.utils import reverse_course_url, reverse_usage_url
from contentstore.course_group_config import GroupConfiguration
from contentstore.course_group_config import GroupConfiguration, CONTENT_GROUP_CONFIGURATION_NAME
from contentstore.tests.utils import CourseTestCase
from xmodule.partitions.partitions import Group, UserPartition
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
......@@ -240,7 +240,7 @@ class GroupConfigurationsListHandlerTestCase(CourseTestCase, GroupConfigurations
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'First name')
self.assertContains(response, 'Group C')
self.assertContains(response, 'Content Group Configuration')
self.assertContains(response, CONTENT_GROUP_CONFIGURATION_NAME)
def test_unsupported_http_accept_header(self):
"""
......
......@@ -44,8 +44,9 @@ from xblock.exceptions import NoSuchHandlerError
from xblock_django.user_service import DjangoXBlockUserService
from opaque_keys.edx.keys import UsageKey, CourseKey
from opaque_keys.edx.locations import Location
from xmodule.partitions.partitions import Group, UserPartition
from xmodule.partitions.partitions_service import ENROLLMENT_TRACK_PARTITION_ID, MINIMUM_STATIC_PARTITION_ID
from xmodule.partitions.partitions import (
Group, UserPartition, ENROLLMENT_TRACK_PARTITION_ID, MINIMUM_STATIC_PARTITION_ID
)
class AsideTest(XBlockAside):
......@@ -348,9 +349,9 @@ class GetItemTest(ItemTest):
self.course.user_partitions = [
UserPartition(
id=MINIMUM_STATIC_PARTITION_ID,
name="Verification user partition",
scheme=UserPartition.get_scheme("verification"),
description="Verification user partition",
name="Random user partition",
scheme=UserPartition.get_scheme("random"),
description="Random user partition",
groups=[
Group(id=MINIMUM_STATIC_PARTITION_ID + 1, name="Group A"), # See note above.
Group(id=MINIMUM_STATIC_PARTITION_ID + 2, name="Group B"), # See note above.
......@@ -370,7 +371,7 @@ class GetItemTest(ItemTest):
self.assertEqual(result["user_partitions"], [
{
"id": ENROLLMENT_TRACK_PARTITION_ID,
"name": "Enrollment Track Partition",
"name": "Enrollment Tracks",
"scheme": "enrollment_track",
"groups": [
{
......@@ -383,8 +384,8 @@ class GetItemTest(ItemTest):
},
{
"id": MINIMUM_STATIC_PARTITION_ID,
"name": "Verification user partition",
"scheme": "verification",
"name": "Random user partition",
"scheme": "random",
"groups": [
{
"id": MINIMUM_STATIC_PARTITION_ID + 1,
......
......@@ -256,19 +256,6 @@ function(Backbone, _, str, ModuleUtils) {
*/
isEditableOnCourseOutline: function() {
return this.isSequential() || this.isChapter() || this.isVertical();
},
/*
* Check whether any verification checkpoints are defined in the course.
* Verification checkpoints are defined if there exists a user partition
* that uses the verification partition scheme.
*/
hasVerifiedCheckpoints: function() {
var partitions = this.get('user_partitions') || [];
return Boolean(_.find(partitions, function(p) {
return p.scheme === 'verification';
}));
}
});
return XBlockInfo;
......
......@@ -15,7 +15,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
'use strict';
var CourseOutlineXBlockModal, SettingsXBlockModal, PublishXBlockModal, AbstractEditor, BaseDateEditor,
ReleaseDateEditor, DueDateEditor, GradingEditor, PublishEditor, AbstractVisibilityEditor, StaffLockEditor,
ContentVisibilityEditor, VerificationAccessEditor, TimedExaminationPreferenceEditor, AccessEditor;
ContentVisibilityEditor, TimedExaminationPreferenceEditor, AccessEditor;
CourseOutlineXBlockModal = BaseModal.extend({
events: _.extend({}, BaseModal.prototype.events, {
......@@ -720,109 +720,6 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
}
});
VerificationAccessEditor = AbstractEditor.extend({
templateName: 'verification-access-editor',
className: 'edit-verification-access',
// This constant MUST match the group ID
// defined by VerificationPartitionScheme on the backend!
ALLOW_GROUP_ID: 1,
getSelectedPartition: function() {
var hasRestrictions = $('#verification-access-checkbox').is(':checked'),
selectedPartitionID = null;
if (hasRestrictions) {
selectedPartitionID = $('#verification-partition-select').val();
}
return parseInt(selectedPartitionID, 10);
},
getGroupAccess: function() {
var groupAccess = _.clone(this.model.get('group_access')) || [],
userPartitions = this.model.get('user_partitions') || [],
selectedPartition = this.getSelectedPartition(),
that = this;
// We display a simplified UI to course authors.
// On the backend, each verification checkpoint is associated
// with a user partition that has two groups. For example,
// if two checkpoints were defined, they might look like:
//
// Midterm A: |-- ALLOW --|-- DENY --|
// Midterm B: |-- ALLOW --|-- DENY --|
//
// To make life easier for course authors, we display
// *one* option for each checkpoint:
//
// [X] Must complete verification checkpoint
// Dropdown:
// * Midterm A
// * Midterm B
//
// This is where we map the simplified UI to
// the underlying user partition. If the user checked
// the box, that means there *is* a restriction,
// so only the "ALLOW" group for the selected partition has access.
// Otherwise, all groups in the partition have access.
//
_.each(userPartitions, function(partition) {
if (partition.scheme === 'verification') {
if (selectedPartition === partition.id) {
groupAccess[partition.id] = [that.ALLOW_GROUP_ID];
} else {
delete groupAccess[partition.id];
}
}
});
return groupAccess;
},
getRequestData: function() {
var groupAccess = this.getGroupAccess(),
hasChanges = !_.isEqual(groupAccess, this.model.get('group_access'));
return hasChanges ? {
publish: 'republish',
metadata: {
group_access: groupAccess
}
} : {};
},
getContext: function() {
var partitions = this.model.get('user_partitions'),
hasRestrictions = false,
verificationPartitions = [],
isSelected = false;
// Display a simplified version of verified partition schemes.
// Although there are two groups defined (ALLOW and DENY),
// we show only the ALLOW group.
// To avoid searching all the groups, we're assuming that the editor
// either sets the ALLOW group or doesn't set any groups (implicitly allow all).
_.each(partitions, function(item) {
if (item.scheme === 'verification') {
isSelected = _.any(_.pluck(item.groups, 'selected'));
hasRestrictions = hasRestrictions || isSelected;
verificationPartitions.push({
'id': item.id,
'name': item.name,
'selected': isSelected
});
}
});
return {
'hasVerificationRestrictions': hasRestrictions,
'verificationPartitions': verificationPartitions
};
}
});
return {
getModal: function(type, xblockInfo, options) {
if (type === 'edit') {
......@@ -837,10 +734,6 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
var editors = [];
if (xblockInfo.isVertical()) {
editors = [StaffLockEditor];
if (xblockInfo.hasVerifiedCheckpoints()) {
editors.push(VerificationAccessEditor);
}
} else {
tabs = [
{
......
......@@ -5,25 +5,26 @@
'use strict';
function VisibilityEditorView(runtime, element) {
this.getGroupAccess = function() {
var groupAccess = {},
checkboxValues,
partitionId,
groupId,
groupId;
// This constant MUST match the group ID
// defined by VerificationPartitionScheme on the backend!
ALLOW_GROUP_ID = 1;
// Get the selected user partition (only allowed to select one).
partitionId = parseInt(element.find('.partition-visibility select').val(), 10);
if (element.find('.visibility-level-all').prop('checked')) {
// "All Learners and Staff" is selected (or "Choose one", which is only shown when
// current visibility is "All Learners and Staff" at the time the dialog is opened).
if (partitionId === -1) {
return {};
}
// Cohort partitions (user is allowed to select more than one)
element.find('.field-visibility-content-group input:checked').each(function(index, input) {
checkboxValues = $(input).val().split('-');
partitionId = parseInt(checkboxValues[0], 10);
groupId = parseInt(checkboxValues[1], 10);
// Otherwise get the checked groups within the selected partition.
element.find(
'.partition-group-visibility-' + partitionId + ' input:checked'
).each(function(index, input) {
groupId = parseInt($(input).val(), 10);
if (groupAccess.hasOwnProperty(partitionId)) {
groupAccess[partitionId].push(groupId);
......@@ -32,38 +33,25 @@
}
});
// Verification partitions (user can select exactly one)
if (element.find('#verification-access-checkbox').prop('checked')) {
partitionId = parseInt($('#verification-access-dropdown').val(), 10);
groupAccess[partitionId] = [ALLOW_GROUP_ID];
}
return groupAccess;
};
// When selecting "all students and staff", uncheck the specific groups
element.find('.field-visibility-level input').change(function(event) {
if ($(event.target).hasClass('visibility-level-all')) {
element.find('.field-visibility-content-group input, .field-visibility-verification input')
.prop('checked', false);
}
});
element.find('.partition-visibility select').change(function(event) {
var partitionId;
// When selecting a specific group, deselect "all students and staff" and
// select "specific content groups" instead.`
element.find('.field-visibility-content-group input, .field-visibility-verification input')
.change(function() {
element.find('.visibility-level-all').prop('checked', false);
element.find('.visibility-level-specific').prop('checked', true);
// Hide all the partition group options.
element.find('.partition-group-control').addClass('is-hidden');
// If a partition is selected, display its groups.
partitionId = parseInt($(event.target).val(), 10);
if (partitionId >= 0) {
element.find('.partition-group-control-' + partitionId).removeClass('is-hidden');
}
});
}
VisibilityEditorView.prototype.collectFieldData = function collectFieldData() {
return {
metadata: {
'group_access': this.getGroupAccess()
}
};
return {metadata: {group_access: this.getGroupAccess()}};
};
function initializeVisibilityEditor(runtime, element) {
......
......@@ -91,3 +91,5 @@
// CAPA Problem Feedback
@import 'edx-pattern-library-shims/buttons';
@import 'edx-pattern-library-shims/base/variables';
// studio - elements - modal-window
// ========================
@import 'edx-pattern-library-shims/base/variables';
// start with the view/body
[class*="view-"] {
......@@ -482,59 +484,59 @@
// MODAL TYPE: component - visibility modal
.xblock-visibility_view {
.visibility-controls-secondary {
max-height: 100%;
overflow-y: auto;
@include margin(($baseline*0.75), 0, 0, $baseline);
}
// We don't wish the dialog to resize for the common case of 2 groups.
min-height: 190px;
.visibility-controls-group {
@extend %wipe-last-child;
margin-bottom: $baseline;
.visibility-header {
padding-bottom: $baseline;
margin-bottom: 0;
color: $gray-d3;
}
// UI: form fields
.list-fields {
.current-visibility-title {
font-weight: font-weight(semi-bold);
.field {
@extend %wipe-last-child;
margin-bottom: ($baseline/4);
.icon {
@include margin-right($baseline/8);
}
}
label {
@extend %t-copy-sub1;
.group-select-title {
font-weight: font-weight(semi-bold);
font-size: inherit;
}
.partition-visibility {
padding-top: $baseline;
}
// UI: radio and checkbox inputs
.field-radio, .field-checkbox {
// UI: form fields
.partition-group-control {
padding-top: ($baseline/2);
.field {
margin-top: ($baseline/4);
label {
@include margin-left($baseline/4);
font-size: inherit;
}
}
}
.field-visibility-verification {
.note {
@extend %t-copy-sub2;
@extend %t-regular;
margin: 14px 0 0 24px;
display: block;
}
}
// CASE: content group has been removed
.field-visibility-content-group.was-removed {
// CASE: content or enrollment group has been removed
.partition-group-visibility.was-removed {
.input-checkbox:checked ~ label {
color: $color-error;
color: $error-color;
}
.note {
@extend %t-copy-sub2;
@extend %t-regular;
display: block;
color: $color-error;
color: $error-color;
}
}
......@@ -698,7 +700,7 @@
}
// UI: staff lock section
.edit-staff-lock, .edit-settings-timed-examination, .edit-verification-access {
.edit-staff-lock, .edit-settings-timed-examination {
.checkbox-cosmetic .input-checkbox {
@extend %cont-text-sr;
......@@ -730,13 +732,6 @@
}
}
.verification-access {
.checkbox-cosmetic .label {
@include float(left);
margin: 2px 6px 0 0;
}
}
// UI: timed and proctored exam section
.edit-settings-timed-examination {
......
......@@ -86,7 +86,7 @@ var visibleToStaffOnly = visibilityState === 'staff_only';
<% if (hasContentGroupComponents) { %>
<p class="note-visibility">
<span class="icon fa fa-eye" aria-hidden="true"></span>
<span class="note-copy"><%- gettext("Some content in this unit is visible only to particular content groups") %></span>
<span class="note-copy"><%- gettext("Some content in this unit is visible only to specific groups of learners.") %></span>
</p>
<% } %>
<ul class="actions-inline">
......
......@@ -8,6 +8,16 @@ from stevedore.extension import ExtensionManager
# pylint: disable=redefined-builtin
# UserPartition IDs must be unique. The Cohort and Random UserPartitions (when they are
# created via Studio) choose an unused ID in the range of 100 (historical) to MAX_INT. Therefore the
# dynamic UserPartitionIDs must be under 100, and they have to be hard-coded to ensure
# they are always the same whenever the dynamic partition is added (since the UserPartition
# ID is stored in the xblock group_access dict).
ENROLLMENT_TRACK_PARTITION_ID = 50
MINIMUM_STATIC_PARTITION_ID = 100
class UserPartitionError(Exception):
"""
Base Exception for when an error was found regarding user partitions.
......
......@@ -7,23 +7,13 @@ from django.conf import settings
from django.utils.translation import ugettext_lazy as _
import logging
from xmodule.partitions.partitions import UserPartition, UserPartitionError
from xmodule.partitions.partitions import UserPartition, UserPartitionError, ENROLLMENT_TRACK_PARTITION_ID
from xmodule.modulestore.django import modulestore
log = logging.getLogger(__name__)
# UserPartition IDs must be unique. The Cohort and Random UserPartitions (when they are
# created via Studio) choose an unused ID in the range of 100 (historical) to MAX_INT. Therefore the
# dynamic UserPartitionIDs must be under 100, and they have to be hard-coded to ensure
# they are always the same whenever the dynamic partition is added (since the UserPartition
# ID is stored in the xblock group_access dict).
ENROLLMENT_TRACK_PARTITION_ID = 50
MINIMUM_STATIC_PARTITION_ID = 100
# settings will not be available when running nosetests.
FEATURES = getattr(settings, 'FEATURES', {})
......@@ -84,7 +74,7 @@ def _create_enrollment_track_partition(course):
partition = enrollment_track_scheme.create_user_partition(
id=ENROLLMENT_TRACK_PARTITION_ID,
name=_(u"Enrollment Track Partition"),
name=_(u"Enrollment Tracks"),
description=_(u"Partition for segmenting users by enrollment track"),
parameters={"course_id": unicode(course.id)}
)
......
......@@ -9,10 +9,11 @@ from mock import Mock
from opaque_keys.edx.locator import CourseLocator
from stevedore.extension import Extension, ExtensionManager
from xmodule.partitions.partitions import (
Group, UserPartition, UserPartitionError, NoSuchUserPartitionGroupError, USER_PARTITION_SCHEME_NAMESPACE
Group, UserPartition, UserPartitionError, NoSuchUserPartitionGroupError,
USER_PARTITION_SCHEME_NAMESPACE, ENROLLMENT_TRACK_PARTITION_ID
)
from xmodule.partitions.partitions_service import (
PartitionService, get_all_partitions_for_course, ENROLLMENT_TRACK_PARTITION_ID, FEATURES
PartitionService, get_all_partitions_for_course, FEATURES
)
......
......@@ -13,8 +13,7 @@ from xmodule.tests import get_test_system
from xmodule.x_module import AUTHOR_VIEW, STUDENT_VIEW
from xmodule.validation import StudioValidationMessage
from xmodule.split_test_module import SplitTestDescriptor, SplitTestFields, get_split_user_partitions
from xmodule.partitions.partitions import Group, UserPartition
from xmodule.partitions.partitions_service import MINIMUM_STATIC_PARTITION_ID
from xmodule.partitions.partitions import Group, UserPartition, MINIMUM_STATIC_PARTITION_ID
class SplitTestModuleFactory(xml.XmlImportFactory):
......
from bok_choy.page_object import PageObject
from selenium.webdriver.common.keys import Keys
from common.test.acceptance.pages.common.utils import click_css
from common.test.acceptance.tests.helpers import select_option_by_text, get_selected_option_text
from selenium.webdriver.support.ui import Select
......@@ -108,43 +109,83 @@ class ComponentVisibilityEditorView(BaseComponentEditorView):
"""
A :class:`.PageObject` representing the rendered view of a component visibility editor.
"""
OPTION_SELECTOR = '.modal-section-content .field'
OPTION_SELECTOR = '.partition-group-control .field'
ALL_LEARNERS_AND_STAFF = 'All Learners and Staff'
CONTENT_GROUP_PARTITION = 'Content Groups'
ENROLLMENT_TRACK_PARTITION = "Enrollment Tracks"
@property
def all_options(self):
def all_group_options(self):
"""
Return all visibility options.
Return all partition groups.
"""
return self.q(css=self._bounded_selector(self.OPTION_SELECTOR)).results
@property
def selected_options(self):
def current_groups_message(self):
"""
Return all selected visibility options.
This returns the message shown at the top of the visibility dialog about the
current visibility state (at the time that the dialog was opened).
For example, "Current visible to: All Learners and Staff".
"""
return self.q(css=self._bounded_selector('.visibility-header'))[0].text
@property
def selected_partition_scheme(self):
"""
Return the selected partition scheme (or "All Learners and Staff"
if no partitioning is selected).
"""
selector = self.q(css=self._bounded_selector('.partition-visibility select'))
return get_selected_option_text(selector)
def select_partition_scheme(self, partition_name):
"""
Sets the selected partition scheme to the one with the
matching name.
"""
selector = self.q(css=self._bounded_selector('.partition-visibility select'))
select_option_by_text(selector, partition_name, focus_out=True)
@property
def selected_groups(self):
"""
Return all selected partition groups. If none are selected,
returns an empty array.
"""
results = []
for option in self.all_options:
button = option.find_element_by_css_selector('input.input')
if button.is_selected():
for option in self.all_group_options:
checkbox = option.find_element_by_css_selector('input')
if checkbox.is_selected():
results.append(option)
return results
def select_option(self, label_text, save=True):
def select_group(self, group_name, save=True):
"""
Click the first option which has a label matching `label_text`.
Select the first group which has a label matching `group_name`.
Arguments:
label_text (str): Text of a label accompanying the input
which should be clicked.
group_name (str): The name of the group.
save (boolean): Whether the "save" button should be clicked
afterwards.
Returns:
bool: Whether the label was found and clicked.
bool: Whether a group with the provided name was found and clicked.
"""
for option in self.all_options:
if label_text in option.text:
option.click()
for option in self.all_group_options:
if group_name in option.text:
checkbox = option.find_element_by_css_selector('input')
checkbox.click()
if save:
self.save()
return True
return False
def select_groups_in_partition_scheme(self, partition_name, group_names):
"""
Select groups in the provided partition scheme. The "save"
button is clicked afterwards.
"""
self.select_partition_scheme(partition_name)
for label in group_names:
self.select_group(label, save=False)
self.save()
......@@ -174,22 +174,18 @@ class CoursewareSearchCohortTest(ContainerBase):
"""
container_page = self.go_to_unit_page()
def set_visibility(html_block_index, content_group, second_content_group=None):
def set_visibility(html_block_index, groups):
"""
Set visibility on html blocks to specified groups.
"""
html_block = container_page.xblocks[html_block_index]
html_block.edit_visibility()
if second_content_group:
ComponentVisibilityEditorView(self.browser, html_block.locator).select_option(
second_content_group, save=False
)
ComponentVisibilityEditorView(self.browser, html_block.locator).select_option(content_group)
visibility_dialog = ComponentVisibilityEditorView(self.browser, html_block.locator)
visibility_dialog.select_groups_in_partition_scheme(visibility_dialog.CONTENT_GROUP_PARTITION, groups)
set_visibility(1, self.content_group_a)
set_visibility(2, self.content_group_b)
set_visibility(3, self.content_group_a, self.content_group_b)
set_visibility(4, 'All Students and Staff') # Does not work without this
set_visibility(1, [self.content_group_a])
set_visibility(2, [self.content_group_b])
set_visibility(3, [self.content_group_a, self.content_group_b])
container_page.publish_action.click()
......
......@@ -17,7 +17,6 @@ from common.test.acceptance.pages.lms.courseware import CoursewarePage
from common.test.acceptance.pages.lms.auto_auth import AutoAuthPage as LmsAutoAuthPage
from common.test.acceptance.tests.lms.test_lms_user_preview import verify_expected_problem_visibility
from bok_choy.promise import EmptyPromise
from bok_choy.page_object import XSS_INJECTION
......@@ -121,18 +120,15 @@ class EndToEndCohortedCoursewareTest(ContainerBase):
"""
container_page = self.go_to_unit_page()
def set_visibility(problem_index, content_group, second_content_group=None):
def set_visibility(problem_index, groups):
problem = container_page.xblocks[problem_index]
problem.edit_visibility()
if second_content_group:
ComponentVisibilityEditorView(self.browser, problem.locator).select_option(
second_content_group, save=False
)
ComponentVisibilityEditorView(self.browser, problem.locator).select_option(content_group)
visibility_dialog = ComponentVisibilityEditorView(self.browser, problem.locator)
visibility_dialog.select_groups_in_partition_scheme(visibility_dialog.CONTENT_GROUP_PARTITION, groups)
set_visibility(1, self.content_group_a)
set_visibility(2, self.content_group_b)
set_visibility(3, self.content_group_a, self.content_group_b)
set_visibility(1, [self.content_group_a])
set_visibility(2, [self.content_group_b])
set_visibility(3, [self.content_group_a, self.content_group_b])
container_page.publish_action.click()
......
......@@ -44,8 +44,7 @@ from xmodule.course_module import (
CATALOG_VISIBILITY_NONE,
)
from xmodule.error_module import ErrorDescriptor
from xmodule.partitions.partitions import Group, UserPartition
from xmodule.partitions.partitions_service import MINIMUM_STATIC_PARTITION_ID
from xmodule.partitions.partitions import Group, UserPartition, MINIMUM_STATIC_PARTITION_ID
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
......
......@@ -15,6 +15,9 @@ from xmodule.partitions.partitions import NoSuchUserPartitionError, NoSuchUserPa
# more information can be found here: https://openedx.atlassian.net/browse/PLAT-902
_ = lambda text: text
INVALID_USER_PARTITION_VALIDATION = _(u"This component's visibility settings refer to deleted or invalid group configurations.")
INVALID_USER_PARTITION_GROUP_VALIDATION = _(u"This component's visibility settings refer to deleted or invalid groups.")
class GroupAccessDict(Dict):
"""Special Dict class for serializing the group_access field"""
......@@ -165,14 +168,14 @@ class LmsBlockMixin(XBlockMixin):
validation.add(
ValidationMessage(
ValidationMessage.ERROR,
_(u"This component refers to deleted or invalid content group configurations.")
INVALID_USER_PARTITION_VALIDATION
)
)
if has_invalid_groups:
validation.add(
ValidationMessage(
ValidationMessage.ERROR,
_(u"This component refers to deleted or invalid content groups.")
INVALID_USER_PARTITION_GROUP_VALIDATION
)
)
return validation
......@@ -4,6 +4,7 @@ Tests of the LMS XBlock Mixin
import ddt
from nose.plugins.attrib import attr
from lms_xblock.mixin import INVALID_USER_PARTITION_VALIDATION, INVALID_USER_PARTITION_GROUP_VALIDATION
from xblock.validation import ValidationMessage
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.factories import CourseFactory, ToyCourseFactory, ItemFactory
......@@ -90,7 +91,7 @@ class XBlockValidationTest(LmsXBlockMixinTestCase):
self.assertEqual(len(validation.messages), 1)
self.verify_validation_message(
validation.messages[0],
u"This component refers to deleted or invalid content group configurations.",
INVALID_USER_PARTITION_VALIDATION,
ValidationMessage.ERROR,
)
......@@ -102,7 +103,7 @@ class XBlockValidationTest(LmsXBlockMixinTestCase):
self.assertEqual(len(validation.messages), 1)
self.verify_validation_message(
validation.messages[0],
u"This component refers to deleted or invalid content group configurations.",
INVALID_USER_PARTITION_VALIDATION,
ValidationMessage.ERROR,
)
......@@ -115,7 +116,7 @@ class XBlockValidationTest(LmsXBlockMixinTestCase):
self.assertEqual(len(validation.messages), 1)
self.verify_validation_message(
validation.messages[0],
u"This component refers to deleted or invalid content groups.",
INVALID_USER_PARTITION_GROUP_VALIDATION,
ValidationMessage.ERROR,
)
......@@ -125,7 +126,7 @@ class XBlockValidationTest(LmsXBlockMixinTestCase):
self.assertEqual(len(validation.messages), 1)
self.verify_validation_message(
validation.messages[0],
u"This component refers to deleted or invalid content groups.",
INVALID_USER_PARTITION_GROUP_VALIDATION,
ValidationMessage.ERROR,
)
......
......@@ -12,8 +12,7 @@ from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.partitions.partitions import UserPartition
from xmodule.partitions.partitions_service import MINIMUM_STATIC_PARTITION_ID
from xmodule.partitions.partitions import UserPartition, MINIMUM_STATIC_PARTITION_ID
class EnrollmentTrackUserPartitionTest(SharedModuleStoreTestCase):
......@@ -160,7 +159,7 @@ def create_enrollment_track_partition(course):
enrollment_track_scheme = UserPartition.get_scheme("enrollment_track")
partition = enrollment_track_scheme.create_user_partition(
id=1,
name="TestEnrollment Track Partition",
name="Test Enrollment Track Partition",
description="Test partition for segmenting users by enrollment track",
parameters={"course_id": unicode(course.id)}
)
......
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