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
e5035746
Commit
e5035746
authored
Mar 13, 2017
by
cahrens
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Introduce EnrollmentTrackUserPartition.
TNL-6674
parent
b6ba57ee
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
53 changed files
with
713 additions
and
237 deletions
+713
-237
cms/djangoapps/contentstore/course_group_config.py
+4
-3
cms/djangoapps/contentstore/tests/test_utils.py
+4
-4
cms/djangoapps/contentstore/utils.py
+4
-3
cms/djangoapps/contentstore/views/preview.py
+15
-0
cms/djangoapps/contentstore/views/tests/test_item.py
+0
-0
cms/envs/bok_choy.py
+2
-0
cms/envs/common.py
+8
-0
cms/envs/test.py
+1
-0
common/djangoapps/course_modes/admin.py
+5
-9
common/lib/xmodule/xmodule/course_module.py
+0
-17
common/lib/xmodule/xmodule/modulestore/mongo/base.py
+5
-0
common/lib/xmodule/xmodule/modulestore/split_mongo/split.py
+5
-1
common/lib/xmodule/xmodule/partitions/partitions.py
+23
-11
common/lib/xmodule/xmodule/partitions/partitions_service.py
+126
-27
common/lib/xmodule/xmodule/partitions/tests/test_partitions.py
+0
-0
common/lib/xmodule/xmodule/split_test_module.py
+7
-10
common/lib/xmodule/xmodule/tests/test_split_test_module.py
+29
-13
lms/djangoapps/ccx/tests/test_field_override_performance.py
+27
-27
lms/djangoapps/course_api/blocks/tests/test_api.py
+2
-2
lms/djangoapps/course_blocks/transformers/user_partitions.py
+2
-5
lms/djangoapps/courseware/access.py
+0
-7
lms/djangoapps/courseware/tests/test_access.py
+14
-4
lms/djangoapps/courseware/tests/test_group_access.py
+0
-28
lms/djangoapps/courseware/tests/test_views.py
+4
-4
lms/djangoapps/courseware/testutils.py
+9
-0
lms/djangoapps/grades/tests/test_tasks.py
+12
-12
lms/djangoapps/instructor/views/instructor_dashboard.py
+1
-1
lms/djangoapps/instructor_task/tasks_helper.py
+2
-2
lms/djangoapps/instructor_task/tests/test_tasks_helper.py
+1
-1
lms/djangoapps/lms_xblock/mixin.py
+5
-3
lms/djangoapps/lms_xblock/runtime.py
+1
-18
lms/djangoapps/lti_provider/tests/test_views.py
+20
-0
lms/envs/bok_choy.py
+2
-0
lms/envs/common.py
+15
-2
lms/envs/test.py
+2
-0
lms/urls.py
+1
-1
openedx/core/djangoapps/credit/verification_access.py
+1
-1
openedx/core/djangoapps/verified_track_content/__init__.py
+0
-0
openedx/core/djangoapps/verified_track_content/admin.py
+2
-2
openedx/core/djangoapps/verified_track_content/forms.py
+1
-1
openedx/core/djangoapps/verified_track_content/migrations/0001_initial.py
+0
-0
openedx/core/djangoapps/verified_track_content/migrations/0002_verifiedtrackcohortedcourse_verified_cohort_name.py
+0
-0
openedx/core/djangoapps/verified_track_content/migrations/__init__.py
+0
-0
openedx/core/djangoapps/verified_track_content/models.py
+2
-2
openedx/core/djangoapps/verified_track_content/partition_scheme.py
+138
-0
openedx/core/djangoapps/verified_track_content/tasks.py
+0
-0
openedx/core/djangoapps/verified_track_content/tests/__init__.py
+0
-0
openedx/core/djangoapps/verified_track_content/tests/test_forms.py
+1
-1
openedx/core/djangoapps/verified_track_content/tests/test_models.py
+20
-8
openedx/core/djangoapps/verified_track_content/tests/test_partition_scheme.py
+182
-0
openedx/core/djangoapps/verified_track_content/tests/test_views.py
+5
-5
openedx/core/djangoapps/verified_track_content/views.py
+2
-2
setup.py
+1
-0
No files found.
cms/djangoapps/contentstore/course_group_config.py
View file @
e5035746
...
@@ -9,10 +9,11 @@ from util.db import generate_int_id, MYSQL_MAX_INT
...
@@ -9,10 +9,11 @@ from util.db import generate_int_id, MYSQL_MAX_INT
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
contentstore.utils
import
reverse_usage_url
from
contentstore.utils
import
reverse_usage_url
from
xmodule.partitions.partitions
import
UserPartition
from
xmodule.partitions.partitions
import
UserPartition
from
xmodule.partitions.partitions_service
import
get_all_partitions_for_course
,
MINIMUM_STATIC_PARTITION_ID
from
xmodule.split_test_module
import
get_split_user_partitions
from
xmodule.split_test_module
import
get_split_user_partitions
from
openedx.core.djangoapps.course_groups.partition_scheme
import
get_cohorted_user_partition
from
openedx.core.djangoapps.course_groups.partition_scheme
import
get_cohorted_user_partition
MINIMUM_GROUP_ID
=
100
MINIMUM_GROUP_ID
=
MINIMUM_STATIC_PARTITION_ID
RANDOM_SCHEME
=
"random"
RANDOM_SCHEME
=
"random"
COHORT_SCHEME
=
"cohort"
COHORT_SCHEME
=
"cohort"
...
@@ -84,7 +85,7 @@ class GroupConfiguration(object):
...
@@ -84,7 +85,7 @@ class GroupConfiguration(object):
"""
"""
Assign ids for the group_configuration's groups.
Assign ids for the group_configuration's groups.
"""
"""
used_ids
=
[
g
.
id
for
p
in
self
.
course
.
user_partitions
for
g
in
p
.
groups
]
used_ids
=
[
g
.
id
for
p
in
get_all_partitions_for_course
(
self
.
course
)
for
g
in
p
.
groups
]
# Assign ids to every group in configuration.
# Assign ids to every group in configuration.
for
group
in
self
.
configuration
.
get
(
'groups'
,
[]):
for
group
in
self
.
configuration
.
get
(
'groups'
,
[]):
if
group
.
get
(
'id'
)
is
None
:
if
group
.
get
(
'id'
)
is
None
:
...
@@ -96,7 +97,7 @@ class GroupConfiguration(object):
...
@@ -96,7 +97,7 @@ class GroupConfiguration(object):
"""
"""
Return a list of IDs that already in use.
Return a list of IDs that already in use.
"""
"""
return
set
([
p
.
id
for
p
in
course
.
user_partitions
])
return
set
([
p
.
id
for
p
in
get_all_partitions_for_course
(
course
)
])
def
get_user_partition
(
self
):
def
get_user_partition
(
self
):
"""
"""
...
...
cms/djangoapps/contentstore/tests/test_utils.py
View file @
e5035746
...
@@ -493,12 +493,12 @@ class GetUserPartitionInfoTest(ModuleStoreTestCase):
...
@@ -493,12 +493,12 @@ class GetUserPartitionInfoTest(ModuleStoreTestCase):
]
]
}
}
]
]
self
.
assertEqual
(
self
.
_get_partition_info
(),
expected
)
self
.
assertEqual
(
self
.
_get_partition_info
(
schemes
=
[
"cohort"
,
"random"
]
),
expected
)
# Update group access and expect that now one group is marked as selected.
# Update group access and expect that now one group is marked as selected.
self
.
_set_group_access
({
0
:
[
1
]})
self
.
_set_group_access
({
0
:
[
1
]})
expected
[
0
][
"groups"
][
1
][
"selected"
]
=
True
expected
[
0
][
"groups"
][
1
][
"selected"
]
=
True
self
.
assertEqual
(
self
.
_get_partition_info
(),
expected
)
self
.
assertEqual
(
self
.
_get_partition_info
(
schemes
=
[
"cohort"
,
"random"
]
),
expected
)
def
test_deleted_groups
(
self
):
def
test_deleted_groups
(
self
):
# Select a group that is not defined in the partition
# Select a group that is not defined in the partition
...
@@ -546,7 +546,7 @@ class GetUserPartitionInfoTest(ModuleStoreTestCase):
...
@@ -546,7 +546,7 @@ class GetUserPartitionInfoTest(ModuleStoreTestCase):
])
])
# Expect that the inactive scheme is excluded from the results
# Expect that the inactive scheme is excluded from the results
partitions
=
self
.
_get_partition_info
()
partitions
=
self
.
_get_partition_info
(
schemes
=
[
"cohort"
,
"verification"
]
)
self
.
assertEqual
(
len
(
partitions
),
1
)
self
.
assertEqual
(
len
(
partitions
),
1
)
self
.
assertEqual
(
partitions
[
0
][
"scheme"
],
"cohort"
)
self
.
assertEqual
(
partitions
[
0
][
"scheme"
],
"cohort"
)
...
@@ -572,7 +572,7 @@ class GetUserPartitionInfoTest(ModuleStoreTestCase):
...
@@ -572,7 +572,7 @@ class GetUserPartitionInfoTest(ModuleStoreTestCase):
])
])
# Expect that the partition with no groups is excluded from the results
# Expect that the partition with no groups is excluded from the results
partitions
=
self
.
_get_partition_info
()
partitions
=
self
.
_get_partition_info
(
schemes
=
[
"cohort"
,
"verification"
]
)
self
.
assertEqual
(
len
(
partitions
),
1
)
self
.
assertEqual
(
len
(
partitions
),
1
)
self
.
assertEqual
(
partitions
[
0
][
"scheme"
],
"verification"
)
self
.
assertEqual
(
partitions
[
0
][
"scheme"
],
"verification"
)
...
...
cms/djangoapps/contentstore/utils.py
View file @
e5035746
...
@@ -14,6 +14,7 @@ from django_comment_common.utils import seed_permissions_roles
...
@@ -14,6 +14,7 @@ from django_comment_common.utils import seed_permissions_roles
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.djangoapps.site_configuration.models
import
SiteConfiguration
from
openedx.core.djangoapps.site_configuration.models
import
SiteConfiguration
from
xmodule.partitions.partitions_service
import
get_all_partitions_for_course
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
...
@@ -373,11 +374,11 @@ def get_user_partition_info(xblock, schemes=None, course=None):
...
@@ -373,11 +374,11 @@ def get_user_partition_info(xblock, schemes=None, course=None):
schemes
=
set
(
schemes
)
schemes
=
set
(
schemes
)
partitions
=
[]
partitions
=
[]
for
p
in
sorted
(
course
.
user_partitions
,
key
=
lambda
p
:
p
.
name
):
for
p
in
sorted
(
get_all_partitions_for_course
(
course
,
active_only
=
True
)
,
key
=
lambda
p
:
p
.
name
):
# Exclude disabled partitions, partitions with no groups defined
# Exclude disabled partitions, partitions with no groups defined
# Also filter by scheme name if there's a filter defined.
# Also filter by scheme name if there's a filter defined.
if
p
.
active
and
p
.
groups
and
(
schemes
is
None
or
p
.
scheme
.
name
in
schemes
):
if
p
.
groups
and
(
schemes
is
None
or
p
.
scheme
.
name
in
schemes
):
# First, add groups defined by the partition
# First, add groups defined by the partition
groups
=
[]
groups
=
[]
...
@@ -408,7 +409,7 @@ def get_user_partition_info(xblock, schemes=None, course=None):
...
@@ -408,7 +409,7 @@ def get_user_partition_info(xblock, schemes=None, course=None):
# Put together the entire partition dictionary
# Put together the entire partition dictionary
partitions
.
append
({
partitions
.
append
({
"id"
:
p
.
id
,
"id"
:
p
.
id
,
"name"
:
p
.
name
,
"name"
:
unicode
(
p
.
name
),
# Convert into a string in case ugettext_lazy was used
"scheme"
:
p
.
scheme
.
name
,
"scheme"
:
p
.
scheme
.
name
,
"groups"
:
groups
,
"groups"
:
groups
,
})
})
...
...
cms/djangoapps/contentstore/views/preview.py
View file @
e5035746
...
@@ -16,6 +16,7 @@ from xmodule.x_module import PREVIEW_VIEWS, STUDENT_VIEW, AUTHOR_VIEW
...
@@ -16,6 +16,7 @@ from xmodule.x_module import PREVIEW_VIEWS, STUDENT_VIEW, AUTHOR_VIEW
from
xmodule.contentstore.django
import
contentstore
from
xmodule.contentstore.django
import
contentstore
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.exceptions
import
NotFoundError
,
ProcessingError
from
xmodule.exceptions
import
NotFoundError
,
ProcessingError
from
xmodule.partitions.partitions_service
import
PartitionService
from
xmodule.studio_editable
import
has_author_view
from
xmodule.studio_editable
import
has_author_view
from
xmodule.services
import
SettingsService
from
xmodule.services
import
SettingsService
from
xmodule.modulestore.django
import
modulestore
,
ModuleI18nService
from
xmodule.modulestore.django
import
modulestore
,
ModuleI18nService
...
@@ -213,10 +214,24 @@ def _preview_module_system(request, descriptor, field_data):
...
@@ -213,10 +214,24 @@ def _preview_module_system(request, descriptor, field_data):
"i18n"
:
ModuleI18nService
,
"i18n"
:
ModuleI18nService
,
"settings"
:
SettingsService
(),
"settings"
:
SettingsService
(),
"user"
:
DjangoXBlockUserService
(
request
.
user
),
"user"
:
DjangoXBlockUserService
(
request
.
user
),
"partitions"
:
StudioPartitionService
(
course_id
=
course_id
)
},
},
)
)
class
StudioPartitionService
(
PartitionService
):
"""
A runtime mixin to allow the display and editing of component visibility based on user partitions.
"""
def
get_user_group_id_for_partition
(
self
,
user
,
user_partition_id
):
"""
Override this method to return None, as the split_test_module calls this
to determine which group a user should see, but is robust to getting a return
value of None meaning that all groups should be shown.
"""
return
None
def
_load_preview_module
(
request
,
descriptor
):
def
_load_preview_module
(
request
,
descriptor
):
"""
"""
Return a preview XModule instantiated from the supplied descriptor. Will use mutable fields
Return a preview XModule instantiated from the supplied descriptor. Will use mutable fields
...
...
cms/djangoapps/contentstore/views/tests/test_item.py
View file @
e5035746
This diff is collapsed.
Click to expand it.
cms/envs/bok_choy.py
View file @
e5035746
...
@@ -93,6 +93,8 @@ FEATURES['LICENSING'] = True
...
@@ -93,6 +93,8 @@ FEATURES['LICENSING'] = True
FEATURES
[
'ENABLE_MOBILE_REST_API'
]
=
True
# Enable video bumper in Studio
FEATURES
[
'ENABLE_MOBILE_REST_API'
]
=
True
# Enable video bumper in Studio
FEATURES
[
'ENABLE_VIDEO_BUMPER'
]
=
True
# Enable video bumper in Studio settings
FEATURES
[
'ENABLE_VIDEO_BUMPER'
]
=
True
# Enable video bumper in Studio settings
FEATURES
[
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION'
]
=
True
# Enable partner support link in Studio footer
# Enable partner support link in Studio footer
PARTNER_SUPPORT_EMAIL
=
'partner-support@example.com'
PARTNER_SUPPORT_EMAIL
=
'partner-support@example.com'
...
...
cms/envs/common.py
View file @
e5035746
...
@@ -88,6 +88,8 @@ from lms.envs.common import (
...
@@ -88,6 +88,8 @@ from lms.envs.common import (
# File upload defaults
# File upload defaults
FILE_UPLOAD_STORAGE_BUCKET_NAME
,
FILE_UPLOAD_STORAGE_BUCKET_NAME
,
FILE_UPLOAD_STORAGE_PREFIX
,
FILE_UPLOAD_STORAGE_PREFIX
,
COURSE_ENROLLMENT_MODES
)
)
from
path
import
Path
as
path
from
path
import
Path
as
path
from
warnings
import
simplefilter
from
warnings
import
simplefilter
...
@@ -225,6 +227,9 @@ FEATURES = {
...
@@ -225,6 +227,9 @@ FEATURES = {
# Allow public account creation
# Allow public account creation
'ALLOW_PUBLIC_ACCOUNT_CREATION'
:
True
,
'ALLOW_PUBLIC_ACCOUNT_CREATION'
:
True
,
# Whether or not the dynamic EnrollmentTrackUserPartition should be registered.
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION'
:
False
,
}
}
ENABLE_JASMINE
=
False
ENABLE_JASMINE
=
False
...
@@ -883,6 +888,9 @@ INSTALLED_APPS = (
...
@@ -883,6 +888,9 @@ INSTALLED_APPS = (
# for managing course modes
# for managing course modes
'course_modes'
,
'course_modes'
,
# Verified Track Content Cohorting (Beta feature that will hopefully be removed)
'openedx.core.djangoapps.verified_track_content'
,
# Dark-launching languages
# Dark-launching languages
'openedx.core.djangoapps.dark_lang'
,
'openedx.core.djangoapps.dark_lang'
,
...
...
cms/envs/test.py
View file @
e5035746
...
@@ -320,6 +320,7 @@ FEATURES['ENABLE_COURSEWARE_INDEX'] = True
...
@@ -320,6 +320,7 @@ FEATURES['ENABLE_COURSEWARE_INDEX'] = True
FEATURES
[
'ENABLE_LIBRARY_INDEX'
]
=
True
FEATURES
[
'ENABLE_LIBRARY_INDEX'
]
=
True
SEARCH_ENGINE
=
"search.tests.mock_search_engine.MockSearchEngine"
SEARCH_ENGINE
=
"search.tests.mock_search_engine.MockSearchEngine"
FEATURES
[
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION'
]
=
True
# teams feature
# teams feature
FEATURES
[
'ENABLE_TEAMS'
]
=
True
FEATURES
[
'ENABLE_TEAMS'
]
=
True
...
...
common/djangoapps/course_modes/admin.py
View file @
e5035746
...
@@ -28,22 +28,18 @@ from course_modes.models import CourseMode, CourseModeExpirationConfig
...
@@ -28,22 +28,18 @@ from course_modes.models import CourseMode, CourseModeExpirationConfig
# the verification deadline table won't exist.
# the verification deadline table won't exist.
from
lms.djangoapps.verify_student
import
models
as
verification_models
from
lms.djangoapps.verify_student
import
models
as
verification_models
COURSE_MODE_SLUG_CHOICES
=
[(
mode_slug
,
mode_slug
)
for
mode_slug
in
settings
.
COURSE_ENROLLMENT_MODES
]
class
CourseModeForm
(
forms
.
ModelForm
):
class
CourseModeForm
(
forms
.
ModelForm
):
"""
Admin form for adding a course mode.
"""
class
Meta
(
object
):
class
Meta
(
object
):
model
=
CourseMode
model
=
CourseMode
fields
=
'__all__'
fields
=
'__all__'
COURSE_MODE_SLUG_CHOICES
=
(
[(
CourseMode
.
DEFAULT_MODE_SLUG
,
CourseMode
.
DEFAULT_MODE_SLUG
)]
+
[(
mode_slug
,
mode_slug
)
for
mode_slug
in
CourseMode
.
VERIFIED_MODES
]
+
[(
CourseMode
.
NO_ID_PROFESSIONAL_MODE
,
CourseMode
.
NO_ID_PROFESSIONAL_MODE
)]
+
[(
mode_slug
,
mode_slug
)
for
mode_slug
in
CourseMode
.
CREDIT_MODES
]
+
# need to keep legacy modes around for awhile
[(
CourseMode
.
DEFAULT_SHOPPINGCART_MODE_SLUG
,
CourseMode
.
DEFAULT_SHOPPINGCART_MODE_SLUG
)]
)
mode_slug
=
forms
.
ChoiceField
(
choices
=
COURSE_MODE_SLUG_CHOICES
,
label
=
_
(
"Mode"
))
mode_slug
=
forms
.
ChoiceField
(
choices
=
COURSE_MODE_SLUG_CHOICES
,
label
=
_
(
"Mode"
))
# The verification deadline is stored outside the course mode in the verify_student app.
# The verification deadline is stored outside the course mode in the verify_student app.
...
...
common/lib/xmodule/xmodule/course_module.py
View file @
e5035746
...
@@ -1341,23 +1341,6 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
...
@@ -1341,23 +1341,6 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
"""
"""
return
self
.
teams_configuration
.
get
(
'topics'
,
None
)
return
self
.
teams_configuration
.
get
(
'topics'
,
None
)
def
get_user_partitions_for_scheme
(
self
,
scheme
):
"""
Retrieve all user partitions defined in the course for a particular
partition scheme.
Arguments:
scheme (object): The user partition scheme.
Returns:
list of `UserPartition`
"""
return
[
p
for
p
in
self
.
user_partitions
if
p
.
scheme
==
scheme
]
def
set_user_partitions_for_scheme
(
self
,
partitions
,
scheme
):
def
set_user_partitions_for_scheme
(
self
,
partitions
,
scheme
):
"""
"""
Set the user partitions for a particular scheme.
Set the user partitions for a particular scheme.
...
...
common/lib/xmodule/xmodule/modulestore/mongo/base.py
View file @
e5035746
...
@@ -47,6 +47,7 @@ from xmodule.modulestore.draft_and_published import ModuleStoreDraftAndPublished
...
@@ -47,6 +47,7 @@ from xmodule.modulestore.draft_and_published import ModuleStoreDraftAndPublished
from
xmodule.modulestore.edit_info
import
EditInfoRuntimeMixin
from
xmodule.modulestore.edit_info
import
EditInfoRuntimeMixin
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
DuplicateCourseError
,
ReferentialIntegrityError
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
DuplicateCourseError
,
ReferentialIntegrityError
from
xmodule.modulestore.inheritance
import
InheritanceMixin
,
inherit_metadata
,
InheritanceKeyValueStore
from
xmodule.modulestore.inheritance
import
InheritanceMixin
,
inherit_metadata
,
InheritanceKeyValueStore
from
xmodule.partitions.partitions_service
import
PartitionService
from
xmodule.modulestore.xml
import
CourseLocationManager
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
...
@@ -934,6 +935,8 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
...
@@ -934,6 +935,8 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
if
self
.
request_cache
:
if
self
.
request_cache
:
services
[
"request_cache"
]
=
self
.
request_cache
services
[
"request_cache"
]
=
self
.
request_cache
services
[
"partitions"
]
=
PartitionService
(
course_key
)
system
=
CachingDescriptorSystem
(
system
=
CachingDescriptorSystem
(
modulestore
=
self
,
modulestore
=
self
,
course_key
=
course_key
,
course_key
=
course_key
,
...
@@ -1346,6 +1349,8 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
...
@@ -1346,6 +1349,8 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
if
self
.
user_service
:
if
self
.
user_service
:
services
[
"user"
]
=
self
.
user_service
services
[
"user"
]
=
self
.
user_service
services
[
"partitions"
]
=
PartitionService
(
course_key
)
runtime
=
CachingDescriptorSystem
(
runtime
=
CachingDescriptorSystem
(
modulestore
=
self
,
modulestore
=
self
,
module_data
=
{},
module_data
=
{},
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/split.py
View file @
e5035746
...
@@ -83,6 +83,7 @@ from xmodule.modulestore import (
...
@@ -83,6 +83,7 @@ from xmodule.modulestore import (
from
..exceptions
import
ItemNotFoundError
from
..exceptions
import
ItemNotFoundError
from
.caching_descriptor_system
import
CachingDescriptorSystem
from
.caching_descriptor_system
import
CachingDescriptorSystem
from
xmodule.partitions.partitions_service
import
PartitionService
from
xmodule.modulestore.split_mongo.mongo_connection
import
MongoConnection
,
DuplicateKeyError
from
xmodule.modulestore.split_mongo.mongo_connection
import
MongoConnection
,
DuplicateKeyError
from
xmodule.modulestore.split_mongo
import
BlockKey
,
CourseEnvelope
from
xmodule.modulestore.split_mongo
import
BlockKey
,
CourseEnvelope
from
xmodule.modulestore.store_utilities
import
DETACHED_XBLOCK_TYPES
from
xmodule.modulestore.store_utilities
import
DETACHED_XBLOCK_TYPES
...
@@ -3359,6 +3360,9 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
...
@@ -3359,6 +3360,9 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
"""
"""
Create the proper runtime for this course
Create the proper runtime for this course
"""
"""
services
=
self
.
services
services
[
"partitions"
]
=
PartitionService
(
course_entry
.
course_key
)
return
CachingDescriptorSystem
(
return
CachingDescriptorSystem
(
modulestore
=
self
,
modulestore
=
self
,
course_entry
=
course_entry
,
course_entry
=
course_entry
,
...
@@ -3370,7 +3374,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
...
@@ -3370,7 +3374,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
mixins
=
self
.
xblock_mixins
,
mixins
=
self
.
xblock_mixins
,
select
=
self
.
xblock_select
,
select
=
self
.
xblock_select
,
disabled_xblock_types
=
self
.
disabled_xblock_types
,
disabled_xblock_types
=
self
.
disabled_xblock_types
,
services
=
se
lf
.
se
rvices
,
services
=
services
,
)
)
def
ensure_indexes
(
self
):
def
ensure_indexes
(
self
):
...
...
common/lib/xmodule/xmodule/partitions/partitions.py
View file @
e5035746
...
@@ -127,7 +127,7 @@ class UserPartition(namedtuple("UserPartition", "id name description groups sche
...
@@ -127,7 +127,7 @@ class UserPartition(namedtuple("UserPartition", "id name description groups sche
try
:
try
:
scheme
=
UserPartition
.
scheme_extensions
[
name
]
.
plugin
scheme
=
UserPartition
.
scheme_extensions
[
name
]
.
plugin
except
KeyError
:
except
KeyError
:
raise
UserPartitionError
(
"Unrecognized scheme
{0}
"
.
format
(
name
))
raise
UserPartitionError
(
"Unrecognized scheme
'{0}'
"
.
format
(
name
))
scheme
.
name
=
name
scheme
.
name
=
name
return
scheme
return
scheme
...
@@ -188,15 +188,25 @@ class UserPartition(namedtuple("UserPartition", "id name description groups sche
...
@@ -188,15 +188,25 @@ class UserPartition(namedtuple("UserPartition", "id name description groups sche
if
not
scheme
:
if
not
scheme
:
raise
TypeError
(
"UserPartition dict {0} has unrecognized scheme {1}"
.
format
(
value
,
scheme_id
))
raise
TypeError
(
"UserPartition dict {0} has unrecognized scheme {1}"
.
format
(
value
,
scheme_id
))
return
UserPartition
(
if
hasattr
(
scheme
,
"create_user_partition"
):
value
[
"id"
],
return
scheme
.
create_user_partition
(
value
[
"name"
],
value
[
"id"
],
value
[
"description"
],
value
[
"name"
],
groups
,
value
[
"description"
],
scheme
,
groups
,
parameters
,
parameters
,
active
,
active
,
)
)
else
:
return
UserPartition
(
value
[
"id"
],
value
[
"name"
],
value
[
"description"
],
groups
,
scheme
,
parameters
,
active
,
)
def
get_group
(
self
,
group_id
):
def
get_group
(
self
,
group_id
):
"""
"""
...
@@ -214,5 +224,7 @@ class UserPartition(namedtuple("UserPartition", "id name description groups sche
...
@@ -214,5 +224,7 @@ class UserPartition(namedtuple("UserPartition", "id name description groups sche
return
group
return
group
raise
NoSuchUserPartitionGroupError
(
raise
NoSuchUserPartitionGroupError
(
"could not find a Group with ID [{}] in UserPartition [{}]"
.
format
(
group_id
,
self
.
id
)
"Could not find a Group with ID [{group_id}] in UserPartition [{partition_id}]."
.
format
(
group_id
=
group_id
,
partition_id
=
self
.
id
)
)
)
common/lib/xmodule/xmodule/partitions/partitions_service.py
View file @
e5035746
...
@@ -3,31 +3,122 @@ This is a service-like API that assigns tracks which groups users are in for var
...
@@ -3,31 +3,122 @@ This is a service-like API that assigns tracks which groups users are in for var
user partitions. It uses the user_service key/value store provided by the LMS runtime to
user partitions. It uses the user_service key/value store provided by the LMS runtime to
persist the assignments.
persist the assignments.
"""
"""
from
abc
import
ABCMeta
,
abstractproperty
from
django.conf
import
settings
from
django.utils.translation
import
ugettext_lazy
as
_
import
logging
from
xmodule.partitions.partitions
import
UserPartition
,
UserPartitionError
from
xmodule.modulestore.django
import
modulestore
class
PartitionService
(
object
):
log
=
logging
.
getLogger
(
__name__
)
# UserPartition IDs must be unique. The Cohort and Random UserPartitions (when they are
# created via Studio) choose an unused ID in the range of 100 (historical) to MAX_INT. Therefore the
# dynamic UserPartitionIDs must be under 100, and they have to be hard-coded to ensure
# they are always the same whenever the dynamic partition is added (since the UserPartition
# ID is stored in the xblock group_access dict).
ENROLLMENT_TRACK_PARTITION_ID
=
50
MINIMUM_STATIC_PARTITION_ID
=
100
# settings will not be available when running nosetests.
FEATURES
=
getattr
(
settings
,
'FEATURES'
,
{})
def
get_all_partitions_for_course
(
course
,
active_only
=
False
):
"""
"""
This is an XBlock service that assigns tracks which groups users are in for various
A method that returns all `UserPartitions` associated with a course, as a List.
user partitions. It uses the provided user_tags service object to
This will include the ones defined in course.user_partitions, but it may also
persist the assignments.
include dynamically included partitions (such as the `EnrollmentTrackUserPartition`).
Args:
course: the course for which user partitions should be returned.
active_only: if `True`, only partitions with `active` set to True will be returned.
Returns:
A List of UserPartitions associated with the course.
"""
"""
__metaclass__
=
ABCMeta
all_partitions
=
course
.
user_partitions
+
_get_dynamic_partitions
(
course
)
if
active_only
:
all_partitions
=
[
partition
for
partition
in
all_partitions
if
partition
.
active
]
return
all_partitions
@abstractproperty
def
course_partitions
(
self
):
"""
Return the set of partitions assigned to self._course_id
"""
raise
NotImplementedError
(
'Subclasses must implement course_partition'
)
def
__init__
(
self
,
user
,
course_id
,
track_function
=
None
,
cache
=
None
):
def
_get_dynamic_partitions
(
course
):
self
.
_user
=
user
"""
Return the dynamic user partitions for this course.
If none exists, returns an empty array.
"""
enrollment_partition
=
_create_enrollment_track_partition
(
course
)
return
[
enrollment_partition
]
if
enrollment_partition
else
[]
def
_create_enrollment_track_partition
(
course
):
"""
Create and return the dynamic enrollment track user partition.
If it cannot be created, None is returned.
"""
if
not
FEATURES
.
get
(
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION'
):
return
None
try
:
enrollment_track_scheme
=
UserPartition
.
get_scheme
(
"enrollment_track"
)
except
UserPartitionError
:
log
.
warning
(
"No 'enrollment_track' scheme registered, EnrollmentTrackUserPartition will not be created."
)
return
None
used_ids
=
set
(
p
.
id
for
p
in
course
.
user_partitions
)
if
ENROLLMENT_TRACK_PARTITION_ID
in
used_ids
:
# TODO: change to Exception after this has been in production for awhile, see TNL-6796.
log
.
warning
(
"Can't add 'enrollment_track' partition, as ID {id} is assigned to {partition} in course {course}."
.
format
(
id
=
ENROLLMENT_TRACK_PARTITION_ID
,
partition
=
_get_partition_from_id
(
course
.
user_partitions
,
ENROLLMENT_TRACK_PARTITION_ID
)
.
name
,
course
=
unicode
(
course
.
id
)
)
)
return
None
partition
=
enrollment_track_scheme
.
create_user_partition
(
id
=
ENROLLMENT_TRACK_PARTITION_ID
,
name
=
_
(
u"Enrollment Track Partition"
),
description
=
_
(
u"Partition for segmenting users by enrollment track"
),
parameters
=
{
"course_id"
:
unicode
(
course
.
id
)}
)
return
partition
class
PartitionService
(
object
):
"""
This is an XBlock service that returns information about the user partitions associated
with a given course.
"""
def
__init__
(
self
,
course_id
,
track_function
=
None
,
cache
=
None
):
self
.
_course_id
=
course_id
self
.
_course_id
=
course_id
self
.
_track_function
=
track_function
self
.
_track_function
=
track_function
self
.
_cache
=
cache
self
.
_cache
=
cache
def
get_user_group_id_for_partition
(
self
,
user_partition_id
):
def
get_course
(
self
):
"""
Return the course instance associated with this PartitionService.
This default implementation looks up the course from the modulestore.
"""
return
modulestore
()
.
get_course
(
self
.
_course_id
)
@property
def
course_partitions
(
self
):
"""
Return the set of partitions assigned to self._course_id (both those set directly on the course
through course.user_partitions, and any dynamic partitions that exist). Note: this returns
both active and inactive partitions.
"""
return
get_all_partitions_for_course
(
self
.
get_course
())
def
get_user_group_id_for_partition
(
self
,
user
,
user_partition_id
):
"""
"""
If the user is already assigned to a group in user_partition_id, return the
If the user is already assigned to a group in user_partition_id, return the
group_id.
group_id.
...
@@ -35,9 +126,6 @@ class PartitionService(object):
...
@@ -35,9 +126,6 @@ class PartitionService(object):
If not, assign them to one of the groups, persist that decision, and
If not, assign them to one of the groups, persist that decision, and
return the group_id.
return the group_id.
If the group they are assigned to doesn't exist anymore, re-assign to one of
the existing groups and return its id.
Args:
Args:
user_partition_id -- an id of a partition that's hopefully in the
user_partition_id -- an id of a partition that's hopefully in the
runtime.user_partitions list.
runtime.user_partitions list.
...
@@ -49,7 +137,7 @@ class PartitionService(object):
...
@@ -49,7 +137,7 @@ class PartitionService(object):
ValueError if the user_partition_id isn't found.
ValueError if the user_partition_id isn't found.
"""
"""
cache_key
=
"PartitionService.ugidfp.{}.{}.{}"
.
format
(
cache_key
=
"PartitionService.ugidfp.{}.{}.{}"
.
format
(
self
.
_
user
.
id
,
self
.
_course_id
,
user_partition_id
user
.
id
,
self
.
_course_id
,
user_partition_id
)
)
if
self
.
_cache
and
(
cache_key
in
self
.
_cache
):
if
self
.
_cache
and
(
cache_key
in
self
.
_cache
):
...
@@ -62,7 +150,7 @@ class PartitionService(object):
...
@@ -62,7 +150,7 @@ class PartitionService(object):
"in course {1}"
.
format
(
user_partition_id
,
self
.
_course_id
)
"in course {1}"
.
format
(
user_partition_id
,
self
.
_course_id
)
)
)
group
=
self
.
get_group
(
user_partition
)
group
=
self
.
get_group
(
user
,
user
_partition
)
group_id
=
group
.
id
if
group
else
None
group_id
=
group
.
id
if
group
else
None
if
self
.
_cache
is
not
None
:
if
self
.
_cache
is
not
None
:
...
@@ -73,22 +161,33 @@ class PartitionService(object):
...
@@ -73,22 +161,33 @@ class PartitionService(object):
def
_get_user_partition
(
self
,
user_partition_id
):
def
_get_user_partition
(
self
,
user_partition_id
):
"""
"""
Look for a user partition with a matching id in the course's partitions.
Look for a user partition with a matching id in the course's partitions.
Note that this method can return an inactive user partition.
Returns:
Returns:
A UserPartition, or None if not found.
A UserPartition, or None if not found.
"""
"""
for
partition
in
self
.
course_partitions
:
return
_get_partition_from_id
(
self
.
course_partitions
,
user_partition_id
)
if
partition
.
id
==
user_partition_id
:
return
partition
return
None
def
get_group
(
self
,
user_partition
,
assign
=
True
):
def
get_group
(
self
,
user
,
user
_partition
,
assign
=
True
):
"""
"""
Returns the group from the specified user partition to which the user is assigned.
Returns the group from the specified user partition to which the user is assigned.
If the user has not yet been assigned, a group will be chosen for them based upon
If the user has not yet been assigned, a group will be chosen for them based upon
the partition's scheme.
the partition's scheme.
"""
"""
return
user_partition
.
scheme
.
get_group_for_user
(
return
user_partition
.
scheme
.
get_group_for_user
(
self
.
_course_id
,
self
.
_
user
,
user_partition
,
assign
=
assign
,
track_function
=
self
.
_track_function
self
.
_course_id
,
user
,
user_partition
,
assign
=
assign
,
track_function
=
self
.
_track_function
)
)
def
_get_partition_from_id
(
partitions
,
user_partition_id
):
"""
Look for a user partition with a matching id in the provided list of partitions.
Returns:
A UserPartition, or None if not found.
"""
for
partition
in
partitions
:
if
partition
.
id
==
user_partition_id
:
return
partition
return
None
common/lib/xmodule/xmodule/partitions/tests/test_partitions.py
View file @
e5035746
This diff is collapsed.
Click to expand it.
common/lib/xmodule/xmodule/split_test_module.py
View file @
e5035746
...
@@ -98,7 +98,8 @@ def get_split_user_partitions(user_partitions):
...
@@ -98,7 +98,8 @@ def get_split_user_partitions(user_partitions):
@XBlock.needs
(
'user_tags'
)
# pylint: disable=abstract-method
@XBlock.needs
(
'user_tags'
)
# pylint: disable=abstract-method
@XBlock.wants
(
'partitions'
)
@XBlock.needs
(
'partitions'
)
@XBlock.needs
(
'user'
)
class
SplitTestModule
(
SplitTestFields
,
XModule
,
StudioEditableModule
):
class
SplitTestModule
(
SplitTestFields
,
XModule
,
StudioEditableModule
):
"""
"""
Show the user the appropriate child. Uses the ExperimentState
Show the user the appropriate child. Uses the ExperimentState
...
@@ -193,9 +194,9 @@ class SplitTestModule(SplitTestFields, XModule, StudioEditableModule):
...
@@ -193,9 +194,9 @@ class SplitTestModule(SplitTestFields, XModule, StudioEditableModule):
Returns the group ID, or None if none is available.
Returns the group ID, or None if none is available.
"""
"""
partitions_service
=
self
.
runtime
.
service
(
self
,
'partitions'
)
partitions_service
=
self
.
runtime
.
service
(
self
,
'partitions'
)
if
not
partitions_service
:
user_service
=
self
.
runtime
.
service
(
self
,
'user'
)
return
None
user
=
user_service
.
_django_user
# pylint: disable=protected-access
return
partitions_service
.
get_user_group_id_for_partition
(
self
.
user_partition_id
)
return
partitions_service
.
get_user_group_id_for_partition
(
user
,
self
.
user_partition_id
)
@property
@property
def
is_configured
(
self
):
def
is_configured
(
self
):
...
@@ -370,8 +371,8 @@ class SplitTestModule(SplitTestFields, XModule, StudioEditableModule):
...
@@ -370,8 +371,8 @@ class SplitTestModule(SplitTestFields, XModule, StudioEditableModule):
@XBlock.needs
(
'user_tags'
)
# pylint: disable=abstract-method
@XBlock.needs
(
'user_tags'
)
# pylint: disable=abstract-method
@XBlock.
want
s
(
'partitions'
)
@XBlock.
need
s
(
'partitions'
)
@XBlock.
want
s
(
'user'
)
@XBlock.
need
s
(
'user'
)
class
SplitTestDescriptor
(
SplitTestFields
,
SequenceDescriptor
,
StudioEditableDescriptor
):
class
SplitTestDescriptor
(
SplitTestFields
,
SequenceDescriptor
,
StudioEditableDescriptor
):
# the editing interface can be the same as for sequences -- just a container
# the editing interface can be the same as for sequences -- just a container
module_class
=
SplitTestModule
module_class
=
SplitTestModule
...
@@ -641,10 +642,6 @@ class SplitTestDescriptor(SplitTestFields, SequenceDescriptor, StudioEditableDes
...
@@ -641,10 +642,6 @@ class SplitTestDescriptor(SplitTestFields, SequenceDescriptor, StudioEditableDes
Called from Studio view.
Called from Studio view.
"""
"""
user_service
=
self
.
runtime
.
service
(
self
,
'user'
)
if
user_service
is
None
:
return
Response
()
user_partition
=
self
.
get_selected_partition
()
user_partition
=
self
.
get_selected_partition
()
changed
=
False
changed
=
False
...
...
common/lib/xmodule/xmodule/tests/test_split_test_module.py
View file @
e5035746
...
@@ -6,7 +6,7 @@ import lxml
...
@@ -6,7 +6,7 @@ import lxml
from
mock
import
Mock
,
patch
from
mock
import
Mock
,
patch
from
fs.memoryfs
import
MemoryFS
from
fs.memoryfs
import
MemoryFS
from
xmodule.partitions.tests.test_partitions
import
Static
PartitionService
,
PartitionTestCase
,
MockUserPartitionScheme
from
xmodule.partitions.tests.test_partitions
import
Mock
PartitionService
,
PartitionTestCase
,
MockUserPartitionScheme
from
xmodule.tests.xml
import
factories
as
xml
from
xmodule.tests.xml
import
factories
as
xml
from
xmodule.tests.xml
import
XModuleXmlImportTest
from
xmodule.tests.xml
import
XModuleXmlImportTest
from
xmodule.tests
import
get_test_system
from
xmodule.tests
import
get_test_system
...
@@ -14,6 +14,7 @@ from xmodule.x_module import AUTHOR_VIEW, STUDENT_VIEW
...
@@ -14,6 +14,7 @@ from xmodule.x_module import AUTHOR_VIEW, STUDENT_VIEW
from
xmodule.validation
import
StudioValidationMessage
from
xmodule.validation
import
StudioValidationMessage
from
xmodule.split_test_module
import
SplitTestDescriptor
,
SplitTestFields
,
get_split_user_partitions
from
xmodule.split_test_module
import
SplitTestDescriptor
,
SplitTestFields
,
get_split_user_partitions
from
xmodule.partitions.partitions
import
Group
,
UserPartition
from
xmodule.partitions.partitions
import
Group
,
UserPartition
from
xmodule.partitions.partitions_service
import
MINIMUM_STATIC_PARTITION_ID
class
SplitTestModuleFactory
(
xml
.
XmlImportFactory
):
class
SplitTestModuleFactory
(
xml
.
XmlImportFactory
):
...
@@ -81,21 +82,30 @@ class SplitTestModuleTest(XModuleXmlImportTest, PartitionTestCase):
...
@@ -81,21 +82,30 @@ class SplitTestModuleTest(XModuleXmlImportTest, PartitionTestCase):
self
.
module_system
.
descriptor_runtime
=
self
.
course
.
_runtime
# pylint: disable=protected-access
self
.
module_system
.
descriptor_runtime
=
self
.
course
.
_runtime
# pylint: disable=protected-access
self
.
course
.
runtime
.
export_fs
=
MemoryFS
()
self
.
course
.
runtime
.
export_fs
=
MemoryFS
()
user
=
Mock
(
username
=
'ma'
,
email
=
'ma@edx.org'
,
is_staff
=
False
,
is_active
=
True
)
# Create mock partition service, as these tests are running with XML in-memory system.
self
.
partitions_service
=
StaticPartitionService
(
self
.
course
.
user_partitions
=
[
[
self
.
user_partition
,
self
.
user_partition
,
UserPartition
(
UserPartition
(
MINIMUM_STATIC_PARTITION_ID
,
'second_partition'
,
'Second Partition'
,
1
,
'second_partition'
,
'Second Partition'
,
[
[
Group
(
"0"
,
'abel'
),
Group
(
"1"
,
'baker'
),
Group
(
"2"
,
'charlie'
)],
Group
(
unicode
(
MINIMUM_STATIC_PARTITION_ID
+
1
),
'abel'
),
MockUserPartitionScheme
()
Group
(
unicode
(
MINIMUM_STATIC_PARTITION_ID
+
2
),
'baker'
),
Group
(
"103"
,
'charlie'
)
)
],
],
MockUserPartitionScheme
()
user
=
user
,
)
]
partitions_service
=
MockPartitionService
(
self
.
course
,
course_id
=
self
.
course
.
id
,
course_id
=
self
.
course
.
id
,
track_function
=
Mock
(
name
=
'track_function'
),
track_function
=
Mock
(
name
=
'track_function'
),
)
)
self
.
module_system
.
_services
[
'partitions'
]
=
self
.
partitions_service
# pylint: disable=protected-access
self
.
module_system
.
_services
[
'partitions'
]
=
partitions_service
# pylint: disable=protected-access
# Mock user_service user
user_service
=
Mock
()
user
=
Mock
(
username
=
'ma'
,
email
=
'ma@edx.org'
,
is_staff
=
False
,
is_active
=
True
)
user_service
.
_django_user
=
user
self
.
module_system
.
_services
[
'user'
]
=
user_service
# pylint: disable=protected-access
self
.
split_test_module
=
self
.
course_sequence
.
get_children
()[
0
]
self
.
split_test_module
=
self
.
course_sequence
.
get_children
()[
0
]
self
.
split_test_module
.
bind_for_student
(
self
.
split_test_module
.
bind_for_student
(
...
@@ -103,6 +113,12 @@ class SplitTestModuleTest(XModuleXmlImportTest, PartitionTestCase):
...
@@ -103,6 +113,12 @@ class SplitTestModuleTest(XModuleXmlImportTest, PartitionTestCase):
user
.
id
user
.
id
)
)
# Create mock modulestore for getting the course. Needed for rendering the HTML
# view, since mock services exist and the rendering code will not short-circuit.
mocked_modulestore
=
Mock
()
mocked_modulestore
.
get_course
.
return_value
=
self
.
course
self
.
split_test_module
.
system
.
modulestore
=
mocked_modulestore
@ddt.ddt
@ddt.ddt
class
SplitTestModuleLMSTest
(
SplitTestModuleTest
):
class
SplitTestModuleLMSTest
(
SplitTestModuleTest
):
...
...
lms/djangoapps/ccx/tests/test_field_override_performance.py
View file @
e5035746
...
@@ -231,18 +231,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
...
@@ -231,18 +231,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
# # of sql queries to default,
# # of sql queries to default,
# # of mongo queries,
# # of mongo queries,
# )
# )
(
'no_overrides'
,
1
,
True
,
False
):
(
2
2
,
6
),
(
'no_overrides'
,
1
,
True
,
False
):
(
2
4
,
6
),
(
'no_overrides'
,
2
,
True
,
False
):
(
2
2
,
6
),
(
'no_overrides'
,
2
,
True
,
False
):
(
2
4
,
6
),
(
'no_overrides'
,
3
,
True
,
False
):
(
2
2
,
6
),
(
'no_overrides'
,
3
,
True
,
False
):
(
2
4
,
6
),
(
'ccx'
,
1
,
True
,
False
):
(
2
2
,
6
),
(
'ccx'
,
1
,
True
,
False
):
(
2
4
,
6
),
(
'ccx'
,
2
,
True
,
False
):
(
2
2
,
6
),
(
'ccx'
,
2
,
True
,
False
):
(
2
4
,
6
),
(
'ccx'
,
3
,
True
,
False
):
(
2
2
,
6
),
(
'ccx'
,
3
,
True
,
False
):
(
2
4
,
6
),
(
'no_overrides'
,
1
,
False
,
False
):
(
2
2
,
6
),
(
'no_overrides'
,
1
,
False
,
False
):
(
2
4
,
6
),
(
'no_overrides'
,
2
,
False
,
False
):
(
2
2
,
6
),
(
'no_overrides'
,
2
,
False
,
False
):
(
2
4
,
6
),
(
'no_overrides'
,
3
,
False
,
False
):
(
2
2
,
6
),
(
'no_overrides'
,
3
,
False
,
False
):
(
2
4
,
6
),
(
'ccx'
,
1
,
False
,
False
):
(
2
2
,
6
),
(
'ccx'
,
1
,
False
,
False
):
(
2
4
,
6
),
(
'ccx'
,
2
,
False
,
False
):
(
2
2
,
6
),
(
'ccx'
,
2
,
False
,
False
):
(
2
4
,
6
),
(
'ccx'
,
3
,
False
,
False
):
(
2
2
,
6
),
(
'ccx'
,
3
,
False
,
False
):
(
2
4
,
6
),
}
}
...
@@ -254,19 +254,19 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
...
@@ -254,19 +254,19 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
__test__
=
True
__test__
=
True
TEST_DATA
=
{
TEST_DATA
=
{
(
'no_overrides'
,
1
,
True
,
False
):
(
2
2
,
3
),
(
'no_overrides'
,
1
,
True
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
2
,
True
,
False
):
(
2
2
,
3
),
(
'no_overrides'
,
2
,
True
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
3
,
True
,
False
):
(
2
2
,
3
),
(
'no_overrides'
,
3
,
True
,
False
):
(
2
4
,
3
),
(
'ccx'
,
1
,
True
,
False
):
(
2
2
,
3
),
(
'ccx'
,
1
,
True
,
False
):
(
2
4
,
3
),
(
'ccx'
,
2
,
True
,
False
):
(
2
2
,
3
),
(
'ccx'
,
2
,
True
,
False
):
(
2
4
,
3
),
(
'ccx'
,
3
,
True
,
False
):
(
2
2
,
3
),
(
'ccx'
,
3
,
True
,
False
):
(
2
4
,
3
),
(
'ccx'
,
1
,
True
,
True
):
(
2
3
,
3
),
(
'ccx'
,
1
,
True
,
True
):
(
2
5
,
3
),
(
'ccx'
,
2
,
True
,
True
):
(
2
3
,
3
),
(
'ccx'
,
2
,
True
,
True
):
(
2
5
,
3
),
(
'ccx'
,
3
,
True
,
True
):
(
2
3
,
3
),
(
'ccx'
,
3
,
True
,
True
):
(
2
5
,
3
),
(
'no_overrides'
,
1
,
False
,
False
):
(
2
2
,
3
),
(
'no_overrides'
,
1
,
False
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
2
,
False
,
False
):
(
2
2
,
3
),
(
'no_overrides'
,
2
,
False
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
3
,
False
,
False
):
(
2
2
,
3
),
(
'no_overrides'
,
3
,
False
,
False
):
(
2
4
,
3
),
(
'ccx'
,
1
,
False
,
False
):
(
2
2
,
3
),
(
'ccx'
,
1
,
False
,
False
):
(
2
4
,
3
),
(
'ccx'
,
2
,
False
,
False
):
(
2
2
,
3
),
(
'ccx'
,
2
,
False
,
False
):
(
2
4
,
3
),
(
'ccx'
,
3
,
False
,
False
):
(
2
2
,
3
),
(
'ccx'
,
3
,
False
,
False
):
(
2
4
,
3
),
}
}
lms/djangoapps/course_api/blocks/tests/test_api.py
View file @
e5035746
...
@@ -146,7 +146,7 @@ class TestGetBlocksQueryCounts(SharedModuleStoreTestCase):
...
@@ -146,7 +146,7 @@ class TestGetBlocksQueryCounts(SharedModuleStoreTestCase):
self
.
_get_blocks
(
self
.
_get_blocks
(
course
,
course
,
expected_mongo_queries
=
0
,
expected_mongo_queries
=
0
,
expected_sql_queries
=
3
if
with_storage_backing
else
2
,
expected_sql_queries
=
5
if
with_storage_backing
else
4
,
)
)
@ddt.data
(
@ddt.data
(
...
@@ -164,5 +164,5 @@ class TestGetBlocksQueryCounts(SharedModuleStoreTestCase):
...
@@ -164,5 +164,5 @@ class TestGetBlocksQueryCounts(SharedModuleStoreTestCase):
self
.
_get_blocks
(
self
.
_get_blocks
(
course
,
course
,
expected_mongo_queries
,
expected_mongo_queries
,
expected_sql_queries
=
1
1
if
with_storage_backing
else
3
,
expected_sql_queries
=
1
3
if
with_storage_backing
else
5
,
)
)
lms/djangoapps/course_blocks/transformers/user_partitions.py
View file @
e5035746
...
@@ -5,6 +5,7 @@ from openedx.core.djangoapps.content.block_structure.transformer import (
...
@@ -5,6 +5,7 @@ from openedx.core.djangoapps.content.block_structure.transformer import (
BlockStructureTransformer
,
BlockStructureTransformer
,
FilteringTransformerMixin
,
FilteringTransformerMixin
,
)
)
from
xmodule.partitions.partitions_service
import
get_all_partitions_for_course
from
.split_test
import
SplitTestTransformer
from
.split_test
import
SplitTestTransformer
from
.utils
import
get_field_on_block
from
.utils
import
get_field_on_block
...
@@ -46,11 +47,7 @@ class UserPartitionTransformer(FilteringTransformerMixin, BlockStructureTransfor
...
@@ -46,11 +47,7 @@ class UserPartitionTransformer(FilteringTransformerMixin, BlockStructureTransfor
# Because user partitions are course-wide, only store data for
# Because user partitions are course-wide, only store data for
# them on the root block.
# them on the root block.
root_block
=
block_structure
.
get_xblock
(
block_structure
.
root_block_usage_key
)
root_block
=
block_structure
.
get_xblock
(
block_structure
.
root_block_usage_key
)
user_partitions
=
[
user_partitions
=
get_all_partitions_for_course
(
root_block
,
active_only
=
True
)
user_partition
for
user_partition
in
getattr
(
root_block
,
'user_partitions'
,
[])
if
user_partition
.
active
]
block_structure
.
set_transformer_data
(
cls
,
'user_partitions'
,
user_partitions
)
block_structure
.
set_transformer_data
(
cls
,
'user_partitions'
,
user_partitions
)
# If there are no user partitions, this transformation is a
# If there are no user partitions, this transformation is a
...
...
lms/djangoapps/courseware/access.py
View file @
e5035746
...
@@ -30,7 +30,6 @@ from xmodule.course_module import (
...
@@ -30,7 +30,6 @@ from xmodule.course_module import (
)
)
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.x_module
import
XModule
from
xmodule.x_module
import
XModule
from
xmodule.split_test_module
import
get_split_user_partitions
from
xmodule.partitions.partitions
import
NoSuchUserPartitionError
,
NoSuchUserPartitionGroupError
from
xmodule.partitions.partitions
import
NoSuchUserPartitionError
,
NoSuchUserPartitionGroupError
from
courseware.access_response
import
(
from
courseware.access_response
import
(
...
@@ -466,12 +465,6 @@ def _has_group_access(descriptor, user, course_key):
...
@@ -466,12 +465,6 @@ def _has_group_access(descriptor, user, course_key):
This function returns a boolean indicating whether or not `user` has
This function returns a boolean indicating whether or not `user` has
sufficient group memberships to "load" a block (the `descriptor`)
sufficient group memberships to "load" a block (the `descriptor`)
"""
"""
if
len
(
descriptor
.
user_partitions
)
==
len
(
get_split_user_partitions
(
descriptor
.
user_partitions
)):
# Short-circuit the process, since there are no defined user partitions that are not
# user_partitions used by the split_test module. The split_test module handles its own access
# via updating the children of the split_test module.
return
ACCESS_GRANTED
# Allow staff and instructors roles group access, as they are not masquerading as a student.
# Allow staff and instructors roles group access, as they are not masquerading as a student.
if
get_user_role
(
user
,
course_key
)
in
[
'staff'
,
'instructor'
]:
if
get_user_role
(
user
,
course_key
)
in
[
'staff'
,
'instructor'
]:
return
ACCESS_GRANTED
return
ACCESS_GRANTED
...
...
lms/djangoapps/courseware/tests/test_access.py
View file @
e5035746
...
@@ -45,6 +45,7 @@ from xmodule.course_module import (
...
@@ -45,6 +45,7 @@ from xmodule.course_module import (
)
)
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.partitions.partitions
import
Group
,
UserPartition
from
xmodule.partitions.partitions
import
Group
,
UserPartition
from
xmodule.partitions.partitions_service
import
MINIMUM_STATIC_PARTITION_ID
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
...
@@ -301,9 +302,11 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
...
@@ -301,9 +302,11 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
"""
"""
Test that a user masquerading as a member of a group sees appropriate content in preview mode.
Test that a user masquerading as a member of a group sees appropriate content in preview mode.
"""
"""
partition_id
=
0
# Note about UserPartition and UserPartition Group IDs: these must not conflict with IDs used
group_0_id
=
0
# by dynamic user partitions.
group_1_id
=
1
partition_id
=
MINIMUM_STATIC_PARTITION_ID
group_0_id
=
MINIMUM_STATIC_PARTITION_ID
+
1
group_1_id
=
MINIMUM_STATIC_PARTITION_ID
+
2
user_partition
=
UserPartition
(
user_partition
=
UserPartition
(
partition_id
,
'Test User Partition'
,
''
,
partition_id
,
'Test User Partition'
,
''
,
[
Group
(
group_0_id
,
'Group 1'
),
Group
(
group_1_id
,
'Group 2'
)],
[
Group
(
group_0_id
,
'Group 1'
),
Group
(
group_1_id
,
'Group 2'
)],
...
@@ -314,7 +317,6 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
...
@@ -314,7 +317,6 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
chapter
=
ItemFactory
.
create
(
category
=
"chapter"
,
parent_location
=
self
.
course
.
location
)
chapter
=
ItemFactory
.
create
(
category
=
"chapter"
,
parent_location
=
self
.
course
.
location
)
chapter
.
group_access
=
{
partition_id
:
[
group_0_id
]}
chapter
.
group_access
=
{
partition_id
:
[
group_0_id
]}
chapter
.
user_partitions
=
self
.
course
.
user_partitions
modulestore
()
.
update_item
(
self
.
course
,
ModuleStoreEnum
.
UserID
.
test
)
modulestore
()
.
update_item
(
self
.
course
,
ModuleStoreEnum
.
UserID
.
test
)
...
@@ -431,6 +433,7 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
...
@@ -431,6 +433,7 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
user
=
Mock
()
user
=
Mock
()
descriptor
=
Mock
(
user_partitions
=
[])
descriptor
=
Mock
(
user_partitions
=
[])
descriptor
.
_class_tags
=
{}
descriptor
.
_class_tags
=
{}
descriptor
.
merged_group_access
=
{}
# Always returns true because DISABLE_START_DATES is set in test.py
# Always returns true because DISABLE_START_DATES is set in test.py
self
.
assertTrue
(
access
.
_has_access_descriptor
(
user
,
'load'
,
descriptor
))
self
.
assertTrue
(
access
.
_has_access_descriptor
(
user
,
'load'
,
descriptor
))
...
@@ -457,6 +460,8 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
...
@@ -457,6 +460,8 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
mock_unit
.
_class_tags
=
{}
# Needed for detached check in _has_access_descriptor
mock_unit
.
_class_tags
=
{}
# Needed for detached check in _has_access_descriptor
mock_unit
.
visible_to_staff_only
=
visible_to_staff_only
mock_unit
.
visible_to_staff_only
=
visible_to_staff_only
mock_unit
.
start
=
start
mock_unit
.
start
=
start
mock_unit
.
merged_group_access
=
{}
self
.
verify_access
(
mock_unit
,
expected_access
,
expected_error_type
)
self
.
verify_access
(
mock_unit
,
expected_access
,
expected_error_type
)
def
test__has_access_descriptor_beta_user
(
self
):
def
test__has_access_descriptor_beta_user
(
self
):
...
@@ -465,6 +470,7 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
...
@@ -465,6 +470,7 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
mock_unit
.
days_early_for_beta
=
2
mock_unit
.
days_early_for_beta
=
2
mock_unit
.
start
=
self
.
TOMORROW
mock_unit
.
start
=
self
.
TOMORROW
mock_unit
.
visible_to_staff_only
=
False
mock_unit
.
visible_to_staff_only
=
False
mock_unit
.
merged_group_access
=
{}
self
.
assertTrue
(
bool
(
access
.
_has_access_descriptor
(
self
.
assertTrue
(
bool
(
access
.
_has_access_descriptor
(
self
.
beta_user
,
'load'
,
mock_unit
,
course_key
=
self
.
course
.
id
)))
self
.
beta_user
,
'load'
,
mock_unit
,
course_key
=
self
.
course
.
id
)))
...
@@ -480,6 +486,8 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
...
@@ -480,6 +486,8 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
mock_unit
.
_class_tags
=
{}
# Needed for detached check in _has_access_descriptor
mock_unit
.
_class_tags
=
{}
# Needed for detached check in _has_access_descriptor
mock_unit
.
visible_to_staff_only
=
False
mock_unit
.
visible_to_staff_only
=
False
mock_unit
.
start
=
start
mock_unit
.
start
=
start
mock_unit
.
merged_group_access
=
{}
self
.
verify_access
(
mock_unit
,
True
)
self
.
verify_access
(
mock_unit
,
True
)
@ddt.data
(
@ddt.data
(
...
@@ -499,6 +507,8 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
...
@@ -499,6 +507,8 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
mock_unit
.
_class_tags
=
{}
# Needed for detached check in _has_access_descriptor
mock_unit
.
_class_tags
=
{}
# Needed for detached check in _has_access_descriptor
mock_unit
.
visible_to_staff_only
=
False
mock_unit
.
visible_to_staff_only
=
False
mock_unit
.
start
=
start
mock_unit
.
start
=
start
mock_unit
.
merged_group_access
=
{}
self
.
verify_access
(
mock_unit
,
expected_access
,
expected_error_type
)
self
.
verify_access
(
mock_unit
,
expected_access
,
expected_error_type
)
def
test__has_access_course_can_enroll
(
self
):
def
test__has_access_course_can_enroll
(
self
):
...
...
lms/djangoapps/courseware/tests/test_group_access.py
View file @
e5035746
...
@@ -406,31 +406,3 @@ class GroupAccessTestCase(ModuleStoreTestCase):
...
@@ -406,31 +406,3 @@ class GroupAccessTestCase(ModuleStoreTestCase):
self
.
check_access
(
self
.
blue_dog
,
block_accessed
,
False
)
self
.
check_access
(
self
.
blue_dog
,
block_accessed
,
False
)
self
.
check_access
(
self
.
gray_worm
,
block_accessed
,
False
)
self
.
check_access
(
self
.
gray_worm
,
block_accessed
,
False
)
self
.
ensure_staff_access
(
block_accessed
)
self
.
ensure_staff_access
(
block_accessed
)
def
test_group_access_short_circuits
(
self
):
"""
Test that the group_access check short-circuits if there are no user_partitions defined
except user_partitions in use by the split_test module.
"""
# Initially, "red_cat" user can't view the vertical.
self
.
set_group_access
(
self
.
chapter_location
,
{
self
.
animal_partition
.
id
:
[
self
.
dog_group
.
id
]})
self
.
check_access
(
self
.
red_cat
,
self
.
vertical_location
,
False
)
# Change the vertical's user_partitions value to the empty list. Now red_cat can view the vertical.
self
.
set_user_partitions
(
self
.
vertical_location
,
[])
self
.
check_access
(
self
.
red_cat
,
self
.
vertical_location
,
True
)
# Change the vertical's user_partitions value to include only "split_test" partitions.
split_test_partition
=
UserPartition
(
199
,
'split_test partition'
,
'nothing to look at here'
,
[
Group
(
2
,
'random group'
)],
scheme
=
UserPartition
.
get_scheme
(
"random"
),
)
self
.
set_user_partitions
(
self
.
vertical_location
,
[
split_test_partition
])
self
.
check_access
(
self
.
red_cat
,
self
.
vertical_location
,
True
)
# Finally, add back in a cohort user_partition
self
.
set_user_partitions
(
self
.
vertical_location
,
[
split_test_partition
,
self
.
animal_partition
])
self
.
check_access
(
self
.
red_cat
,
self
.
vertical_location
,
False
)
lms/djangoapps/courseware/tests/test_views.py
View file @
e5035746
...
@@ -206,7 +206,7 @@ class IndexQueryTestCase(ModuleStoreTestCase):
...
@@ -206,7 +206,7 @@ class IndexQueryTestCase(ModuleStoreTestCase):
NUM_PROBLEMS
=
20
NUM_PROBLEMS
=
20
@ddt.data
(
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
9
),
(
ModuleStoreEnum
.
Type
.
mongo
,
10
),
(
ModuleStoreEnum
.
Type
.
split
,
4
),
(
ModuleStoreEnum
.
Type
.
split
,
4
),
)
)
@ddt.unpack
@ddt.unpack
...
@@ -1420,17 +1420,17 @@ class ProgressPageTests(ModuleStoreTestCase):
...
@@ -1420,17 +1420,17 @@ class ProgressPageTests(ModuleStoreTestCase):
"""Test that query counts remain the same for self-paced and instructor-paced courses."""
"""Test that query counts remain the same for self-paced and instructor-paced courses."""
SelfPacedConfiguration
(
enabled
=
self_paced_enabled
)
.
save
()
SelfPacedConfiguration
(
enabled
=
self_paced_enabled
)
.
save
()
self
.
setup_course
(
self_paced
=
self_paced
)
self
.
setup_course
(
self_paced
=
self_paced
)
with
self
.
assertNumQueries
(
39
),
check_mongo_calls
(
4
):
with
self
.
assertNumQueries
(
41
),
check_mongo_calls
(
4
):
self
.
_get_progress_page
()
self
.
_get_progress_page
()
def
test_progress_queries
(
self
):
def
test_progress_queries
(
self
):
self
.
setup_course
()
self
.
setup_course
()
with
self
.
assertNumQueries
(
39
),
check_mongo_calls
(
4
):
with
self
.
assertNumQueries
(
41
),
check_mongo_calls
(
4
):
self
.
_get_progress_page
()
self
.
_get_progress_page
()
# subsequent accesses to the progress page require fewer queries.
# subsequent accesses to the progress page require fewer queries.
for
_
in
range
(
2
):
for
_
in
range
(
2
):
with
self
.
assertNumQueries
(
2
5
),
check_mongo_calls
(
4
):
with
self
.
assertNumQueries
(
2
7
),
check_mongo_calls
(
4
):
self
.
_get_progress_page
()
self
.
_get_progress_page
()
@patch
(
@patch
(
...
...
lms/djangoapps/courseware/testutils.py
View file @
e5035746
...
@@ -178,6 +178,8 @@ class RenderXBlockTestMixin(object):
...
@@ -178,6 +178,8 @@ class RenderXBlockTestMixin(object):
@ddt.unpack
@ddt.unpack
def
test_success_enrolled_staff
(
self
,
default_store
,
mongo_calls
):
def
test_success_enrolled_staff
(
self
,
default_store
,
mongo_calls
):
with
self
.
store
.
default_store
(
default_store
):
with
self
.
store
.
default_store
(
default_store
):
if
default_store
is
ModuleStoreEnum
.
Type
.
mongo
:
mongo_calls
=
self
.
get_success_enrolled_staff_mongo_count
()
self
.
setup_course
(
default_store
)
self
.
setup_course
(
default_store
)
self
.
setup_user
(
admin
=
True
,
enroll
=
True
,
login
=
True
)
self
.
setup_user
(
admin
=
True
,
enroll
=
True
,
login
=
True
)
...
@@ -197,6 +199,13 @@ class RenderXBlockTestMixin(object):
...
@@ -197,6 +199,13 @@ class RenderXBlockTestMixin(object):
with
check_mongo_calls
(
mongo_calls
):
with
check_mongo_calls
(
mongo_calls
):
self
.
verify_response
()
self
.
verify_response
()
def
get_success_enrolled_staff_mongo_count
(
self
):
"""
Helper method used by test_success_enrolled_staff because one test
class using this mixin has an increased number of mongo (only) queries.
"""
return
5
def
test_success_unenrolled_staff
(
self
):
def
test_success_unenrolled_staff
(
self
):
self
.
setup_course
()
self
.
setup_course
()
self
.
setup_user
(
admin
=
True
,
enroll
=
False
,
login
=
True
)
self
.
setup_user
(
admin
=
True
,
enroll
=
False
,
login
=
True
)
...
...
lms/djangoapps/grades/tests/test_tasks.py
View file @
e5035746
...
@@ -154,10 +154,10 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest
...
@@ -154,10 +154,10 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest
self
.
assertEquals
(
mock_block_structure_create
.
call_count
,
1
)
self
.
assertEquals
(
mock_block_structure_create
.
call_count
,
1
)
@ddt.data
(
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
1
,
2
4
,
True
),
(
ModuleStoreEnum
.
Type
.
mongo
,
1
,
2
6
,
True
),
(
ModuleStoreEnum
.
Type
.
mongo
,
1
,
2
1
,
False
),
(
ModuleStoreEnum
.
Type
.
mongo
,
1
,
2
3
,
False
),
(
ModuleStoreEnum
.
Type
.
split
,
3
,
2
3
,
True
),
(
ModuleStoreEnum
.
Type
.
split
,
3
,
2
5
,
True
),
(
ModuleStoreEnum
.
Type
.
split
,
3
,
2
0
,
False
),
(
ModuleStoreEnum
.
Type
.
split
,
3
,
2
2
,
False
),
)
)
@ddt.unpack
@ddt.unpack
def
test_query_counts
(
self
,
default_store
,
num_mongo_calls
,
num_sql_calls
,
create_multiple_subsections
):
def
test_query_counts
(
self
,
default_store
,
num_mongo_calls
,
num_sql_calls
,
create_multiple_subsections
):
...
@@ -169,8 +169,8 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest
...
@@ -169,8 +169,8 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest
self
.
_apply_recalculate_subsection_grade
()
self
.
_apply_recalculate_subsection_grade
()
@ddt.data
(
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
1
,
2
4
),
(
ModuleStoreEnum
.
Type
.
mongo
,
1
,
2
6
),
(
ModuleStoreEnum
.
Type
.
split
,
3
,
2
3
),
(
ModuleStoreEnum
.
Type
.
split
,
3
,
2
5
),
)
)
@ddt.unpack
@ddt.unpack
def
test_query_counts_dont_change_with_more_content
(
self
,
default_store
,
num_mongo_calls
,
num_sql_calls
):
def
test_query_counts_dont_change_with_more_content
(
self
,
default_store
,
num_mongo_calls
,
num_sql_calls
):
...
@@ -215,8 +215,8 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest
...
@@ -215,8 +215,8 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest
)
)
@ddt.data
(
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
1
,
9
),
(
ModuleStoreEnum
.
Type
.
mongo
,
1
,
11
),
(
ModuleStoreEnum
.
Type
.
split
,
3
,
8
),
(
ModuleStoreEnum
.
Type
.
split
,
3
,
10
),
)
)
@ddt.unpack
@ddt.unpack
def
test_persistent_grades_not_enabled_on_course
(
self
,
default_store
,
num_mongo_queries
,
num_sql_queries
):
def
test_persistent_grades_not_enabled_on_course
(
self
,
default_store
,
num_mongo_queries
,
num_sql_queries
):
...
@@ -230,8 +230,8 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest
...
@@ -230,8 +230,8 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest
self
.
assertEqual
(
len
(
PersistentSubsectionGrade
.
bulk_read_grades
(
self
.
user
.
id
,
self
.
course
.
id
)),
0
)
self
.
assertEqual
(
len
(
PersistentSubsectionGrade
.
bulk_read_grades
(
self
.
user
.
id
,
self
.
course
.
id
)),
0
)
@ddt.data
(
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
1
,
2
2
),
(
ModuleStoreEnum
.
Type
.
mongo
,
1
,
2
4
),
(
ModuleStoreEnum
.
Type
.
split
,
3
,
2
1
),
(
ModuleStoreEnum
.
Type
.
split
,
3
,
2
3
),
)
)
@ddt.unpack
@ddt.unpack
def
test_persistent_grades_enabled_on_course
(
self
,
default_store
,
num_mongo_queries
,
num_sql_queries
):
def
test_persistent_grades_enabled_on_course
(
self
,
default_store
,
num_mongo_queries
,
num_sql_queries
):
...
@@ -409,8 +409,8 @@ class ComputeGradesForCourseTest(HasCourseWithProblemsMixin, ModuleStoreTestCase
...
@@ -409,8 +409,8 @@ class ComputeGradesForCourseTest(HasCourseWithProblemsMixin, ModuleStoreTestCase
@ddt.data
(
*
xrange
(
1
,
12
,
3
))
@ddt.data
(
*
xrange
(
1
,
12
,
3
))
def
test_database_calls
(
self
,
batch_size
):
def
test_database_calls
(
self
,
batch_size
):
per_user_queries
=
1
6
*
min
(
batch_size
,
6
)
# No more than 6 due to offset
per_user_queries
=
1
8
*
min
(
batch_size
,
6
)
# No more than 6 due to offset
with
self
.
assertNumQueries
(
3
+
16
*
min
(
batch_size
,
6
)
):
with
self
.
assertNumQueries
(
3
+
per_user_queries
):
with
check_mongo_calls
(
1
):
with
check_mongo_calls
(
1
):
compute_grades_for_course
.
delay
(
compute_grades_for_course
.
delay
(
course_key
=
six
.
text_type
(
self
.
course
.
id
),
course_key
=
six
.
text_type
(
self
.
course
.
id
),
...
...
lms/djangoapps/instructor/views/instructor_dashboard.py
View file @
e5035746
...
@@ -54,6 +54,7 @@ from class_dashboard.dashboard_data import get_section_display_name, get_array_s
...
@@ -54,6 +54,7 @@ from class_dashboard.dashboard_data import get_section_display_name, get_array_s
from
.tools
import
get_units_with_due_date
,
title_or_url
from
.tools
import
get_units_with_due_date
,
title_or_url
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.djangoapps.verified_track_content.models
import
VerifiedTrackCohortedCourse
from
openedx.core.djangolib.markup
import
HTML
,
Text
from
openedx.core.djangolib.markup
import
HTML
,
Text
...
@@ -640,7 +641,6 @@ def _section_send_email(course, access):
...
@@ -640,7 +641,6 @@ def _section_send_email(course, access):
if
is_course_cohorted
(
course_key
):
if
is_course_cohorted
(
course_key
):
cohorts
=
get_course_cohorts
(
course
)
cohorts
=
get_course_cohorts
(
course
)
course_modes
=
[]
course_modes
=
[]
from
verified_track_content.models
import
VerifiedTrackCohortedCourse
if
not
VerifiedTrackCohortedCourse
.
is_verified_track_cohort_enabled
(
course_key
):
if
not
VerifiedTrackCohortedCourse
.
is_verified_track_cohort_enabled
(
course_key
):
course_modes
=
CourseMode
.
modes_for_course
(
course_key
,
include_expired
=
True
,
only_selectable
=
False
)
course_modes
=
CourseMode
.
modes_for_course
(
course_key
,
include_expired
=
True
,
only_selectable
=
False
)
email_editor
=
fragment
.
content
email_editor
=
fragment
.
content
...
...
lms/djangoapps/instructor_task/tasks_helper.py
View file @
e5035746
...
@@ -30,6 +30,7 @@ from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
...
@@ -30,6 +30,7 @@ from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
from
pytz
import
UTC
from
pytz
import
UTC
from
track
import
contexts
from
track
import
contexts
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.partitions.partitions_service
import
PartitionService
from
xmodule.split_test_module
import
get_split_user_partitions
from
xmodule.split_test_module
import
get_split_user_partitions
from
certificates.api
import
generate_user_certificates
from
certificates.api
import
generate_user_certificates
...
@@ -59,7 +60,6 @@ from shoppingcart.models import (
...
@@ -59,7 +60,6 @@ from shoppingcart.models import (
)
)
from
openassessment.data
import
OraAggregateData
from
openassessment.data
import
OraAggregateData
from
lms.djangoapps.instructor_task.models
import
ReportStore
,
InstructorTask
,
PROGRESS
from
lms.djangoapps.instructor_task.models
import
ReportStore
,
InstructorTask
,
PROGRESS
from
lms.djangoapps.lms_xblock.runtime
import
LmsPartitionService
from
openedx.core.djangoapps.course_groups.cohorts
import
get_cohort
from
openedx.core.djangoapps.course_groups.cohorts
import
get_cohort
from
openedx.core.djangoapps.course_groups.models
import
CourseUserGroup
from
openedx.core.djangoapps.course_groups.models
import
CourseUserGroup
from
opaque_keys.edx.keys
import
UsageKey
from
opaque_keys.edx.keys
import
UsageKey
...
@@ -806,7 +806,7 @@ def upload_grades_csv(_xmodule_instance_args, _entry_id, course_id, _task_input,
...
@@ -806,7 +806,7 @@ def upload_grades_csv(_xmodule_instance_args, _entry_id, course_id, _task_input,
group_configs_group_names
=
[]
group_configs_group_names
=
[]
for
partition
in
experiment_partitions
:
for
partition
in
experiment_partitions
:
group
=
LmsPartitionService
(
student
,
course_id
)
.
get_group
(
partition
,
assign
=
False
)
group
=
PartitionService
(
course_id
)
.
get_group
(
student
,
partition
,
assign
=
False
)
group_configs_group_names
.
append
(
group
.
name
if
group
else
''
)
group_configs_group_names
.
append
(
group
.
name
if
group
else
''
)
team_name
=
[]
team_name
=
[]
...
...
lms/djangoapps/instructor_task/tests/test_tasks_helper.py
View file @
e5035746
...
@@ -1775,7 +1775,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
...
@@ -1775,7 +1775,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
'failed'
:
3
,
'failed'
:
3
,
'skipped'
:
2
'skipped'
:
2
}
}
with
self
.
assertNumQueries
(
1
68
):
with
self
.
assertNumQueries
(
1
84
):
self
.
assertCertificatesGenerated
(
task_input
,
expected_results
)
self
.
assertCertificatesGenerated
(
task_input
,
expected_results
)
expected_results
=
{
expected_results
=
{
...
...
lms/djangoapps/lms_xblock/mixin.py
View file @
e5035746
...
@@ -5,6 +5,7 @@ Namespace that defines fields common to all blocks used in the LMS
...
@@ -5,6 +5,7 @@ Namespace that defines fields common to all blocks used in the LMS
#from django.utils.translation import ugettext_noop as _
#from django.utils.translation import ugettext_noop as _
from
lazy
import
lazy
from
lazy
import
lazy
from
xblock.core
import
XBlock
from
xblock.fields
import
Boolean
,
Scope
,
String
,
XBlockMixin
,
Dict
from
xblock.fields
import
Boolean
,
Scope
,
String
,
XBlockMixin
,
Dict
from
xblock.validation
import
ValidationMessage
from
xblock.validation
import
ValidationMessage
from
xmodule.modulestore.inheritance
import
UserPartitionList
from
xmodule.modulestore.inheritance
import
UserPartitionList
...
@@ -26,6 +27,7 @@ class GroupAccessDict(Dict):
...
@@ -26,6 +27,7 @@ class GroupAccessDict(Dict):
return
{
unicode
(
k
):
access_dict
[
k
]
for
k
in
access_dict
}
return
{
unicode
(
k
):
access_dict
[
k
]
for
k
in
access_dict
}
@XBlock.needs
(
'partitions'
)
class
LmsBlockMixin
(
XBlockMixin
):
class
LmsBlockMixin
(
XBlockMixin
):
"""
"""
Mixin that defines fields common to all blocks used in the LMS
Mixin that defines fields common to all blocks used in the LMS
...
@@ -128,10 +130,10 @@ class LmsBlockMixin(XBlockMixin):
...
@@ -128,10 +130,10 @@ class LmsBlockMixin(XBlockMixin):
def
_get_user_partition
(
self
,
user_partition_id
):
def
_get_user_partition
(
self
,
user_partition_id
):
"""
"""
Returns the user partition with the specified id.
Raises
Returns the user partition with the specified id.
Note that this method can return
`NoSuchUserPartitionError` if the lookup fails.
an inactive user partition. Raises
`NoSuchUserPartitionError` if the lookup fails.
"""
"""
for
user_partition
in
self
.
user
_partitions
:
for
user_partition
in
self
.
runtime
.
service
(
self
,
'partitions'
)
.
course
_partitions
:
if
user_partition
.
id
==
user_partition_id
:
if
user_partition
.
id
==
user_partition_id
:
return
user_partition
return
user_partition
...
...
lms/djangoapps/lms_xblock/runtime.py
View file @
e5035746
...
@@ -80,22 +80,6 @@ def local_resource_url(block, uri):
...
@@ -80,22 +80,6 @@ def local_resource_url(block, uri):
return
xblock_local_resource_url
(
block
,
uri
)
return
xblock_local_resource_url
(
block
,
uri
)
class
LmsPartitionService
(
PartitionService
):
"""
Another runtime mixin that provides access to the student partitions defined on the
course.
(If and when XBlock directly provides access from one block (e.g. a split_test_module)
to another (e.g. a course_module), this won't be necessary, but for now it seems like
the least messy way to hook things through)
"""
@property
def
course_partitions
(
self
):
course
=
modulestore
()
.
get_course
(
self
.
_course_id
)
return
course
.
user_partitions
class
UserTagsService
(
object
):
class
UserTagsService
(
object
):
"""
"""
A runtime class that provides an interface to the user service. It handles filling in
A runtime class that provides an interface to the user service. It handles filling in
...
@@ -154,8 +138,7 @@ class LmsModuleSystem(ModuleSystem): # pylint: disable=abstract-method
...
@@ -154,8 +138,7 @@ class LmsModuleSystem(ModuleSystem): # pylint: disable=abstract-method
services
[
'fs'
]
=
xblock
.
reference
.
plugins
.
FSService
()
services
[
'fs'
]
=
xblock
.
reference
.
plugins
.
FSService
()
services
[
'i18n'
]
=
ModuleI18nService
services
[
'i18n'
]
=
ModuleI18nService
services
[
'library_tools'
]
=
LibraryToolsService
(
modulestore
())
services
[
'library_tools'
]
=
LibraryToolsService
(
modulestore
())
services
[
'partitions'
]
=
LmsPartitionService
(
services
[
'partitions'
]
=
PartitionService
(
user
=
kwargs
.
get
(
'user'
),
course_id
=
kwargs
.
get
(
'course_id'
),
course_id
=
kwargs
.
get
(
'course_id'
),
track_function
=
kwargs
.
get
(
'track_function'
,
None
),
track_function
=
kwargs
.
get
(
'track_function'
,
None
),
cache
=
request_cache_dict
cache
=
request_cache_dict
...
...
lms/djangoapps/lti_provider/tests/test_views.py
View file @
e5035746
...
@@ -170,6 +170,8 @@ class LtiLaunchTestRender(LtiTestMixin, RenderXBlockTestMixin, ModuleStoreTestCa
...
@@ -170,6 +170,8 @@ class LtiLaunchTestRender(LtiTestMixin, RenderXBlockTestMixin, ModuleStoreTestCa
This class overrides the get_response method, which is used by
This class overrides the get_response method, which is used by
the tests defined in RenderXBlockTestMixin.
the tests defined in RenderXBlockTestMixin.
"""
"""
SUCCESS_ENROLLED_STAFF_MONGO_COUNT
=
9
def
setUp
(
self
):
def
setUp
(
self
):
"""
"""
Set up tests
Set up tests
...
@@ -212,3 +214,21 @@ class LtiLaunchTestRender(LtiTestMixin, RenderXBlockTestMixin, ModuleStoreTestCa
...
@@ -212,3 +214,21 @@ class LtiLaunchTestRender(LtiTestMixin, RenderXBlockTestMixin, ModuleStoreTestCa
self
.
setup_course
()
self
.
setup_course
()
self
.
setup_user
(
admin
=
False
,
enroll
=
True
,
login
=
False
)
self
.
setup_user
(
admin
=
False
,
enroll
=
True
,
login
=
False
)
self
.
verify_response
()
self
.
verify_response
()
def
get_success_enrolled_staff_mongo_count
(
self
):
"""
Override because mongo queries are higher for this
particular test. This has not been investigated exhaustively
as mongo is no longer used much, and removing user_partitions
from inheritance fixes the problem.
# The 9 mongoDB calls include calls for
# Old Mongo:
# (1) fill_in_run
# (2) get_course in get_course_with_access
# (3) get_item for HTML block in get_module_by_usage_id
# (4) get_parent when loading HTML block
# (5)-(8) calls related to the inherited user_partitions field.
# (9) edx_notes descriptor call to get_course
"""
return
9
lms/envs/bok_choy.py
View file @
e5035746
...
@@ -146,6 +146,8 @@ FEATURES['AUTOMATIC_AUTH_FOR_TESTING'] = True
...
@@ -146,6 +146,8 @@ FEATURES['AUTOMATIC_AUTH_FOR_TESTING'] = True
# Open up endpoint for faking Software Secure responses
# Open up endpoint for faking Software Secure responses
FEATURES
[
'ENABLE_SOFTWARE_SECURE_FAKE'
]
=
True
FEATURES
[
'ENABLE_SOFTWARE_SECURE_FAKE'
]
=
True
FEATURES
[
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION'
]
=
True
########################### Entrance Exams #################################
########################### Entrance Exams #################################
FEATURES
[
'ENTRANCE_EXAMS'
]
=
True
FEATURES
[
'ENTRANCE_EXAMS'
]
=
True
...
...
lms/envs/common.py
View file @
e5035746
...
@@ -371,6 +371,9 @@ FEATURES = {
...
@@ -371,6 +371,9 @@ FEATURES = {
# Enable footer banner for cookie consent.
# Enable footer banner for cookie consent.
# See https://cookieconsent.insites.com/ for more.
# See https://cookieconsent.insites.com/ for more.
'ENABLE_COOKIE_CONSENT'
:
False
,
'ENABLE_COOKIE_CONSENT'
:
False
,
# Whether or not the dynamic EnrollmentTrackUserPartition should be registered.
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION'
:
False
,
}
}
# Ignore static asset files on import which match this pattern
# Ignore static asset files on import which match this pattern
...
@@ -2146,8 +2149,8 @@ INSTALLED_APPS = (
...
@@ -2146,8 +2149,8 @@ INSTALLED_APPS = (
# API access administration
# API access administration
'openedx.core.djangoapps.api_admin'
,
'openedx.core.djangoapps.api_admin'
,
# Verified Track Content Cohorting
# Verified Track Content Cohorting
(Beta feature that will hopefully be removed)
'verified_track_content'
,
'
openedx.core.djangoapps.
verified_track_content'
,
# Learner's dashboard
# Learner's dashboard
'learner_dashboard'
,
'learner_dashboard'
,
...
@@ -3068,3 +3071,13 @@ DOC_LINK_BASE_URL = None
...
@@ -3068,3 +3071,13 @@ DOC_LINK_BASE_URL = None
ENTERPRISE_ENROLLMENT_API_URL
=
LMS_ROOT_URL
+
"/api/enrollment/v1/"
ENTERPRISE_ENROLLMENT_API_URL
=
LMS_ROOT_URL
+
"/api/enrollment/v1/"
ENTERPRISE_PUBLIC_ENROLLMENT_API_URL
=
ENTERPRISE_ENROLLMENT_API_URL
ENTERPRISE_PUBLIC_ENROLLMENT_API_URL
=
ENTERPRISE_ENROLLMENT_API_URL
ENTERPRISE_API_CACHE_TIMEOUT
=
3600
# Value is in seconds
ENTERPRISE_API_CACHE_TIMEOUT
=
3600
# Value is in seconds
############## Settings for Course Enrollment Modes ######################
COURSE_ENROLLMENT_MODES
=
{
"audit"
:
1
,
"verified"
:
2
,
"professional"
:
3
,
"no-id-professional"
:
4
,
"credit"
:
5
,
"honor"
:
6
,
}
lms/envs/test.py
View file @
e5035746
...
@@ -78,6 +78,8 @@ FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION'] = True
...
@@ -78,6 +78,8 @@ FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION'] = True
# Enable the milestones app in tests to be consistent with it being enabled in production
# Enable the milestones app in tests to be consistent with it being enabled in production
FEATURES
[
'MILESTONES_APP'
]
=
True
FEATURES
[
'MILESTONES_APP'
]
=
True
FEATURES
[
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION'
]
=
True
# Need wiki for courseware views to work. TODO (vshnayder): shouldn't need it.
# Need wiki for courseware views to work. TODO (vshnayder): shouldn't need it.
WIKI_ENABLED
=
True
WIKI_ENABLED
=
True
...
...
lms/urls.py
View file @
e5035746
...
@@ -553,7 +553,7 @@ urlpatterns += (
...
@@ -553,7 +553,7 @@ urlpatterns += (
r'^courses/{}/verified_track_content/settings'
.
format
(
r'^courses/{}/verified_track_content/settings'
.
format
(
settings
.
COURSE_KEY_PATTERN
,
settings
.
COURSE_KEY_PATTERN
,
),
),
'verified_track_content.views.cohorting_settings'
,
'
openedx.core.djangoapps.
verified_track_content.views.cohorting_settings'
,
name
=
'verified_track_cohorting'
,
name
=
'verified_track_cohorting'
,
),
),
url
(
url
(
...
...
openedx/core/djangoapps/credit/verification_access.py
View file @
e5035746
...
@@ -141,7 +141,7 @@ def _set_verification_partitions(course_key, icrv_blocks):
...
@@ -141,7 +141,7 @@ def _set_verification_partitions(course_key, icrv_blocks):
log
.
error
(
"Could not find course
%
s"
,
course_key
)
log
.
error
(
"Could not find course
%
s"
,
course_key
)
return
[]
return
[]
verified_partitions
=
course
.
get_user_partitions_for_scheme
(
scheme
)
verified_partitions
=
[
p
for
p
in
course
.
user_partitions
if
p
.
scheme
==
scheme
]
partition_id_for_location
=
{
partition_id_for_location
=
{
p
.
parameters
[
"location"
]:
p
.
id
p
.
parameters
[
"location"
]:
p
.
id
for
p
in
verified_partitions
for
p
in
verified_partitions
...
...
lms
/djangoapps/verified_track_content/__init__.py
→
openedx/core
/djangoapps/verified_track_content/__init__.py
View file @
e5035746
File moved
lms
/djangoapps/verified_track_content/admin.py
→
openedx/core
/djangoapps/verified_track_content/admin.py
View file @
e5035746
...
@@ -4,8 +4,8 @@ Django admin page for verified track configuration
...
@@ -4,8 +4,8 @@ Django admin page for verified track configuration
from
django.contrib
import
admin
from
django.contrib
import
admin
from
verified_track_content.forms
import
VerifiedTrackCourseForm
from
openedx.core.djangoapps.
verified_track_content.forms
import
VerifiedTrackCourseForm
from
verified_track_content.models
import
VerifiedTrackCohortedCourse
from
openedx.core.djangoapps.
verified_track_content.models
import
VerifiedTrackCohortedCourse
@admin.register
(
VerifiedTrackCohortedCourse
)
@admin.register
(
VerifiedTrackCohortedCourse
)
...
...
lms
/djangoapps/verified_track_content/forms.py
→
openedx/core
/djangoapps/verified_track_content/forms.py
View file @
e5035746
...
@@ -9,7 +9,7 @@ from xmodule.modulestore.django import modulestore
...
@@ -9,7 +9,7 @@ from xmodule.modulestore.django import modulestore
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
verified_track_content.models
import
VerifiedTrackCohortedCourse
from
openedx.core.djangoapps.
verified_track_content.models
import
VerifiedTrackCohortedCourse
class
VerifiedTrackCourseForm
(
forms
.
ModelForm
):
class
VerifiedTrackCourseForm
(
forms
.
ModelForm
):
...
...
lms
/djangoapps/verified_track_content/migrations/0001_initial.py
→
openedx/core
/djangoapps/verified_track_content/migrations/0001_initial.py
View file @
e5035746
File moved
lms
/djangoapps/verified_track_content/migrations/0002_verifiedtrackcohortedcourse_verified_cohort_name.py
→
openedx/core
/djangoapps/verified_track_content/migrations/0002_verifiedtrackcohortedcourse_verified_cohort_name.py
View file @
e5035746
File moved
lms
/djangoapps/verified_track_content/migrations/__init__.py
→
openedx/core
/djangoapps/verified_track_content/migrations/__init__.py
View file @
e5035746
File moved
lms
/djangoapps/verified_track_content/models.py
→
openedx/core
/djangoapps/verified_track_content/models.py
View file @
e5035746
...
@@ -8,9 +8,9 @@ from django.db.models.signals import post_save, pre_save
...
@@ -8,9 +8,9 @@ from django.db.models.signals import post_save, pre_save
from
openedx.core.djangoapps.xmodule_django.models
import
CourseKeyField
from
openedx.core.djangoapps.xmodule_django.models
import
CourseKeyField
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
courseware.courses
import
get_course_by_id
from
lms.djangoapps.
courseware.courses
import
get_course_by_id
from
verified_track_content.tasks
import
sync_cohort_with_mode
from
openedx.core.djangoapps.
verified_track_content.tasks
import
sync_cohort_with_mode
from
openedx.core.djangoapps.course_groups.cohorts
import
(
from
openedx.core.djangoapps.course_groups.cohorts
import
(
get_course_cohorts
,
CourseCohort
,
is_course_cohorted
,
get_random_cohort
get_course_cohorts
,
CourseCohort
,
is_course_cohorted
,
get_random_cohort
)
)
...
...
openedx/core/djangoapps/verified_track_content/partition_scheme.py
0 → 100644
View file @
e5035746
"""
UserPartitionScheme for enrollment tracks.
"""
from
django.conf
import
settings
from
courseware.masquerade
import
(
get_course_masquerade
,
get_masquerading_group_info
,
is_masquerading_as_specific_student
,
)
from
course_modes.models
import
CourseMode
from
student.models
import
CourseEnrollment
from
opaque_keys.edx.keys
import
CourseKey
from
openedx.core.djangoapps.verified_track_content.models
import
VerifiedTrackCohortedCourse
from
xmodule.partitions.partitions
import
NoSuchUserPartitionGroupError
,
Group
,
UserPartition
# These IDs must be less than 100 so that they do not overlap with Groups in
# CohortUserPartition or RandomUserPartitionScheme
# (CMS' course_group_config uses a minimum value of 100 for all generated IDs).
ENROLLMENT_GROUP_IDS
=
settings
.
COURSE_ENROLLMENT_MODES
class
EnrollmentTrackUserPartition
(
UserPartition
):
"""
Extends UserPartition to support dynamic groups pulled from the current course Enrollment tracks.
"""
@property
def
groups
(
self
):
"""
Return the groups (based on CourseModes) for the course associated with this
EnrollmentTrackUserPartition instance.
If a course is using the Verified Track Cohorting pilot feature, this method
returns an empty array regardless of registered CourseModes.
"""
course_key
=
CourseKey
.
from_string
(
self
.
parameters
[
"course_id"
])
if
is_course_using_cohort_instead
(
course_key
):
return
[]
return
[
Group
(
ENROLLMENT_GROUP_IDS
[
mode
.
slug
],
unicode
(
mode
.
name
))
for
mode
in
CourseMode
.
modes_for_course
(
course_key
,
include_expired
=
True
,
only_selectable
=
False
)
]
def
to_json
(
self
):
"""
Because this partition is dynamic, to_json and from_json are not supported.
Calling this method will raise a TypeError.
"""
raise
TypeError
(
"Because EnrollmentTrackUserPartition is a dynamic partition, 'to_json' is not supported."
)
def
from_json
(
self
):
"""
Because this partition is dynamic, to_json and from_json are not supported.
Calling this method will raise a TypeError.
"""
raise
TypeError
(
"Because EnrollmentTrackUserPartition is a dynamic partition, 'from_json' is not supported."
)
class
EnrollmentTrackPartitionScheme
(
object
):
"""
This scheme uses learner enrollment tracks to map learners into partition groups.
"""
@classmethod
def
get_group_for_user
(
cls
,
course_key
,
user
,
user_partition
,
**
kwargs
):
# pylint: disable=unused-argument
"""
Returns the Group from the specified user partition to which the user
is assigned, via enrollment mode.
If a course is using the Verified Track Cohorting pilot feature, this method
returns None regardless of the user's enrollment mode.
"""
if
is_course_using_cohort_instead
(
course_key
):
return
None
# NOTE: masquerade code was copied from CohortPartitionScheme, and it may need
# some changes (or if not, code should be refactored out and shared).
# This work will be done in a future story TNL-6739.
# First, check if we have to deal with masquerading.
# If the current user is masquerading as a specific student, use the
# same logic as normal to return that student's group. If the current
# user is masquerading as a generic student in a specific group, then
# return that group.
if
get_course_masquerade
(
user
,
course_key
)
and
not
is_masquerading_as_specific_student
(
user
,
course_key
):
group_id
,
user_partition_id
=
get_masquerading_group_info
(
user
,
course_key
)
if
user_partition_id
==
user_partition
.
id
and
group_id
is
not
None
:
try
:
return
user_partition
.
get_group
(
group_id
)
except
NoSuchUserPartitionGroupError
:
return
None
# The user is masquerading as a generic student. We can't show any particular group.
return
None
mode_slug
,
is_active
=
CourseEnrollment
.
enrollment_mode_for_user
(
user
,
course_key
)
if
mode_slug
and
is_active
:
course_mode
=
CourseMode
.
mode_for_course
(
course_key
,
mode_slug
,
modes
=
CourseMode
.
modes_for_course
(
course_key
,
include_expired
=
True
,
only_selectable
=
False
),
)
if
not
course_mode
:
course_mode
=
CourseMode
.
DEFAULT_MODE
return
Group
(
ENROLLMENT_GROUP_IDS
[
course_mode
.
slug
],
unicode
(
course_mode
.
name
))
else
:
return
None
@classmethod
def
create_user_partition
(
cls
,
id
,
name
,
description
,
groups
=
None
,
parameters
=
None
,
active
=
True
):
# pylint: disable=redefined-builtin, invalid-name, unused-argument
"""
Create a custom UserPartition to support dynamic groups.
A Partition has an id, name, scheme, description, parameters, and a list
of groups. The id is intended to be unique within the context where these
are used. (e.g., for partitions of users within a course, the ids should
be unique per-course). The scheme is used to assign users into groups.
The parameters field is used to save extra parameters e.g., location of
the course ID for this partition scheme.
Partitions can be marked as inactive by setting the "active" flag to False.
Any group access rule referencing inactive partitions will be ignored
when performing access checks.
"""
return
EnrollmentTrackUserPartition
(
id
,
name
,
description
,
[],
cls
,
parameters
,
active
)
def
is_course_using_cohort_instead
(
course_key
):
"""
Returns whether the given course_context is using verified-track cohorts
and therefore shouldn't use a track-based partition.
"""
return
VerifiedTrackCohortedCourse
.
is_verified_track_cohort_enabled
(
course_key
)
lms
/djangoapps/verified_track_content/tasks.py
→
openedx/core
/djangoapps/verified_track_content/tasks.py
View file @
e5035746
File moved
lms
/djangoapps/verified_track_content/tests/__init__.py
→
openedx/core
/djangoapps/verified_track_content/tests/__init__.py
View file @
e5035746
File moved
lms
/djangoapps/verified_track_content/tests/test_forms.py
→
openedx/core
/djangoapps/verified_track_content/tests/test_forms.py
View file @
e5035746
...
@@ -4,7 +4,7 @@ Test for forms helpers.
...
@@ -4,7 +4,7 @@ Test for forms helpers.
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
verified_track_content.forms
import
VerifiedTrackCourseForm
from
openedx.core.djangoapps.
verified_track_content.forms
import
VerifiedTrackCourseForm
class
TestVerifiedTrackCourseForm
(
SharedModuleStoreTestCase
):
class
TestVerifiedTrackCourseForm
(
SharedModuleStoreTestCase
):
...
...
lms
/djangoapps/verified_track_content/tests/test_models.py
→
openedx/core
/djangoapps/verified_track_content/tests/test_models.py
View file @
e5035746
"""
"""
Tests for Verified Track Cohorting models
Tests for Verified Track Cohorting models
"""
"""
# pylint: disable=attribute-defined-outside-init
# pylint: disable=no-member
from
django.test
import
TestCase
from
django.test
import
TestCase
import
mock
import
mock
from
mock
import
patch
from
mock
import
patch
...
@@ -12,11 +15,12 @@ from student.models import CourseMode
...
@@ -12,11 +15,12 @@ from student.models import CourseMode
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
verified_track_content
.models
import
VerifiedTrackCohortedCourse
,
DEFAULT_VERIFIED_COHORT_NAME
from
.
.models
import
VerifiedTrackCohortedCourse
,
DEFAULT_VERIFIED_COHORT_NAME
from
verified_track_content
.tasks
import
sync_cohort_with_mode
from
.
.tasks
import
sync_cohort_with_mode
from
openedx.core.djangoapps.course_groups.cohorts
import
(
from
openedx.core.djangoapps.course_groups.cohorts
import
(
set_course_cohort_settings
,
add_cohort
,
CourseCohort
,
DEFAULT_COHORT_NAME
set_course_cohort_settings
,
add_cohort
,
CourseCohort
,
DEFAULT_COHORT_NAME
)
)
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
class
TestVerifiedTrackCohortedCourse
(
TestCase
):
class
TestVerifiedTrackCohortedCourse
(
TestCase
):
...
@@ -48,13 +52,13 @@ class TestVerifiedTrackCohortedCourse(TestCase):
...
@@ -48,13 +52,13 @@ class TestVerifiedTrackCohortedCourse(TestCase):
self
.
assertEqual
(
unicode
(
config
),
"Course: {}, enabled: True"
.
format
(
self
.
SAMPLE_COURSE
))
self
.
assertEqual
(
unicode
(
config
),
"Course: {}, enabled: True"
.
format
(
self
.
SAMPLE_COURSE
))
def
test_verified_cohort_name
(
self
):
def
test_verified_cohort_name
(
self
):
COHORT_NAME
=
'verified cohort'
cohort_name
=
'verified cohort'
course_key
=
CourseKey
.
from_string
(
self
.
SAMPLE_COURSE
)
course_key
=
CourseKey
.
from_string
(
self
.
SAMPLE_COURSE
)
config
=
VerifiedTrackCohortedCourse
.
objects
.
create
(
config
=
VerifiedTrackCohortedCourse
.
objects
.
create
(
course_key
=
course_key
,
enabled
=
True
,
verified_cohort_name
=
COHORT_NAME
course_key
=
course_key
,
enabled
=
True
,
verified_cohort_name
=
cohort_name
)
)
config
.
save
()
config
.
save
()
self
.
assertEqual
(
VerifiedTrackCohortedCourse
.
verified_cohort_name_for_course
(
course_key
),
COHORT_NAME
)
self
.
assertEqual
(
VerifiedTrackCohortedCourse
.
verified_cohort_name_for_course
(
course_key
),
cohort_name
)
def
test_unset_verified_cohort_name
(
self
):
def
test_unset_verified_cohort_name
(
self
):
fake_course_id
=
'fake/course/key'
fake_course_id
=
'fake/course/key'
...
@@ -62,6 +66,7 @@ class TestVerifiedTrackCohortedCourse(TestCase):
...
@@ -62,6 +66,7 @@ class TestVerifiedTrackCohortedCourse(TestCase):
self
.
assertEqual
(
VerifiedTrackCohortedCourse
.
verified_cohort_name_for_course
(
course_key
),
None
)
self
.
assertEqual
(
VerifiedTrackCohortedCourse
.
verified_cohort_name_for_course
(
course_key
),
None
)
@skip_unless_lms
class
TestMoveToVerified
(
SharedModuleStoreTestCase
):
class
TestMoveToVerified
(
SharedModuleStoreTestCase
):
""" Tests for the post-save listener. """
""" Tests for the post-save listener. """
...
@@ -82,12 +87,15 @@ class TestMoveToVerified(SharedModuleStoreTestCase):
...
@@ -82,12 +87,15 @@ class TestMoveToVerified(SharedModuleStoreTestCase):
self
.
addCleanup
(
celery_task_patcher
.
stop
)
self
.
addCleanup
(
celery_task_patcher
.
stop
)
def
_enable_cohorting
(
self
):
def
_enable_cohorting
(
self
):
""" Turn on cohorting in the course. """
set_course_cohort_settings
(
self
.
course
.
id
,
is_cohorted
=
True
)
set_course_cohort_settings
(
self
.
course
.
id
,
is_cohorted
=
True
)
def
_create_verified_cohort
(
self
,
name
=
DEFAULT_VERIFIED_COHORT_NAME
):
def
_create_verified_cohort
(
self
,
name
=
DEFAULT_VERIFIED_COHORT_NAME
):
""" Create a verified cohort. """
add_cohort
(
self
.
course
.
id
,
name
,
CourseCohort
.
MANUAL
)
add_cohort
(
self
.
course
.
id
,
name
,
CourseCohort
.
MANUAL
)
def
_create_named_random_cohort
(
self
,
name
):
def
_create_named_random_cohort
(
self
,
name
):
""" Create a random cohort with the supplied name. """
return
add_cohort
(
self
.
course
.
id
,
name
,
CourseCohort
.
RANDOM
)
return
add_cohort
(
self
.
course
.
id
,
name
,
CourseCohort
.
RANDOM
)
def
_enable_verified_track_cohorting
(
self
,
cohort_name
=
None
):
def
_enable_verified_track_cohorting
(
self
,
cohort_name
=
None
):
...
@@ -101,6 +109,7 @@ class TestMoveToVerified(SharedModuleStoreTestCase):
...
@@ -101,6 +109,7 @@ class TestMoveToVerified(SharedModuleStoreTestCase):
config
.
save
()
config
.
save
()
def
_enroll_in_course
(
self
):
def
_enroll_in_course
(
self
):
""" Enroll self.user in self.course. """
self
.
enrollment
=
CourseEnrollmentFactory
(
course_id
=
self
.
course
.
id
,
user
=
self
.
user
)
self
.
enrollment
=
CourseEnrollmentFactory
(
course_id
=
self
.
course
.
id
,
user
=
self
.
user
)
def
_upgrade_to_verified
(
self
):
def
_upgrade_to_verified
(
self
):
...
@@ -108,6 +117,7 @@ class TestMoveToVerified(SharedModuleStoreTestCase):
...
@@ -108,6 +117,7 @@ class TestMoveToVerified(SharedModuleStoreTestCase):
self
.
enrollment
.
update_enrollment
(
mode
=
CourseMode
.
VERIFIED
)
self
.
enrollment
.
update_enrollment
(
mode
=
CourseMode
.
VERIFIED
)
def
_verify_no_automatic_cohorting
(
self
):
def
_verify_no_automatic_cohorting
(
self
):
""" Check that upgrading self.user to verified does not move them into a cohort. """
self
.
_enroll_in_course
()
self
.
_enroll_in_course
()
self
.
assertIsNone
(
get_cohort
(
self
.
user
,
self
.
course
.
id
,
assign
=
False
))
self
.
assertIsNone
(
get_cohort
(
self
.
user
,
self
.
course
.
id
,
assign
=
False
))
self
.
_upgrade_to_verified
()
self
.
_upgrade_to_verified
()
...
@@ -115,13 +125,15 @@ class TestMoveToVerified(SharedModuleStoreTestCase):
...
@@ -115,13 +125,15 @@ class TestMoveToVerified(SharedModuleStoreTestCase):
self
.
assertEqual
(
0
,
self
.
mocked_celery_task
.
call_count
)
self
.
assertEqual
(
0
,
self
.
mocked_celery_task
.
call_count
)
def
_unenroll
(
self
):
def
_unenroll
(
self
):
""" Unenroll self.user from self.course. """
self
.
enrollment
.
unenroll
(
self
.
user
,
self
.
course
.
id
)
self
.
enrollment
.
unenroll
(
self
.
user
,
self
.
course
.
id
)
def
_reenroll
(
self
):
def
_reenroll
(
self
):
""" Re-enroll the learner into mode AUDIT. """
self
.
enrollment
.
activate
()
self
.
enrollment
.
activate
()
self
.
enrollment
.
change_mode
(
CourseMode
.
AUDIT
)
self
.
enrollment
.
change_mode
(
CourseMode
.
AUDIT
)
@mock.patch
(
'verified_track_content.models.log.error'
)
@mock.patch
(
'
openedx.core.djangoapps.
verified_track_content.models.log.error'
)
def
test_automatic_cohorting_disabled
(
self
,
error_logger
):
def
test_automatic_cohorting_disabled
(
self
,
error_logger
):
"""
"""
If the VerifiedTrackCohortedCourse feature is disabled for a course, enrollment mode changes do not move
If the VerifiedTrackCohortedCourse feature is disabled for a course, enrollment mode changes do not move
...
@@ -136,7 +148,7 @@ class TestMoveToVerified(SharedModuleStoreTestCase):
...
@@ -136,7 +148,7 @@ class TestMoveToVerified(SharedModuleStoreTestCase):
# No logging occurs if feature is disabled for course.
# No logging occurs if feature is disabled for course.
self
.
assertFalse
(
error_logger
.
called
)
self
.
assertFalse
(
error_logger
.
called
)
@mock.patch
(
'verified_track_content.models.log.error'
)
@mock.patch
(
'
openedx.core.djangoapps.
verified_track_content.models.log.error'
)
def
test_cohorting_enabled_course_not_cohorted
(
self
,
error_logger
):
def
test_cohorting_enabled_course_not_cohorted
(
self
,
error_logger
):
"""
"""
If the VerifiedTrackCohortedCourse feature is enabled for a course, but the course is not cohorted,
If the VerifiedTrackCohortedCourse feature is enabled for a course, but the course is not cohorted,
...
@@ -149,7 +161,7 @@ class TestMoveToVerified(SharedModuleStoreTestCase):
...
@@ -149,7 +161,7 @@ class TestMoveToVerified(SharedModuleStoreTestCase):
self
.
assertTrue
(
error_logger
.
called
)
self
.
assertTrue
(
error_logger
.
called
)
self
.
assertIn
(
"course is not cohorted"
,
error_logger
.
call_args
[
0
][
0
])
self
.
assertIn
(
"course is not cohorted"
,
error_logger
.
call_args
[
0
][
0
])
@mock.patch
(
'verified_track_content.models.log.error'
)
@mock.patch
(
'
openedx.core.djangoapps.
verified_track_content.models.log.error'
)
def
test_cohorting_enabled_missing_verified_cohort
(
self
,
error_logger
):
def
test_cohorting_enabled_missing_verified_cohort
(
self
,
error_logger
):
"""
"""
If the VerifiedTrackCohortedCourse feature is enabled for a course and the course is cohorted,
If the VerifiedTrackCohortedCourse feature is enabled for a course and the course is cohorted,
...
...
openedx/core/djangoapps/verified_track_content/tests/test_partition_scheme.py
0 → 100644
View file @
e5035746
"""
Tests for verified_track_content/partition_scheme.py.
"""
from
datetime
import
datetime
,
timedelta
import
pytz
from
..partition_scheme
import
EnrollmentTrackPartitionScheme
,
EnrollmentTrackUserPartition
,
ENROLLMENT_GROUP_IDS
from
..models
import
VerifiedTrackCohortedCourse
from
course_modes.models
import
CourseMode
from
student.models
import
CourseEnrollment
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.partitions.partitions
import
UserPartition
from
xmodule.partitions.partitions_service
import
MINIMUM_STATIC_PARTITION_ID
class
EnrollmentTrackUserPartitionTest
(
SharedModuleStoreTestCase
):
"""
Tests for the custom EnrollmentTrackUserPartition (dynamic groups).
"""
@classmethod
def
setUpClass
(
cls
):
super
(
EnrollmentTrackUserPartitionTest
,
cls
)
.
setUpClass
()
cls
.
course
=
CourseFactory
.
create
()
def
test_only_default_mode
(
self
):
partition
=
create_enrollment_track_partition
(
self
.
course
)
groups
=
partition
.
groups
self
.
assertEqual
(
1
,
len
(
groups
))
self
.
assertEqual
(
"Audit"
,
groups
[
0
]
.
name
)
def
test_using_verified_track_cohort
(
self
):
VerifiedTrackCohortedCourse
.
objects
.
create
(
course_key
=
self
.
course
.
id
,
enabled
=
True
)
.
save
()
partition
=
create_enrollment_track_partition
(
self
.
course
)
self
.
assertEqual
(
0
,
len
(
partition
.
groups
))
def
test_multiple_groups
(
self
):
create_mode
(
self
.
course
,
CourseMode
.
AUDIT
,
"Audit Enrollment Track"
,
min_price
=
0
)
# Note that the verified mode is expired-- this is intentional.
create_mode
(
self
.
course
,
CourseMode
.
VERIFIED
,
"Verified Enrollment Track"
,
min_price
=
1
,
expiration_datetime
=
datetime
.
now
(
pytz
.
UTC
)
+
timedelta
(
days
=-
1
)
)
# Note that the credit mode is not selectable-- this is intentional.
create_mode
(
self
.
course
,
CourseMode
.
CREDIT_MODE
,
"Credit Mode"
,
min_price
=
2
)
partition
=
create_enrollment_track_partition
(
self
.
course
)
groups
=
partition
.
groups
self
.
assertEqual
(
3
,
len
(
groups
))
self
.
assertIsNotNone
(
self
.
get_group_by_name
(
partition
,
"Audit Enrollment Track"
))
self
.
assertIsNotNone
(
self
.
get_group_by_name
(
partition
,
"Verified Enrollment Track"
))
self
.
assertIsNotNone
(
self
.
get_group_by_name
(
partition
,
"Credit Mode"
))
def
test_to_json_not_supported
(
self
):
user_partition
=
create_enrollment_track_partition
(
self
.
course
)
with
self
.
assertRaises
(
TypeError
):
user_partition
.
to_json
()
def
test_from_json_not_supported
(
self
):
with
self
.
assertRaises
(
TypeError
):
EnrollmentTrackUserPartition
.
from_json
()
def
test_group_ids
(
self
):
"""
Test that group IDs are all less than MINIMUM_STATIC_PARTITION_ID (to avoid overlapping
with group IDs associated with cohort and random user partitions).
"""
for
mode
in
ENROLLMENT_GROUP_IDS
:
self
.
assertLess
(
ENROLLMENT_GROUP_IDS
[
mode
],
MINIMUM_STATIC_PARTITION_ID
)
@staticmethod
def
get_group_by_name
(
partition
,
name
):
"""
Return the group in the EnrollmentTrackUserPartition with the given name.
If no such group exists, returns `None`.
"""
for
group
in
partition
.
groups
:
if
group
.
name
==
name
:
return
group
return
None
class
EnrollmentTrackPartitionSchemeTest
(
SharedModuleStoreTestCase
):
"""
Tests for EnrollmentTrackPartitionScheme.
"""
@classmethod
def
setUpClass
(
cls
):
super
(
EnrollmentTrackPartitionSchemeTest
,
cls
)
.
setUpClass
()
cls
.
course
=
CourseFactory
.
create
()
cls
.
student
=
UserFactory
()
def
test_get_scheme
(
self
):
"""
Ensure that the scheme extension is correctly plugged in (via entry point in setup.py)
"""
self
.
assertEquals
(
UserPartition
.
get_scheme
(
'enrollment_track'
),
EnrollmentTrackPartitionScheme
)
def
test_create_user_partition
(
self
):
user_partition
=
UserPartition
.
get_scheme
(
'enrollment_track'
)
.
create_user_partition
(
301
,
"partition"
,
"test partition"
,
parameters
=
{
"course_id"
:
unicode
(
self
.
course
.
id
)}
)
self
.
assertEqual
(
type
(
user_partition
),
EnrollmentTrackUserPartition
)
self
.
assertEqual
(
user_partition
.
name
,
"partition"
)
groups
=
user_partition
.
groups
self
.
assertEqual
(
1
,
len
(
groups
))
self
.
assertEqual
(
"Audit"
,
groups
[
0
]
.
name
)
def
test_not_enrolled
(
self
):
self
.
assertIsNone
(
self
.
_get_user_group
())
def
test_default_enrollment
(
self
):
CourseEnrollment
.
enroll
(
self
.
student
,
self
.
course
.
id
)
self
.
assertEqual
(
"Audit"
,
self
.
_get_user_group
()
.
name
)
def
test_enrolled_in_nonexistent_mode
(
self
):
CourseEnrollment
.
enroll
(
self
.
student
,
self
.
course
.
id
,
mode
=
CourseMode
.
VERIFIED
)
self
.
assertEqual
(
"Audit"
,
self
.
_get_user_group
()
.
name
)
def
test_enrolled_in_verified
(
self
):
create_mode
(
self
.
course
,
CourseMode
.
VERIFIED
,
"Verified Enrollment Track"
,
min_price
=
1
)
CourseEnrollment
.
enroll
(
self
.
student
,
self
.
course
.
id
,
mode
=
CourseMode
.
VERIFIED
)
self
.
assertEqual
(
"Verified Enrollment Track"
,
self
.
_get_user_group
()
.
name
)
def
test_enrolled_in_expired
(
self
):
create_mode
(
self
.
course
,
CourseMode
.
VERIFIED
,
"Verified Enrollment Track"
,
min_price
=
1
,
expiration_datetime
=
datetime
.
now
(
pytz
.
UTC
)
+
timedelta
(
days
=-
1
)
)
CourseEnrollment
.
enroll
(
self
.
student
,
self
.
course
.
id
,
mode
=
CourseMode
.
VERIFIED
)
self
.
assertEqual
(
"Verified Enrollment Track"
,
self
.
_get_user_group
()
.
name
)
def
test_enrolled_in_non_selectable
(
self
):
create_mode
(
self
.
course
,
CourseMode
.
CREDIT_MODE
,
"Credit Enrollment Track"
,
min_price
=
1
)
CourseEnrollment
.
enroll
(
self
.
student
,
self
.
course
.
id
,
mode
=
CourseMode
.
CREDIT_MODE
)
self
.
assertEqual
(
"Credit Enrollment Track"
,
self
.
_get_user_group
()
.
name
)
def
test_using_verified_track_cohort
(
self
):
VerifiedTrackCohortedCourse
.
objects
.
create
(
course_key
=
self
.
course
.
id
,
enabled
=
True
)
.
save
()
CourseEnrollment
.
enroll
(
self
.
student
,
self
.
course
.
id
)
self
.
assertIsNone
(
self
.
_get_user_group
())
def
_get_user_group
(
self
):
"""
Gets the group the user is assigned to.
"""
user_partition
=
create_enrollment_track_partition
(
self
.
course
)
return
user_partition
.
scheme
.
get_group_for_user
(
self
.
course
.
id
,
self
.
student
,
user_partition
)
def
create_enrollment_track_partition
(
course
):
"""
Create an EnrollmentTrackUserPartition instance for the given course.
"""
enrollment_track_scheme
=
UserPartition
.
get_scheme
(
"enrollment_track"
)
partition
=
enrollment_track_scheme
.
create_user_partition
(
id
=
1
,
name
=
"TestEnrollment Track Partition"
,
description
=
"Test partition for segmenting users by enrollment track"
,
parameters
=
{
"course_id"
:
unicode
(
course
.
id
)}
)
return
partition
def
create_mode
(
course
,
mode_slug
,
mode_name
,
min_price
=
0
,
expiration_datetime
=
None
):
"""
Create a new course mode for the given course.
"""
return
CourseMode
.
objects
.
get_or_create
(
course_id
=
course
.
id
,
mode_display_name
=
mode_name
,
mode_slug
=
mode_slug
,
min_price
=
min_price
,
suggested_prices
=
''
,
_expiration_datetime
=
expiration_datetime
,
currency
=
'usd'
)
lms
/djangoapps/verified_track_content/tests/test_views.py
→
openedx/core
/djangoapps/verified_track_content/tests/test_views.py
View file @
e5035746
...
@@ -5,22 +5,21 @@ Tests for verified track content views.
...
@@ -5,22 +5,21 @@ Tests for verified track content views.
import
json
import
json
from
nose.plugins.attrib
import
attr
from
nose.plugins.attrib
import
attr
from
unittest
import
skipUnless
from
django.http
import
Http404
from
django.http
import
Http404
from
django.test.client
import
RequestFactory
from
django.test.client
import
RequestFactory
from
django.conf
import
settings
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
from
student.tests.factories
import
UserFactory
,
AdminFactory
from
student.tests.factories
import
UserFactory
,
AdminFactory
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
verified_track_content
.models
import
VerifiedTrackCohortedCourse
from
.
.models
import
VerifiedTrackCohortedCourse
from
verified_track_content
.views
import
cohorting_settings
from
.
.views
import
cohorting_settings
@attr
(
shard
=
2
)
@attr
(
shard
=
2
)
@skip
Unless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Tests only valid in LMS'
)
@skip
_unless_lms
class
CohortingSettingsTestCase
(
SharedModuleStoreTestCase
):
class
CohortingSettingsTestCase
(
SharedModuleStoreTestCase
):
"""
"""
Tests the `cohort_discussion_topics` view.
Tests the `cohort_discussion_topics` view.
...
@@ -65,6 +64,7 @@ class CohortingSettingsTestCase(SharedModuleStoreTestCase):
...
@@ -65,6 +64,7 @@ class CohortingSettingsTestCase(SharedModuleStoreTestCase):
self
.
_verify_cohort_settings_response
(
expected_response
)
self
.
_verify_cohort_settings_response
(
expected_response
)
def
_verify_cohort_settings_response
(
self
,
expected_response
):
def
_verify_cohort_settings_response
(
self
,
expected_response
):
""" Verify that the response was successful and matches the expected JSON payload. """
request
=
RequestFactory
()
.
get
(
"dummy_url"
)
request
=
RequestFactory
()
.
get
(
"dummy_url"
)
request
.
user
=
AdminFactory
()
request
.
user
=
AdminFactory
()
response
=
cohorting_settings
(
request
,
unicode
(
self
.
course
.
id
))
response
=
cohorting_settings
(
request
,
unicode
(
self
.
course
.
id
))
...
...
lms
/djangoapps/verified_track_content/views.py
→
openedx/core
/djangoapps/verified_track_content/views.py
View file @
e5035746
...
@@ -6,9 +6,9 @@ from util.json_request import expect_json, JsonResponse
...
@@ -6,9 +6,9 @@ from util.json_request import expect_json, JsonResponse
from
django.contrib.auth.decorators
import
login_required
from
django.contrib.auth.decorators
import
login_required
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
courseware.courses
import
get_course_with_access
from
lms.djangoapps.
courseware.courses
import
get_course_with_access
from
verified_track_content.models
import
VerifiedTrackCohortedCourse
from
openedx.core.djangoapps.
verified_track_content.models
import
VerifiedTrackCohortedCourse
@expect_json
@expect_json
...
...
setup.py
View file @
e5035746
...
@@ -42,6 +42,7 @@ setup(
...
@@ -42,6 +42,7 @@ setup(
"random = openedx.core.djangoapps.user_api.partition_schemes:RandomUserPartitionScheme"
,
"random = openedx.core.djangoapps.user_api.partition_schemes:RandomUserPartitionScheme"
,
"cohort = openedx.core.djangoapps.course_groups.partition_scheme:CohortPartitionScheme"
,
"cohort = openedx.core.djangoapps.course_groups.partition_scheme:CohortPartitionScheme"
,
"verification = openedx.core.djangoapps.credit.partition_schemes:VerificationPartitionScheme"
,
"verification = openedx.core.djangoapps.credit.partition_schemes:VerificationPartitionScheme"
,
"enrollment_track = openedx.core.djangoapps.verified_track_content.partition_scheme:EnrollmentTrackPartitionScheme"
,
],
],
"openedx.block_structure_transformer"
:
[
"openedx.block_structure_transformer"
:
[
"library_content = lms.djangoapps.course_blocks.transformers.library_content:ContentLibraryTransformer"
,
"library_content = lms.djangoapps.course_blocks.transformers.library_content:ContentLibraryTransformer"
,
...
...
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