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
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
342 additions
and
128 deletions
+342
-128
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
+21
-4
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
+128
-72
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
+16
-2
lms/envs/common.py
+7
-0
No files found.
cms/envs/aws.py
View file @
a5b10ca0
...
...
@@ -283,6 +283,17 @@ else:
DATABASES
=
AUTH_TOKENS
[
'DATABASES'
]
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'
]
DOC_STORE_CONFIG
=
AUTH_TOKENS
[
'DOC_STORE_CONFIG'
]
# Datadog for events!
...
...
cms/envs/common.py
View file @
a5b10ca0
...
...
@@ -383,6 +383,9 @@ XBLOCK_MIXINS = (
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_BRANCH
=
'draft-preferred'
...
...
@@ -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 ############################################
CODE_JAIL
=
{
...
...
common/lib/xmodule/xmodule/modulestore/__init__.py
View file @
a5b10ca0
...
...
@@ -1160,7 +1160,7 @@ class ModuleStoreReadBase(BulkOperationsMixin, ModuleStoreRead):
contentstore
=
None
,
doc_store_config
=
None
,
# ignore if passed up
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
db
=
None
,
collection
=
None
,
host
=
None
,
port
=
None
,
tz_aware
=
True
,
user
=
None
,
password
=
None
,
# allow lower level init args to pass harmlessly
...
...
@@ -1177,6 +1177,7 @@ class ModuleStoreReadBase(BulkOperationsMixin, ModuleStoreRead):
self
.
request_cache
=
request_cache
self
.
xblock_mixins
=
xblock_mixins
self
.
xblock_select
=
xblock_select
self
.
xblock_field_data_wrappers
=
xblock_field_data_wrappers
self
.
disabled_xblock_types
=
disabled_xblock_types
self
.
contentstore
=
contentstore
...
...
common/lib/xmodule/xmodule/modulestore/django.py
View file @
a5b10ca0
...
...
@@ -120,11 +120,25 @@ def load_function(path):
"""
Load a function by name.
path is a string of the form "path.to.module.function"
returns the imported python object `function` from `path.to.module`
Arguments:
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'.
"""
module_path
,
_
,
name
=
path
.
rpartition
(
'.'
)
return
getattr
(
import_module
(
module_path
),
name
)
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
(
'.'
)
function
=
getattr
(
import_module
(
module_path
),
name
)
return
function
def
create_modulestore_instance
(
...
...
@@ -179,12 +193,15 @@ def create_modulestore_instance(
else
:
disabled_xblock_types
=
()
xblock_field_data_wrappers
=
[
load_function
(
path
)
for
path
in
settings
.
XBLOCK_FIELD_DATA_WRAPPERS
]
return
class_
(
contentstore
=
content_store
,
metadata_inheritance_cache_subsystem
=
metadata_inheritance_cache
,
request_cache
=
request_cache
,
xblock_mixins
=
getattr
(
settings
,
'XBLOCK_MIXINS'
,
()),
xblock_select
=
getattr
(
settings
,
'XBLOCK_SELECT_FUNCTION'
,
None
),
xblock_field_data_wrappers
=
xblock_field_data_wrappers
,
disabled_xblock_types
=
disabled_xblock_types
,
doc_store_config
=
doc_store_config
,
i18n_service
=
i18n_service
or
ModuleI18nService
(),
...
...
common/lib/xmodule/xmodule/modulestore/inheritance.py
View file @
a5b10ca0
...
...
@@ -218,6 +218,17 @@ class InheritanceMixin(XBlockMixin):
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
):
"""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:
}
"""
import
pymongo
import
sys
import
logging
import
copy
from
datetime
import
datetime
from
importlib
import
import_module
import
logging
import
pymongo
import
re
import
sys
from
uuid
import
uuid4
from
bson.son
import
SON
from
datetime
import
datetime
from
contracts
import
contract
,
new_contract
from
fs.osfs
import
OSFS
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.locations
import
Location
,
BlockUsageLocator
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
Location
,
BlockUsageLocator
,
SlashSeparatedCourseKey
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.exceptions
import
InvalidScopeError
from
xblock.fields
import
Scope
,
ScopeIds
,
Reference
,
ReferenceList
,
ReferenceValueDict
...
...
@@ -54,6 +51,7 @@ from xmodule.modulestore.xml import CourseLocationManager
from
xmodule.modulestore.store_utilities
import
DETACHED_XBLOCK_TYPES
from
xmodule.services
import
SettingsService
log
=
logging
.
getLogger
(
__name__
)
new_contract
(
'CourseKey'
,
CourseKey
)
...
...
@@ -318,6 +316,9 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
)
.
replace
(
tzinfo
=
UTC
)
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
module
.
save
()
return
module
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py
View file @
a5b10ca0
import
sys
import
logging
from
contracts
import
contract
,
new_contract
from
fs.osfs
import
OSFS
from
lazy
import
lazy
...
...
@@ -7,6 +8,7 @@ from xblock.runtime import KvsFieldData, KeyValueStore
from
xblock.fields
import
ScopeIds
from
xblock.core
import
XBlock
from
opaque_keys.edx.locator
import
BlockUsageLocator
,
LocalId
,
CourseLocator
,
LibraryLocator
,
DefinitionLocator
from
xmodule.library_tools
import
LibraryToolsService
from
xmodule.mako_module
import
MakoDescriptorSystem
from
xmodule.error_module
import
ErrorDescriptor
...
...
@@ -263,6 +265,10 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
module
.
update_version
=
edit_info
.
update_version
module
.
source_version
=
edit_info
.
source_version
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
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
`LmsFieldData`. This means overrides will be in effect for all scopes covered
by `authored_data`, e.g. course content and settings stored in Mongo.
"""
import
threading
from
abc
import
ABCMeta
,
abstractmethod
from
contextlib
import
contextmanager
import
threading
from
django.conf
import
settings
from
request_cache.middleware
import
RequestCache
from
xblock.field_data
import
FieldData
from
request_cache.middleware
import
RequestCache
from
xmodule.modulestore.inheritance
import
InheritanceMixin
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
):
...
...
@@ -46,6 +49,88 @@ def resolve_dotted(name):
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
):
"""
A :class:`~xblock.field_data.FieldData` which wraps another `FieldData`
...
...
@@ -171,83 +256,54 @@ class OverrideFieldData(FieldData):
return
self
.
fallback
.
default
(
block
,
name
)
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
OverrideModulestoreFieldData
(
OverrideFieldData
):
"""Apply field data overrides at the modulestore level. No student context required."""
@classmethod
def
wrap
(
cls
,
block
,
field_data
):
# pylint: disable=arguments-differ
"""
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.
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.
Arguments:
block: An XBlock
field_data: An instance of FieldData to be wrapped
"""
if
cls
.
provider_classes
is
None
:
cls
.
provider_classes
=
[
resolve_dotted
(
name
)
for
name
in
settings
.
MODULESTORE_FIELD_OVERRIDE_PROVIDERS
]
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
enabled_providers
=
cls
.
_providers_for_block
(
block
)
if
enabled_providers
:
return
cls
(
field_data
,
enabled_providers
)
def
__init__
(
self
,
user
):
self
.
user
=
user
return
field_data
@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.
@classmethod
def
_providers_for_block
(
cls
,
block
):
"""
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
def
enabled_for
(
self
,
course
):
# pragma no cover
Arguments:
block: An XBlock
"""
Return True if this provider should be enabled for a given course,
and False otherwise.
Concrete implementations are responsible for implementing this method.
course_id
=
unicode
(
block
.
location
.
course_key
)
cache_key
=
ENABLED_MODULESTORE_OVERRIDE_PROVIDERS_KEY
.
format
(
course_id
=
course_id
)
Arguments:
course (CourseModule or None
)
request_cache
=
RequestCache
.
get_request_cache
()
enabled_providers
=
request_cache
.
data
.
get
(
cache_key
)
Returns:
bool
"""
return
False
if
enabled_providers
is
None
:
enabled_providers
=
[
provider_class
for
provider_class
in
cls
.
provider_classes
if
provider_class
.
enabled_for
(
block
)
]
request_cache
.
data
[
cache_key
]
=
enabled_providers
return
enabled_providers
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
()
def
__init__
(
self
,
fallback
,
providers
):
super
(
OverrideModulestoreFieldData
,
self
)
.
__init__
(
None
,
fallback
,
providers
)
lms/djangoapps/courseware/self_paced_overrides.py
View file @
a5b10ca0
...
...
@@ -20,9 +20,10 @@ class SelfPacedDateOverrideProvider(FieldOverrideProvider):
# Remove release dates for course content
if
name
==
'start'
and
block
.
category
!=
'course'
:
return
None
return
default
@classmethod
def
enabled_for
(
cls
,
course
):
def
enabled_for
(
cls
,
block
):
"""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.
"""
# pylint: disable=missing-docstring
import
unittest
from
nose.plugins.attrib
import
attr
...
...
@@ -10,16 +11,39 @@ from xmodule.modulestore.tests.factories import CourseFactory
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
..field_overrides
import
(
resolve_dotted
,
disable_overrides
,
FieldOverrideProvider
,
OverrideFieldData
,
resolve_dotted
,
OverrideModulestoreFieldData
,
)
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'
)
@override_settings
(
FIELD_OVERRIDE_PROVIDERS
=
(
'courseware.tests.test_field_overrides.TestOverrideProvider'
,))
...
...
@@ -101,6 +125,31 @@ class OverrideFieldDataTests(SharedModuleStoreTestCase):
@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
):
"""
Tests for `resolve_dotted`.
...
...
@@ -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
):
"""
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
pytz
...
...
@@ -11,17 +9,17 @@ from mock import patch
from
courseware.tests.factories
import
BetaTesterFactory
from
courseware.access
import
has_access
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
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
@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
):
"""
...
...
@@ -29,14 +27,19 @@ class SelfPacedDateOverrideTest(ModuleStoreTestCase):
"""
def
setUp
(
self
):
SelfPacedConfiguration
(
enabled
=
True
)
.
save
()
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
.
now
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
.
replace
(
microsecond
=
0
)
self
.
future
=
self
.
now
+
datetime
.
timedelta
(
days
=
30
)
def
tearDown
(
self
):
super
(
SelfPacedDateOverrideTest
,
self
)
.
tearDown
()
OverrideFieldData
.
provider_classes
=
None
OverrideModulestoreFieldData
.
provider_classes
=
None
def
setup_course
(
self
,
**
course_kwargs
):
"""Set up a course with provided course attributes.
...
...
@@ -45,22 +48,39 @@ class SelfPacedDateOverrideTest(ModuleStoreTestCase):
overrides are correctly applied for both blocks.
"""
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
)
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
)
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
)
self
.
assertIsNone
(
sp_section
.
due
)
def
test_self_paced_disabled
(
self
):
def
test_self_paced_disabled
_due_date
(
self
):
SelfPacedConfiguration
(
enabled
=
False
)
.
save
()
__
,
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
})
def
test_course_access_to_beta_users
(
self
):
...
...
@@ -89,3 +109,34 @@ class SelfPacedDateOverrideTest(ModuleStoreTestCase):
# 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_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
...
...
@@ -389,7 +389,7 @@ if FEATURES.get('ENABLE_CORS_HEADERS') or FEATURES.get('ENABLE_CROSS_DOMAIN_CSRF
CROSS_DOMAIN_CSRF_COOKIE_DOMAIN
=
ENV_TOKENS
.
get
(
'CROSS_DOMAIN_CSRF_COOKIE_DOMAIN'
)
# Field overrides.
To use the IDDE feature, add
# Field overrides. To use the IDDE feature, add
# 'courseware.student_field_overrides.IndividualStudentOverrideProvider'.
FIELD_OVERRIDE_PROVIDERS
=
tuple
(
ENV_TOKENS
.
get
(
'FIELD_OVERRIDE_PROVIDERS'
,
[]))
...
...
@@ -406,6 +406,16 @@ if 'DJFS' in AUTH_TOKENS and AUTH_TOKENS['DJFS'] is not None:
############### Module Store Items ##########
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 ##########
LMS_SEGMENT_KEY
=
AUTH_TOKENS
.
get
(
'SEGMENT_KEY'
)
...
...
@@ -693,7 +703,11 @@ if FEATURES.get('INDIVIDUAL_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'
,
)
...
...
lms/envs/common.py
View file @
a5b10ca0
...
...
@@ -694,6 +694,9 @@ XBLOCK_MIXINS = (LmsBlockMixin, InheritanceMixin, XModuleMixin, EditInfoMixin)
# Allow any XBlock in the LMS
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_BRANCH
=
'published-only'
...
...
@@ -2644,6 +2647,10 @@ CHECKPOINT_PATTERN = r'(?P<checkpoint_name>[^/]+)'
# this setting.
FIELD_OVERRIDE_PROVIDERS
=
()
# Modulestore-level field override providers. These field override providers don't
# require student context.
MODULESTORE_FIELD_OVERRIDE_PROVIDERS
=
()
# PROFILE IMAGE CONFIG
# WARNING: Certain django storage backends do not support atomic
# 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