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
7d43e399
Commit
7d43e399
authored
Apr 07, 2016
by
Nimisha Asthagiri
Committed by
Brian Beggs
Apr 11, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix sequence navigation issues for CCX courses
MA-2258
parent
0173642f
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
35 additions
and
17 deletions
+35
-17
common/lib/xmodule/xmodule/modulestore/__init__.py
+5
-0
common/lib/xmodule/xmodule/modulestore/mixed.py
+4
-4
common/lib/xmodule/xmodule/modulestore/mongo/base.py
+2
-0
common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
+2
-2
lms/djangoapps/ccx/modulestore.py
+9
-4
lms/djangoapps/ccx/tests/test_views.py
+13
-7
No files found.
common/lib/xmodule/xmodule/modulestore/__init__.py
View file @
7d43e399
...
@@ -38,6 +38,11 @@ new_contract('XBlock', XBlock)
...
@@ -38,6 +38,11 @@ new_contract('XBlock', XBlock)
LIBRARY_ROOT
=
'library.xml'
LIBRARY_ROOT
=
'library.xml'
COURSE_ROOT
=
'course.xml'
COURSE_ROOT
=
'course.xml'
# List of names of computed fields on xmodules that are of type usage keys.
# This list can be used to determine which fields need to be stripped of
# extraneous usage key data when entering/exiting modulestores.
XMODULE_FIELDS_WITH_USAGE_KEYS
=
[
'location'
,
'parent'
]
class
ModuleStoreEnum
(
object
):
class
ModuleStoreEnum
(
object
):
"""
"""
...
...
common/lib/xmodule/xmodule/modulestore/mixed.py
View file @
7d43e399
...
@@ -17,8 +17,7 @@ from opaque_keys.edx.locator import LibraryLocator
...
@@ -17,8 +17,7 @@ from opaque_keys.edx.locator import LibraryLocator
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
xmodule.assetstore
import
AssetMetadata
from
xmodule.assetstore
import
AssetMetadata
from
.
import
ModuleStoreWriteBase
from
.
import
ModuleStoreWriteBase
,
ModuleStoreEnum
,
XMODULE_FIELDS_WITH_USAGE_KEYS
from
.
import
ModuleStoreEnum
from
.exceptions
import
ItemNotFoundError
,
DuplicateCourseError
from
.exceptions
import
ItemNotFoundError
,
DuplicateCourseError
from
.draft_and_published
import
ModuleStoreDraftAndPublished
from
.draft_and_published
import
ModuleStoreDraftAndPublished
from
.split_migrator
import
SplitMigrator
from
.split_migrator
import
SplitMigrator
...
@@ -67,8 +66,9 @@ def strip_key(func):
...
@@ -67,8 +66,9 @@ def strip_key(func):
retval
=
retval
.
version_agnostic
()
retval
=
retval
.
version_agnostic
()
if
rem_branch
and
hasattr
(
retval
,
'for_branch'
):
if
rem_branch
and
hasattr
(
retval
,
'for_branch'
):
retval
=
retval
.
for_branch
(
None
)
retval
=
retval
.
for_branch
(
None
)
if
hasattr
(
retval
,
'location'
):
for
field_name
in
XMODULE_FIELDS_WITH_USAGE_KEYS
:
retval
.
location
=
strip_key_func
(
retval
.
location
)
if
hasattr
(
retval
,
field_name
):
setattr
(
retval
,
field_name
,
strip_key_func
(
getattr
(
retval
,
field_name
)))
return
retval
return
retval
# function for stripping both, collection of, and individual, values
# function for stripping both, collection of, and individual, values
...
...
common/lib/xmodule/xmodule/modulestore/mongo/base.py
View file @
7d43e399
...
@@ -127,6 +127,8 @@ class MongoKeyValueStore(InheritanceKeyValueStore):
...
@@ -127,6 +127,8 @@ class MongoKeyValueStore(InheritanceKeyValueStore):
def
set
(
self
,
key
,
value
):
def
set
(
self
,
key
,
value
):
if
key
.
scope
==
Scope
.
children
:
if
key
.
scope
==
Scope
.
children
:
self
.
_children
=
value
self
.
_children
=
value
elif
key
.
scope
==
Scope
.
parent
:
self
.
_parent
=
value
elif
key
.
scope
==
Scope
.
settings
:
elif
key
.
scope
==
Scope
.
settings
:
self
.
_metadata
[
key
.
field_name
]
=
value
self
.
_metadata
[
key
.
field_name
]
=
value
elif
key
.
scope
==
Scope
.
content
:
elif
key
.
scope
==
Scope
.
content
:
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
View file @
7d43e399
...
@@ -767,15 +767,15 @@ class TestMongoKeyValueStore(unittest.TestCase):
...
@@ -767,15 +767,15 @@ class TestMongoKeyValueStore(unittest.TestCase):
def
test_write
(
self
):
def
test_write
(
self
):
yield
(
self
.
_check_write
,
KeyValueStore
.
Key
(
Scope
.
content
,
None
,
None
,
'foo'
),
'new_data'
)
yield
(
self
.
_check_write
,
KeyValueStore
.
Key
(
Scope
.
content
,
None
,
None
,
'foo'
),
'new_data'
)
yield
(
self
.
_check_write
,
KeyValueStore
.
Key
(
Scope
.
children
,
None
,
None
,
'children'
),
[])
yield
(
self
.
_check_write
,
KeyValueStore
.
Key
(
Scope
.
children
,
None
,
None
,
'children'
),
[])
yield
(
self
.
_check_write
,
KeyValueStore
.
Key
(
Scope
.
children
,
None
,
None
,
'parent'
),
None
)
yield
(
self
.
_check_write
,
KeyValueStore
.
Key
(
Scope
.
settings
,
None
,
None
,
'meta'
),
'new_settings'
)
yield
(
self
.
_check_write
,
KeyValueStore
.
Key
(
Scope
.
settings
,
None
,
None
,
'meta'
),
'new_settings'
)
# write Scope.parent raises InvalidScope, which is covered in test_write_invalid_scope
def
test_write_non_dict_data
(
self
):
def
test_write_non_dict_data
(
self
):
self
.
kvs
=
MongoKeyValueStore
(
'xml_data'
,
self
.
parent
,
self
.
children
,
self
.
metadata
)
self
.
kvs
=
MongoKeyValueStore
(
'xml_data'
,
self
.
parent
,
self
.
children
,
self
.
metadata
)
self
.
_check_write
(
KeyValueStore
.
Key
(
Scope
.
content
,
None
,
None
,
'data'
),
'new_data'
)
self
.
_check_write
(
KeyValueStore
.
Key
(
Scope
.
content
,
None
,
None
,
'data'
),
'new_data'
)
def
test_write_invalid_scope
(
self
):
def
test_write_invalid_scope
(
self
):
for
scope
in
(
Scope
.
preferences
,
Scope
.
user_info
,
Scope
.
user_state
,
Scope
.
parent
):
for
scope
in
(
Scope
.
preferences
,
Scope
.
user_info
,
Scope
.
user_state
):
with
assert_raises
(
InvalidScopeError
):
with
assert_raises
(
InvalidScopeError
):
self
.
kvs
.
set
(
KeyValueStore
.
Key
(
scope
,
None
,
None
,
'foo'
),
'new_value'
)
self
.
kvs
.
set
(
KeyValueStore
.
Key
(
scope
,
None
,
None
,
'foo'
),
'new_value'
)
...
...
lms/djangoapps/ccx/modulestore.py
View file @
7d43e399
...
@@ -13,6 +13,7 @@ from contextlib import contextmanager
...
@@ -13,6 +13,7 @@ from contextlib import contextmanager
from
functools
import
partial
from
functools
import
partial
from
ccx_keys.locator
import
CCXLocator
,
CCXBlockUsageLocator
from
ccx_keys.locator
import
CCXLocator
,
CCXBlockUsageLocator
from
opaque_keys.edx.locator
import
CourseLocator
,
BlockUsageLocator
from
opaque_keys.edx.locator
import
CourseLocator
,
BlockUsageLocator
from
xmodule.modulestore
import
XMODULE_FIELDS_WITH_USAGE_KEYS
def
strip_ccx
(
val
):
def
strip_ccx
(
val
):
...
@@ -28,8 +29,11 @@ def strip_ccx(val):
...
@@ -28,8 +29,11 @@ def strip_ccx(val):
elif
isinstance
(
retval
,
CCXBlockUsageLocator
):
elif
isinstance
(
retval
,
CCXBlockUsageLocator
):
ccx_id
=
retval
.
course_key
.
ccx
ccx_id
=
retval
.
course_key
.
ccx
retval
=
retval
.
to_block_locator
()
retval
=
retval
.
to_block_locator
()
elif
hasattr
(
retval
,
'location'
):
else
:
retval
.
location
,
ccx_id
=
strip_ccx
(
retval
.
location
)
for
field_name
in
XMODULE_FIELDS_WITH_USAGE_KEYS
:
if
hasattr
(
retval
,
field_name
):
stripped_field_value
,
ccx_id
=
strip_ccx
(
getattr
(
retval
,
field_name
))
setattr
(
retval
,
field_name
,
stripped_field_value
)
return
retval
,
ccx_id
return
retval
,
ccx_id
...
@@ -43,8 +47,9 @@ def restore_ccx(val, ccx_id):
...
@@ -43,8 +47,9 @@ def restore_ccx(val, ccx_id):
elif
isinstance
(
val
,
BlockUsageLocator
):
elif
isinstance
(
val
,
BlockUsageLocator
):
ccx_key
=
restore_ccx
(
val
.
course_key
,
ccx_id
)
ccx_key
=
restore_ccx
(
val
.
course_key
,
ccx_id
)
val
=
CCXBlockUsageLocator
(
ccx_key
,
val
.
block_type
,
val
.
block_id
)
val
=
CCXBlockUsageLocator
(
ccx_key
,
val
.
block_type
,
val
.
block_id
)
if
hasattr
(
val
,
'location'
):
for
field_name
in
XMODULE_FIELDS_WITH_USAGE_KEYS
:
val
.
location
=
restore_ccx
(
val
.
location
,
ccx_id
)
if
hasattr
(
val
,
field_name
):
setattr
(
val
,
field_name
,
restore_ccx
(
getattr
(
val
,
field_name
),
ccx_id
))
if
hasattr
(
val
,
'children'
):
if
hasattr
(
val
,
'children'
):
val
.
children
=
restore_ccx_collection
(
val
.
children
,
ccx_id
)
val
.
children
=
restore_ccx_collection
(
val
.
children
,
ccx_id
)
return
val
return
val
...
...
lms/djangoapps/ccx/tests/test_views.py
View file @
7d43e399
...
@@ -53,6 +53,7 @@ from xmodule.modulestore.tests.django_utils import (
...
@@ -53,6 +53,7 @@ from xmodule.modulestore.tests.django_utils import (
from
xmodule.modulestore.tests.factories
import
(
from
xmodule.modulestore.tests.factories
import
(
CourseFactory
,
CourseFactory
,
ItemFactory
,
ItemFactory
,
SampleCourseFactory
,
)
)
from
ccx_keys.locator
import
CCXLocator
from
ccx_keys.locator
import
CCXLocator
...
@@ -1049,9 +1050,9 @@ class CCXCoachTabTestCase(SharedModuleStoreTestCase):
...
@@ -1049,9 +1050,9 @@ class CCXCoachTabTestCase(SharedModuleStoreTestCase):
)
)
class
TestStudent
Dashboard
WithCCX
(
ModuleStoreTestCase
):
class
TestStudent
Views
WithCCX
(
ModuleStoreTestCase
):
"""
"""
Test to ensure that the student dashboard works for users enrolled in CCX
Test to ensure that the student dashboard
and courseware
works for users enrolled in CCX
courses.
courses.
"""
"""
...
@@ -1059,13 +1060,13 @@ class TestStudentDashboardWithCCX(ModuleStoreTestCase):
...
@@ -1059,13 +1060,13 @@ class TestStudentDashboardWithCCX(ModuleStoreTestCase):
"""
"""
Set up courses and enrollments.
Set up courses and enrollments.
"""
"""
super
(
TestStudent
Dashboard
WithCCX
,
self
)
.
setUp
()
super
(
TestStudent
Views
WithCCX
,
self
)
.
setUp
()
# Create a Draft Mongo and a Split Mongo course and enroll a student user in them.
# Create a Draft Mongo and a Split Mongo course and enroll a student user in them.
self
.
student_password
=
"foobar"
self
.
student_password
=
"foobar"
self
.
student
=
UserFactory
.
create
(
username
=
"test"
,
password
=
self
.
student_password
,
is_staff
=
False
)
self
.
student
=
UserFactory
.
create
(
username
=
"test"
,
password
=
self
.
student_password
,
is_staff
=
False
)
self
.
draft_course
=
CourseFactory
.
create
(
default_store
=
ModuleStoreEnum
.
Type
.
mongo
)
self
.
draft_course
=
Sample
CourseFactory
.
create
(
default_store
=
ModuleStoreEnum
.
Type
.
mongo
)
self
.
split_course
=
CourseFactory
.
create
(
default_store
=
ModuleStoreEnum
.
Type
.
split
)
self
.
split_course
=
Sample
CourseFactory
.
create
(
default_store
=
ModuleStoreEnum
.
Type
.
split
)
CourseEnrollment
.
enroll
(
self
.
student
,
self
.
draft_course
.
id
)
CourseEnrollment
.
enroll
(
self
.
student
,
self
.
draft_course
.
id
)
CourseEnrollment
.
enroll
(
self
.
student
,
self
.
split_course
.
id
)
CourseEnrollment
.
enroll
(
self
.
student
,
self
.
split_course
.
id
)
...
@@ -1078,11 +1079,16 @@ class TestStudentDashboardWithCCX(ModuleStoreTestCase):
...
@@ -1078,11 +1079,16 @@ class TestStudentDashboardWithCCX(ModuleStoreTestCase):
self
.
ccx
=
CcxFactory
(
course_id
=
self
.
split_course
.
id
,
coach
=
self
.
coach
)
self
.
ccx
=
CcxFactory
(
course_id
=
self
.
split_course
.
id
,
coach
=
self
.
coach
)
last_week
=
datetime
.
datetime
.
now
(
UTC
())
-
datetime
.
timedelta
(
days
=
7
)
last_week
=
datetime
.
datetime
.
now
(
UTC
())
-
datetime
.
timedelta
(
days
=
7
)
override_field_for_ccx
(
self
.
ccx
,
self
.
split_course
,
'start'
,
last_week
)
# Required by self.ccx.has_started().
override_field_for_ccx
(
self
.
ccx
,
self
.
split_course
,
'start'
,
last_week
)
# Required by self.ccx.has_started().
course_key
=
CCXLocator
.
from_course_locator
(
self
.
split_course
.
id
,
self
.
ccx
.
id
)
self
.
ccx_
course_key
=
CCXLocator
.
from_course_locator
(
self
.
split_course
.
id
,
self
.
ccx
.
id
)
CourseEnrollment
.
enroll
(
self
.
student
,
course_key
)
CourseEnrollment
.
enroll
(
self
.
student
,
self
.
ccx_
course_key
)
def
test_load_student_dashboard
(
self
):
def
test_load_student_dashboard
(
self
):
self
.
client
.
login
(
username
=
self
.
student
.
username
,
password
=
self
.
student_password
)
self
.
client
.
login
(
username
=
self
.
student
.
username
,
password
=
self
.
student_password
)
response
=
self
.
client
.
get
(
reverse
(
'dashboard'
))
response
=
self
.
client
.
get
(
reverse
(
'dashboard'
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertTrue
(
re
.
search
(
'Test CCX'
,
response
.
content
))
self
.
assertTrue
(
re
.
search
(
'Test CCX'
,
response
.
content
))
def
test_load_courseware
(
self
):
self
.
client
.
login
(
username
=
self
.
student
.
username
,
password
=
self
.
student_password
)
response
=
self
.
client
.
get
(
reverse
(
'courseware'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
ccx_course_key
)}))
self
.
assertEqual
(
response
.
status_code
,
200
)
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