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
c776272a
Commit
c776272a
authored
Apr 30, 2015
by
Martyn James
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #7654 from edx/mjames/SOL-280
Index course information alongside courseware
parents
c006befd
b9f1c5db
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
331 additions
and
7 deletions
+331
-7
cms/djangoapps/contentstore/courseware_index.py
+227
-1
cms/djangoapps/contentstore/tests/test_courseware_index.py
+101
-5
cms/djangoapps/contentstore/tests/test_libraries.py
+2
-0
requirements/edx/github.txt
+1
-1
No files found.
cms/djangoapps/contentstore/courseware_index.py
View file @
c776272a
...
@@ -3,14 +3,19 @@ from __future__ import absolute_import
...
@@ -3,14 +3,19 @@ from __future__ import absolute_import
from
abc
import
ABCMeta
,
abstractmethod
from
abc
import
ABCMeta
,
abstractmethod
from
datetime
import
timedelta
from
datetime
import
timedelta
import
logging
import
logging
import
re
from
six
import
add_metaclass
from
six
import
add_metaclass
from
django.conf
import
settings
from
django.conf
import
settings
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
contentstore.utils
import
course_image_url
from
course_modes.models
import
CourseMode
from
eventtracking
import
tracker
from
eventtracking
import
tracker
from
search.search_engine_base
import
SearchEngine
from
xmodule.annotator_mixin
import
html_to_text
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.library_tools
import
normalize_key_for_search
from
xmodule.library_tools
import
normalize_key_for_search
from
search.search_engine_base
import
SearchEngine
# REINDEX_AGE is the default amount of time that we look back for changes
# REINDEX_AGE is the default amount of time that we look back for changes
# that might have happened. If we are provided with a time at which the
# that might have happened. If we are provided with a time at which the
...
@@ -22,6 +27,25 @@ REINDEX_AGE = timedelta(0, 60) # 60 seconds
...
@@ -22,6 +27,25 @@ REINDEX_AGE = timedelta(0, 60) # 60 seconds
log
=
logging
.
getLogger
(
'edx.modulestore'
)
log
=
logging
.
getLogger
(
'edx.modulestore'
)
def
strip_html_content_to_text
(
html_content
):
""" Gets only the textual part for html content - useful for building text to be searched """
# Removing HTML-encoded non-breaking space characters
text_content
=
re
.
sub
(
r"(\s| |//)+"
,
" "
,
html_to_text
(
html_content
))
# Removing HTML CDATA
text_content
=
re
.
sub
(
r"<!\[CDATA\[.*\]\]>"
,
""
,
text_content
)
# Removing HTML comments
text_content
=
re
.
sub
(
r"<!--.*-->"
,
""
,
text_content
)
return
text_content
def
indexing_is_enabled
():
"""
Checks to see if the indexing feature is enabled
"""
return
settings
.
FEATURES
.
get
(
'ENABLE_COURSEWARE_INDEX'
,
False
)
class
SearchIndexingError
(
Exception
):
class
SearchIndexingError
(
Exception
):
""" Indicates some error(s) occured during indexing """
""" Indicates some error(s) occured during indexing """
...
@@ -94,6 +118,8 @@ class SearchIndexerBase(object):
...
@@ -94,6 +118,8 @@ class SearchIndexerBase(object):
Process course for indexing
Process course for indexing
Arguments:
Arguments:
modulestore - modulestore object to use for operations
structure_key (CourseKey|LibraryKey) - course or library identifier
structure_key (CourseKey|LibraryKey) - course or library identifier
triggered_at (datetime) - provides time at which indexing was triggered;
triggered_at (datetime) - provides time at which indexing was triggered;
...
@@ -174,6 +200,11 @@ class SearchIndexerBase(object):
...
@@ -174,6 +200,11 @@ class SearchIndexerBase(object):
try
:
try
:
with
modulestore
.
branch_setting
(
ModuleStoreEnum
.
RevisionOption
.
published_only
):
with
modulestore
.
branch_setting
(
ModuleStoreEnum
.
RevisionOption
.
published_only
):
structure
=
cls
.
_fetch_top_level
(
modulestore
,
structure_key
)
structure
=
cls
.
_fetch_top_level
(
modulestore
,
structure_key
)
# First perform any additional indexing from the structure object
cls
.
supplemental_index_information
(
modulestore
,
structure
)
# Now index the content
for
item
in
structure
.
get_children
():
for
item
in
structure
.
get_children
():
index_item
(
item
)
index_item
(
item
)
cls
.
remove_deleted_items
(
searcher
,
structure_key
,
indexed_items
)
cls
.
remove_deleted_items
(
searcher
,
structure_key
,
indexed_items
)
...
@@ -224,6 +255,21 @@ class SearchIndexerBase(object):
...
@@ -224,6 +255,21 @@ class SearchIndexerBase(object):
data
data
)
)
@classmethod
def
supplemental_index_information
(
cls
,
modulestore
,
structure
):
"""
Perform any supplemental indexing given that the structure object has
already been loaded. Base implementation performs no operation.
Arguments:
modulestore - modulestore object used during the indexing operation
structure - structure object loaded during the indexing job
Returns:
None
"""
pass
class
CoursewareSearchIndexer
(
SearchIndexerBase
):
class
CoursewareSearchIndexer
(
SearchIndexerBase
):
"""
"""
...
@@ -260,6 +306,13 @@ class CoursewareSearchIndexer(SearchIndexerBase):
...
@@ -260,6 +306,13 @@ class CoursewareSearchIndexer(SearchIndexerBase):
"""
"""
return
cls
.
_do_reindex
(
modulestore
,
course_key
)
return
cls
.
_do_reindex
(
modulestore
,
course_key
)
@classmethod
def
supplemental_index_information
(
cls
,
modulestore
,
structure
):
"""
Perform additional indexing from loaded structure object
"""
CourseAboutSearchIndexer
.
index_about_information
(
modulestore
,
structure
)
class
LibrarySearchIndexer
(
SearchIndexerBase
):
class
LibrarySearchIndexer
(
SearchIndexerBase
):
"""
"""
...
@@ -300,3 +353,176 @@ class LibrarySearchIndexer(SearchIndexerBase):
...
@@ -300,3 +353,176 @@ class LibrarySearchIndexer(SearchIndexerBase):
(Re)index all content within the given library, tracking the fact that a full reindex has taken place
(Re)index all content within the given library, tracking the fact that a full reindex has taken place
"""
"""
return
cls
.
_do_reindex
(
modulestore
,
library_key
)
return
cls
.
_do_reindex
(
modulestore
,
library_key
)
class
AboutInfo
(
object
):
""" About info structure to contain
1) Property name to use
2) Where to add in the index (using flags above)
3) Where to source the properties value
"""
# Bitwise Flags for where to index the information
#
# ANALYSE - states that the property text contains content that we wish to be able to find matched within
# e.g. "joe" should yield a result for "I'd like to drink a cup of joe"
#
# PROPERTY - states that the property text should be a property of the indexed document, to be returned with the
# results: search matches will only be made on exact string matches
# e.g. "joe" will only match on "joe"
#
# We are using bitwise flags because one may want to add the property to EITHER or BOTH parts of the index
# e.g. university name is desired to be analysed, so that a search on "Oxford" will match
# property values "University of Oxford" and "Oxford Brookes University",
# but it is also a useful property, because within a (future) filtered search a user
# may have chosen to filter courses from "University of Oxford"
#
# see https://wiki.python.org/moin/BitwiseOperators for information about bitwise shift operator used below
#
ANALYSE
=
1
<<
0
# Add the information to the analysed content of the index
PROPERTY
=
1
<<
1
# Add the information as a property of the object being indexed (not analysed)
def
__init__
(
self
,
property_name
,
index_flags
,
source_from
):
self
.
property_name
=
property_name
self
.
index_flags
=
index_flags
self
.
source_from
=
source_from
def
get_value
(
self
,
**
kwargs
):
""" get the value for this piece of information, using the correct source """
return
self
.
source_from
(
self
,
**
kwargs
)
def
from_about_dictionary
(
self
,
**
kwargs
):
""" gets the value from the kwargs provided 'about_dictionary' """
about_dictionary
=
kwargs
.
get
(
'about_dictionary'
,
None
)
if
not
about_dictionary
:
raise
ValueError
(
"Context dictionary does not contain expected argument 'about_dictionary'"
)
return
about_dictionary
.
get
(
self
.
property_name
,
None
)
def
from_course_property
(
self
,
**
kwargs
):
""" gets the value from the kwargs provided 'course' """
course
=
kwargs
.
get
(
'course'
,
None
)
if
not
course
:
raise
ValueError
(
"Context dictionary does not contain expected argument 'course'"
)
return
getattr
(
course
,
self
.
property_name
,
None
)
def
from_course_mode
(
self
,
**
kwargs
):
""" fetches the available course modes from the CourseMode model """
course
=
kwargs
.
get
(
'course'
,
None
)
if
not
course
:
raise
ValueError
(
"Context dictionary does not contain expected argument 'course'"
)
return
[
mode
.
slug
for
mode
in
CourseMode
.
modes_for_course
(
course
.
id
)]
# Source location options - either from the course or the about info
FROM_ABOUT_INFO
=
from_about_dictionary
FROM_COURSE_PROPERTY
=
from_course_property
FROM_COURSE_MODE
=
from_course_mode
class
CourseAboutSearchIndexer
(
object
):
"""
Class to perform indexing of about information from course object
"""
DISCOVERY_DOCUMENT_TYPE
=
"course_info"
INDEX_NAME
=
CoursewareSearchIndexer
.
INDEX_NAME
# List of properties to add to the index - each item in the list is an instance of AboutInfo object
ABOUT_INFORMATION_TO_INCLUDE
=
[
AboutInfo
(
"advertised_start"
,
AboutInfo
.
PROPERTY
,
AboutInfo
.
FROM_COURSE_PROPERTY
),
AboutInfo
(
"announcement"
,
AboutInfo
.
PROPERTY
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"start"
,
AboutInfo
.
PROPERTY
,
AboutInfo
.
FROM_COURSE_PROPERTY
),
AboutInfo
(
"end"
,
AboutInfo
.
PROPERTY
,
AboutInfo
.
FROM_COURSE_PROPERTY
),
AboutInfo
(
"effort"
,
AboutInfo
.
PROPERTY
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"display_name"
,
AboutInfo
.
ANALYSE
,
AboutInfo
.
FROM_COURSE_PROPERTY
),
AboutInfo
(
"overview"
,
AboutInfo
.
ANALYSE
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"title"
,
AboutInfo
.
ANALYSE
|
AboutInfo
.
PROPERTY
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"university"
,
AboutInfo
.
ANALYSE
|
AboutInfo
.
PROPERTY
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"number"
,
AboutInfo
.
ANALYSE
|
AboutInfo
.
PROPERTY
,
AboutInfo
.
FROM_COURSE_PROPERTY
),
AboutInfo
(
"short_description"
,
AboutInfo
.
ANALYSE
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"description"
,
AboutInfo
.
ANALYSE
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"key_dates"
,
AboutInfo
.
ANALYSE
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"video"
,
AboutInfo
.
ANALYSE
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"course_staff_short"
,
AboutInfo
.
ANALYSE
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"course_staff_extended"
,
AboutInfo
.
ANALYSE
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"requirements"
,
AboutInfo
.
ANALYSE
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"syllabus"
,
AboutInfo
.
ANALYSE
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"textbook"
,
AboutInfo
.
ANALYSE
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"faq"
,
AboutInfo
.
ANALYSE
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"more_info"
,
AboutInfo
.
ANALYSE
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"ocw_links"
,
AboutInfo
.
ANALYSE
,
AboutInfo
.
FROM_ABOUT_INFO
),
AboutInfo
(
"enrollment_start"
,
AboutInfo
.
PROPERTY
,
AboutInfo
.
FROM_COURSE_PROPERTY
),
AboutInfo
(
"enrollment_end"
,
AboutInfo
.
PROPERTY
,
AboutInfo
.
FROM_COURSE_PROPERTY
),
AboutInfo
(
"org"
,
AboutInfo
.
PROPERTY
,
AboutInfo
.
FROM_COURSE_PROPERTY
),
AboutInfo
(
"modes"
,
AboutInfo
.
PROPERTY
,
AboutInfo
.
FROM_COURSE_MODE
),
]
@classmethod
def
index_about_information
(
cls
,
modulestore
,
course
):
"""
Add the given course to the course discovery index
Arguments:
modulestore - modulestore object to use for operations
course - course object from which to take properties, locate about information
"""
searcher
=
SearchEngine
.
get_search_engine
(
cls
.
INDEX_NAME
)
if
not
searcher
:
return
course_id
=
unicode
(
course
.
id
)
course_info
=
{
'id'
:
course_id
,
'course'
:
course_id
,
'content'
:
{},
'image_url'
:
course_image_url
(
course
),
}
# load data for all of the 'about' modules for this course into a dictionary
about_dictionary
=
{
item
.
location
.
name
:
item
.
data
for
item
in
modulestore
.
get_items
(
course
.
id
,
qualifiers
=
{
"category"
:
"about"
})
}
about_context
=
{
"course"
:
course
,
"about_dictionary"
:
about_dictionary
,
}
for
about_information
in
cls
.
ABOUT_INFORMATION_TO_INCLUDE
:
# Broad exception handler so that a single bad property does not scupper the collection of others
try
:
section_content
=
about_information
.
get_value
(
**
about_context
)
except
:
# pylint: disable=bare-except
section_content
=
None
log
.
warning
(
"Course discovery could not collect property
%
s for course
%
s"
,
about_information
.
property_name
,
course_id
,
exc_info
=
True
,
)
if
section_content
:
if
about_information
.
index_flags
&
AboutInfo
.
ANALYSE
:
analyse_content
=
section_content
if
isinstance
(
section_content
,
basestring
):
analyse_content
=
strip_html_content_to_text
(
section_content
)
course_info
[
'content'
][
about_information
.
property_name
]
=
analyse_content
if
about_information
.
index_flags
&
AboutInfo
.
PROPERTY
:
course_info
[
about_information
.
property_name
]
=
section_content
# Broad exception handler to protect around and report problems with indexing
try
:
searcher
.
index
(
cls
.
DISCOVERY_DOCUMENT_TYPE
,
course_info
)
except
:
# pylint: disable=bare-except
log
.
exception
(
"Course discovery indexing error encountered, course discovery index may be out of date
%
s"
,
course_id
,
)
raise
log
.
debug
(
"Successfully added
%
s course to the course discovery index"
,
course_id
)
cms/djangoapps/contentstore/tests/test_courseware_index.py
View file @
c776272a
...
@@ -10,10 +10,12 @@ from pytz import UTC
...
@@ -10,10 +10,12 @@ from pytz import UTC
from
uuid
import
uuid4
from
uuid
import
uuid4
from
unittest
import
skip
from
unittest
import
skip
from
course_modes.models
import
CourseMode
from
xmodule.library_tools
import
normalize_key_for_search
from
xmodule.library_tools
import
normalize_key_for_search
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
SignalHandler
from
xmodule.modulestore.django
import
SignalHandler
from
xmodule.modulestore.edit_info
import
EditInfoMixin
from
xmodule.modulestore.edit_info
import
EditInfoMixin
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.modulestore.inheritance
import
InheritanceMixin
from
xmodule.modulestore.inheritance
import
InheritanceMixin
from
xmodule.modulestore.mixed
import
MixedModuleStore
from
xmodule.modulestore.mixed
import
MixedModuleStore
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
...
@@ -26,7 +28,12 @@ from xmodule.x_module import XModuleMixin
...
@@ -26,7 +28,12 @@ from xmodule.x_module import XModuleMixin
from
search.search_engine_base
import
SearchEngine
from
search.search_engine_base
import
SearchEngine
from
contentstore.courseware_index
import
CoursewareSearchIndexer
,
LibrarySearchIndexer
,
SearchIndexingError
from
contentstore.courseware_index
import
(
CoursewareSearchIndexer
,
LibrarySearchIndexer
,
SearchIndexingError
,
CourseAboutSearchIndexer
,
)
from
contentstore.signals
import
listen_for_course_publish
,
listen_for_library_update
from
contentstore.signals
import
listen_for_course_publish
,
listen_for_library_update
...
@@ -120,6 +127,7 @@ class MixedWithOptionsTestCase(MixedSplitTestCase):
...
@@ -120,6 +127,7 @@ class MixedWithOptionsTestCase(MixedSplitTestCase):
}
}
INDEX_NAME
=
None
INDEX_NAME
=
None
DOCUMENT_TYPE
=
None
def
setUp
(
self
):
def
setUp
(
self
):
super
(
MixedWithOptionsTestCase
,
self
)
.
setUp
()
super
(
MixedWithOptionsTestCase
,
self
)
.
setUp
()
...
@@ -140,7 +148,7 @@ class MixedWithOptionsTestCase(MixedSplitTestCase):
...
@@ -140,7 +148,7 @@ class MixedWithOptionsTestCase(MixedSplitTestCase):
def
search
(
self
,
field_dictionary
=
None
):
def
search
(
self
,
field_dictionary
=
None
):
""" Performs index search according to passed parameters """
""" Performs index search according to passed parameters """
fields
=
field_dictionary
if
field_dictionary
else
self
.
_get_default_search
()
fields
=
field_dictionary
if
field_dictionary
else
self
.
_get_default_search
()
return
self
.
searcher
.
search
(
field_dictionary
=
fields
)
return
self
.
searcher
.
search
(
field_dictionary
=
fields
,
doc_type
=
self
.
DOCUMENT_TYPE
)
def
_perform_test_using_store
(
self
,
store_type
,
test_to_perform
):
def
_perform_test_using_store
(
self
,
store_type
,
test_to_perform
):
""" Helper method to run a test function that uses a specific store """
""" Helper method to run a test function that uses a specific store """
...
@@ -172,6 +180,26 @@ class MixedWithOptionsTestCase(MixedSplitTestCase):
...
@@ -172,6 +180,26 @@ class MixedWithOptionsTestCase(MixedSplitTestCase):
with
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
draft_preferred
):
with
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
draft_preferred
):
store
.
update_item
(
item
,
ModuleStoreEnum
.
UserID
.
test
)
store
.
update_item
(
item
,
ModuleStoreEnum
.
UserID
.
test
)
def
update_about_item
(
self
,
store
,
about_key
,
data
):
"""
Update the about item with the new data blob. If data is None, then
delete the about item.
"""
temploc
=
self
.
course
.
id
.
make_usage_key
(
'about'
,
about_key
)
if
data
is
None
:
try
:
self
.
delete_item
(
store
,
temploc
)
# Ignore an attempt to delete an item that doesn't exist
except
ValueError
:
pass
else
:
try
:
about_item
=
store
.
get_item
(
temploc
)
except
ItemNotFoundError
:
about_item
=
store
.
create_xblock
(
self
.
course
.
runtime
,
self
.
course
.
id
,
'about'
,
about_key
)
about_item
.
data
=
data
store
.
update_item
(
about_item
,
ModuleStoreEnum
.
UserID
.
test
,
allow_not_found
=
True
)
@ddt.ddt
@ddt.ddt
class
TestCoursewareSearchIndexer
(
MixedWithOptionsTestCase
):
class
TestCoursewareSearchIndexer
(
MixedWithOptionsTestCase
):
...
@@ -228,6 +256,7 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
...
@@ -228,6 +256,7 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
)
)
INDEX_NAME
=
CoursewareSearchIndexer
.
INDEX_NAME
INDEX_NAME
=
CoursewareSearchIndexer
.
INDEX_NAME
DOCUMENT_TYPE
=
CoursewareSearchIndexer
.
DOCUMENT_TYPE
def
reindex_course
(
self
,
store
):
def
reindex_course
(
self
,
store
):
""" kick off complete reindex of the course """
""" kick off complete reindex of the course """
...
@@ -407,6 +436,55 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
...
@@ -407,6 +436,55 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
indexed_count
=
self
.
reindex_course
(
store
)
indexed_count
=
self
.
reindex_course
(
store
)
self
.
assertEqual
(
indexed_count
,
7
)
self
.
assertEqual
(
indexed_count
,
7
)
def
_test_course_about_property_index
(
self
,
store
):
""" Test that informational properties in the course object end up in the course_info index """
display_name
=
"Help, I need somebody!"
self
.
course
.
display_name
=
display_name
self
.
update_item
(
store
,
self
.
course
)
self
.
reindex_course
(
store
)
response
=
self
.
searcher
.
search
(
doc_type
=
CourseAboutSearchIndexer
.
DISCOVERY_DOCUMENT_TYPE
,
field_dictionary
=
{
"course"
:
unicode
(
self
.
course
.
id
)}
)
self
.
assertEqual
(
response
[
"total"
],
1
)
self
.
assertEqual
(
response
[
"results"
][
0
][
"data"
][
"content"
][
"display_name"
],
display_name
)
def
_test_course_about_store_index
(
self
,
store
):
""" Test that informational properties in the about store end up in the course_info index """
short_description
=
"Not just anybody"
self
.
update_about_item
(
store
,
"short_description"
,
short_description
)
self
.
reindex_course
(
store
)
response
=
self
.
searcher
.
search
(
doc_type
=
CourseAboutSearchIndexer
.
DISCOVERY_DOCUMENT_TYPE
,
field_dictionary
=
{
"course"
:
unicode
(
self
.
course
.
id
)}
)
self
.
assertEqual
(
response
[
"total"
],
1
)
self
.
assertEqual
(
response
[
"results"
][
0
][
"data"
][
"content"
][
"short_description"
],
short_description
)
def
_test_course_about_mode_index
(
self
,
store
):
""" Test that informational properties in the course modes store end up in the course_info index """
honour_mode
=
CourseMode
(
course_id
=
unicode
(
self
.
course
.
id
),
mode_slug
=
CourseMode
.
HONOR
,
mode_display_name
=
CourseMode
.
HONOR
)
honour_mode
.
save
()
verified_mode
=
CourseMode
(
course_id
=
unicode
(
self
.
course
.
id
),
mode_slug
=
CourseMode
.
VERIFIED
,
mode_display_name
=
CourseMode
.
VERIFIED
)
verified_mode
.
save
()
self
.
reindex_course
(
store
)
response
=
self
.
searcher
.
search
(
doc_type
=
CourseAboutSearchIndexer
.
DISCOVERY_DOCUMENT_TYPE
,
field_dictionary
=
{
"course"
:
unicode
(
self
.
course
.
id
)}
)
self
.
assertEqual
(
response
[
"total"
],
1
)
self
.
assertIn
(
CourseMode
.
HONOR
,
response
[
"results"
][
0
][
"data"
][
"modes"
])
self
.
assertIn
(
CourseMode
.
VERIFIED
,
response
[
"results"
][
0
][
"data"
][
"modes"
])
@patch
(
'django.conf.settings.SEARCH_ENGINE'
,
'search.tests.utils.ErroringIndexEngine'
)
@patch
(
'django.conf.settings.SEARCH_ENGINE'
,
'search.tests.utils.ErroringIndexEngine'
)
def
_test_exception
(
self
,
store
):
def
_test_exception
(
self
,
store
):
""" Test that exception within indexing yields a SearchIndexingError """
""" Test that exception within indexing yields a SearchIndexingError """
...
@@ -446,6 +524,18 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
...
@@ -446,6 +524,18 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
def
test_exception
(
self
,
store_type
):
def
test_exception
(
self
,
store_type
):
self
.
_perform_test_using_store
(
store_type
,
self
.
_test_exception
)
self
.
_perform_test_using_store
(
store_type
,
self
.
_test_exception
)
@ddt.data
(
*
WORKS_WITH_STORES
)
def
test_course_about_property_index
(
self
,
store_type
):
self
.
_perform_test_using_store
(
store_type
,
self
.
_test_course_about_property_index
)
@ddt.data
(
*
WORKS_WITH_STORES
)
def
test_course_about_store_index
(
self
,
store_type
):
self
.
_perform_test_using_store
(
store_type
,
self
.
_test_course_about_store_index
)
@ddt.data
(
*
WORKS_WITH_STORES
)
def
test_course_about_mode_index
(
self
,
store_type
):
self
.
_perform_test_using_store
(
store_type
,
self
.
_test_course_about_mode_index
)
@patch
(
'django.conf.settings.SEARCH_ENGINE'
,
'search.tests.utils.ForceRefreshElasticSearchEngine'
)
@patch
(
'django.conf.settings.SEARCH_ENGINE'
,
'search.tests.utils.ForceRefreshElasticSearchEngine'
)
@ddt.ddt
@ddt.ddt
...
@@ -462,7 +552,6 @@ class TestLargeCourseDeletions(MixedWithOptionsTestCase):
...
@@ -462,7 +552,6 @@ class TestLargeCourseDeletions(MixedWithOptionsTestCase):
while
response
[
"total"
]
>
0
:
while
response
[
"total"
]
>
0
:
for
item
in
response
[
"results"
]:
for
item
in
response
[
"results"
]:
self
.
searcher
.
remove
(
CoursewareSearchIndexer
.
DOCUMENT_TYPE
,
item
[
"data"
][
"id"
])
self
.
searcher
.
remove
(
CoursewareSearchIndexer
.
DOCUMENT_TYPE
,
item
[
"data"
][
"id"
])
self
.
searcher
.
remove
(
CoursewareSearchIndexer
.
DOCUMENT_TYPE
,
item
[
"data"
][
"id"
])
response
=
self
.
searcher
.
search
(
field_dictionary
=
{
"course"
:
self
.
course_id
})
response
=
self
.
searcher
.
search
(
field_dictionary
=
{
"course"
:
self
.
course_id
})
self
.
course_id
=
None
self
.
course_id
=
None
...
@@ -590,13 +679,19 @@ class TestTaskExecution(ModuleStoreTestCase):
...
@@ -590,13 +679,19 @@ class TestTaskExecution(ModuleStoreTestCase):
def
test_task_indexing_course
(
self
):
def
test_task_indexing_course
(
self
):
""" Making sure that the receiver correctly fires off the task when invoked by signal """
""" Making sure that the receiver correctly fires off the task when invoked by signal """
searcher
=
SearchEngine
.
get_search_engine
(
CoursewareSearchIndexer
.
INDEX_NAME
)
searcher
=
SearchEngine
.
get_search_engine
(
CoursewareSearchIndexer
.
INDEX_NAME
)
response
=
searcher
.
search
(
field_dictionary
=
{
"course"
:
unicode
(
self
.
course
.
id
)})
response
=
searcher
.
search
(
doc_type
=
CoursewareSearchIndexer
.
DOCUMENT_TYPE
,
field_dictionary
=
{
"course"
:
unicode
(
self
.
course
.
id
)}
)
self
.
assertEqual
(
response
[
"total"
],
0
)
self
.
assertEqual
(
response
[
"total"
],
0
)
listen_for_course_publish
(
self
,
self
.
course
.
id
)
listen_for_course_publish
(
self
,
self
.
course
.
id
)
# Note that this test will only succeed if celery is working in inline mode
# Note that this test will only succeed if celery is working in inline mode
response
=
searcher
.
search
(
field_dictionary
=
{
"course"
:
unicode
(
self
.
course
.
id
)})
response
=
searcher
.
search
(
doc_type
=
CoursewareSearchIndexer
.
DOCUMENT_TYPE
,
field_dictionary
=
{
"course"
:
unicode
(
self
.
course
.
id
)}
)
self
.
assertEqual
(
response
[
"total"
],
3
)
self
.
assertEqual
(
response
[
"total"
],
3
)
def
test_task_library_update
(
self
):
def
test_task_library_update
(
self
):
...
@@ -650,6 +745,7 @@ class TestLibrarySearchIndexer(MixedWithOptionsTestCase):
...
@@ -650,6 +745,7 @@ class TestLibrarySearchIndexer(MixedWithOptionsTestCase):
)
)
INDEX_NAME
=
LibrarySearchIndexer
.
INDEX_NAME
INDEX_NAME
=
LibrarySearchIndexer
.
INDEX_NAME
DOCUMENT_TYPE
=
LibrarySearchIndexer
.
DOCUMENT_TYPE
def
_get_default_search
(
self
):
def
_get_default_search
(
self
):
""" Returns field_dictionary for default search """
""" Returns field_dictionary for default search """
...
...
cms/djangoapps/contentstore/tests/test_libraries.py
View file @
c776272a
"""
"""
Content library unit tests that require the CMS runtime.
Content library unit tests that require the CMS runtime.
"""
"""
from
django.test.utils
import
override_settings
from
contentstore.tests.utils
import
AjaxEnabledTestClient
,
parse_json
from
contentstore.tests.utils
import
AjaxEnabledTestClient
,
parse_json
from
contentstore.utils
import
reverse_url
,
reverse_usage_url
,
reverse_library_url
from
contentstore.utils
import
reverse_url
,
reverse_usage_url
,
reverse_library_url
from
contentstore.views.item
import
_duplicate_item
from
contentstore.views.item
import
_duplicate_item
...
@@ -730,6 +731,7 @@ class TestLibraryAccess(SignalDisconnectTestMixin, LibraryTestCase):
...
@@ -730,6 +731,7 @@ class TestLibraryAccess(SignalDisconnectTestMixin, LibraryTestCase):
@ddt.ddt
@ddt.ddt
@override_settings
(
SEARCH_ENGINE
=
None
)
class
TestOverrides
(
LibraryTestCase
):
class
TestOverrides
(
LibraryTestCase
):
"""
"""
Test that overriding block Scope.settings fields from a library in a specific course works
Test that overriding block Scope.settings fields from a library in a specific course works
...
...
requirements/edx/github.txt
View file @
c776272a
...
@@ -43,7 +43,7 @@ git+https://github.com/pmitros/pyfs.git@96e1922348bfe6d99201b9512a9ed946c87b7e0b
...
@@ -43,7 +43,7 @@ git+https://github.com/pmitros/pyfs.git@96e1922348bfe6d99201b9512a9ed946c87b7e0b
-e git+https://github.com/edx/edx-val.git@d6087908aa3dd05ceaa7f56a21284f86c53cb3f0#egg=edx-val
-e git+https://github.com/edx/edx-val.git@d6087908aa3dd05ceaa7f56a21284f86c53cb3f0#egg=edx-val
-e git+https://github.com/pmitros/RecommenderXBlock.git@9b07e807c89ba5761827d0387177f71aa57ef056#egg=recommender-xblock
-e git+https://github.com/pmitros/RecommenderXBlock.git@9b07e807c89ba5761827d0387177f71aa57ef056#egg=recommender-xblock
-e git+https://github.com/edx/edx-milestones.git@547f2250ee49e73ce8d7ff4e78ecf1b049892510#egg=edx-milestones
-e git+https://github.com/edx/edx-milestones.git@547f2250ee49e73ce8d7ff4e78ecf1b049892510#egg=edx-milestones
-e git+https://github.com/edx/edx-search.git@
6ce8b25a2539370de32dc7cb643ae27c9b8f798d
#egg=edx-search
-e git+https://github.com/edx/edx-search.git@
9d566b88fd80cb0b60c052eee2bee30eb9f35b9c
#egg=edx-search
git+https://github.com/edx/edx-lint.git@8bf82a32ecb8598c415413df66f5232ab8d974e9#egg=edx_lint==0.2.1
git+https://github.com/edx/edx-lint.git@8bf82a32ecb8598c415413df66f5232ab8d974e9#egg=edx_lint==0.2.1
-e git+https://github.com/edx/xblock-utils.git@581ed636c862b286002bb9a3724cc883570eb54c#egg=xblock-utils
-e git+https://github.com/edx/xblock-utils.git@581ed636c862b286002bb9a3724cc883570eb54c#egg=xblock-utils
-e git+https://github.com/edx-solutions/xblock-google-drive.git@138e6fa0bf3a2013e904a085b9fed77dab7f3f21#egg=xblock-google-drive
-e git+https://github.com/edx-solutions/xblock-google-drive.git@138e6fa0bf3a2013e904a085b9fed77dab7f3f21#egg=xblock-google-drive
...
...
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