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
a5b10ca0
Commit
a5b10ca0
authored
Mar 21, 2016
by
Renzo Lucioni
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #11805 from edx/renzo/self-paced-modulestore-wrapper
Override field data within the XBlock runtime
parents
7eb079df
cd9986b6
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
338 additions
and
124 deletions
+338
-124
cms/envs/aws.py
+11
-0
cms/envs/common.py
+7
-0
common/lib/xmodule/xmodule/modulestore/__init__.py
+2
-1
common/lib/xmodule/xmodule/modulestore/django.py
+20
-3
common/lib/xmodule/xmodule/modulestore/inheritance.py
+11
-0
common/lib/xmodule/xmodule/modulestore/mongo/base.py
+13
-12
common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py
+6
-0
lms/djangoapps/courseware/field_overrides.py
+126
-70
lms/djangoapps/courseware/self_paced_overrides.py
+3
-2
lms/djangoapps/courseware/tests/test_field_overrides.py
+50
-19
lms/djangoapps/courseware/tests/test_self_paced_overrides.py
+67
-16
lms/envs/aws.py
+15
-1
lms/envs/common.py
+7
-0
No files found.
cms/envs/aws.py
View file @
a5b10ca0
...
@@ -283,6 +283,17 @@ else:
...
@@ -283,6 +283,17 @@ else:
DATABASES
=
AUTH_TOKENS
[
'DATABASES'
]
DATABASES
=
AUTH_TOKENS
[
'DATABASES'
]
MODULESTORE
=
convert_module_store_setting_if_needed
(
AUTH_TOKENS
.
get
(
'MODULESTORE'
,
MODULESTORE
))
MODULESTORE
=
convert_module_store_setting_if_needed
(
AUTH_TOKENS
.
get
(
'MODULESTORE'
,
MODULESTORE
))
MODULESTORE_FIELD_OVERRIDE_PROVIDERS
=
ENV_TOKENS
.
get
(
'MODULESTORE_FIELD_OVERRIDE_PROVIDERS'
,
MODULESTORE_FIELD_OVERRIDE_PROVIDERS
)
XBLOCK_FIELD_DATA_WRAPPERS
=
ENV_TOKENS
.
get
(
'XBLOCK_FIELD_DATA_WRAPPERS'
,
XBLOCK_FIELD_DATA_WRAPPERS
)
CONTENTSTORE
=
AUTH_TOKENS
[
'CONTENTSTORE'
]
CONTENTSTORE
=
AUTH_TOKENS
[
'CONTENTSTORE'
]
DOC_STORE_CONFIG
=
AUTH_TOKENS
[
'DOC_STORE_CONFIG'
]
DOC_STORE_CONFIG
=
AUTH_TOKENS
[
'DOC_STORE_CONFIG'
]
# Datadog for events!
# Datadog for events!
...
...
cms/envs/common.py
View file @
a5b10ca0
...
@@ -383,6 +383,9 @@ XBLOCK_MIXINS = (
...
@@ -383,6 +383,9 @@ XBLOCK_MIXINS = (
XBLOCK_SELECT_FUNCTION
=
prefer_xmodules
XBLOCK_SELECT_FUNCTION
=
prefer_xmodules
# Paths to wrapper methods which should be applied to every XBlock's FieldData.
XBLOCK_FIELD_DATA_WRAPPERS
=
()
############################ Modulestore Configuration ################################
############################ Modulestore Configuration ################################
MODULESTORE_BRANCH
=
'draft-preferred'
MODULESTORE_BRANCH
=
'draft-preferred'
...
@@ -417,6 +420,10 @@ MODULESTORE = {
...
@@ -417,6 +420,10 @@ MODULESTORE = {
}
}
}
}
# Modulestore-level field override providers. These field override providers don't
# require student context.
MODULESTORE_FIELD_OVERRIDE_PROVIDERS
=
()
#################### Python sandbox ############################################
#################### Python sandbox ############################################
CODE_JAIL
=
{
CODE_JAIL
=
{
...
...
common/lib/xmodule/xmodule/modulestore/__init__.py
View file @
a5b10ca0
...
@@ -1160,7 +1160,7 @@ class ModuleStoreReadBase(BulkOperationsMixin, ModuleStoreRead):
...
@@ -1160,7 +1160,7 @@ class ModuleStoreReadBase(BulkOperationsMixin, ModuleStoreRead):
contentstore
=
None
,
contentstore
=
None
,
doc_store_config
=
None
,
# ignore if passed up
doc_store_config
=
None
,
# ignore if passed up
metadata_inheritance_cache_subsystem
=
None
,
request_cache
=
None
,
metadata_inheritance_cache_subsystem
=
None
,
request_cache
=
None
,
xblock_mixins
=
(),
xblock_select
=
None
,
disabled_xblock_types
=
(),
# pylint: disable=bad-continuation
xblock_mixins
=
(),
xblock_select
=
None
,
xblock_field_data_wrappers
=
(),
disabled_xblock_types
=
(),
# pylint: disable=bad-continuation
# temporary parms to enable backward compatibility. remove once all envs migrated
# temporary parms to enable backward compatibility. remove once all envs migrated
db
=
None
,
collection
=
None
,
host
=
None
,
port
=
None
,
tz_aware
=
True
,
user
=
None
,
password
=
None
,
db
=
None
,
collection
=
None
,
host
=
None
,
port
=
None
,
tz_aware
=
True
,
user
=
None
,
password
=
None
,
# allow lower level init args to pass harmlessly
# allow lower level init args to pass harmlessly
...
@@ -1177,6 +1177,7 @@ class ModuleStoreReadBase(BulkOperationsMixin, ModuleStoreRead):
...
@@ -1177,6 +1177,7 @@ class ModuleStoreReadBase(BulkOperationsMixin, ModuleStoreRead):
self
.
request_cache
=
request_cache
self
.
request_cache
=
request_cache
self
.
xblock_mixins
=
xblock_mixins
self
.
xblock_mixins
=
xblock_mixins
self
.
xblock_select
=
xblock_select
self
.
xblock_select
=
xblock_select
self
.
xblock_field_data_wrappers
=
xblock_field_data_wrappers
self
.
disabled_xblock_types
=
disabled_xblock_types
self
.
disabled_xblock_types
=
disabled_xblock_types
self
.
contentstore
=
contentstore
self
.
contentstore
=
contentstore
...
...
common/lib/xmodule/xmodule/modulestore/django.py
View file @
a5b10ca0
...
@@ -120,11 +120,25 @@ def load_function(path):
...
@@ -120,11 +120,25 @@ def load_function(path):
"""
"""
Load a function by name.
Load a function by name.
path is a string of the form "path.to.module.function"
Arguments:
returns the imported python object `function` from `path.to.module`
path: String of the form 'path.to.module.function'. Strings of the form
'path.to.module:Class.function' are also valid.
Returns:
The imported object 'function'.
"""
"""
if
':'
in
path
:
module_path
,
_
,
method_path
=
path
.
rpartition
(
':'
)
module
=
import_module
(
module_path
)
class_name
,
method_name
=
method_path
.
split
(
'.'
)
_class
=
getattr
(
module
,
class_name
)
function
=
getattr
(
_class
,
method_name
)
else
:
module_path
,
_
,
name
=
path
.
rpartition
(
'.'
)
module_path
,
_
,
name
=
path
.
rpartition
(
'.'
)
return
getattr
(
import_module
(
module_path
),
name
)
function
=
getattr
(
import_module
(
module_path
),
name
)
return
function
def
create_modulestore_instance
(
def
create_modulestore_instance
(
...
@@ -179,12 +193,15 @@ def create_modulestore_instance(
...
@@ -179,12 +193,15 @@ def create_modulestore_instance(
else
:
else
:
disabled_xblock_types
=
()
disabled_xblock_types
=
()
xblock_field_data_wrappers
=
[
load_function
(
path
)
for
path
in
settings
.
XBLOCK_FIELD_DATA_WRAPPERS
]
return
class_
(
return
class_
(
contentstore
=
content_store
,
contentstore
=
content_store
,
metadata_inheritance_cache_subsystem
=
metadata_inheritance_cache
,
metadata_inheritance_cache_subsystem
=
metadata_inheritance_cache
,
request_cache
=
request_cache
,
request_cache
=
request_cache
,
xblock_mixins
=
getattr
(
settings
,
'XBLOCK_MIXINS'
,
()),
xblock_mixins
=
getattr
(
settings
,
'XBLOCK_MIXINS'
,
()),
xblock_select
=
getattr
(
settings
,
'XBLOCK_SELECT_FUNCTION'
,
None
),
xblock_select
=
getattr
(
settings
,
'XBLOCK_SELECT_FUNCTION'
,
None
),
xblock_field_data_wrappers
=
xblock_field_data_wrappers
,
disabled_xblock_types
=
disabled_xblock_types
,
disabled_xblock_types
=
disabled_xblock_types
,
doc_store_config
=
doc_store_config
,
doc_store_config
=
doc_store_config
,
i18n_service
=
i18n_service
or
ModuleI18nService
(),
i18n_service
=
i18n_service
or
ModuleI18nService
(),
...
...
common/lib/xmodule/xmodule/modulestore/inheritance.py
View file @
a5b10ca0
...
@@ -218,6 +218,17 @@ class InheritanceMixin(XBlockMixin):
...
@@ -218,6 +218,17 @@ class InheritanceMixin(XBlockMixin):
default
=
False
default
=
False
)
)
self_paced
=
Boolean
(
display_name
=
_
(
'Self Paced'
),
help
=
_
(
'Set this to "true" to mark this course as self-paced. Self-paced courses do not have '
'due dates for assignments, and students can progress through the course at any rate before '
'the course ends.'
),
default
=
False
,
scope
=
Scope
.
settings
)
def
compute_inherited_metadata
(
descriptor
):
def
compute_inherited_metadata
(
descriptor
):
"""Given a descriptor, traverse all of its descendants and do metadata
"""Given a descriptor, traverse all of its descendants and do metadata
...
...
common/lib/xmodule/xmodule/modulestore/mongo/base.py
View file @
a5b10ca0
...
@@ -12,27 +12,24 @@ structure:
...
@@ -12,27 +12,24 @@ structure:
}
}
"""
"""
import
pymongo
import
sys
import
logging
import
copy
import
copy
from
datetime
import
datetime
from
importlib
import
import_module
import
logging
import
pymongo
import
re
import
re
import
sys
from
uuid
import
uuid4
from
uuid
import
uuid4
from
bson.son
import
SON
from
bson.son
import
SON
from
datetime
import
datetime
from
contracts
import
contract
,
new_contract
from
fs.osfs
import
OSFS
from
fs.osfs
import
OSFS
from
mongodb_proxy
import
autoretry_read
from
mongodb_proxy
import
autoretry_read
from
path
import
Path
as
path
from
pytz
import
UTC
from
contracts
import
contract
,
new_contract
from
importlib
import
import_module
from
opaque_keys.edx.keys
import
UsageKey
,
CourseKey
,
AssetKey
from
opaque_keys.edx.keys
import
UsageKey
,
CourseKey
,
AssetKey
from
opaque_keys.edx.locations
import
Location
,
BlockUsageLocator
from
opaque_keys.edx.locations
import
Location
,
BlockUsageLocator
,
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locator
import
CourseLocator
,
LibraryLocator
from
opaque_keys.edx.locator
import
CourseLocator
,
LibraryLocator
from
path
import
Path
as
path
from
pytz
import
UTC
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
xblock.exceptions
import
InvalidScopeError
from
xblock.exceptions
import
InvalidScopeError
from
xblock.fields
import
Scope
,
ScopeIds
,
Reference
,
ReferenceList
,
ReferenceValueDict
from
xblock.fields
import
Scope
,
ScopeIds
,
Reference
,
ReferenceList
,
ReferenceValueDict
...
@@ -54,6 +51,7 @@ from xmodule.modulestore.xml import CourseLocationManager
...
@@ -54,6 +51,7 @@ from xmodule.modulestore.xml import CourseLocationManager
from
xmodule.modulestore.store_utilities
import
DETACHED_XBLOCK_TYPES
from
xmodule.modulestore.store_utilities
import
DETACHED_XBLOCK_TYPES
from
xmodule.services
import
SettingsService
from
xmodule.services
import
SettingsService
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
new_contract
(
'CourseKey'
,
CourseKey
)
new_contract
(
'CourseKey'
,
CourseKey
)
...
@@ -318,6 +316,9 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
...
@@ -318,6 +316,9 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
)
.
replace
(
tzinfo
=
UTC
)
)
.
replace
(
tzinfo
=
UTC
)
module
.
_edit_info
[
'published_by'
]
=
raw_metadata
.
get
(
'published_by'
)
module
.
_edit_info
[
'published_by'
]
=
raw_metadata
.
get
(
'published_by'
)
for
wrapper
in
self
.
modulestore
.
xblock_field_data_wrappers
:
module
.
_field_data
=
wrapper
(
module
,
module
.
_field_data
)
# pylint: disable=protected-access
# decache any computed pending field settings
# decache any computed pending field settings
module
.
save
()
module
.
save
()
return
module
return
module
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py
View file @
a5b10ca0
import
sys
import
sys
import
logging
import
logging
from
contracts
import
contract
,
new_contract
from
contracts
import
contract
,
new_contract
from
fs.osfs
import
OSFS
from
fs.osfs
import
OSFS
from
lazy
import
lazy
from
lazy
import
lazy
...
@@ -7,6 +8,7 @@ from xblock.runtime import KvsFieldData, KeyValueStore
...
@@ -7,6 +8,7 @@ from xblock.runtime import KvsFieldData, KeyValueStore
from
xblock.fields
import
ScopeIds
from
xblock.fields
import
ScopeIds
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
opaque_keys.edx.locator
import
BlockUsageLocator
,
LocalId
,
CourseLocator
,
LibraryLocator
,
DefinitionLocator
from
opaque_keys.edx.locator
import
BlockUsageLocator
,
LocalId
,
CourseLocator
,
LibraryLocator
,
DefinitionLocator
from
xmodule.library_tools
import
LibraryToolsService
from
xmodule.library_tools
import
LibraryToolsService
from
xmodule.mako_module
import
MakoDescriptorSystem
from
xmodule.mako_module
import
MakoDescriptorSystem
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.error_module
import
ErrorDescriptor
...
@@ -263,6 +265,10 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
...
@@ -263,6 +265,10 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
module
.
update_version
=
edit_info
.
update_version
module
.
update_version
=
edit_info
.
update_version
module
.
source_version
=
edit_info
.
source_version
module
.
source_version
=
edit_info
.
source_version
module
.
definition_locator
=
DefinitionLocator
(
block_key
.
type
,
definition_id
)
module
.
definition_locator
=
DefinitionLocator
(
block_key
.
type
,
definition_id
)
for
wrapper
in
self
.
modulestore
.
xblock_field_data_wrappers
:
module
.
_field_data
=
wrapper
(
module
,
module
.
_field_data
)
# pylint: disable=protected-access
# decache any pending field settings
# decache any pending field settings
module
.
save
()
module
.
save
()
...
...
lms/djangoapps/courseware/field_overrides.py
View file @
a5b10ca0
...
@@ -14,17 +14,20 @@ package and is used to wrap the `authored_data` when constructing an
...
@@ -14,17 +14,20 @@ package and is used to wrap the `authored_data` when constructing an
`LmsFieldData`. This means overrides will be in effect for all scopes covered
`LmsFieldData`. This means overrides will be in effect for all scopes covered
by `authored_data`, e.g. course content and settings stored in Mongo.
by `authored_data`, e.g. course content and settings stored in Mongo.
"""
"""
import
threading
from
abc
import
ABCMeta
,
abstractmethod
from
abc
import
ABCMeta
,
abstractmethod
from
contextlib
import
contextmanager
from
contextlib
import
contextmanager
import
threading
from
django.conf
import
settings
from
django.conf
import
settings
from
request_cache.middleware
import
RequestCache
from
xblock.field_data
import
FieldData
from
xblock.field_data
import
FieldData
from
request_cache.middleware
import
RequestCache
from
xmodule.modulestore.inheritance
import
InheritanceMixin
from
xmodule.modulestore.inheritance
import
InheritanceMixin
NOTSET
=
object
()
NOTSET
=
object
()
ENABLED_OVERRIDE_PROVIDERS_KEY
=
"courseware.field_overrides.enabled_providers.{course_id}"
ENABLED_OVERRIDE_PROVIDERS_KEY
=
u'courseware.field_overrides.enabled_providers.{course_id}'
ENABLED_MODULESTORE_OVERRIDE_PROVIDERS_KEY
=
u'courseware.modulestore_field_overrides.enabled_providers.{course_id}'
def
resolve_dotted
(
name
):
def
resolve_dotted
(
name
):
...
@@ -46,6 +49,88 @@ def resolve_dotted(name):
...
@@ -46,6 +49,88 @@ def resolve_dotted(name):
return
target
return
target
def
_lineage
(
block
):
"""
Returns an iterator over all ancestors of the given block, starting with
its immediate parent and ending at the root of the block tree.
"""
parent
=
block
.
get_parent
()
while
parent
:
yield
parent
parent
=
parent
.
get_parent
()
class
_OverridesDisabled
(
threading
.
local
):
"""
A thread local used to manage state of overrides being disabled or not.
"""
disabled
=
()
_OVERRIDES_DISABLED
=
_OverridesDisabled
()
@contextmanager
def
disable_overrides
():
"""
A context manager which disables field overrides inside the context of a
`with` statement, allowing code to get at the `original` value of a field.
"""
prev
=
_OVERRIDES_DISABLED
.
disabled
_OVERRIDES_DISABLED
.
disabled
+=
(
True
,)
yield
_OVERRIDES_DISABLED
.
disabled
=
prev
def
overrides_disabled
():
"""
Checks to see whether overrides are disabled in the current context.
Returns a boolean value. See `disable_overrides`.
"""
return
bool
(
_OVERRIDES_DISABLED
.
disabled
)
class
FieldOverrideProvider
(
object
):
"""
Abstract class which defines the interface that a `FieldOverrideProvider`
must provide. In general, providers should derive from this class, but
it's not strictly necessary as long as they correctly implement this
interface.
A `FieldOverrideProvider` implementation is only responsible for looking up
field overrides. To set overrides, there will be a domain specific API for
the concrete override implementation being used.
"""
__metaclass__
=
ABCMeta
def
__init__
(
self
,
user
):
self
.
user
=
user
@abstractmethod
def
get
(
self
,
block
,
name
,
default
):
# pragma no cover
"""
Look for an override value for the field named `name` in `block`.
Returns the overridden value or `default` if no override is found.
"""
raise
NotImplementedError
@abstractmethod
def
enabled_for
(
self
,
course
):
# pragma no cover
"""
Return True if this provider should be enabled for a given course,
and False otherwise.
Concrete implementations are responsible for implementing this method.
Arguments:
course (CourseModule or None)
Returns:
bool
"""
return
False
class
OverrideFieldData
(
FieldData
):
class
OverrideFieldData
(
FieldData
):
"""
"""
A :class:`~xblock.field_data.FieldData` which wraps another `FieldData`
A :class:`~xblock.field_data.FieldData` which wraps another `FieldData`
...
@@ -171,83 +256,54 @@ class OverrideFieldData(FieldData):
...
@@ -171,83 +256,54 @@ class OverrideFieldData(FieldData):
return
self
.
fallback
.
default
(
block
,
name
)
return
self
.
fallback
.
default
(
block
,
name
)
class
_OverridesDisabled
(
threading
.
local
):
class
OverrideModulestoreFieldData
(
OverrideFieldData
):
"""
"""Apply field data overrides at the modulestore level. No student context required."""
A thread local used to manage state of overrides being disabled or not.
"""
disabled
=
()
_OVERRIDES_DISABLED
=
_OverridesDisabled
()
@contextmanager
@classmethod
def
disable_overrides
():
def
wrap
(
cls
,
block
,
field_data
):
# pylint: disable=arguments-differ
"""
A context manager which disables field overrides inside the context of a
`with` statement, allowing code to get at the `original` value of a field.
"""
prev
=
_OVERRIDES_DISABLED
.
disabled
_OVERRIDES_DISABLED
.
disabled
+=
(
True
,)
yield
_OVERRIDES_DISABLED
.
disabled
=
prev
def
overrides_disabled
():
"""
Checks to see whether overrides are disabled in the current context.
Returns a boolean value. See `disable_overrides`.
"""
"""
return
bool
(
_OVERRIDES_DISABLED
.
disabled
)
Returns an instance of FieldData wrapped by FieldOverrideProviders which
extend read-only functionality. If no MODULESTORE_FIELD_OVERRIDE_PROVIDERS
are configured, an unwrapped FieldData instance is returned.
Arguments:
class
FieldOverrideProvider
(
object
):
block: An XBlock
field_data: An instance of FieldData to be wrapped
"""
"""
Abstract class which defines the interface that a `FieldOverrideProvider`
if
cls
.
provider_classes
is
None
:
must provide. In general, providers should derive from this class, but
cls
.
provider_classes
=
[
it's not strictly necessary as long as they correctly implement this
resolve_dotted
(
name
)
for
name
in
settings
.
MODULESTORE_FIELD_OVERRIDE_PROVIDERS
interface.
]
A `FieldOverrideProvider` implementation is only responsible for looking up
enabled_providers
=
cls
.
_providers_for_block
(
block
)
field overrides. To set overrides, there will be a domain specific API for
if
enabled_providers
:
the concrete override implementation being used.
return
cls
(
field_data
,
enabled_providers
)
"""
__metaclass__
=
ABCMeta
def
__init__
(
self
,
user
):
return
field_data
self
.
user
=
user
@abstractmethod
@classmethod
def
get
(
self
,
block
,
name
,
default
):
# pragma no cover
def
_providers_for_block
(
cls
,
block
):
"""
Look for an override value for the field named `name` in `block`.
Returns the overridden value or `default` if no override is found.
"""
"""
raise
NotImplementedError
Computes a list of enabled providers based on the given XBlock.
The result is cached per request to avoid the overhead incurred
by filtering override providers hundreds of times.
@abstractmethod
Arguments:
def
enabled_for
(
self
,
course
):
# pragma no cover
block: An XBlock
"""
"""
Return True if this provider should be enabled for a given course,
course_id
=
unicode
(
block
.
location
.
course_key
)
and False otherwise.
cache_key
=
ENABLED_MODULESTORE_OVERRIDE_PROVIDERS_KEY
.
format
(
course_id
=
course_id
)
Concrete implementations are responsible for implementing this method.
request_cache
=
RequestCache
.
get_request_cache
()
enabled_providers
=
request_cache
.
data
.
get
(
cache_key
)
Arguments:
course (CourseModule or None)
Returns:
if
enabled_providers
is
None
:
bool
enabled_providers
=
[
"""
provider_class
for
provider_class
in
cls
.
provider_classes
if
provider_class
.
enabled_for
(
block
)
return
False
]
request_cache
.
data
[
cache_key
]
=
enabled_providers
return
enabled_providers
def
_lineage
(
block
):
def
__init__
(
self
,
fallback
,
providers
):
"""
super
(
OverrideModulestoreFieldData
,
self
)
.
__init__
(
None
,
fallback
,
providers
)
Returns an iterator over all ancestors of the given block, starting with
its immediate parent and ending at the root of the block tree.
"""
parent
=
block
.
get_parent
()
while
parent
:
yield
parent
parent
=
parent
.
get_parent
()
lms/djangoapps/courseware/self_paced_overrides.py
View file @
a5b10ca0
...
@@ -20,9 +20,10 @@ class SelfPacedDateOverrideProvider(FieldOverrideProvider):
...
@@ -20,9 +20,10 @@ class SelfPacedDateOverrideProvider(FieldOverrideProvider):
# Remove release dates for course content
# Remove release dates for course content
if
name
==
'start'
and
block
.
category
!=
'course'
:
if
name
==
'start'
and
block
.
category
!=
'course'
:
return
None
return
None
return
default
return
default
@classmethod
@classmethod
def
enabled_for
(
cls
,
course
):
def
enabled_for
(
cls
,
block
):
"""This provider is enabled for self-paced courses only."""
"""This provider is enabled for self-paced courses only."""
return
course
is
not
None
and
course
.
self_paced
and
SelfPacedConfiguration
.
current
()
.
enabled
return
block
is
not
None
and
block
.
self_paced
and
SelfPacedConfiguration
.
current
()
.
enabled
lms/djangoapps/courseware/tests/test_field_overrides.py
View file @
a5b10ca0
"""
"""
Tests for `field_overrides` module.
Tests for `field_overrides` module.
"""
"""
# pylint: disable=missing-docstring
import
unittest
import
unittest
from
nose.plugins.attrib
import
attr
from
nose.plugins.attrib
import
attr
...
@@ -10,16 +11,39 @@ from xmodule.modulestore.tests.factories import CourseFactory
...
@@ -10,16 +11,39 @@ from xmodule.modulestore.tests.factories import CourseFactory
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
..field_overrides
import
(
from
..field_overrides
import
(
resolve_dotted
,
disable_overrides
,
disable_overrides
,
FieldOverrideProvider
,
FieldOverrideProvider
,
OverrideFieldData
,
OverrideFieldData
,
resolve_dotted
,
OverrideModulestoreFieldData
,
)
)
TESTUSER
=
"testuser"
TESTUSER
=
"testuser"
class
TestOverrideProvider
(
FieldOverrideProvider
):
"""
A concrete implementation of `FieldOverrideProvider` for testing.
"""
def
get
(
self
,
block
,
name
,
default
):
if
self
.
user
:
assert
self
.
user
is
TESTUSER
assert
block
==
'block'
if
name
==
'foo'
:
return
'fu'
elif
name
==
'oh'
:
return
'man'
return
default
@classmethod
def
enabled_for
(
cls
,
course
):
return
True
@attr
(
'shard_1'
)
@attr
(
'shard_1'
)
@override_settings
(
FIELD_OVERRIDE_PROVIDERS
=
(
@override_settings
(
FIELD_OVERRIDE_PROVIDERS
=
(
'courseware.tests.test_field_overrides.TestOverrideProvider'
,))
'courseware.tests.test_field_overrides.TestOverrideProvider'
,))
...
@@ -101,6 +125,31 @@ class OverrideFieldDataTests(SharedModuleStoreTestCase):
...
@@ -101,6 +125,31 @@ class OverrideFieldDataTests(SharedModuleStoreTestCase):
@attr
(
'shard_1'
)
@attr
(
'shard_1'
)
@override_settings
(
MODULESTORE_FIELD_OVERRIDE_PROVIDERS
=
[
'courseware.tests.test_field_overrides.TestOverrideProvider'
]
)
class
OverrideModulestoreFieldDataTests
(
OverrideFieldDataTests
):
def
setUp
(
self
):
super
(
OverrideModulestoreFieldDataTests
,
self
)
.
setUp
()
OverrideModulestoreFieldData
.
provider_classes
=
None
def
tearDown
(
self
):
super
(
OverrideModulestoreFieldDataTests
,
self
)
.
tearDown
()
OverrideModulestoreFieldData
.
provider_classes
=
None
def
make_one
(
self
):
return
OverrideModulestoreFieldData
.
wrap
(
self
.
course
,
DictFieldData
({
'foo'
:
'bar'
,
'bees'
:
'knees'
,
}))
@override_settings
(
MODULESTORE_FIELD_OVERRIDE_PROVIDERS
=
[])
def
test_no_overrides_configured
(
self
):
data
=
self
.
make_one
()
self
.
assertIsInstance
(
data
,
DictFieldData
)
@attr
(
'shard_1'
)
class
ResolveDottedTests
(
unittest
.
TestCase
):
class
ResolveDottedTests
(
unittest
.
TestCase
):
"""
"""
Tests for `resolve_dotted`.
Tests for `resolve_dotted`.
...
@@ -121,24 +170,6 @@ class ResolveDottedTests(unittest.TestCase):
...
@@ -121,24 +170,6 @@ class ResolveDottedTests(unittest.TestCase):
)
)
class
TestOverrideProvider
(
FieldOverrideProvider
):
"""
A concrete implementation of `FieldOverrideProvider` for testing.
"""
def
get
(
self
,
block
,
name
,
default
):
assert
self
.
user
is
TESTUSER
assert
block
==
'block'
if
name
==
'foo'
:
return
'fu'
if
name
==
'oh'
:
return
'man'
return
default
@classmethod
def
enabled_for
(
cls
,
course
):
return
True
def
inject_field_overrides
(
blocks
,
course
,
user
):
def
inject_field_overrides
(
blocks
,
course
,
user
):
"""
"""
Apparently the test harness doesn't use LmsFieldStorage, and I'm
Apparently the test harness doesn't use LmsFieldStorage, and I'm
...
...
lms/djangoapps/courseware/tests/test_self_paced_overrides.py
View file @
a5b10ca0
"""
"""Tests for self-paced course due date overrides."""
Tests for self-paced course due date overrides.
# pylint: disable=missing-docstring
"""
import
datetime
import
datetime
import
pytz
import
pytz
...
@@ -11,17 +9,17 @@ from mock import patch
...
@@ -11,17 +9,17 @@ from mock import patch
from
courseware.tests.factories
import
BetaTesterFactory
from
courseware.tests.factories
import
BetaTesterFactory
from
courseware.access
import
has_access
from
courseware.access
import
has_access
from
lms.djangoapps.ccx.tests.test_overrides
import
inject_field_overrides
from
lms.djangoapps.ccx.tests.test_overrides
import
inject_field_overrides
from
lms.djangoapps.courseware.field_overrides
import
OverrideFieldData
from
lms.djangoapps.django_comment_client.utils
import
get_accessible_discussion_modules
from
lms.djangoapps.courseware.field_overrides
import
OverrideFieldData
,
OverrideModulestoreFieldData
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
@override_settings
(
@override_settings
(
FIELD_OVERRIDE_PROVIDERS
=
(
'courseware.self_paced_overrides.SelfPacedDateOverrideProvider'
,)
XBLOCK_FIELD_DATA_WRAPPERS
=
[
'lms.djangoapps.courseware.field_overrides:OverrideModulestoreFieldData.wrap'
],
MODULESTORE_FIELD_OVERRIDE_PROVIDERS
=
[
'courseware.self_paced_overrides.SelfPacedDateOverrideProvider'
],
)
)
class
SelfPacedDateOverrideTest
(
ModuleStoreTestCase
):
class
SelfPacedDateOverrideTest
(
ModuleStoreTestCase
):
"""
"""
...
@@ -29,14 +27,19 @@ class SelfPacedDateOverrideTest(ModuleStoreTestCase):
...
@@ -29,14 +27,19 @@ class SelfPacedDateOverrideTest(ModuleStoreTestCase):
"""
"""
def
setUp
(
self
):
def
setUp
(
self
):
SelfPacedConfiguration
(
enabled
=
True
)
.
save
()
super
(
SelfPacedDateOverrideTest
,
self
)
.
setUp
()
super
(
SelfPacedDateOverrideTest
,
self
)
.
setUp
()
self
.
due_date
=
datetime
.
datetime
(
2015
,
5
,
26
,
8
,
30
,
00
)
.
replace
(
tzinfo
=
tzutc
())
SelfPacedConfiguration
(
enabled
=
True
)
.
save
()
self
.
non_staff_user
,
__
=
self
.
create_non_staff_user
()
self
.
non_staff_user
,
__
=
self
.
create_non_staff_user
()
self
.
now
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
.
replace
(
microsecond
=
0
)
self
.
future
=
self
.
now
+
datetime
.
timedelta
(
days
=
30
)
def
tearDown
(
self
):
def
tearDown
(
self
):
super
(
SelfPacedDateOverrideTest
,
self
)
.
tearDown
()
super
(
SelfPacedDateOverrideTest
,
self
)
.
tearDown
()
OverrideFieldData
.
provider_classes
=
None
OverrideFieldData
.
provider_classes
=
None
OverrideModulestoreFieldData
.
provider_classes
=
None
def
setup_course
(
self
,
**
course_kwargs
):
def
setup_course
(
self
,
**
course_kwargs
):
"""Set up a course with provided course attributes.
"""Set up a course with provided course attributes.
...
@@ -45,22 +48,39 @@ class SelfPacedDateOverrideTest(ModuleStoreTestCase):
...
@@ -45,22 +48,39 @@ class SelfPacedDateOverrideTest(ModuleStoreTestCase):
overrides are correctly applied for both blocks.
overrides are correctly applied for both blocks.
"""
"""
course
=
CourseFactory
.
create
(
**
course_kwargs
)
course
=
CourseFactory
.
create
(
**
course_kwargs
)
section
=
ItemFactory
.
create
(
parent
=
course
,
due
=
self
.
due_date
)
section
=
ItemFactory
.
create
(
parent
=
course
,
due
=
self
.
now
)
inject_field_overrides
((
course
,
section
),
course
,
self
.
user
)
inject_field_overrides
((
course
,
section
),
course
,
self
.
user
)
return
(
course
,
section
)
return
(
course
,
section
)
def
test_instructor_paced
(
self
):
def
create_discussion_modules
(
self
,
parent
):
# Create a released discussion module
ItemFactory
.
create
(
parent
=
parent
,
category
=
'discussion'
,
display_name
=
'released'
,
start
=
self
.
now
,
)
# Create a scheduled discussion module
ItemFactory
.
create
(
parent
=
parent
,
category
=
'discussion'
,
display_name
=
'scheduled'
,
start
=
self
.
future
,
)
def
test_instructor_paced_due_date
(
self
):
__
,
ip_section
=
self
.
setup_course
(
display_name
=
"Instructor Paced Course"
,
self_paced
=
False
)
__
,
ip_section
=
self
.
setup_course
(
display_name
=
"Instructor Paced Course"
,
self_paced
=
False
)
self
.
assertEqual
(
self
.
due_date
,
ip_section
.
due
)
self
.
assertEqual
(
ip_section
.
due
,
self
.
now
)
def
test_self_paced
(
self
):
def
test_self_paced
_due_date
(
self
):
__
,
sp_section
=
self
.
setup_course
(
display_name
=
"Self-Paced Course"
,
self_paced
=
True
)
__
,
sp_section
=
self
.
setup_course
(
display_name
=
"Self-Paced Course"
,
self_paced
=
True
)
self
.
assertIsNone
(
sp_section
.
due
)
self
.
assertIsNone
(
sp_section
.
due
)
def
test_self_paced_disabled
(
self
):
def
test_self_paced_disabled
_due_date
(
self
):
SelfPacedConfiguration
(
enabled
=
False
)
.
save
()
SelfPacedConfiguration
(
enabled
=
False
)
.
save
()
__
,
sp_section
=
self
.
setup_course
(
display_name
=
"Self-Paced Course"
,
self_paced
=
True
)
__
,
sp_section
=
self
.
setup_course
(
display_name
=
"Self-Paced Course"
,
self_paced
=
True
)
self
.
assertEqual
(
s
elf
.
due_date
,
sp_section
.
due
)
self
.
assertEqual
(
s
p_section
.
due
,
self
.
now
)
@patch.dict
(
'courseware.access.settings.FEATURES'
,
{
'DISABLE_START_DATES'
:
False
})
@patch.dict
(
'courseware.access.settings.FEATURES'
,
{
'DISABLE_START_DATES'
:
False
})
def
test_course_access_to_beta_users
(
self
):
def
test_course_access_to_beta_users
(
self
):
...
@@ -89,3 +109,34 @@ class SelfPacedDateOverrideTest(ModuleStoreTestCase):
...
@@ -89,3 +109,34 @@ class SelfPacedDateOverrideTest(ModuleStoreTestCase):
# Verify beta tester can access the course as well as the course sections
# Verify beta tester can access the course as well as the course sections
self
.
assertTrue
(
has_access
(
beta_tester
,
'load'
,
self_paced_course
))
self
.
assertTrue
(
has_access
(
beta_tester
,
'load'
,
self_paced_course
))
self
.
assertTrue
(
has_access
(
beta_tester
,
'load'
,
self_paced_section
,
self_paced_course
.
id
))
self
.
assertTrue
(
has_access
(
beta_tester
,
'load'
,
self_paced_section
,
self_paced_course
.
id
))
@patch.dict
(
'courseware.access.settings.FEATURES'
,
{
'DISABLE_START_DATES'
:
False
})
def
test_instructor_paced_discussion_module_visibility
(
self
):
"""
Verify that discussion modules scheduled for release in the future are
not visible to students in an instructor-paced course.
"""
course
,
section
=
self
.
setup_course
(
start
=
self
.
now
,
self_paced
=
False
)
self
.
create_discussion_modules
(
section
)
# Only the released module should be visible when the course is instructor-paced.
modules
=
get_accessible_discussion_modules
(
course
,
self
.
non_staff_user
)
self
.
assertTrue
(
all
(
module
.
display_name
==
'released'
for
module
in
modules
)
)
@patch.dict
(
'courseware.access.settings.FEATURES'
,
{
'DISABLE_START_DATES'
:
False
})
def
test_self_paced_discussion_module_visibility
(
self
):
"""
Regression test. Verify that discussion modules scheduled for release
in the future are visible to students in a self-paced course.
"""
course
,
section
=
self
.
setup_course
(
start
=
self
.
now
,
self_paced
=
True
)
self
.
create_discussion_modules
(
section
)
# The scheduled module should be visible when the course is self-paced.
modules
=
get_accessible_discussion_modules
(
course
,
self
.
non_staff_user
)
self
.
assertEqual
(
len
(
modules
),
2
)
self
.
assertTrue
(
any
(
module
.
display_name
==
'scheduled'
for
module
in
modules
)
)
lms/envs/aws.py
View file @
a5b10ca0
...
@@ -406,6 +406,16 @@ if 'DJFS' in AUTH_TOKENS and AUTH_TOKENS['DJFS'] is not None:
...
@@ -406,6 +406,16 @@ if 'DJFS' in AUTH_TOKENS and AUTH_TOKENS['DJFS'] is not None:
############### Module Store Items ##########
############### Module Store Items ##########
HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS
=
ENV_TOKENS
.
get
(
'HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS'
,
{})
HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS
=
ENV_TOKENS
.
get
(
'HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS'
,
{})
MODULESTORE_FIELD_OVERRIDE_PROVIDERS
=
ENV_TOKENS
.
get
(
'MODULESTORE_FIELD_OVERRIDE_PROVIDERS'
,
MODULESTORE_FIELD_OVERRIDE_PROVIDERS
)
XBLOCK_FIELD_DATA_WRAPPERS
=
ENV_TOKENS
.
get
(
'XBLOCK_FIELD_DATA_WRAPPERS'
,
XBLOCK_FIELD_DATA_WRAPPERS
)
############### Mixed Related(Secure/Not-Secure) Items ##########
############### Mixed Related(Secure/Not-Secure) Items ##########
LMS_SEGMENT_KEY
=
AUTH_TOKENS
.
get
(
'SEGMENT_KEY'
)
LMS_SEGMENT_KEY
=
AUTH_TOKENS
.
get
(
'SEGMENT_KEY'
)
...
@@ -693,7 +703,11 @@ if FEATURES.get('INDIVIDUAL_DUE_DATES'):
...
@@ -693,7 +703,11 @@ if FEATURES.get('INDIVIDUAL_DUE_DATES'):
)
)
##### Self-Paced Course Due Dates #####
##### Self-Paced Course Due Dates #####
FIELD_OVERRIDE_PROVIDERS
+=
(
XBLOCK_FIELD_DATA_WRAPPERS
+=
(
'lms.djangoapps.courseware.field_overrides:OverrideModulestoreFieldData.wrap'
,
)
MODULESTORE_FIELD_OVERRIDE_PROVIDERS
+=
(
'courseware.self_paced_overrides.SelfPacedDateOverrideProvider'
,
'courseware.self_paced_overrides.SelfPacedDateOverrideProvider'
,
)
)
...
...
lms/envs/common.py
View file @
a5b10ca0
...
@@ -694,6 +694,9 @@ XBLOCK_MIXINS = (LmsBlockMixin, InheritanceMixin, XModuleMixin, EditInfoMixin)
...
@@ -694,6 +694,9 @@ XBLOCK_MIXINS = (LmsBlockMixin, InheritanceMixin, XModuleMixin, EditInfoMixin)
# Allow any XBlock in the LMS
# Allow any XBlock in the LMS
XBLOCK_SELECT_FUNCTION
=
prefer_xmodules
XBLOCK_SELECT_FUNCTION
=
prefer_xmodules
# Paths to wrapper methods which should be applied to every XBlock's FieldData.
XBLOCK_FIELD_DATA_WRAPPERS
=
()
############# ModuleStore Configuration ##########
############# ModuleStore Configuration ##########
MODULESTORE_BRANCH
=
'published-only'
MODULESTORE_BRANCH
=
'published-only'
...
@@ -2644,6 +2647,10 @@ CHECKPOINT_PATTERN = r'(?P<checkpoint_name>[^/]+)'
...
@@ -2644,6 +2647,10 @@ CHECKPOINT_PATTERN = r'(?P<checkpoint_name>[^/]+)'
# this setting.
# this setting.
FIELD_OVERRIDE_PROVIDERS
=
()
FIELD_OVERRIDE_PROVIDERS
=
()
# Modulestore-level field override providers. These field override providers don't
# require student context.
MODULESTORE_FIELD_OVERRIDE_PROVIDERS
=
()
# PROFILE IMAGE CONFIG
# PROFILE IMAGE CONFIG
# WARNING: Certain django storage backends do not support atomic
# WARNING: Certain django storage backends do not support atomic
# file overwrites (including the default, OverwriteStorage) - instead
# file overwrites (including the default, OverwriteStorage) - instead
...
...
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