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
99e6d539
Commit
99e6d539
authored
Dec 22, 2016
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Preload definitions when collecting block structures
parent
82647ee8
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
288 additions
and
119 deletions
+288
-119
common/lib/xmodule/xmodule/modulestore/split_mongo/split.py
+10
-3
common/lib/xmodule/xmodule/modulestore/tests/test_mongo_call_count.py
+70
-38
common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore_bulk_operations.py
+15
-0
common/lib/xmodule/xmodule/modulestore/tests/utils.py
+13
-12
lms/djangoapps/course_api/blocks/tests/test_api.py
+49
-2
lms/djangoapps/courseware/tests/test_views.py
+42
-0
lms/djangoapps/courseware/views/index.py
+1
-1
lms/djangoapps/grades/tasks.py
+37
-35
lms/djangoapps/grades/tests/test_tasks.py
+29
-16
lms/djangoapps/grades/tests/test_transformer.py
+12
-5
openedx/core/djangoapps/bookmarks/tests/test_tasks.py
+8
-5
openedx/core/lib/block_structure/factory.py
+1
-1
openedx/core/lib/block_structure/tests/helpers.py
+1
-1
No files found.
common/lib/xmodule/xmodule/modulestore/split_mongo/split.py
View file @
99e6d539
...
...
@@ -432,9 +432,11 @@ class SplitBulkWriteMixin(BulkOperationsMixin):
if
len
(
ids
):
# Query the db for the definitions.
defs_from_db
=
self
.
db_connection
.
get_definitions
(
list
(
ids
),
course_key
)
defs_from_db
=
list
(
self
.
db_connection
.
get_definitions
(
list
(
ids
),
course_key
))
defs_dict
=
{
d
.
get
(
'_id'
):
d
for
d
in
defs_from_db
}
# Add the retrieved definitions to the cache.
bulk_write_record
.
definitions
.
update
({
d
.
get
(
'_id'
):
d
for
d
in
defs_from_db
})
bulk_write_record
.
definitions_in_db
.
update
(
defs_dict
.
iterkeys
())
bulk_write_record
.
definitions
.
update
(
defs_dict
)
definitions
.
extend
(
defs_from_db
)
return
definitions
...
...
@@ -772,11 +774,16 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
Load the definitions into each block if lazy is in kwargs and is False;
otherwise, do not load the definitions - they'll be loaded later when needed.
"""
lazy
=
kwargs
.
pop
(
'lazy'
,
True
)
should_cache_items
=
not
lazy
runtime
=
self
.
_get_cache
(
course_entry
.
structure
[
'_id'
])
if
runtime
is
None
:
lazy
=
kwargs
.
pop
(
'lazy'
,
True
)
runtime
=
self
.
create_runtime
(
course_entry
,
lazy
)
self
.
_add_cache
(
course_entry
.
structure
[
'_id'
],
runtime
)
should_cache_items
=
True
if
should_cache_items
:
self
.
cache_items
(
runtime
,
block_keys
,
course_entry
.
course_key
,
depth
,
lazy
)
return
[
runtime
.
load_item
(
block_key
,
course_entry
,
**
kwargs
)
for
block_key
in
block_keys
]
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_mongo_call_count.py
View file @
99e6d539
...
...
@@ -13,7 +13,8 @@ from xmodule.modulestore.xml_exporter import export_course_to_xml
from
xmodule.modulestore.tests.factories
import
check_mongo_calls
from
xmodule.modulestore.tests.utils
import
(
MixedModulestoreBuilder
,
VersioningModulestoreBuilder
,
MongoModulestoreBuilder
,
TEST_DATA_DIR
MongoModulestoreBuilder
,
TEST_DATA_DIR
,
MemoryCache
,
)
MIXED_OLD_MONGO_MODULESTORE_BUILDER
=
MixedModulestoreBuilder
([(
'draft'
,
MongoModulestoreBuilder
())])
...
...
@@ -87,6 +88,46 @@ class CountMongoCallsCourseTraversal(TestCase):
to the leaf nodes.
"""
def
_traverse_blocks_in_course
(
self
,
course
,
access_all_block_fields
):
"""
Traverses all the blocks in the given course.
If access_all_block_fields is True, also reads all the
xblock fields in each block in the course.
"""
all_blocks
=
[]
stack
=
[
course
]
while
stack
:
curr_block
=
stack
.
pop
()
all_blocks
.
append
(
curr_block
)
if
curr_block
.
has_children
:
for
block
in
reversed
(
curr_block
.
get_children
()):
stack
.
append
(
block
)
if
access_all_block_fields
:
# Read the fields on each block in order to ensure each block and its definition is loaded.
for
xblock
in
all_blocks
:
for
__
,
field
in
xblock
.
fields
.
iteritems
():
if
field
.
is_set_on
(
xblock
):
__
=
field
.
read_from
(
xblock
)
def
_import_course
(
self
,
content_store
,
modulestore
):
"""
Imports a course for testing.
Returns the course key.
"""
course_key
=
modulestore
.
make_course_key
(
'a'
,
'course'
,
'course'
)
import_course_from_xml
(
modulestore
,
'test_user'
,
TEST_DATA_DIR
,
source_dirs
=
[
'manual-testing-complete'
],
static_content_store
=
content_store
,
target_id
=
course_key
,
create_if_not_present
=
True
,
raise_on_failure
=
True
,
)
return
course_key
# Suppose you want to traverse a course - maybe accessing the fields of each XBlock in the course,
# maybe not. What parameters should one use for get_course() in order to minimize the number of
# mongo calls? The tests below both ensure that code changes don't increase the number of mongo calls
...
...
@@ -109,52 +150,43 @@ class CountMongoCallsCourseTraversal(TestCase):
# (if you'll eventually access all the fields and load all the definitions anyway).
(
MIXED_SPLIT_MODULESTORE_BUILDER
,
None
,
False
,
True
,
4
),
(
MIXED_SPLIT_MODULESTORE_BUILDER
,
None
,
True
,
True
,
38
),
(
MIXED_SPLIT_MODULESTORE_BUILDER
,
0
,
False
,
True
,
131
),
(
MIXED_SPLIT_MODULESTORE_BUILDER
,
0
,
False
,
True
,
38
),
(
MIXED_SPLIT_MODULESTORE_BUILDER
,
0
,
True
,
True
,
38
),
(
MIXED_SPLIT_MODULESTORE_BUILDER
,
None
,
False
,
False
,
4
),
(
MIXED_SPLIT_MODULESTORE_BUILDER
,
None
,
True
,
False
,
4
),
# TODO: The call count below seems like a bug - should be 4?
# Seems to be related to using self.lazy in CachingDescriptorSystem.get_module_data().
(
MIXED_SPLIT_MODULESTORE_BUILDER
,
0
,
False
,
False
,
131
),
(
MIXED_SPLIT_MODULESTORE_BUILDER
,
0
,
False
,
False
,
4
),
(
MIXED_SPLIT_MODULESTORE_BUILDER
,
0
,
True
,
False
,
4
)
)
@ddt.unpack
def
test_number_mongo_calls
(
self
,
store
,
depth
,
lazy
,
access_all_block_fields
,
num_mongo_calls
):
with
store
.
build
()
as
(
source_content
,
source_store
):
source_course_key
=
source_store
.
make_course_key
(
'a'
,
'course'
,
'course'
)
# First, import a course.
import_course_from_xml
(
source_store
,
'test_user'
,
TEST_DATA_DIR
,
source_dirs
=
[
'manual-testing-complete'
],
static_content_store
=
source_content
,
target_id
=
source_course_key
,
create_if_not_present
=
True
,
raise_on_failure
=
True
,
)
def
test_number_mongo_calls
(
self
,
store_builder
,
depth
,
lazy
,
access_all_block_fields
,
num_mongo_calls
):
request_cache
=
MemoryCache
()
with
store_builder
.
build
(
request_cache
=
request_cache
)
as
(
content_store
,
modulestore
):
course_key
=
self
.
_import_course
(
content_store
,
modulestore
)
# Course traversal modeled after the traversal done here:
# lms/djangoapps/mobile_api/video_outlines/serializers.py:BlockOutline
# Starting at the root course block, do a breadth-first traversal using
# get_children() to retrieve each block's children.
with
check_mongo_calls
(
num_mongo_calls
):
with
source_store
.
bulk_operations
(
source_course_key
):
start_block
=
source_store
.
get_course
(
source_course_key
,
depth
=
depth
,
lazy
=
lazy
)
all_blocks
=
[]
stack
=
[
start_block
]
while
stack
:
curr_block
=
stack
.
pop
()
all_blocks
.
append
(
curr_block
)
if
curr_block
.
has_children
:
for
block
in
reversed
(
curr_block
.
get_children
()):
stack
.
append
(
block
)
if
access_all_block_fields
:
# Read the fields on each block in order to ensure each block and its definition is loaded.
for
xblock
in
all_blocks
:
for
__
,
field
in
xblock
.
fields
.
iteritems
():
if
field
.
is_set_on
(
xblock
):
__
=
field
.
read_from
(
xblock
)
with
modulestore
.
bulk_operations
(
course_key
):
start_block
=
modulestore
.
get_course
(
course_key
,
depth
=
depth
,
lazy
=
lazy
)
self
.
_traverse_blocks_in_course
(
start_block
,
access_all_block_fields
)
@ddt.data
(
(
MIXED_OLD_MONGO_MODULESTORE_BUILDER
,
176
),
(
MIXED_SPLIT_MODULESTORE_BUILDER
,
5
),
)
@ddt.unpack
def
test_lazy_when_course_previously_cached
(
self
,
store_builder
,
num_mongo_calls
):
request_cache
=
MemoryCache
()
with
store_builder
.
build
(
request_cache
=
request_cache
)
as
(
content_store
,
modulestore
):
course_key
=
self
.
_import_course
(
content_store
,
modulestore
)
with
check_mongo_calls
(
num_mongo_calls
):
with
modulestore
.
bulk_operations
(
course_key
):
# assume the course was retrieved earlier
course
=
modulestore
.
get_course
(
course_key
,
depth
=
0
,
lazy
=
True
)
# and then subsequently retrieved with the lazy and depth=None values
course
=
modulestore
.
get_item
(
course
.
location
,
depth
=
None
,
lazy
=
False
)
self
.
_traverse_blocks_in_course
(
course
,
access_all_block_fields
=
True
)
common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore_bulk_operations.py
View file @
99e6d539
"""
Tests for bulk operations in Split Modulestore.
"""
# pylint: disable=protected-access
import
copy
import
ddt
import
unittest
...
...
@@ -444,6 +448,17 @@ class TestBulkWriteMixinFindMethods(TestBulkWriteMixin):
else
:
self
.
assertNotIn
(
db_definition
(
_id
),
results
)
def
test_get_definitions_doesnt_update_db
(
self
):
test_ids
=
[
1
,
2
]
db_definition
=
lambda
_id
:
{
'db'
:
'definition'
,
'_id'
:
_id
}
db_definitions
=
[
db_definition
(
_id
)
for
_id
in
test_ids
]
self
.
conn
.
get_definitions
.
return_value
=
db_definitions
self
.
bulk
.
_begin_bulk_operation
(
self
.
course_key
)
self
.
bulk
.
get_definitions
(
self
.
course_key
,
test_ids
)
self
.
bulk
.
_end_bulk_operation
(
self
.
course_key
)
self
.
assertFalse
(
self
.
conn
.
insert_definition
.
called
)
def
test_no_bulk_find_structures_derived_from
(
self
):
ids
=
[
Mock
(
name
=
'id'
)]
self
.
conn
.
find_structures_derived_from
.
return_value
=
[
MagicMock
(
name
=
'result'
)]
...
...
common/lib/xmodule/xmodule/modulestore/tests/utils.py
View file @
99e6d539
...
...
@@ -194,7 +194,7 @@ class MemoryCache(object):
the modulestore, and stores the data in a dictionary in memory.
"""
def
__init__
(
self
):
self
.
_
data
=
{}
self
.
data
=
{}
def
get
(
self
,
key
,
default
=
None
):
"""
...
...
@@ -204,7 +204,7 @@ class MemoryCache(object):
key: The key to update.
default: The value to return if the key hasn't been set previously.
"""
return
self
.
_
data
.
get
(
key
,
default
)
return
self
.
data
.
get
(
key
,
default
)
def
set
(
self
,
key
,
value
):
"""
...
...
@@ -214,7 +214,7 @@ class MemoryCache(object):
key: The key to update.
value: The value change the key to.
"""
self
.
_
data
[
key
]
=
value
self
.
data
[
key
]
=
value
class
MongoContentstoreBuilder
(
object
):
...
...
@@ -255,19 +255,19 @@ class StoreBuilderBase(object):
"""
contentstore
=
kwargs
.
pop
(
'contentstore'
,
None
)
if
not
contentstore
:
with
self
.
build_without_contentstore
()
as
(
contentstore
,
modulestore
):
with
self
.
build_without_contentstore
(
**
kwargs
)
as
(
contentstore
,
modulestore
):
yield
contentstore
,
modulestore
else
:
with
self
.
build_with_contentstore
(
contentstore
)
as
modulestore
:
with
self
.
build_with_contentstore
(
contentstore
,
**
kwargs
)
as
modulestore
:
yield
modulestore
@contextmanager
def
build_without_contentstore
(
self
):
def
build_without_contentstore
(
self
,
**
kwargs
):
"""
Build both the contentstore and the modulestore.
"""
with
MongoContentstoreBuilder
()
.
build
()
as
contentstore
:
with
self
.
build_with_contentstore
(
contentstore
)
as
modulestore
:
with
self
.
build_with_contentstore
(
contentstore
,
**
kwargs
)
as
modulestore
:
yield
contentstore
,
modulestore
...
...
@@ -276,7 +276,7 @@ class MongoModulestoreBuilder(StoreBuilderBase):
A builder class for a DraftModuleStore.
"""
@contextmanager
def
build_with_contentstore
(
self
,
contentstore
):
def
build_with_contentstore
(
self
,
contentstore
,
**
kwargs
):
"""
A contextmanager that returns an isolated mongo modulestore, and then deletes
all of its data at the end of the context.
...
...
@@ -324,7 +324,7 @@ class VersioningModulestoreBuilder(StoreBuilderBase):
A builder class for a VersioningModuleStore.
"""
@contextmanager
def
build_with_contentstore
(
self
,
contentstore
):
def
build_with_contentstore
(
self
,
contentstore
,
**
kwargs
):
"""
A contextmanager that returns an isolated versioning modulestore, and then deletes
all of its data at the end of the context.
...
...
@@ -347,6 +347,7 @@ class VersioningModulestoreBuilder(StoreBuilderBase):
fs_root
,
render_template
=
repr
,
xblock_mixins
=
XBLOCK_MIXINS
,
**
kwargs
)
modulestore
.
ensure_indexes
()
...
...
@@ -369,7 +370,7 @@ class XmlModulestoreBuilder(StoreBuilderBase):
"""
# pylint: disable=unused-argument
@contextmanager
def
build_with_contentstore
(
self
,
contentstore
=
None
,
course_ids
=
None
):
def
build_with_contentstore
(
self
,
contentstore
=
None
,
course_ids
=
None
,
**
kwargs
):
"""
A contextmanager that returns an isolated xml modulestore
...
...
@@ -403,7 +404,7 @@ class MixedModulestoreBuilder(StoreBuilderBase):
self
.
mixed_modulestore
=
None
@contextmanager
def
build_with_contentstore
(
self
,
contentstore
):
def
build_with_contentstore
(
self
,
contentstore
,
**
kwargs
):
"""
A contextmanager that returns a mixed modulestore built on top of modulestores
generated by other builder classes.
...
...
@@ -414,7 +415,7 @@ class MixedModulestoreBuilder(StoreBuilderBase):
"""
names
,
generators
=
zip
(
*
self
.
store_builders
)
with
nested
(
*
(
gen
.
build_with_contentstore
(
contentstore
)
for
gen
in
generators
))
as
modulestores
:
with
nested
(
*
(
gen
.
build_with_contentstore
(
contentstore
,
**
kwargs
)
for
gen
in
generators
))
as
modulestores
:
# Make the modulestore creation function just return the already-created modulestores
store_iterator
=
iter
(
modulestores
)
next_modulestore
=
lambda
*
args
,
**
kwargs
:
store_iterator
.
next
()
...
...
lms/djangoapps/course_api/blocks/tests/test_api.py
View file @
99e6d539
...
...
@@ -2,12 +2,14 @@
Tests for Blocks api.py
"""
import
ddt
from
django.test.client
import
RequestFactory
from
openedx.core.djangoapps.content.block_structure.api
import
clear_course_from_cache
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
SampleCourseFactory
from
xmodule.modulestore.tests.factories
import
SampleCourseFactory
,
check_mongo_calls
from
..api
import
get_blocks
...
...
@@ -19,7 +21,8 @@ class TestGetBlocks(SharedModuleStoreTestCase):
@classmethod
def
setUpClass
(
cls
):
super
(
TestGetBlocks
,
cls
)
.
setUpClass
()
cls
.
course
=
SampleCourseFactory
.
create
()
with
cls
.
store
.
default_store
(
ModuleStoreEnum
.
Type
.
split
):
cls
.
course
=
SampleCourseFactory
.
create
()
# hide the html block
cls
.
html_block
=
cls
.
store
.
get_item
(
cls
.
course
.
id
.
make_usage_key
(
'html'
,
'html_x1a_1'
))
...
...
@@ -95,3 +98,47 @@ class TestGetBlocks(SharedModuleStoreTestCase):
self
.
assertEquals
(
len
(
blocks
[
'blocks'
]),
3
)
for
block
in
blocks
[
'blocks'
]
.
itervalues
():
self
.
assertEqual
(
block
[
'type'
],
'problem'
)
@ddt.ddt
class
TestGetBlocksQueryCounts
(
SharedModuleStoreTestCase
):
"""
Tests query counts for the get_blocks function.
"""
def
setUp
(
self
):
super
(
TestGetBlocksQueryCounts
,
self
)
.
setUp
()
self
.
user
=
UserFactory
.
create
()
self
.
request
=
RequestFactory
()
.
get
(
"/dummy"
)
self
.
request
.
user
=
self
.
user
def
_create_course
(
self
,
store_type
):
"""
Creates the sample course in the given store type.
"""
with
self
.
store
.
default_store
(
store_type
):
return
SampleCourseFactory
.
create
()
def
_get_blocks
(
self
,
course
,
expected_mongo_queries
):
"""
Verifies the number of expected queries when calling
get_blocks on the given course.
"""
with
check_mongo_calls
(
expected_mongo_queries
):
with
self
.
assertNumQueries
(
2
):
get_blocks
(
self
.
request
,
course
.
location
,
self
.
user
)
@ddt.data
(
ModuleStoreEnum
.
Type
.
mongo
,
ModuleStoreEnum
.
Type
.
split
)
def
test_query_counts_cached
(
self
,
store_type
):
course
=
self
.
_create_course
(
store_type
)
self
.
_get_blocks
(
course
,
expected_mongo_queries
=
0
)
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
5
),
(
ModuleStoreEnum
.
Type
.
split
,
3
),
)
@ddt.unpack
def
test_query_counts_uncached
(
self
,
store_type
,
expected_mongo_queries
):
course
=
self
.
_create_course
(
store_type
)
clear_course_from_cache
(
course
.
id
)
self
.
_get_blocks
(
course
,
expected_mongo_queries
)
lms/djangoapps/courseware/tests/test_views.py
View file @
99e6d539
...
...
@@ -191,6 +191,48 @@ class TestJumpTo(ModuleStoreTestCase):
@attr
(
shard
=
2
)
@ddt.ddt
class
IndexQueryTestCase
(
ModuleStoreTestCase
):
"""
Tests for query count.
"""
CREATE_USER
=
False
NUM_PROBLEMS
=
20
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
8
),
(
ModuleStoreEnum
.
Type
.
split
,
4
),
)
@ddt.unpack
def
test_index_query_counts
(
self
,
store_type
,
expected_query_count
):
with
self
.
store
.
default_store
(
store_type
):
course
=
CourseFactory
.
create
()
with
self
.
store
.
bulk_operations
(
course
.
id
):
chapter
=
ItemFactory
.
create
(
category
=
'chapter'
,
parent_location
=
course
.
location
)
section
=
ItemFactory
.
create
(
category
=
'sequential'
,
parent_location
=
chapter
.
location
)
vertical
=
ItemFactory
.
create
(
category
=
'vertical'
,
parent_location
=
section
.
location
)
for
_
in
range
(
self
.
NUM_PROBLEMS
):
ItemFactory
.
create
(
category
=
'problem'
,
parent_location
=
vertical
.
location
)
password
=
'test'
self
.
user
=
UserFactory
(
password
=
password
)
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
password
)
CourseEnrollment
.
enroll
(
self
.
user
,
course
.
id
)
with
check_mongo_calls
(
expected_query_count
):
url
=
reverse
(
'courseware_section'
,
kwargs
=
{
'course_id'
:
unicode
(
course
.
id
),
'chapter'
:
unicode
(
chapter
.
location
.
name
),
'section'
:
unicode
(
section
.
location
.
name
),
}
)
response
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
response
.
status_code
,
200
)
@attr
(
shard
=
2
)
@ddt.ddt
class
ViewsTestCase
(
ModuleStoreTestCase
):
"""
Tests for views.py methods.
...
...
lms/djangoapps/courseware/views/index.py
View file @
99e6d539
...
...
@@ -348,7 +348,7 @@ class CoursewareIndex(View):
sets up the runtime, which binds the request user to the section.
"""
# Pre-fetch all descendant data
self
.
section
=
modulestore
()
.
get_item
(
self
.
section
.
location
,
depth
=
None
)
self
.
section
=
modulestore
()
.
get_item
(
self
.
section
.
location
,
depth
=
None
,
lazy
=
False
)
self
.
field_data_cache
.
add_descriptor_descendents
(
self
.
section
,
depth
=
None
)
# Bind section to user
...
...
lms/djangoapps/grades/tasks.py
View file @
99e6d539
...
...
@@ -158,43 +158,45 @@ def _update_subsection_grades(
that those subsection grades were updated.
"""
student
=
User
.
objects
.
get
(
id
=
user_id
)
course_structure
=
get_course_blocks
(
student
,
modulestore
()
.
make_course_usage_key
(
course_key
))
subsections_to_update
=
course_structure
.
get_transformer_block_field
(
scored_block_usage_key
,
GradesTransformer
,
'subsections'
,
set
(),
)
course
=
modulestore
()
.
get_course
(
course_key
,
depth
=
0
)
subsection_grade_factory
=
SubsectionGradeFactory
(
student
,
course
,
course_structure
)
try
:
for
subsection_usage_key
in
subsections_to_update
:
if
subsection_usage_key
in
course_structure
:
subsection_grade
=
subsection_grade_factory
.
update
(
course_structure
[
subsection_usage_key
],
only_if_higher
,
)
SUBSECTION_SCORE_CHANGED
.
send
(
sender
=
recalculate_subsection_grade
,
course
=
course
,
course_structure
=
course_structure
,
user
=
student
,
subsection_grade
=
subsection_grade
,
)
except
DatabaseError
as
exc
:
raise
_retry_recalculate_subsection_grade
(
user_id
,
course_id
,
usage_id
,
only_if_higher
,
expected_modified_time
,
score_deleted
,
exc
,
store
=
modulestore
()
with
store
.
bulk_operations
(
course_key
):
course_structure
=
get_course_blocks
(
student
,
store
.
make_course_usage_key
(
course_key
))
subsections_to_update
=
course_structure
.
get_transformer_block_field
(
scored_block_usage_key
,
GradesTransformer
,
'subsections'
,
set
(),
)
course
=
store
.
get_course
(
course_key
,
depth
=
0
)
subsection_grade_factory
=
SubsectionGradeFactory
(
student
,
course
,
course_structure
)
try
:
for
subsection_usage_key
in
subsections_to_update
:
if
subsection_usage_key
in
course_structure
:
subsection_grade
=
subsection_grade_factory
.
update
(
course_structure
[
subsection_usage_key
],
only_if_higher
,
)
SUBSECTION_SCORE_CHANGED
.
send
(
sender
=
recalculate_subsection_grade
,
course
=
course
,
course_structure
=
course_structure
,
user
=
student
,
subsection_grade
=
subsection_grade
,
)
except
DatabaseError
as
exc
:
raise
_retry_recalculate_subsection_grade
(
user_id
,
course_id
,
usage_id
,
only_if_higher
,
expected_modified_time
,
score_deleted
,
exc
,
)
def
_retry_recalculate_subsection_grade
(
user_id
,
...
...
lms/djangoapps/grades/tests/test_tasks.py
View file @
99e6d539
...
...
@@ -121,16 +121,17 @@ class RecalculateSubsectionGradeTest(ModuleStoreTestCase):
self
.
assertTrue
(
mock_subsection_signal
.
called
)
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
1
),
(
ModuleStoreEnum
.
Type
.
split
,
0
),
(
ModuleStoreEnum
.
Type
.
mongo
,
1
,
23
),
(
ModuleStoreEnum
.
Type
.
split
,
3
,
22
),
)
@ddt.unpack
def
test_subsection_grade_updated
(
self
,
default_store
,
added_querie
s
):
def
test_subsection_grade_updated
(
self
,
default_store
,
num_mongo_calls
,
num_sql_call
s
):
with
self
.
store
.
default_store
(
default_store
):
self
.
set_up_course
()
self
.
assertTrue
(
PersistentGradesEnabledFlag
.
feature_enabled
(
self
.
course
.
id
))
with
check_mongo_calls
(
2
)
and
self
.
assertNumQueries
(
22
+
added_queries
):
self
.
_apply_recalculate_subsection_grade
()
with
check_mongo_calls
(
num_mongo_calls
):
with
self
.
assertNumQueries
(
num_sql_calls
):
self
.
_apply_recalculate_subsection_grade
()
@patch
(
'lms.djangoapps.grades.signals.signals.SUBSECTION_SCORE_CHANGED.send'
)
def
test_other_inaccessible_subsection
(
self
,
mock_subsection_signal
):
...
...
@@ -166,27 +167,38 @@ class RecalculateSubsectionGradeTest(ModuleStoreTestCase):
self
.
_apply_recalculate_subsection_grade
()
self
.
assertEquals
(
mock_block_structure_create
.
call_count
,
1
)
# TODO (TNL-6225) Fix the number of SQL queries so they
# don't grow linearly with the number of sequentials.
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
1
),
(
ModuleStoreEnum
.
Type
.
split
,
0
),
(
ModuleStoreEnum
.
Type
.
mongo
,
1
,
46
),
(
ModuleStoreEnum
.
Type
.
split
,
3
,
45
),
)
@ddt.unpack
def
test_query_count_does_not_change_with_more_
problems
(
self
,
default_store
,
added_querie
s
):
def
test_query_count_does_not_change_with_more_
content
(
self
,
default_store
,
num_mongo_calls
,
num_sql_call
s
):
with
self
.
store
.
default_store
(
default_store
):
self
.
set_up_course
()
self
.
assertTrue
(
PersistentGradesEnabledFlag
.
feature_enabled
(
self
.
course
.
id
))
ItemFactory
.
create
(
parent
=
self
.
sequential
,
category
=
'problem'
,
display_name
=
'problem2'
)
ItemFactory
.
create
(
parent
=
self
.
sequential
,
category
=
'problem'
,
display_name
=
'problem3'
)
with
check_mongo_calls
(
2
)
and
self
.
assertNumQueries
(
22
+
added_queries
):
self
.
_apply_recalculate_subsection_grade
()
num_problems
=
10
for
_
in
range
(
num_problems
):
ItemFactory
.
create
(
parent
=
self
.
sequential
,
category
=
'problem'
)
num_sequentials
=
10
for
_
in
range
(
num_sequentials
):
ItemFactory
.
create
(
parent
=
self
.
chapter
,
category
=
'sequential'
)
with
check_mongo_calls
(
num_mongo_calls
):
with
self
.
assertNumQueries
(
num_sql_calls
):
self
.
_apply_recalculate_subsection_grade
()
@ddt.data
(
ModuleStoreEnum
.
Type
.
mongo
,
ModuleStoreEnum
.
Type
.
split
)
def
test_subsection_grades_not_enabled_on_course
(
self
,
default_store
):
with
self
.
store
.
default_store
(
default_store
):
self
.
set_up_course
(
enable_subsection_grades
=
False
)
self
.
assertFalse
(
PersistentGradesEnabledFlag
.
feature_enabled
(
self
.
course
.
id
))
with
check_mongo_calls
(
2
)
and
self
.
assertNumQueries
(
0
):
self
.
_apply_recalculate_subsection_grade
()
with
check_mongo_calls
(
0
):
with
self
.
assertNumQueries
(
0
):
self
.
_apply_recalculate_subsection_grade
()
@skip
(
"Pending completion of TNL-5089"
)
@ddt.data
(
...
...
@@ -200,8 +212,9 @@ class RecalculateSubsectionGradeTest(ModuleStoreTestCase):
PersistentGradesEnabledFlag
.
objects
.
create
(
enabled
=
feature_flag
)
with
self
.
store
.
default_store
(
default_store
):
self
.
set_up_course
()
with
check_mongo_calls
(
0
)
and
self
.
assertNumQueries
(
3
if
feature_flag
else
2
):
self
.
_apply_recalculate_subsection_grade
()
with
check_mongo_calls
(
0
):
with
self
.
assertNumQueries
(
3
if
feature_flag
else
2
):
self
.
_apply_recalculate_subsection_grade
()
@patch
(
'lms.djangoapps.grades.tasks.recalculate_subsection_grade_v2.retry'
)
@patch
(
'lms.djangoapps.grades.new.subsection_grade.SubsectionGradeFactory.update'
)
...
...
lms/djangoapps/grades/tests/test_transformer.py
View file @
99e6d539
...
...
@@ -17,7 +17,7 @@ from xmodule.modulestore.tests.factories import check_mongo_calls
from
lms.djangoapps.course_blocks.api
import
get_course_blocks
from
lms.djangoapps.course_blocks.transformers.tests.helpers
import
CourseStructureTestCase
from
openedx.core.djangoapps.content.block_structure.api
import
get
_cache
from
openedx.core.djangoapps.content.block_structure.api
import
clear_course_from
_cache
from
..transformer
import
GradesTransformer
...
...
@@ -390,6 +390,7 @@ class GradesTransformerTestCase(CourseStructureTestCase):
)
@ddt.ddt
class
MultiProblemModulestoreAccessTestCase
(
CourseStructureTestCase
,
SharedModuleStoreTestCase
):
"""
Test mongo usage in GradesTransformer.
...
...
@@ -403,7 +404,12 @@ class MultiProblemModulestoreAccessTestCase(CourseStructureTestCase, SharedModul
self
.
student
=
UserFactory
.
create
(
is_staff
=
False
,
username
=
u'test_student'
,
password
=
password
)
self
.
client
.
login
(
username
=
self
.
student
.
username
,
password
=
password
)
def
test_modulestore_performance
(
self
):
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
split
,
3
),
(
ModuleStoreEnum
.
Type
.
mongo
,
2
),
)
@ddt.unpack
def
test_modulestore_performance
(
self
,
store_type
,
expected_mongo_queries
):
"""
Test that a constant number of mongo calls are made regardless of how
many grade-related blocks are in the course.
...
...
@@ -437,7 +443,8 @@ class MultiProblemModulestoreAccessTestCase(CourseStructureTestCase, SharedModul
</problem>'''
.
format
(
number
=
problem_number
),
}
)
blocks
=
self
.
build_course
(
course
)
get_cache
()
.
clear
()
with
check_mongo_calls
(
2
):
with
self
.
store
.
default_store
(
store_type
):
blocks
=
self
.
build_course
(
course
)
clear_course_from_cache
(
blocks
[
u'course'
]
.
id
)
with
check_mongo_calls
(
expected_mongo_queries
):
get_course_blocks
(
self
.
student
,
blocks
[
u'course'
]
.
location
,
self
.
transformers
)
openedx/core/djangoapps/bookmarks/tests/test_tasks.py
View file @
99e6d539
...
...
@@ -103,11 +103,11 @@ class XBlockCacheTaskTests(BookmarksTestsBase):
}
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
2
,
2
,
3
),
(
ModuleStoreEnum
.
Type
.
mongo
,
4
,
2
,
3
),
(
ModuleStoreEnum
.
Type
.
mongo
,
2
,
3
,
4
),
(
ModuleStoreEnum
.
Type
.
mongo
,
4
,
3
,
4
),
(
ModuleStoreEnum
.
Type
.
mongo
,
2
,
4
,
5
),
(
ModuleStoreEnum
.
Type
.
mongo
,
2
,
2
,
4
),
(
ModuleStoreEnum
.
Type
.
mongo
,
4
,
2
,
4
),
(
ModuleStoreEnum
.
Type
.
mongo
,
2
,
3
,
5
),
(
ModuleStoreEnum
.
Type
.
mongo
,
4
,
3
,
5
),
(
ModuleStoreEnum
.
Type
.
mongo
,
2
,
4
,
6
),
# (ModuleStoreEnum.Type.mongo, 4, 4, 6), Too slow.
(
ModuleStoreEnum
.
Type
.
split
,
2
,
2
,
3
),
(
ModuleStoreEnum
.
Type
.
split
,
4
,
2
,
3
),
...
...
@@ -119,6 +119,9 @@ class XBlockCacheTaskTests(BookmarksTestsBase):
course
=
self
.
create_course_with_blocks
(
children_per_block
,
depth
,
store_type
)
# clear cache to get consistent query counts
self
.
clear_caches
()
with
check_mongo_calls
(
expected_mongo_calls
):
blocks_data
=
_calculate_course_xblocks_data
(
course
.
id
)
self
.
assertGreater
(
len
(
blocks_data
),
children_per_block
**
depth
)
...
...
openedx/core/lib/block_structure/factory.py
View file @
99e6d539
...
...
@@ -53,7 +53,7 @@ class BlockStructureFactory(object):
block_structure
.
_add_relation
(
xblock
.
location
,
child
.
location
)
# pylint: disable=protected-access
build_block_structure
(
child
)
root_xblock
=
modulestore
.
get_item
(
root_block_usage_key
,
depth
=
None
)
root_xblock
=
modulestore
.
get_item
(
root_block_usage_key
,
depth
=
None
,
lazy
=
False
)
build_block_structure
(
root_xblock
)
return
block_structure
...
...
openedx/core/lib/block_structure/tests/helpers.py
View file @
99e6d539
...
...
@@ -55,7 +55,7 @@ class MockModulestore(object):
"""
self
.
blocks
=
blocks
def
get_item
(
self
,
block_key
,
depth
=
None
):
# pylint: disable=unused-argument
def
get_item
(
self
,
block_key
,
depth
=
None
,
lazy
=
False
):
# pylint: disable=unused-argument
"""
Returns the mock XBlock (MockXBlock) associated with the
given block_key.
...
...
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