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
2e06e592
Commit
2e06e592
authored
Apr 28, 2015
by
Calen Pennington
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use DjangoXBlockUserStateClient to implement UserStateCache
parent
257660ed
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
89 additions
and
33 deletions
+89
-33
lms/djangoapps/courseware/model_data.py
+0
-0
lms/djangoapps/courseware/tests/test_model_data.py
+28
-14
lms/djangoapps/courseware/user_state_client.py
+60
-18
lms/djangoapps/mobile_api/users/views.py
+1
-1
No files found.
lms/djangoapps/courseware/model_data.py
View file @
2e06e592
This diff is collapsed.
Click to expand it.
lms/djangoapps/courseware/tests/test_model_data.py
View file @
2e06e592
...
...
@@ -134,7 +134,10 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
def
test_set_existing_field
(
self
):
"Test that setting an existing user_state field changes the value"
# We are updating a problem, so we write to courseware_studentmodulehistory
# as well as courseware_studentmodule
# as well as courseware_studentmodule. We also need to read the database
# to discover if something other than the DjangoXBlockUserStateClient
# has written to the StudentModule (such as UserStateCache setting the score
# on the StudentModule).
with
self
.
assertNumQueries
(
3
):
self
.
kvs
.
set
(
user_state_key
(
'a_field'
),
'new_value'
)
self
.
assertEquals
(
1
,
StudentModule
.
objects
.
all
()
.
count
())
...
...
@@ -143,7 +146,10 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
def
test_set_missing_field
(
self
):
"Test that setting a new user_state field changes the value"
# We are updating a problem, so we write to courseware_studentmodulehistory
# as well as courseware_studentmodule
# as well as courseware_studentmodule. We also need to read the database
# to discover if something other than the DjangoXBlockUserStateClient
# has written to the StudentModule (such as UserStateCache setting the score
# on the StudentModule).
with
self
.
assertNumQueries
(
3
):
self
.
kvs
.
set
(
user_state_key
(
'not_a_field'
),
'new_value'
)
self
.
assertEquals
(
1
,
StudentModule
.
objects
.
all
()
.
count
())
...
...
@@ -152,7 +158,10 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
def
test_delete_existing_field
(
self
):
"Test that deleting an existing field removes it from the StudentModule"
# We are updating a problem, so we write to courseware_studentmodulehistory
# as well as courseware_studentmodule
# as well as courseware_studentmodule. We also need to read the database
# to discover if something other than the DjangoXBlockUserStateClient
# has written to the StudentModule (such as UserStateCache setting the score
# on the StudentModule).
with
self
.
assertNumQueries
(
3
):
self
.
kvs
.
delete
(
user_state_key
(
'a_field'
))
self
.
assertEquals
(
1
,
StudentModule
.
objects
.
all
()
.
count
())
...
...
@@ -190,6 +199,9 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
# Scope.user_state is stored in a single row in the database, so we only
# need to send a single update to that table.
# We also are updating a problem, so we write to courseware student module history
# We also need to read the database to discover if something other than the
# DjangoXBlockUserStateClient has written to the StudentModule (such as
# UserStateCache setting the score on the StudentModule).
with
self
.
assertNumQueries
(
3
):
self
.
kvs
.
set_many
(
kv_dict
)
...
...
@@ -207,7 +219,7 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
with
patch
(
'django.db.models.Model.save'
,
side_effect
=
DatabaseError
):
with
self
.
assertRaises
(
KeyValueMultiSaveError
)
as
exception_context
:
self
.
kvs
.
set_many
(
kv_dict
)
self
.
assertEquals
(
len
(
exception_context
.
exception
.
saved_field_names
),
0
)
self
.
assertEquals
(
exception_context
.
exception
.
saved_field_names
,
[]
)
@attr
(
'shard_1'
)
...
...
@@ -234,8 +246,11 @@ class TestMissingStudentModule(TestCase):
self
.
assertEquals
(
0
,
StudentModule
.
objects
.
all
()
.
count
())
# We are updating a problem, so we write to courseware_studentmodulehistory
# as well as courseware_studentmodule
with
self
.
assertNumQueries
(
6
):
# as well as courseware_studentmodule. We also need to read the database
# to discover if something other than the DjangoXBlockUserStateClient
# has written to the StudentModule (such as UserStateCache setting the score
# on the StudentModule).
with
self
.
assertNumQueries
(
3
):
self
.
kvs
.
set
(
user_state_key
(
'a_field'
),
'a_value'
)
self
.
assertEquals
(
1
,
sum
(
len
(
cache
)
for
cache
in
self
.
field_data_cache
.
cache
.
values
()))
...
...
@@ -289,7 +304,7 @@ class StorageTestBase(object):
self
.
kvs
=
DjangoKeyValueStore
(
self
.
field_data_cache
)
def
test_set_and_get_existing_field
(
self
):
with
self
.
assertNumQueries
(
2
):
with
self
.
assertNumQueries
(
1
):
self
.
kvs
.
set
(
self
.
key_factory
(
'existing_field'
),
'test_value'
)
with
self
.
assertNumQueries
(
0
):
self
.
assertEquals
(
'test_value'
,
self
.
kvs
.
get
(
self
.
key_factory
(
'existing_field'
)))
...
...
@@ -306,14 +321,14 @@ class StorageTestBase(object):
def
test_set_existing_field
(
self
):
"Test that setting an existing field changes the value"
with
self
.
assertNumQueries
(
2
):
with
self
.
assertNumQueries
(
1
):
self
.
kvs
.
set
(
self
.
key_factory
(
'existing_field'
),
'new_value'
)
self
.
assertEquals
(
1
,
self
.
storage_class
.
objects
.
all
()
.
count
())
self
.
assertEquals
(
'new_value'
,
json
.
loads
(
self
.
storage_class
.
objects
.
all
()[
0
]
.
value
))
def
test_set_missing_field
(
self
):
"Test that setting a new field changes the value"
with
self
.
assertNumQueries
(
4
):
with
self
.
assertNumQueries
(
1
):
self
.
kvs
.
set
(
self
.
key_factory
(
'missing_field'
),
'new_value'
)
self
.
assertEquals
(
2
,
self
.
storage_class
.
objects
.
all
()
.
count
())
self
.
assertEquals
(
'old_value'
,
json
.
loads
(
self
.
storage_class
.
objects
.
get
(
field_name
=
'existing_field'
)
.
value
))
...
...
@@ -355,7 +370,7 @@ class StorageTestBase(object):
# Each field is a separate row in the database, hence
# a separate query
with
self
.
assertNumQueries
(
len
(
kv_dict
)
*
3
):
with
self
.
assertNumQueries
(
len
(
kv_dict
)):
self
.
kvs
.
set_many
(
kv_dict
)
for
key
in
kv_dict
:
self
.
assertEquals
(
self
.
kvs
.
get
(
key
),
kv_dict
[
key
])
...
...
@@ -363,8 +378,8 @@ class StorageTestBase(object):
def
test_set_many_failure
(
self
):
"""Test that setting many regular fields with a DB error """
kv_dict
=
self
.
construct_kv_dict
()
with
self
.
assertNumQueries
(
6
)
:
for
key
in
kv_dict
:
for
key
in
kv_dict
:
with
self
.
assertNumQueries
(
1
)
:
self
.
kvs
.
set
(
key
,
'test value'
)
with
patch
(
'django.db.models.Model.save'
,
side_effect
=
[
None
,
DatabaseError
]):
...
...
@@ -372,8 +387,7 @@ class StorageTestBase(object):
self
.
kvs
.
set_many
(
kv_dict
)
exception
=
exception_context
.
exception
self
.
assertEquals
(
len
(
exception
.
saved_field_names
),
1
)
self
.
assertIn
(
exception
.
saved_field_names
[
0
],
(
'existing_field'
,
'other_existing_field'
))
self
.
assertEquals
(
exception
.
saved_field_names
,
[
'existing_field'
,
'other_existing_field'
])
class
TestUserStateSummaryStorage
(
StorageTestBase
,
TestCase
):
...
...
lms/djangoapps/courseware/user_state_client.py
View file @
2e06e592
...
...
@@ -40,7 +40,6 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
pass
def
__init__
(
self
,
user
):
self
.
_student_module_cache
=
{}
self
.
user
=
user
def
get
(
self
,
username
,
block_key
,
scope
=
Scope
.
user_state
,
fields
=
None
):
...
...
@@ -93,6 +92,28 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
assert
self
.
user
.
username
==
username
return
self
.
delete_many
(
username
,
[
block_key
],
scope
,
fields
=
fields
)
@contract
(
username
=
"basestring"
,
block_key
=
UsageKey
,
scope
=
ScopeBase
,
fields
=
"seq(basestring)|set(basestring)|None"
)
def
get_mod_date
(
self
,
username
,
block_key
,
scope
=
Scope
.
user_state
,
fields
=
None
):
"""
Get the last modification date for fields from the specified blocks.
Arguments:
username: The name of the user whose state should be deleted
block_key (UsageKey): The UsageKey identifying which xblock modification dates to retrieve.
scope (Scope): The scope to retrieve from.
fields: A list of fields to query. If None, delete all stored fields.
Specific implementations are free to return the same modification date
for all fields, if they don't store changes individually per field.
Implementations may omit fields for which data has not been stored.
Returns: list a dict of {field_name: modified_date} for each selected field.
"""
results
=
self
.
get_mod_date_many
(
username
,
[
block_key
],
scope
,
fields
=
fields
)
return
{
field
:
date
for
(
_
,
field
,
date
)
in
results
}
@contract
(
username
=
"basestring"
,
block_keys
=
"seq(UsageKey)|set(UsageKey)"
)
def
_get_field_objects
(
self
,
username
,
block_keys
):
"""
Retrieve the :class:`~StudentModule`s for the supplied ``username`` and ``block_keys``.
...
...
@@ -108,23 +129,15 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
)
for
course_key
,
usage_keys
in
by_course
:
not_cached
=
[]
for
usage_key
in
usage_keys
:
if
usage_key
in
self
.
_student_module_cache
:
yield
self
.
_student_module_cache
[
usage_key
]
else
:
not_cached
.
append
(
usage_key
)
query
=
StudentModule
.
objects
.
chunked_filter
(
'module_state_key__in'
,
not_cached
,
usage_keys
,
student__username
=
username
,
course_id
=
course_key
,
)
for
student_module
in
query
:
usage_key
=
student_module
.
module_state_key
.
map_into_course
(
student_module
.
course_id
)
self
.
_student_module_cache
[
usage_key
]
=
student_module
yield
(
student_module
,
usage_key
)
...
...
@@ -170,14 +183,10 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
if
scope
!=
Scope
.
user_state
:
raise
ValueError
(
"Only Scope.user_state is supported"
)
field_objects
=
self
.
_get_field_objects
(
username
,
block_keys_to_state
.
keys
())
for
field_object
in
field_objects
:
usage_key
=
field_object
.
module_state_key
.
map_into_course
(
field_object
.
course_id
)
current_state
=
json
.
loads
(
field_object
.
state
)
current_state
.
update
(
block_keys_to_state
.
pop
(
usage_key
))
field_object
.
state
=
json
.
dumps
(
current_state
)
field_object
.
save
()
# We do a find_or_create for every block (rather than re-using field objects
# that were queried in get_many) so that if the score has
# been changed by some other piece of the code, we don't overwrite
# that score.
for
usage_key
,
state
in
block_keys_to_state
.
items
():
student_module
,
created
=
StudentModule
.
objects
.
get_or_create
(
student
=
self
.
user
,
...
...
@@ -227,6 +236,39 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
# We just read this object, so we know that we can do an update
student_module
.
save
(
force_update
=
True
)
@contract
(
username
=
"basestring"
,
block_keys
=
"seq(UsageKey)|set(UsageKey)"
,
scope
=
ScopeBase
,
fields
=
"seq(basestring)|set(basestring)|None"
)
def
get_mod_date_many
(
self
,
username
,
block_keys
,
scope
=
Scope
.
user_state
,
fields
=
None
):
"""
Get the last modification date for fields from the specified blocks.
Arguments:
username: The name of the user whose state should be deleted
block_key (UsageKey): The UsageKey identifying which xblock modification dates to retrieve.
scope (Scope): The scope to retrieve from.
fields: A list of fields to query. If None, delete all stored fields.
Specific implementations are free to return the same modification date
for all fields, if they don't store changes individually per field.
Implementations may omit fields for which data has not been stored.
Yields: tuples of (block, field_name, modified_date) for each selected field.
"""
assert
self
.
user
.
username
==
username
if
scope
!=
Scope
.
user_state
:
raise
ValueError
(
"Only Scope.user_state is supported"
)
field_objects
=
self
.
_get_field_objects
(
username
,
block_keys
)
for
field_object
,
usage_key
in
field_objects
:
if
field_object
.
state
is
None
:
continue
for
field
in
json
.
loads
(
field_object
.
state
):
yield
(
usage_key
,
field
,
field_object
.
modified
)
def
get_history
(
self
,
username
,
block_key
,
scope
=
Scope
.
user_state
):
"""We don't guarantee that history for many blocks will be fast."""
assert
self
.
user
.
username
==
username
...
...
lms/djangoapps/mobile_api/users/views.py
View file @
2e06e592
...
...
@@ -147,7 +147,7 @@ class UserCourseStatus(views.APIView):
scope
=
Scope
.
user_state
,
user_id
=
request
.
user
.
id
,
block_scope_id
=
course
.
location
,
field_name
=
None
field_name
=
'position'
)
original_store_date
=
field_data_cache
.
last_modified
(
key
)
if
original_store_date
is
not
None
and
modification_date
<
original_store_date
:
...
...
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