Commit 099be9e7 by dino-cikatic

Merge pull request #8229 from edx/dcikatic/SOL-536

Dcikatic/sol 536 [WIP]
parents 110a7b1a d3aa9dfd
...@@ -153,6 +153,12 @@ class SearchIndexerBase(object): ...@@ -153,6 +153,12 @@ class SearchIndexerBase(object):
# list - those are ready to be destroyed # list - those are ready to be destroyed
indexed_items = set() indexed_items = set()
def get_item_location(item):
"""
Gets the version agnostic item location
"""
return item.location.version_agnostic().replace(branch=None)
def index_item(item, skip_index=False, groups_usage_info=None): def index_item(item, skip_index=False, groups_usage_info=None):
""" """
Add this item to the search index and indexed_items list Add this item to the search index and indexed_items list
...@@ -175,8 +181,25 @@ class SearchIndexerBase(object): ...@@ -175,8 +181,25 @@ class SearchIndexerBase(object):
return return
item_content_groups = None item_content_groups = None
if item.category == "split_test":
split_partition = item.get_selected_partition()
for split_test_child in item.get_children():
if split_partition:
for group in split_partition.groups:
group_id = unicode(group.id)
child_location = item.group_id_to_child.get(group_id, None)
if child_location == split_test_child.location:
groups_usage_info.update({
unicode(get_item_location(split_test_child)): [group_id],
})
for component in split_test_child.get_children():
groups_usage_info.update({
unicode(get_item_location(component)): [group_id]
})
if groups_usage_info: if groups_usage_info:
item_location = item.location.version_agnostic().replace(branch=None) item_location = get_item_location(item)
item_content_groups = groups_usage_info.get(unicode(item_location), None) item_content_groups = groups_usage_info.get(unicode(item_location), None)
item_id = unicode(cls._id_modifier(item.scope_ids.usage_id)) item_id = unicode(cls._id_modifier(item.scope_ids.usage_id))
......
...@@ -952,10 +952,20 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase): ...@@ -952,10 +952,20 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
Tests indexing of content groups on course modules using mongo modulestore. Tests indexing of content groups on course modules using mongo modulestore.
""" """
MODULESTORE = TEST_DATA_MONGO_MODULESTORE MODULESTORE = TEST_DATA_MONGO_MODULESTORE
INDEX_NAME = CoursewareSearchIndexer.INDEX_NAME
def setUp(self): def setUp(self):
super(GroupConfigurationSearchMongo, self).setUp() super(GroupConfigurationSearchMongo, self).setUp()
self._setup_course_with_content()
self._setup_split_test_module()
self._setup_content_groups()
self.reload_course()
def _setup_course_with_content(self):
"""
Set up course with html content in it.
"""
self.chapter = ItemFactory.create( self.chapter = ItemFactory.create(
parent_location=self.course.location, parent_location=self.course.location,
category='chapter', category='chapter',
...@@ -964,6 +974,7 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase): ...@@ -964,6 +974,7 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
publish_item=True, publish_item=True,
start=datetime(2015, 3, 1, tzinfo=UTC), start=datetime(2015, 3, 1, tzinfo=UTC),
) )
self.sequential = ItemFactory.create( self.sequential = ItemFactory.create(
parent_location=self.chapter.location, parent_location=self.chapter.location,
category='sequential', category='sequential',
...@@ -972,6 +983,16 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase): ...@@ -972,6 +983,16 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
publish_item=True, publish_item=True,
start=datetime(2015, 3, 1, tzinfo=UTC), start=datetime(2015, 3, 1, tzinfo=UTC),
) )
self.sequential2 = ItemFactory.create(
parent_location=self.chapter.location,
category='sequential',
display_name="Lesson 2",
modulestore=self.store,
publish_item=True,
start=datetime(2015, 3, 1, tzinfo=UTC),
)
self.vertical = ItemFactory.create( self.vertical = ItemFactory.create(
parent_location=self.sequential.location, parent_location=self.sequential.location,
category='vertical', category='vertical',
...@@ -990,6 +1011,15 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase): ...@@ -990,6 +1011,15 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
start=datetime(2015, 4, 1, tzinfo=UTC), start=datetime(2015, 4, 1, tzinfo=UTC),
) )
self.vertical3 = ItemFactory.create(
parent_location=self.sequential2.location,
category='vertical',
display_name='Subsection 3',
modulestore=self.store,
publish_item=True,
start=datetime(2015, 4, 1, tzinfo=UTC),
)
# unspecified start - should inherit from container # unspecified start - should inherit from container
self.html_unit1 = ItemFactory.create( self.html_unit1 = ItemFactory.create(
parent_location=self.vertical.location, parent_location=self.vertical.location,
...@@ -1018,7 +1048,75 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase): ...@@ -1018,7 +1048,75 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
) )
self.html_unit3.parent = self.vertical2 self.html_unit3.parent = self.vertical2
groups_list = { def _setup_split_test_module(self):
"""
Set up split test module.
"""
c0_url = self.course.id.make_usage_key("vertical", "condition_0_vertical")
c1_url = self.course.id.make_usage_key("vertical", "condition_1_vertical")
c2_url = self.course.id.make_usage_key("vertical", "condition_2_vertical")
self.split_test_unit = ItemFactory.create(
parent_location=self.vertical3.location,
category='split_test',
user_partition_id=0,
display_name="Test Content Experiment 1",
group_id_to_child={"2": c0_url, "3": c1_url, "4": c2_url}
)
self.condition_0_vertical = ItemFactory.create(
parent_location=self.split_test_unit.location,
category="vertical",
display_name="Group ID 2",
location=c0_url,
)
self.condition_0_vertical.parent = self.vertical3
self.condition_1_vertical = ItemFactory.create(
parent_location=self.split_test_unit.location,
category="vertical",
display_name="Group ID 3",
location=c1_url,
)
self.condition_1_vertical.parent = self.vertical3
self.condition_2_vertical = ItemFactory.create(
parent_location=self.split_test_unit.location,
category="vertical",
display_name="Group ID 4",
location=c2_url,
)
self.condition_2_vertical.parent = self.vertical3
self.html_unit4 = ItemFactory.create(
parent_location=self.condition_0_vertical.location,
category="html",
display_name="Split A",
publish_item=True,
)
self.html_unit4.parent = self.condition_0_vertical
self.html_unit5 = ItemFactory.create(
parent_location=self.condition_1_vertical.location,
category="html",
display_name="Split B",
publish_item=True,
)
self.html_unit5.parent = self.condition_1_vertical
self.html_unit6 = ItemFactory.create(
parent_location=self.condition_2_vertical.location,
category="html",
display_name="Split C",
publish_item=True,
)
self.html_unit6.parent = self.condition_2_vertical
def _setup_content_groups(self):
"""
Set up cohort and experiment content groups.
"""
cohort_groups_list = {
u'id': 666, u'id': 666,
u'name': u'Test name', u'name': u'Test name',
u'scheme': u'cohort', u'scheme': u'cohort',
...@@ -1029,18 +1127,33 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase): ...@@ -1029,18 +1127,33 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
{u'id': 1, u'name': u'Group B', u'version': 1, u'usage': []}, {u'id': 1, u'name': u'Group B', u'version': 1, u'usage': []},
], ],
} }
experiment_groups_list = {
u'id': 0,
u'name': u'Experiment aware partition',
u'scheme': u'random',
u'description': u'Experiment aware description',
u'version': UserPartition.VERSION,
u'groups': [
{u'id': 2, u'name': u'Group A', u'version': 1, u'usage': []},
{u'id': 3, u'name': u'Group B', u'version': 1, u'usage': []},
{u'id': 4, u'name': u'Group C', u'version': 1, u'usage': []}
],
}
self.client.put( self.client.put(
self._group_conf_url(cid=666), self._group_conf_url(cid=666),
data=json.dumps(groups_list), data=json.dumps(cohort_groups_list),
content_type="application/json",
HTTP_ACCEPT="application/json",
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.client.put(
self._group_conf_url(cid=0),
data=json.dumps(experiment_groups_list),
content_type="application/json", content_type="application/json",
HTTP_ACCEPT="application/json", HTTP_ACCEPT="application/json",
HTTP_X_REQUESTED_WITH="XMLHttpRequest", HTTP_X_REQUESTED_WITH="XMLHttpRequest",
) )
self.reload_course()
INDEX_NAME = CoursewareSearchIndexer.INDEX_NAME
def _group_conf_url(self, cid=-1): def _group_conf_url(self, cid=-1):
""" """
...@@ -1075,6 +1188,52 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase): ...@@ -1075,6 +1188,52 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
} }
) )
def _html_experiment_group_result(self, html_unit, content_groups):
"""
Return call object with arguments and content group for html_unit.
"""
return call(
'courseware_content',
{
'course_name': unicode(self.course.display_name),
'id': unicode(html_unit.location),
'content': {'html_content': '', 'display_name': unicode(html_unit.display_name)},
'course': unicode(self.course.id),
'location': [
unicode(self.chapter.display_name),
unicode(self.sequential2.display_name),
unicode(self.vertical3.display_name)
],
'content_type': 'Text',
'org': self.course.org,
'content_groups': content_groups,
'start_date': datetime(2015, 4, 1, 0, 0, tzinfo=tzutc())
}
)
def _vertical_experiment_group_result(self, vertical, content_groups):
"""
Return call object with arguments and content group for split_test vertical.
"""
return call(
'courseware_content',
{
'start_date': datetime(2015, 4, 1, 0, 0, tzinfo=tzutc()),
'content': {'display_name': unicode(vertical.display_name)},
'course': unicode(self.course.id),
'location': [
unicode(self.chapter.display_name),
unicode(self.sequential2.display_name),
unicode(vertical.parent.display_name)
],
'content_type': 'Sequence',
'content_groups': content_groups,
'id': unicode(vertical.location),
'course_name': unicode(self.course.display_name),
'org': self.course.org
}
)
def _html_nogroup_result(self, html_unit): def _html_nogroup_result(self, html_unit):
""" """
Return call object with arguments and content group set to empty array for html_unit. Return call object with arguments and content group set to empty array for html_unit.
...@@ -1107,9 +1266,9 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase): ...@@ -1107,9 +1266,9 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
# Only published modules should be in the index # Only published modules should be in the index
added_to_index = self.reindex_course(self.store) added_to_index = self.reindex_course(self.store)
self.assertEqual(added_to_index, 7) self.assertEqual(added_to_index, 16)
response = self.searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = self.searcher.search(field_dictionary={"course": unicode(self.course.id)})
self.assertEqual(response["total"], 8) self.assertEqual(response["total"], 23)
group_access_content = {'group_access': {666: [1]}} group_access_content = {'group_access': {666: [1]}}
...@@ -1119,11 +1278,52 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase): ...@@ -1119,11 +1278,52 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
) )
self.publish_item(self.store, self.html_unit1.location) self.publish_item(self.store, self.html_unit1.location)
self.publish_item(self.store, self.split_test_unit.location)
with patch(settings.SEARCH_ENGINE + '.index') as mock_index: with patch(settings.SEARCH_ENGINE + '.index') as mock_index:
self.reindex_course(self.store) self.reindex_course(self.store)
self.assertTrue(mock_index.called) self.assertTrue(mock_index.called)
self.assertIn(self._html_group_result(self.html_unit1, [1]), mock_index.mock_calls) self.assertIn(self._html_group_result(self.html_unit1, [1]), mock_index.mock_calls)
self.assertIn(self._html_experiment_group_result(self.html_unit4, [unicode(2)]), mock_index.mock_calls)
self.assertIn(self._html_experiment_group_result(self.html_unit5, [unicode(3)]), mock_index.mock_calls)
self.assertIn(self._html_experiment_group_result(self.html_unit6, [unicode(4)]), mock_index.mock_calls)
self.assertNotIn(self._html_experiment_group_result(self.html_unit6, [unicode(5)]), mock_index.mock_calls)
self.assertIn(
self._vertical_experiment_group_result(self.condition_0_vertical, [unicode(2)]),
mock_index.mock_calls
)
self.assertNotIn(
self._vertical_experiment_group_result(self.condition_1_vertical, [unicode(2)]),
mock_index.mock_calls
)
self.assertNotIn(
self._vertical_experiment_group_result(self.condition_2_vertical, [unicode(2)]),
mock_index.mock_calls
)
self.assertNotIn(
self._vertical_experiment_group_result(self.condition_0_vertical, [unicode(3)]),
mock_index.mock_calls
)
self.assertIn(
self._vertical_experiment_group_result(self.condition_1_vertical, [unicode(3)]),
mock_index.mock_calls
)
self.assertNotIn(
self._vertical_experiment_group_result(self.condition_2_vertical, [unicode(3)]),
mock_index.mock_calls
)
self.assertNotIn(
self._vertical_experiment_group_result(self.condition_0_vertical, [unicode(4)]),
mock_index.mock_calls
)
self.assertNotIn(
self._vertical_experiment_group_result(self.condition_1_vertical, [unicode(4)]),
mock_index.mock_calls
)
self.assertIn(
self._vertical_experiment_group_result(self.condition_2_vertical, [unicode(4)]),
mock_index.mock_calls
)
mock_index.reset_mock() mock_index.reset_mock()
def test_content_group_not_assigned(self): def test_content_group_not_assigned(self):
......
"""
Test courseware search
"""
import os
import json
from ...pages.common.logout import LogoutPage
from ...pages.studio.overview import CourseOutlinePage
from ...pages.lms.courseware_search import CoursewareSearchPage
from ...pages.lms.course_nav import CourseNavPage
from ...fixtures.course import XBlockFixtureDesc
from ..helpers import create_user_partition_json
from xmodule.partitions.partitions import Group
from nose.plugins.attrib import attr
from ..studio.base_studio_test import ContainerBase
from ...pages.studio.auto_auth import AutoAuthPage as StudioAutoAuthPage
@attr('shard_1')
class SplitTestCoursewareSearchTest(ContainerBase):
"""
Test courseware search on Split Test Module.
"""
USERNAME = 'STUDENT_TESTER'
EMAIL = 'student101@example.com'
TEST_INDEX_FILENAME = "test_root/index_file.dat"
def setUp(self, is_staff=True):
"""
Create search page and course content to search
"""
# create test file in which index for this test will live
with open(self.TEST_INDEX_FILENAME, "w+") as index_file:
json.dump({}, index_file)
super(SplitTestCoursewareSearchTest, self).setUp(is_staff=is_staff)
self.staff_user = self.user
self.courseware_search_page = CoursewareSearchPage(self.browser, self.course_id)
self.course_navigation_page = CourseNavPage(self.browser)
self.course_outline = CourseOutlinePage(
self.browser,
self.course_info['org'],
self.course_info['number'],
self.course_info['run']
)
self._add_and_configure_split_test()
self._studio_reindex()
def tearDown(self):
super(SplitTestCoursewareSearchTest, self).tearDown()
os.remove(self.TEST_INDEX_FILENAME)
def _auto_auth(self, username, email, staff):
"""
Logout and login with given credentials.
"""
LogoutPage(self.browser).visit()
StudioAutoAuthPage(self.browser, username=username, email=email,
course_id=self.course_id, staff=staff).visit()
def _studio_reindex(self):
"""
Reindex course content on studio course page
"""
self._auto_auth(self.staff_user["username"], self.staff_user["email"], True)
self.course_outline.visit()
self.course_outline.start_reindex()
self.course_outline.wait_for_ajax()
def _add_and_configure_split_test(self):
"""
Add a split test and a configuration to a test course fixture
"""
# Create a new group configurations
# pylint: disable=W0212
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
create_user_partition_json(
0,
"Name",
"Description.",
[Group("0", "Group A"), Group("1", "Group B")]
),
create_user_partition_json(
456,
"Name 2",
"Description 2.",
[Group("2", "Group C"), Group("3", "Group D")]
),
],
},
})
# Add a split test module to the 'Test Unit' vertical in the course tree
split_test_1 = XBlockFixtureDesc('split_test', 'Test Content Experiment 1', metadata={'user_partition_id': 0})
split_test_1_parent_vertical = self.course_fixture.get_nested_xblocks(category="vertical")[1]
self.course_fixture.create_xblock(split_test_1_parent_vertical.locator, split_test_1)
# Add a split test module to the 'Test 2 Unit' vertical in the course tree
split_test_2 = XBlockFixtureDesc('split_test', 'Test Content Experiment 2', metadata={'user_partition_id': 456})
split_test_2_parent_vertical = self.course_fixture.get_nested_xblocks(category="vertical")[2]
self.course_fixture.create_xblock(split_test_2_parent_vertical.locator, split_test_2)
def populate_course_fixture(self, course_fixture):
"""
Populate the children of the test course fixture.
"""
course_fixture.add_advanced_settings({
u"advanced_modules": {"value": ["split_test"]},
})
course_fixture.add_children(
XBlockFixtureDesc('chapter', 'Content Section').add_children(
XBlockFixtureDesc('sequential', 'Content Subsection').add_children(
XBlockFixtureDesc('vertical', 'Content Unit').add_children(
XBlockFixtureDesc('html', 'VISIBLETOALLCONTENT', data='<html>VISIBLETOALLCONTENT</html>')
)
)
),
XBlockFixtureDesc('chapter', 'Test Section').add_children(
XBlockFixtureDesc('sequential', 'Test Subsection').add_children(
XBlockFixtureDesc('vertical', 'Test Unit')
)
),
XBlockFixtureDesc('chapter', 'X Section').add_children(
XBlockFixtureDesc('sequential', 'X Subsection').add_children(
XBlockFixtureDesc('vertical', 'X Unit')
)
),
)
self.test_1_breadcrumb = "Test Section \xe2\x96\xb8 Test Subsection \xe2\x96\xb8 Test Unit".decode("utf-8")
self.test_2_breadcrumb = "X Section \xe2\x96\xb8 X Subsection \xe2\x96\xb8 X Unit".decode("utf-8")
def test_page_existence(self):
"""
Make sure that the page is accessible.
"""
self._auto_auth(self.USERNAME, self.EMAIL, False)
self.courseware_search_page.visit()
def test_search_for_experiment_content_user_not_assigned(self):
"""
Test user can't search for experiment content if not assigned to a group.
"""
self._auto_auth(self.USERNAME, self.EMAIL, False)
self.courseware_search_page.visit()
self.courseware_search_page.search_for_term("Group")
assert "Sorry, no results were found." in self.courseware_search_page.search_results.html[0]
def test_search_for_experiment_content_user_assigned_to_one_group(self):
"""
Test user can search for experiment content restricted to his group
when assigned to just one experiment group
"""
self._auto_auth(self.USERNAME, self.EMAIL, False)
self.courseware_search_page.visit()
self.course_navigation_page.go_to_section("Test Section", "Test Subsection")
self.courseware_search_page.search_for_term("Group")
assert "1 result" in self.courseware_search_page.search_results.html[0]
assert self.test_1_breadcrumb in self.courseware_search_page.search_results.html[0]
assert self.test_2_breadcrumb not in self.courseware_search_page.search_results.html[0]
...@@ -8,20 +8,64 @@ from student.models import CourseEnrollment ...@@ -8,20 +8,64 @@ from student.models import CourseEnrollment
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
from xmodule.modulestore.django import modulestore
from search.filter_generator import SearchFilterGenerator 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 from courseware.access import get_user_role
INCLUDE_SCHEMES = [CohortPartitionScheme, RandomUserPartitionScheme, ]
SCHEME_SUPPORTS_ASSIGNMENT = [RandomUserPartitionScheme, ]
class LmsSearchFilterGenerator(SearchFilterGenerator): class LmsSearchFilterGenerator(SearchFilterGenerator):
""" SearchFilterGenerator for LMS Search """ """ 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): def filter_dictionary(self, **kwargs):
""" base implementation which filters via start_date """ """ LMS implementation, adds filtering by user partition, course id and 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,
user_partition,
assign=False,
)
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) filter_dictionary = super(LmsSearchFilterGenerator, self).filter_dictionary(**kwargs)
if 'user' in kwargs and 'course_id' in kwargs and kwargs['course_id']: if 'user' in kwargs:
user = kwargs['user'] user = kwargs['user']
if 'course_id' in kwargs and kwargs['course_id']:
try: try:
course_key = CourseKey.from_string(kwargs['course_id']) course_key = CourseKey.from_string(kwargs['course_id'])
except InvalidKeyError: except InvalidKeyError:
...@@ -30,15 +74,22 @@ class LmsSearchFilterGenerator(SearchFilterGenerator): ...@@ -30,15 +74,22 @@ class LmsSearchFilterGenerator(SearchFilterGenerator):
# Staff user looking at course as staff user # Staff user looking at course as staff user
if get_user_role(user, course_key) == 'staff': if get_user_role(user, course_key) == 'staff':
return filter_dictionary 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
cohorted_user_partition = get_cohorted_user_partition(course_key)
if cohorted_user_partition:
partition_group = cohorted_user_partition.scheme.get_group_for_user(
course_key,
user,
cohorted_user_partition,
)
filter_dictionary['content_groups'] = unicode(partition_group.id) if partition_group else None
return filter_dictionary return filter_dictionary
def field_dictionary(self, **kwargs): def field_dictionary(self, **kwargs):
...@@ -47,7 +98,7 @@ class LmsSearchFilterGenerator(SearchFilterGenerator): ...@@ -47,7 +98,7 @@ class LmsSearchFilterGenerator(SearchFilterGenerator):
if not kwargs.get('user'): if not kwargs.get('user'):
field_dictionary['course'] = [] field_dictionary['course'] = []
elif not kwargs.get('course_id'): 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] 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 # if we have an org filter, only include results for this org filter
...@@ -62,8 +113,9 @@ class LmsSearchFilterGenerator(SearchFilterGenerator): ...@@ -62,8 +113,9 @@ class LmsSearchFilterGenerator(SearchFilterGenerator):
exclude_dictionary = super(LmsSearchFilterGenerator, self).exclude_dictionary(**kwargs) exclude_dictionary = super(LmsSearchFilterGenerator, self).exclude_dictionary(**kwargs)
course_org_filter = microsite.get_value('course_org_filter') 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 # If we have a course filter we are ensuring that we only get those courses above
if not course_org_filter:
org_filter_out_set = microsite.get_all_orgs() org_filter_out_set = microsite.get_all_orgs()
if not course_org_filter and org_filter_out_set: if org_filter_out_set:
exclude_dictionary['org'] = list(org_filter_out_set) exclude_dictionary['org'] = list(org_filter_out_set)
return exclude_dictionary return exclude_dictionary
...@@ -62,9 +62,10 @@ class LmsSearchResultProcessor(SearchResultProcessor): ...@@ -62,9 +62,10 @@ class LmsSearchResultProcessor(SearchResultProcessor):
def should_remove(self, user): def should_remove(self, user):
""" Test to see if this result should be removed due to access restriction """ """ Test to see if this result should be removed due to access restriction """
return not has_access( user_has_access = has_access(
user, user,
"load", "load",
self.get_item(self.get_usage_key()), self.get_item(self.get_usage_key()),
self.get_course_key() self.get_course_key()
) )
return not user_has_access
...@@ -11,10 +11,12 @@ from student.models import CourseEnrollment ...@@ -11,10 +11,12 @@ from student.models import CourseEnrollment
from xmodule.partitions.partitions import Group, UserPartition from xmodule.partitions.partitions import Group, UserPartition
from openedx.core.djangoapps.course_groups.partition_scheme import CohortPartitionScheme 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.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.cohorts import add_user_to_cohort
from openedx.core.djangoapps.course_groups.views import link_cohort_to_partition_group from openedx.core.djangoapps.course_groups.views import link_cohort_to_partition_group
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from lms.lib.courseware_search.lms_filter_generator import LmsSearchFilterGenerator from lms.lib.courseware_search.lms_filter_generator import LmsSearchFilterGenerator
...@@ -49,6 +51,13 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase): ...@@ -49,6 +51,13 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase):
publish_item=True, 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.groups = [Group(1, 'Group 1'), Group(2, 'Group 2')]
self.content_groups = [1, 2] self.content_groups = [1, 2]
...@@ -56,64 +65,11 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase): ...@@ -56,64 +65,11 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase):
def setUp(self): def setUp(self):
super(LmsSearchFilterGeneratorTestCase, self).setUp() super(LmsSearchFilterGeneratorTestCase, self).setUp()
self.build_courses() 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') self.user = UserFactory.create(username="jack", email="jack@fake.edx.org", password='test')
for course in self.courses: for course in self.courses:
CourseEnrollment.enroll(self.user, course.location.course_key) 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): 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 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): ...@@ -191,6 +147,152 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase):
self.assertIn('org', field_dictionary) self.assertIn('org', field_dictionary)
self.assertEqual('TestMicrosite3', field_dictionary['org']) 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): def test_content_group_id_provided(self):
""" """
Tests that we get the content group ID when course is assigned to cohort Tests that we get the content group ID when course is assigned to cohort
...@@ -205,7 +307,7 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase): ...@@ -205,7 +307,7 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase):
self.assertTrue('start_date' in filter_dictionary) self.assertTrue('start_date' in filter_dictionary)
self.assertEqual(unicode(self.courses[0].id), field_dictionary['course']) 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): def test_content_multiple_groups_id_provided(self):
""" """
...@@ -233,7 +335,7 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase): ...@@ -233,7 +335,7 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase):
self.assertTrue('start_date' in filter_dictionary) self.assertTrue('start_date' in filter_dictionary)
self.assertEqual(unicode(self.courses[0].id), field_dictionary['course']) self.assertEqual(unicode(self.courses[0].id), field_dictionary['course'])
# returns only first group, relevant to current user # 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): def test_content_group_id_not_provided(self):
""" """
...@@ -266,6 +368,44 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase): ...@@ -266,6 +368,44 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase):
self.assertEqual(unicode(self.courses[0].id), field_dictionary['course']) self.assertEqual(unicode(self.courses[0].id), field_dictionary['course'])
self.assertEqual(None, filter_dictionary['content_groups']) 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): def test_invalid_course_key(self):
""" """
Test system raises an error if no course found. Test system raises an error if no course found.
......
...@@ -97,7 +97,7 @@ class LmsSearchInitializerTestCase(StaffMasqueradeTestCase): ...@@ -97,7 +97,7 @@ class LmsSearchInitializerTestCase(StaffMasqueradeTestCase):
user=self.global_staff, user=self.global_staff,
course_id=unicode(self.course.id) 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): def test_staff_masquerading_as_a_staff_user(self):
""" """
......
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