SOL-536 Experiment-Aware content search

This commit is contained in:
Davorin Sego
2015-04-18 14:52:42 +02:00
committed by Dino Cikatic
parent 0d975674c2
commit d3aa9dfd87
7 changed files with 674 additions and 88 deletions

View File

@@ -8,37 +8,88 @@ from student.models import CourseEnrollment
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from xmodule.modulestore.django import modulestore
from search.filter_generator import SearchFilterGenerator
from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_user_partition
from openedx.core.djangoapps.user_api.partition_schemes import RandomUserPartitionScheme
from openedx.core.djangoapps.course_groups.partition_scheme import CohortPartitionScheme
from courseware.access import get_user_role
INCLUDE_SCHEMES = [CohortPartitionScheme, RandomUserPartitionScheme, ]
SCHEME_SUPPORTS_ASSIGNMENT = [RandomUserPartitionScheme, ]
class LmsSearchFilterGenerator(SearchFilterGenerator):
""" SearchFilterGenerator for LMS Search """
_user_enrollments = {}
def _enrollments_for_user(self, user):
""" Return the specified user's course enrollments """
if user not in self._user_enrollments:
self._user_enrollments[user] = CourseEnrollment.enrollments_for_user(user)
return self._user_enrollments[user]
def filter_dictionary(self, **kwargs):
""" base implementation which filters via start_date """
filter_dictionary = super(LmsSearchFilterGenerator, self).filter_dictionary(**kwargs)
if 'user' in kwargs and 'course_id' in kwargs and kwargs['course_id']:
user = kwargs['user']
try:
course_key = CourseKey.from_string(kwargs['course_id'])
except InvalidKeyError:
course_key = SlashSeparatedCourseKey.from_deprecated_string(kwargs['course_id'])
""" LMS implementation, adds filtering by user partition, course id and user """
# Staff user looking at course as staff user
if get_user_role(user, course_key) == 'staff':
return filter_dictionary
cohorted_user_partition = get_cohorted_user_partition(course_key)
if cohorted_user_partition:
partition_group = cohorted_user_partition.scheme.get_group_for_user(
def get_group_for_user_partition(user_partition, course_key, user):
""" Returns the specified user's group for user partition """
if user_partition.scheme in SCHEME_SUPPORTS_ASSIGNMENT:
return user_partition.scheme.get_group_for_user(
course_key,
user,
cohorted_user_partition,
user_partition,
assign=False,
)
filter_dictionary['content_groups'] = unicode(partition_group.id) if partition_group else None
else:
return user_partition.scheme.get_group_for_user(
course_key,
user,
user_partition,
)
def get_group_ids_for_user(course, user):
""" Collect user partition group ids for user for this course """
partition_groups = []
for user_partition in course.user_partitions:
if user_partition.scheme in INCLUDE_SCHEMES:
group = get_group_for_user_partition(user_partition, course.id, user)
if group:
partition_groups.append(group)
partition_group_ids = [unicode(partition_group.id) for partition_group in partition_groups]
return partition_group_ids if partition_group_ids else None
filter_dictionary = super(LmsSearchFilterGenerator, self).filter_dictionary(**kwargs)
if 'user' in kwargs:
user = kwargs['user']
if 'course_id' in kwargs and kwargs['course_id']:
try:
course_key = CourseKey.from_string(kwargs['course_id'])
except InvalidKeyError:
course_key = SlashSeparatedCourseKey.from_deprecated_string(kwargs['course_id'])
# Staff user looking at course as staff user
if get_user_role(user, course_key) == 'staff':
return filter_dictionary
# Need to check course exist (if course gets deleted enrollments don't get cleaned up)
course = modulestore().get_course(course_key)
if course:
filter_dictionary['content_groups'] = get_group_ids_for_user(course, user)
else:
user_enrollments = self._enrollments_for_user(user)
content_groups = []
for enrollment in user_enrollments:
course = modulestore().get_course(enrollment.course_id)
if course:
enrollment_group_ids = get_group_ids_for_user(course, user)
if enrollment_group_ids:
content_groups.extend(enrollment_group_ids)
filter_dictionary['content_groups'] = content_groups if content_groups else None
return filter_dictionary
def field_dictionary(self, **kwargs):
@@ -47,7 +98,7 @@ class LmsSearchFilterGenerator(SearchFilterGenerator):
if not kwargs.get('user'):
field_dictionary['course'] = []
elif not kwargs.get('course_id'):
user_enrollments = CourseEnrollment.enrollments_for_user(kwargs['user'])
user_enrollments = self._enrollments_for_user(kwargs['user'])
field_dictionary['course'] = [unicode(enrollment.course_id) for enrollment in user_enrollments]
# if we have an org filter, only include results for this org filter
@@ -62,8 +113,9 @@ class LmsSearchFilterGenerator(SearchFilterGenerator):
exclude_dictionary = super(LmsSearchFilterGenerator, self).exclude_dictionary(**kwargs)
course_org_filter = microsite.get_value('course_org_filter')
# If we have a course filter we are ensuring that we only get those courses above
org_filter_out_set = microsite.get_all_orgs()
if not course_org_filter and org_filter_out_set:
exclude_dictionary['org'] = list(org_filter_out_set)
if not course_org_filter:
org_filter_out_set = microsite.get_all_orgs()
if org_filter_out_set:
exclude_dictionary['org'] = list(org_filter_out_set)
return exclude_dictionary

View File

@@ -62,9 +62,10 @@ class LmsSearchResultProcessor(SearchResultProcessor):
def should_remove(self, user):
""" Test to see if this result should be removed due to access restriction """
return not has_access(
user_has_access = has_access(
user,
"load",
self.get_item(self.get_usage_key()),
self.get_course_key()
)
return not user_has_access

View File

@@ -11,10 +11,12 @@ from student.models import CourseEnrollment
from xmodule.partitions.partitions import Group, UserPartition
from openedx.core.djangoapps.course_groups.partition_scheme import CohortPartitionScheme
from openedx.core.djangoapps.user_api.partition_schemes import RandomUserPartitionScheme
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory, config_course_cohorts
from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort
from openedx.core.djangoapps.course_groups.views import link_cohort_to_partition_group
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from lms.lib.courseware_search.lms_filter_generator import LmsSearchFilterGenerator
@@ -49,6 +51,13 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase):
publish_item=True,
)
self.chapter2 = ItemFactory.create(
parent_location=self.courses[1].location,
category='chapter',
display_name="Week 1",
publish_item=True,
)
self.groups = [Group(1, 'Group 1'), Group(2, 'Group 2')]
self.content_groups = [1, 2]
@@ -56,64 +65,11 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase):
def setUp(self):
super(LmsSearchFilterGeneratorTestCase, self).setUp()
self.build_courses()
self.user_partition = None
self.first_cohort = None
self.second_cohort = None
self.user = UserFactory.create(username="jack", email="jack@fake.edx.org", password='test')
for course in self.courses:
CourseEnrollment.enroll(self.user, course.location.course_key)
def add_seq_with_content_groups(self, groups=None):
"""
Adds sequential and two content groups to first course in courses list.
"""
config_course_cohorts(self.courses[0], is_cohorted=True)
if groups is None:
groups = self.groups
self.user_partition = UserPartition(
id=0,
name='Partition 1',
description='This is partition 1',
groups=groups,
scheme=CohortPartitionScheme
)
self.user_partition.scheme.name = "cohort"
ItemFactory.create(
parent_location=self.chapter.location,
category='sequential',
display_name="Lesson 1",
publish_item=True,
metadata={u"user_partitions": [self.user_partition.to_json()]}
)
self.first_cohort, self.second_cohort = [
CohortFactory(course_id=self.courses[0].id) for _ in range(2)
]
self.courses[0].user_partitions = [self.user_partition]
self.courses[0].save()
modulestore().update_item(self.courses[0], self.user.id)
def add_user_to_cohort_group(self):
"""
adds user to cohort and links cohort to content group
"""
add_user_to_cohort(self.first_cohort, self.user.username)
link_cohort_to_partition_group(
self.first_cohort,
self.user_partition.id,
self.groups[0].id,
)
self.courses[0].save()
modulestore().update_item(self.courses[0], self.user.id)
def test_course_id_not_provided(self):
"""
Tests that we get the list of IDs of courses the user is enrolled in when the course ID is null or not provided
@@ -191,6 +147,152 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase):
self.assertIn('org', field_dictionary)
self.assertEqual('TestMicrosite3', field_dictionary['org'])
class LmsSearchFilterGeneratorGroupsTestCase(LmsSearchFilterGeneratorTestCase):
"""
Test case class to test search result processor
with content and user groups present within the course
"""
def setUp(self):
super(LmsSearchFilterGeneratorGroupsTestCase, self).setUp()
self.user_partition = None
self.split_test_user_partition = None
self.first_cohort = None
self.second_cohort = None
def add_seq_with_content_groups(self, groups=None):
"""
Adds sequential and two content groups to first course in courses list.
"""
config_course_cohorts(self.courses[0], is_cohorted=True)
if groups is None:
groups = self.groups
self.user_partition = UserPartition(
id=0,
name='Partition 1',
description='This is partition 1',
groups=groups,
scheme=CohortPartitionScheme
)
self.user_partition.scheme.name = "cohort"
ItemFactory.create(
parent_location=self.chapter.location,
category='sequential',
display_name="Lesson 1",
publish_item=True,
metadata={u"user_partitions": [self.user_partition.to_json()]}
)
self.first_cohort, self.second_cohort = [
CohortFactory(course_id=self.courses[0].id) for _ in range(2)
]
self.courses[0].user_partitions = [self.user_partition]
self.courses[0].save()
modulestore().update_item(self.courses[0], self.user.id)
def add_user_to_cohort_group(self):
"""
adds user to cohort and links cohort to content group
"""
add_user_to_cohort(self.first_cohort, self.user.username)
link_cohort_to_partition_group(
self.first_cohort,
self.user_partition.id,
self.groups[0].id,
)
self.courses[0].save()
modulestore().update_item(self.courses[0], self.user.id)
def add_split_test(self, groups=None):
"""
Adds split test and two content groups to second course in courses list.
"""
if groups is None:
groups = self.groups
self.split_test_user_partition = UserPartition(
id=0,
name='Partition 2',
description='This is partition 2',
groups=groups,
scheme=RandomUserPartitionScheme
)
self.split_test_user_partition.scheme.name = "random"
sequential = ItemFactory.create(
parent_location=self.chapter.location,
category='sequential',
display_name="Lesson 2",
publish_item=True,
)
vertical = ItemFactory.create(
parent_location=sequential.location,
category='vertical',
display_name='Subsection 3',
publish_item=True,
)
split_test_unit = ItemFactory.create(
parent_location=vertical.location,
category='split_test',
user_partition_id=0,
display_name="Test Content Experiment 1",
)
condition_1_vertical = ItemFactory.create(
parent_location=split_test_unit.location,
category="vertical",
display_name="Group ID 1",
)
condition_2_vertical = ItemFactory.create(
parent_location=split_test_unit.location,
category="vertical",
display_name="Group ID 2",
)
ItemFactory.create(
parent_location=condition_1_vertical.location,
category="html",
display_name="Group A",
publish_item=True,
)
ItemFactory.create(
parent_location=condition_2_vertical.location,
category="html",
display_name="Group B",
publish_item=True,
)
self.courses[1].user_partitions = [self.split_test_user_partition]
self.courses[1].save()
modulestore().update_item(self.courses[1], self.user.id)
def add_user_to_splittest_group(self):
"""
adds user to a random split test group
"""
self.split_test_user_partition.scheme.get_group_for_user(
CourseKey.from_string(unicode(self.courses[1].id)),
self.user,
self.split_test_user_partition,
assign=True,
)
self.courses[1].save()
modulestore().update_item(self.courses[1], self.user.id)
def test_content_group_id_provided(self):
"""
Tests that we get the content group ID when course is assigned to cohort
@@ -205,7 +307,7 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase):
self.assertTrue('start_date' in filter_dictionary)
self.assertEqual(unicode(self.courses[0].id), field_dictionary['course'])
self.assertEqual(unicode(self.content_groups[0]), filter_dictionary['content_groups'])
self.assertEqual([unicode(self.content_groups[0])], filter_dictionary['content_groups'])
def test_content_multiple_groups_id_provided(self):
"""
@@ -233,7 +335,7 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase):
self.assertTrue('start_date' in filter_dictionary)
self.assertEqual(unicode(self.courses[0].id), field_dictionary['course'])
# returns only first group, relevant to current user
self.assertEqual(unicode(self.content_groups[0]), filter_dictionary['content_groups'])
self.assertEqual([unicode(self.content_groups[0])], filter_dictionary['content_groups'])
def test_content_group_id_not_provided(self):
"""
@@ -266,6 +368,44 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase):
self.assertEqual(unicode(self.courses[0].id), field_dictionary['course'])
self.assertEqual(None, filter_dictionary['content_groups'])
def test_split_test_with_user_groups_user_not_assigned(self):
"""
Tests that we don't get user group ID when user is not assigned to a split test group
"""
self.add_split_test()
field_dictionary, filter_dictionary, _ = LmsSearchFilterGenerator.generate_field_filters(
user=self.user,
course_id=unicode(self.courses[1].id)
)
self.assertTrue('start_date' in filter_dictionary)
self.assertEqual(unicode(self.courses[1].id), field_dictionary['course'])
self.assertEqual(None, filter_dictionary['content_groups'])
def test_split_test_with_user_groups_user_assigned(self):
"""
Tests that we get user group ID when user is assigned to a split test group
"""
self.add_split_test()
self.add_user_to_splittest_group()
field_dictionary, filter_dictionary, _ = LmsSearchFilterGenerator.generate_field_filters(
user=self.user,
course_id=unicode(self.courses[1].id)
)
partition_group = self.split_test_user_partition.scheme.get_group_for_user(
CourseKey.from_string(unicode(self.courses[1].id)),
self.user,
self.split_test_user_partition,
assign=False,
)
self.assertTrue('start_date' in filter_dictionary)
self.assertEqual(unicode(self.courses[1].id), field_dictionary['course'])
self.assertEqual([unicode(partition_group.id)], filter_dictionary['content_groups'])
def test_invalid_course_key(self):
"""
Test system raises an error if no course found.

View File

@@ -97,7 +97,7 @@ class LmsSearchInitializerTestCase(StaffMasqueradeTestCase):
user=self.global_staff,
course_id=unicode(self.course.id)
)
self.assertEqual(filter_directory['content_groups'], unicode(1))
self.assertEqual(filter_directory['content_groups'], [unicode(1)])
def test_staff_masquerading_as_a_staff_user(self):
"""