Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
099be9e7
Commit
099be9e7
authored
Jun 03, 2015
by
dino-cikatic
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #8229 from edx/dcikatic/SOL-536
Dcikatic/sol 536 [WIP]
parents
110a7b1a
d3aa9dfd
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
665 additions
and
79 deletions
+665
-79
cms/djangoapps/contentstore/courseware_index.py
+24
-1
cms/djangoapps/contentstore/tests/test_courseware_index.py
+208
-8
common/test/acceptance/tests/lms/test_lms_split_test_courseware_search.py
+170
-0
lms/lib/courseware_search/lms_filter_generator.py
+65
-13
lms/lib/courseware_search/lms_result_processor.py
+2
-1
lms/lib/courseware_search/test/test_lms_filter_generator.py
+195
-55
lms/lib/courseware_search/test/test_lms_search_initializer.py
+1
-1
No files found.
cms/djangoapps/contentstore/courseware_index.py
View file @
099be9e7
...
...
@@ -153,6 +153,12 @@ class SearchIndexerBase(object):
# list - those are ready to be destroyed
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
):
"""
Add this item to the search index and indexed_items list
...
...
@@ -175,8 +181,25 @@ class SearchIndexerBase(object):
return
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
:
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_id
=
unicode
(
cls
.
_id_modifier
(
item
.
scope_ids
.
usage_id
))
...
...
cms/djangoapps/contentstore/tests/test_courseware_index.py
View file @
099be9e7
...
...
@@ -952,10 +952,20 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
Tests indexing of content groups on course modules using mongo modulestore.
"""
MODULESTORE
=
TEST_DATA_MONGO_MODULESTORE
INDEX_NAME
=
CoursewareSearchIndexer
.
INDEX_NAME
def
setUp
(
self
):
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
(
parent_location
=
self
.
course
.
location
,
category
=
'chapter'
,
...
...
@@ -964,6 +974,7 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
publish_item
=
True
,
start
=
datetime
(
2015
,
3
,
1
,
tzinfo
=
UTC
),
)
self
.
sequential
=
ItemFactory
.
create
(
parent_location
=
self
.
chapter
.
location
,
category
=
'sequential'
,
...
...
@@ -972,6 +983,16 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
publish_item
=
True
,
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
(
parent_location
=
self
.
sequential
.
location
,
category
=
'vertical'
,
...
...
@@ -990,6 +1011,15 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
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
self
.
html_unit1
=
ItemFactory
.
create
(
parent_location
=
self
.
vertical
.
location
,
...
...
@@ -1018,7 +1048,75 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
)
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'name'
:
u'Test name'
,
u'scheme'
:
u'cohort'
,
...
...
@@ -1029,18 +1127,33 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
{
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
.
_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"
,
HTTP_ACCEPT
=
"application/json"
,
HTTP_X_REQUESTED_WITH
=
"XMLHttpRequest"
,
)
self
.
reload_course
()
INDEX_NAME
=
CoursewareSearchIndexer
.
INDEX_NAME
def
_group_conf_url
(
self
,
cid
=-
1
):
"""
...
...
@@ -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
):
"""
Return call object with arguments and content group set to empty array for html_unit.
...
...
@@ -1107,9 +1266,9 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
# Only published modules should be in the index
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
)})
self
.
assertEqual
(
response
[
"total"
],
8
)
self
.
assertEqual
(
response
[
"total"
],
23
)
group_access_content
=
{
'group_access'
:
{
666
:
[
1
]}}
...
...
@@ -1119,11 +1278,52 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
)
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
:
self
.
reindex_course
(
self
.
store
)
self
.
assertTrue
(
mock_index
.
called
)
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
()
def
test_content_group_not_assigned
(
self
):
...
...
common/test/acceptance/tests/lms/test_lms_split_test_courseware_search.py
0 → 100644
View file @
099be9e7
"""
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
]
lms/lib/courseware_search/lms_filter_generator.py
View file @
099be9e7
...
...
@@ -8,20 +8,64 @@ 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 """
""" 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
)
if
'user'
in
kwargs
and
'course_id'
in
kwargs
and
kwargs
[
'course_id'
]
:
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
:
...
...
@@ -30,15 +74,22 @@ class LmsSearchFilterGenerator(SearchFilterGenerator):
# 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
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
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
if
not
course_org_filter
:
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
)
return
exclude_dictionary
lms/lib/courseware_search/lms_result_processor.py
View file @
099be9e7
...
...
@@ -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
lms/lib/courseware_search/test/test_lms_filter_generator.py
View file @
099be9e7
...
...
@@ -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.
...
...
lms/lib/courseware_search/test/test_lms_search_initializer.py
View file @
099be9e7
...
...
@@ -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
):
"""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment