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
cd053913
Commit
cd053913
authored
Mar 11, 2015
by
Matt Drayer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Cleaned up milestones API references
parent
cce00e69
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
414 additions
and
181 deletions
+414
-181
cms/djangoapps/contentstore/tests/test_course_settings.py
+5
-14
cms/djangoapps/contentstore/views/entrance_exam.py
+31
-11
cms/djangoapps/contentstore/views/tests/test_entrance_exam.py
+29
-14
common/djangoapps/util/milestones_helpers.py
+170
-54
common/djangoapps/util/tests/test_milestones_helpers.py
+87
-0
lms/djangoapps/courseware/module_render.py
+8
-10
lms/djangoapps/courseware/tabs.py
+3
-5
lms/djangoapps/courseware/tests/test_entrance_exam.py
+73
-62
lms/djangoapps/courseware/tests/test_tabs.py
+8
-11
No files found.
cms/djangoapps/contentstore/tests/test_course_settings.py
View file @
cd053913
...
@@ -6,6 +6,7 @@ import json
...
@@ -6,6 +6,7 @@ import json
import
copy
import
copy
import
mock
import
mock
from
mock
import
patch
from
mock
import
patch
import
unittest
from
django.utils.timezone
import
UTC
from
django.utils.timezone
import
UTC
from
django.test.utils
import
override_settings
from
django.test.utils
import
override_settings
...
@@ -139,19 +140,9 @@ class CourseDetailsTestCase(CourseTestCase):
...
@@ -139,19 +140,9 @@ class CourseDetailsTestCase(CourseTestCase):
self
.
assertNotContains
(
response
,
"Course Introduction Video"
)
self
.
assertNotContains
(
response
,
"Course Introduction Video"
)
self
.
assertNotContains
(
response
,
"Requirements"
)
self
.
assertNotContains
(
response
,
"Requirements"
)
def
_seed_milestone_relationship_types
(
self
):
@unittest.skipUnless
(
settings
.
FEATURES
.
get
(
'ENTRANCE_EXAMS'
,
False
),
True
)
"""
Helper method to prepopulate MRTs so the tests can run
Note the settings check -- exams feature must be enabled for the tests to run correctly
"""
if
settings
.
FEATURES
.
get
(
'ENTRANCE_EXAMS'
,
False
):
from
milestones.models
import
MilestoneRelationshipType
MilestoneRelationshipType
.
objects
.
create
(
name
=
'requires'
)
MilestoneRelationshipType
.
objects
.
create
(
name
=
'fulfills'
)
@patch.dict
(
settings
.
FEATURES
,
{
'ENTRANCE_EXAMS'
:
True
})
def
test_entrance_exam_created_updated_and_deleted_successfully
(
self
):
def
test_entrance_exam_created_updated_and_deleted_successfully
(
self
):
se
lf
.
_se
ed_milestone_relationship_types
()
seed_milestone_relationship_types
()
settings_details_url
=
get_url
(
self
.
course
.
id
)
settings_details_url
=
get_url
(
self
.
course
.
id
)
data
=
{
data
=
{
'entrance_exam_enabled'
:
'true'
,
'entrance_exam_enabled'
:
'true'
,
...
@@ -196,13 +187,13 @@ class CourseDetailsTestCase(CourseTestCase):
...
@@ -196,13 +187,13 @@ class CourseDetailsTestCase(CourseTestCase):
self
.
assertFalse
(
course
.
entrance_exam_enabled
)
self
.
assertFalse
(
course
.
entrance_exam_enabled
)
self
.
assertEquals
(
course
.
entrance_exam_minimum_score_pct
,
None
)
self
.
assertEquals
(
course
.
entrance_exam_minimum_score_pct
,
None
)
@
patch.dict
(
settings
.
FEATURES
,
{
'ENTRANCE_EXAMS'
:
True
}
)
@
unittest.skipUnless
(
settings
.
FEATURES
.
get
(
'ENTRANCE_EXAMS'
,
False
),
True
)
def
test_entrance_exam_store_default_min_score
(
self
):
def
test_entrance_exam_store_default_min_score
(
self
):
"""
"""
test that creating an entrance exam should store the default value, if key missing in json request
test that creating an entrance exam should store the default value, if key missing in json request
or entrance_exam_minimum_score_pct is an empty string
or entrance_exam_minimum_score_pct is an empty string
"""
"""
se
lf
.
_se
ed_milestone_relationship_types
()
seed_milestone_relationship_types
()
settings_details_url
=
get_url
(
self
.
course
.
id
)
settings_details_url
=
get_url
(
self
.
course
.
id
)
test_data_1
=
{
test_data_1
=
{
'entrance_exam_enabled'
:
'true'
,
'entrance_exam_enabled'
:
'true'
,
...
...
cms/djangoapps/contentstore/views/entrance_exam.py
View file @
cd053913
...
@@ -2,22 +2,22 @@
...
@@ -2,22 +2,22 @@
Entrance Exams view module -- handles all requests related to entrance exam management via Studio
Entrance Exams view module -- handles all requests related to entrance exam management via Studio
Intended to be utilized as an AJAX callback handler, versus a proper view/screen
Intended to be utilized as an AJAX callback handler, versus a proper view/screen
"""
"""
from
functools
import
wraps
import
json
import
json
import
logging
import
logging
from
django.contrib.auth.decorators
import
login_required
from
django.contrib.auth.decorators
import
login_required
from
django_future.csrf
import
ensure_csrf_cookie
from
django_future.csrf
import
ensure_csrf_cookie
from
django.http
import
HttpResponse
from
django.http
import
HttpResponse
,
HttpResponseBadRequest
from
django.test
import
RequestFactory
from
django.test
import
RequestFactory
from
contentstore.views.helpers
import
create_xblock
from
contentstore.views.helpers
import
create_xblock
from
contentstore.views.item
import
delete_item
from
contentstore.views.item
import
delete_item
from
milestones
import
api
as
milestones_api
from
models.settings.course_metadata
import
CourseMetadata
from
models.settings.course_metadata
import
CourseMetadata
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
student.auth
import
has_course_author_access
from
student.auth
import
has_course_author_access
from
util
.milestones_helpers
import
generate_milestone_namespace
,
NAMESPACE_CHOICES
from
util
import
milestones_helpers
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
django.conf
import
settings
from
django.conf
import
settings
...
@@ -40,8 +40,24 @@ def _get_default_entrance_exam_minimum_pct():
...
@@ -40,8 +40,24 @@ def _get_default_entrance_exam_minimum_pct():
return
entrance_exam_minimum_score_pct
return
entrance_exam_minimum_score_pct
# pylint: disable=missing-docstring
def
check_feature_enabled
(
feature_name
):
"""
Ensure the specified feature is turned on. Return an HTTP 400 code if not.
"""
def
_check_feature_enabled
(
view_func
):
def
_decorator
(
request
,
*
args
,
**
kwargs
):
# Deny access if the entrance exam feature is disabled
if
not
settings
.
FEATURES
.
get
(
feature_name
,
False
):
return
HttpResponseBadRequest
()
return
view_func
(
request
,
*
args
,
**
kwargs
)
return
wraps
(
view_func
)(
_decorator
)
return
_check_feature_enabled
@login_required
@login_required
@ensure_csrf_cookie
@ensure_csrf_cookie
@check_feature_enabled
(
feature_name
=
'ENTRANCE_EXAMS'
)
def
entrance_exam
(
request
,
course_key_string
):
def
entrance_exam
(
request
,
course_key_string
):
"""
"""
The restful handler for entrance exams.
The restful handler for entrance exams.
...
@@ -88,6 +104,7 @@ def entrance_exam(request, course_key_string):
...
@@ -88,6 +104,7 @@ def entrance_exam(request, course_key_string):
return
HttpResponse
(
status
=
405
)
return
HttpResponse
(
status
=
405
)
@check_feature_enabled
(
feature_name
=
'ENTRANCE_EXAMS'
)
def
create_entrance_exam
(
request
,
course_key
,
entrance_exam_minimum_score_pct
):
def
create_entrance_exam
(
request
,
course_key
,
entrance_exam_minimum_score_pct
):
"""
"""
api method to create an entrance exam.
api method to create an entrance exam.
...
@@ -150,27 +167,28 @@ def _create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct=N
...
@@ -150,27 +167,28 @@ def _create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct=N
)
)
# Add an entrance exam milestone if one does not already exist
# Add an entrance exam milestone if one does not already exist
milestone_namespace
=
generate_milestone_namespace
(
namespace_choices
=
milestones_helpers
.
get_namespace_choices
()
NAMESPACE_CHOICES
[
'ENTRANCE_EXAM'
],
milestone_namespace
=
milestones_helpers
.
generate_milestone_namespace
(
namespace_choices
.
get
(
'ENTRANCE_EXAM'
),
course_key
course_key
)
)
milestones
=
milestones_
api
.
get_milestones
(
milestone_namespace
)
milestones
=
milestones_
helpers
.
get_milestones
(
milestone_namespace
)
if
len
(
milestones
):
if
len
(
milestones
):
milestone
=
milestones
[
0
]
milestone
=
milestones
[
0
]
else
:
else
:
description
=
'Autogenerated during {} entrance exam creation.'
.
format
(
unicode
(
course
.
id
))
description
=
'Autogenerated during {} entrance exam creation.'
.
format
(
unicode
(
course
.
id
))
milestone
=
milestones_
api
.
add_milestone
({
milestone
=
milestones_
helpers
.
add_milestone
({
'name'
:
'Completed Course Entrance Exam'
,
'name'
:
'Completed Course Entrance Exam'
,
'namespace'
:
milestone_namespace
,
'namespace'
:
milestone_namespace
,
'description'
:
description
'description'
:
description
})
})
relationship_types
=
milestones_
api
.
get_milestone_relationship_types
()
relationship_types
=
milestones_
helpers
.
get_milestone_relationship_types
()
milestones_
api
.
add_course_milestone
(
milestones_
helpers
.
add_course_milestone
(
unicode
(
course
.
id
),
unicode
(
course
.
id
),
relationship_types
[
'REQUIRES'
],
relationship_types
[
'REQUIRES'
],
milestone
milestone
)
)
milestones_
api
.
add_course_content_milestone
(
milestones_
helpers
.
add_course_content_milestone
(
unicode
(
course
.
id
),
unicode
(
course
.
id
),
unicode
(
created_block
.
location
),
unicode
(
created_block
.
location
),
relationship_types
[
'FULFILLS'
],
relationship_types
[
'FULFILLS'
],
...
@@ -202,6 +220,7 @@ def _get_entrance_exam(request, course_key): # pylint: disable=W0613
...
@@ -202,6 +220,7 @@ def _get_entrance_exam(request, course_key): # pylint: disable=W0613
return
HttpResponse
(
status
=
404
)
return
HttpResponse
(
status
=
404
)
@check_feature_enabled
(
feature_name
=
'ENTRANCE_EXAMS'
)
def
update_entrance_exam
(
request
,
course_key
,
exam_data
):
def
update_entrance_exam
(
request
,
course_key
,
exam_data
):
"""
"""
Operation to update course fields pertaining to entrance exams
Operation to update course fields pertaining to entrance exams
...
@@ -215,6 +234,7 @@ def update_entrance_exam(request, course_key, exam_data):
...
@@ -215,6 +234,7 @@ def update_entrance_exam(request, course_key, exam_data):
CourseMetadata
.
update_from_dict
(
metadata
,
course
,
request
.
user
)
CourseMetadata
.
update_from_dict
(
metadata
,
course
,
request
.
user
)
@check_feature_enabled
(
feature_name
=
'ENTRANCE_EXAMS'
)
def
delete_entrance_exam
(
request
,
course_key
):
def
delete_entrance_exam
(
request
,
course_key
):
"""
"""
api method to delete an entrance exam
api method to delete an entrance exam
...
@@ -238,7 +258,7 @@ def _delete_entrance_exam(request, course_key):
...
@@ -238,7 +258,7 @@ def _delete_entrance_exam(request, course_key):
for
course_child
in
course_children
:
for
course_child
in
course_children
:
if
course_child
.
is_entrance_exam
:
if
course_child
.
is_entrance_exam
:
delete_item
(
request
,
course_child
.
scope_ids
.
usage_id
)
delete_item
(
request
,
course_child
.
scope_ids
.
usage_id
)
milestones_
api
.
remove_content_references
(
unicode
(
course_child
.
scope_ids
.
usage_id
))
milestones_
helpers
.
remove_content_references
(
unicode
(
course_child
.
scope_ids
.
usage_id
))
# Reset the entrance exam flags on the course
# Reset the entrance exam flags on the course
# Reload the course so we have the latest state
# Reload the course so we have the latest state
...
...
cms/djangoapps/contentstore/views/tests/test_entrance_exam.py
View file @
cd053913
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
Test module for Entrance Exams AJAX callback handler workflows
Test module for Entrance Exams AJAX callback handler workflows
"""
"""
import
json
import
json
from
mock
import
patch
from
django.conf
import
settings
from
django.conf
import
settings
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
...
@@ -9,18 +10,14 @@ from django.test.client import RequestFactory
...
@@ -9,18 +10,14 @@ from django.test.client import RequestFactory
from
contentstore.tests.utils
import
AjaxEnabledTestClient
,
CourseTestCase
from
contentstore.tests.utils
import
AjaxEnabledTestClient
,
CourseTestCase
from
contentstore.utils
import
reverse_url
from
contentstore.utils
import
reverse_url
from
contentstore.views.entrance_exam
import
create_entrance_exam
from
contentstore.views.entrance_exam
import
create_entrance_exam
,
update_entrance_exam
,
delete_entrance_exam
from
models.settings.course_grading
import
CourseGradingModel
from
models.settings.course_grading
import
CourseGradingModel
from
models.settings.course_metadata
import
CourseMetadata
from
models.settings.course_metadata
import
CourseMetadata
from
opaque_keys.edx.keys
import
UsageKey
from
opaque_keys.edx.keys
import
UsageKey
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
from
util
import
milestones_helpers
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
if
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
from
milestones
import
api
as
milestones_api
from
milestones.models
import
MilestoneRelationshipType
from
util.milestones_helpers
import
serialize_user
class
EntranceExamHandlerTests
(
CourseTestCase
):
class
EntranceExamHandlerTests
(
CourseTestCase
):
"""
"""
...
@@ -36,9 +33,8 @@ class EntranceExamHandlerTests(CourseTestCase):
...
@@ -36,9 +33,8 @@ class EntranceExamHandlerTests(CourseTestCase):
self
.
usage_key
=
self
.
course
.
location
self
.
usage_key
=
self
.
course
.
location
self
.
course_url
=
'/course/{}'
.
format
(
unicode
(
self
.
course
.
id
))
self
.
course_url
=
'/course/{}'
.
format
(
unicode
(
self
.
course
.
id
))
self
.
exam_url
=
'/course/{}/entrance_exam/'
.
format
(
unicode
(
self
.
course
.
id
))
self
.
exam_url
=
'/course/{}/entrance_exam/'
.
format
(
unicode
(
self
.
course
.
id
))
MilestoneRelationshipType
.
objects
.
create
(
name
=
'requires'
,
active
=
True
)
milestones_helpers
.
seed_milestone_relationship_types
()
MilestoneRelationshipType
.
objects
.
create
(
name
=
'fulfills'
,
active
=
True
)
self
.
milestone_relationship_types
=
milestones_helpers
.
get_milestone_relationship_types
()
self
.
milestone_relationship_types
=
milestones_api
.
get_milestone_relationship_types
()
def
test_contentstore_views_entrance_exam_post
(
self
):
def
test_contentstore_views_entrance_exam_post
(
self
):
"""
"""
...
@@ -55,8 +51,8 @@ class EntranceExamHandlerTests(CourseTestCase):
...
@@ -55,8 +51,8 @@ class EntranceExamHandlerTests(CourseTestCase):
self
.
assertTrue
(
metadata
[
'entrance_exam_enabled'
])
self
.
assertTrue
(
metadata
[
'entrance_exam_enabled'
])
self
.
assertIsNotNone
(
metadata
[
'entrance_exam_minimum_score_pct'
])
self
.
assertIsNotNone
(
metadata
[
'entrance_exam_minimum_score_pct'
])
self
.
assertIsNotNone
(
metadata
[
'entrance_exam_id'
][
'value'
])
self
.
assertIsNotNone
(
metadata
[
'entrance_exam_id'
][
'value'
])
self
.
assertTrue
(
len
(
milestones_
api
.
get_course_milestones
(
unicode
(
self
.
course
.
id
))))
self
.
assertTrue
(
len
(
milestones_
helpers
.
get_course_milestones
(
unicode
(
self
.
course
.
id
))))
content_milestones
=
milestones_
api
.
get_course_content_milestones
(
content_milestones
=
milestones_
helpers
.
get_course_content_milestones
(
unicode
(
self
.
course
.
id
),
unicode
(
self
.
course
.
id
),
metadata
[
'entrance_exam_id'
][
'value'
],
metadata
[
'entrance_exam_id'
][
'value'
],
self
.
milestone_relationship_types
[
'FULFILLS'
]
self
.
milestone_relationship_types
[
'FULFILLS'
]
...
@@ -123,12 +119,12 @@ class EntranceExamHandlerTests(CourseTestCase):
...
@@ -123,12 +119,12 @@ class EntranceExamHandlerTests(CourseTestCase):
)
)
user
.
set_password
(
'test'
)
user
.
set_password
(
'test'
)
user
.
save
()
user
.
save
()
milestones
=
milestones_
api
.
get_course_milestones
(
unicode
(
self
.
course_key
))
milestones
=
milestones_
helpers
.
get_course_milestones
(
unicode
(
self
.
course_key
))
self
.
assertEqual
(
len
(
milestones
),
1
)
self
.
assertEqual
(
len
(
milestones
),
1
)
milestone_key
=
'{}.{}'
.
format
(
milestones
[
0
][
'namespace'
],
milestones
[
0
][
'name'
])
milestone_key
=
'{}.{}'
.
format
(
milestones
[
0
][
'namespace'
],
milestones
[
0
][
'name'
])
paths
=
milestones_
api
.
get_course_milestones_fulfillment_paths
(
paths
=
milestones_
helpers
.
get_course_milestones_fulfillment_paths
(
unicode
(
self
.
course_key
),
unicode
(
self
.
course_key
),
serialize_user
(
user
)
milestones_helpers
.
serialize_user
(
user
)
)
)
# What we have now is a course milestone requirement and no valid fulfillment
# What we have now is a course milestone requirement and no valid fulfillment
...
@@ -250,3 +246,22 @@ class EntranceExamHandlerTests(CourseTestCase):
...
@@ -250,3 +246,22 @@ class EntranceExamHandlerTests(CourseTestCase):
resp
=
create_entrance_exam
(
request
,
self
.
course
.
id
,
None
)
resp
=
create_entrance_exam
(
request
,
self
.
course
.
id
,
None
)
self
.
assertEqual
(
resp
.
status_code
,
201
)
self
.
assertEqual
(
resp
.
status_code
,
201
)
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'ENTRANCE_EXAMS'
:
False
})
def
test_entrance_exam_feature_flag_gating
(
self
):
user
=
UserFactory
()
user
.
is_staff
=
True
request
=
RequestFactory
()
request
.
user
=
user
resp
=
self
.
client
.
get
(
self
.
exam_url
)
self
.
assertEqual
(
resp
.
status_code
,
400
)
resp
=
create_entrance_exam
(
request
,
self
.
course
.
id
,
None
)
self
.
assertEqual
(
resp
.
status_code
,
400
)
resp
=
delete_entrance_exam
(
request
,
self
.
course
.
id
)
self
.
assertEqual
(
resp
.
status_code
,
400
)
# No return, so we'll just ensure no exception is thrown
update_entrance_exam
(
request
,
self
.
course
.
id
,
{})
common/djangoapps/util/milestones_helpers.py
View file @
cd053913
...
@@ -5,30 +5,24 @@ Utility library for working with the edx-milestones app
...
@@ -5,30 +5,24 @@ Utility library for working with the edx-milestones app
from
django.conf
import
settings
from
django.conf
import
settings
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
courseware.models
import
StudentModule
from
courseware.models
import
StudentModule
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
milestones.api
import
(
get_course_milestones
,
add_milestone
,
add_course_milestone
,
remove_course_milestone
,
get_course_milestones_fulfillment_paths
,
add_user_milestone
,
get_user_milestones
,
)
from
milestones.models
import
MilestoneRelationshipType
from
milestones.exceptions
import
InvalidMilestoneRelationshipTypeException
from
opaque_keys.edx.keys
import
UsageKey
NAMESPACE_CHOICES
=
{
NAMESPACE_CHOICES
=
{
'ENTRANCE_EXAM'
:
'entrance_exams'
'ENTRANCE_EXAM'
:
'entrance_exams'
}
}
def
get_namespace_choices
():
"""
Return the enum to the caller
"""
return
NAMESPACE_CHOICES
def
add_prerequisite_course
(
course_key
,
prerequisite_course_key
):
def
add_prerequisite_course
(
course_key
,
prerequisite_course_key
):
"""
"""
It would create a milestone, then it would set newly created
It would create a milestone, then it would set newly created
...
@@ -36,18 +30,23 @@ def add_prerequisite_course(course_key, prerequisite_course_key):
...
@@ -36,18 +30,23 @@ def add_prerequisite_course(course_key, prerequisite_course_key):
and it would set newly created milestone as fulfilment
and it would set newly created milestone as fulfilment
milestone for course referred by `prerequisite_course_key`.
milestone for course referred by `prerequisite_course_key`.
"""
"""
if
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
if
not
settings
.
FEATURES
.
get
(
'ENABLE_PREREQUISITE_COURSES'
,
False
):
# create a milestone
return
None
milestone
=
add_milestone
({
from
milestones
import
api
as
milestones_api
'name'
:
_
(
'Course {} requires {}'
.
format
(
unicode
(
course_key
),
unicode
(
prerequisite_course_key
))),
milestone_name
=
_
(
'Course {course_id} requires {prerequisite_course_id}'
)
.
format
(
'namespace'
:
unicode
(
prerequisite_course_key
),
course_id
=
unicode
(
course_key
),
'description'
:
_
(
'System defined milestone'
),
prerequisite_course_id
=
unicode
(
prerequisite_course_key
)
})
)
# add requirement course milestone
milestone
=
milestones_api
.
add_milestone
({
add_course_milestone
(
course_key
,
'requires'
,
milestone
)
'name'
:
milestone_name
,
'namespace'
:
unicode
(
prerequisite_course_key
),
'description'
:
_
(
'System defined milestone'
),
})
# add requirement course milestone
milestones_api
.
add_course_milestone
(
course_key
,
'requires'
,
milestone
)
# add fulfillment course milestone
# add fulfillment course milestone
add_course_milestone
(
prerequisite_course_key
,
'fulfills'
,
milestone
)
milestones_api
.
add_course_milestone
(
prerequisite_course_key
,
'fulfills'
,
milestone
)
def
remove_prerequisite_course
(
course_key
,
milestone
):
def
remove_prerequisite_course
(
course_key
,
milestone
):
...
@@ -55,11 +54,13 @@ def remove_prerequisite_course(course_key, milestone):
...
@@ -55,11 +54,13 @@ def remove_prerequisite_course(course_key, milestone):
It would remove pre-requisite course milestone for course
It would remove pre-requisite course milestone for course
referred by `course_key`.
referred by `course_key`.
"""
"""
if
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
if
not
settings
.
FEATURES
.
get
(
'ENABLE_PREREQUISITE_COURSES'
,
False
):
remove_course_milestone
(
return
None
course_key
,
from
milestones
import
api
as
milestones_api
milestone
,
milestones_api
.
remove_course_milestone
(
)
course_key
,
milestone
,
)
def
set_prerequisite_courses
(
course_key
,
prerequisite_course_keys
):
def
set_prerequisite_courses
(
course_key
,
prerequisite_course_keys
):
...
@@ -69,18 +70,20 @@ def set_prerequisite_courses(course_key, prerequisite_course_keys):
...
@@ -69,18 +70,20 @@ def set_prerequisite_courses(course_key, prerequisite_course_keys):
To only remove course milestones pass `course_key` and empty list or
To only remove course milestones pass `course_key` and empty list or
None as `prerequisite_course_keys` .
None as `prerequisite_course_keys` .
"""
"""
if
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
if
not
settings
.
FEATURES
.
get
(
'ENABLE_PREREQUISITE_COURSES'
,
False
):
#remove any existing requirement milestones with this pre-requisite course as requirement
return
None
course_milestones
=
get_course_milestones
(
course_key
=
course_key
,
relationship
=
"requires"
)
from
milestones
import
api
as
milestones_api
if
course_milestones
:
#remove any existing requirement milestones with this pre-requisite course as requirement
for
milestone
in
course_milestones
:
course_milestones
=
milestones_api
.
get_course_milestones
(
course_key
=
course_key
,
relationship
=
"requires"
)
remove_prerequisite_course
(
course_key
,
milestone
)
if
course_milestones
:
for
milestone
in
course_milestones
:
remove_prerequisite_course
(
course_key
,
milestone
)
# add milestones if pre-requisite course is selected
# add milestones if pre-requisite course is selected
if
prerequisite_course_keys
:
if
prerequisite_course_keys
:
for
prerequisite_course_key_string
in
prerequisite_course_keys
:
for
prerequisite_course_key_string
in
prerequisite_course_keys
:
prerequisite_course_key
=
CourseKey
.
from_string
(
prerequisite_course_key_string
)
prerequisite_course_key
=
CourseKey
.
from_string
(
prerequisite_course_key_string
)
add_prerequisite_course
(
course_key
,
prerequisite_course_key
)
add_prerequisite_course
(
course_key
,
prerequisite_course_key
)
def
get_pre_requisite_courses_not_completed
(
user
,
enrolled_courses
):
def
get_pre_requisite_courses_not_completed
(
user
,
enrolled_courses
):
...
@@ -91,10 +94,11 @@ def get_pre_requisite_courses_not_completed(user, enrolled_courses):
...
@@ -91,10 +94,11 @@ def get_pre_requisite_courses_not_completed(user, enrolled_courses):
prerequisite courses yet to be completed.
prerequisite courses yet to be completed.
"""
"""
pre_requisite_courses
=
{}
pre_requisite_courses
=
{}
if
settings
.
FEATURES
.
get
(
'ENABLE_PREREQUISITE_COURSES'
):
if
settings
.
FEATURES
.
get
(
'ENABLE_PREREQUISITE_COURSES'
,
False
):
from
milestones
import
api
as
milestones_api
for
course_key
in
enrolled_courses
:
for
course_key
in
enrolled_courses
:
required_courses
=
[]
required_courses
=
[]
fulfilment_paths
=
get_course_milestones_fulfillment_paths
(
course_key
,
{
'id'
:
user
.
id
})
fulfilment_paths
=
milestones_api
.
get_course_milestones_fulfillment_paths
(
course_key
,
{
'id'
:
user
.
id
})
for
milestone_key
,
milestone_value
in
fulfilment_paths
.
items
():
# pylint: disable=unused-variable
for
milestone_key
,
milestone_value
in
fulfilment_paths
.
items
():
# pylint: disable=unused-variable
for
key
,
value
in
milestone_value
.
items
():
for
key
,
value
in
milestone_value
.
items
():
if
key
==
'courses'
and
value
:
if
key
==
'courses'
and
value
:
...
@@ -146,10 +150,12 @@ def fulfill_course_milestone(course_key, user):
...
@@ -146,10 +150,12 @@ def fulfill_course_milestone(course_key, user):
Marks the course specified by the given course_key as complete for the given user.
Marks the course specified by the given course_key as complete for the given user.
If any other courses require this course as a prerequisite, their milestones will be appropriately updated.
If any other courses require this course as a prerequisite, their milestones will be appropriately updated.
"""
"""
if
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
course_milestones
=
get_course_milestones
(
course_key
=
course_key
,
relationship
=
"fulfills"
)
return
None
for
milestone
in
course_milestones
:
from
milestones
import
api
as
milestones_api
add_user_milestone
({
'id'
:
user
.
id
},
milestone
)
course_milestones
=
milestones_api
.
get_course_milestones
(
course_key
=
course_key
,
relationship
=
"fulfills"
)
for
milestone
in
course_milestones
:
milestones_api
.
add_user_milestone
({
'id'
:
user
.
id
},
milestone
)
def
get_required_content
(
course
,
user
):
def
get_required_content
(
course
,
user
):
...
@@ -159,9 +165,12 @@ def get_required_content(course, user):
...
@@ -159,9 +165,12 @@ def get_required_content(course, user):
"""
"""
required_content
=
[]
required_content
=
[]
if
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
if
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
from
milestones
import
api
as
milestones_api
from
milestones.exceptions
import
InvalidMilestoneRelationshipTypeException
# Get all of the outstanding milestones for this course, for this user
# Get all of the outstanding milestones for this course, for this user
try
:
try
:
milestone_paths
=
get_course_milestones_fulfillment_paths
(
milestone_paths
=
milestones_api
.
get_course_milestones_fulfillment_paths
(
unicode
(
course
.
id
),
unicode
(
course
.
id
),
serialize_user
(
user
)
serialize_user
(
user
)
)
)
...
@@ -221,8 +230,10 @@ def milestones_achieved_by_user(user, namespace):
...
@@ -221,8 +230,10 @@ def milestones_achieved_by_user(user, namespace):
"""
"""
It would fetch list of milestones completed by user
It would fetch list of milestones completed by user
"""
"""
if
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
return
get_user_milestones
({
'id'
:
user
.
id
},
namespace
)
return
None
from
milestones
import
api
as
milestones_api
return
milestones_api
.
get_user_milestones
({
'id'
:
user
.
id
},
namespace
)
def
is_valid_course_key
(
key
):
def
is_valid_course_key
(
key
):
...
@@ -240,9 +251,11 @@ def seed_milestone_relationship_types():
...
@@ -240,9 +251,11 @@ def seed_milestone_relationship_types():
"""
"""
Helper method to pre-populate MRTs so the tests can run
Helper method to pre-populate MRTs so the tests can run
"""
"""
if
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
MilestoneRelationshipType
.
objects
.
create
(
name
=
'requires'
)
return
None
MilestoneRelationshipType
.
objects
.
create
(
name
=
'fulfills'
)
from
milestones.models
import
MilestoneRelationshipType
MilestoneRelationshipType
.
objects
.
create
(
name
=
'requires'
)
MilestoneRelationshipType
.
objects
.
create
(
name
=
'fulfills'
)
def
generate_milestone_namespace
(
namespace
,
course_key
=
None
):
def
generate_milestone_namespace
(
namespace
,
course_key
=
None
):
...
@@ -261,3 +274,106 @@ def serialize_user(user):
...
@@ -261,3 +274,106 @@ def serialize_user(user):
return
{
return
{
'id'
:
user
.
id
,
'id'
:
user
.
id
,
}
}
def
add_milestone
(
milestone_data
):
"""
Client API operation adapter/wrapper
"""
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
return
None
from
milestones
import
api
as
milestones_api
return
milestones_api
.
add_milestone
(
milestone_data
)
def
get_milestones
(
namespace
):
"""
Client API operation adapter/wrapper
"""
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
return
[]
from
milestones
import
api
as
milestones_api
return
milestones_api
.
get_milestones
(
namespace
)
def
get_milestone_relationship_types
():
"""
Client API operation adapter/wrapper
"""
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
return
{}
from
milestones
import
api
as
milestones_api
return
milestones_api
.
get_milestone_relationship_types
()
def
add_course_milestone
(
course_id
,
relationship
,
milestone
):
"""
Client API operation adapter/wrapper
"""
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
return
None
from
milestones
import
api
as
milestones_api
return
milestones_api
.
add_course_milestone
(
course_id
,
relationship
,
milestone
)
def
get_course_milestones
(
course_id
):
"""
Client API operation adapter/wrapper
"""
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
return
[]
from
milestones
import
api
as
milestones_api
return
milestones_api
.
get_course_milestones
(
course_id
)
def
add_course_content_milestone
(
course_id
,
content_id
,
relationship
,
milestone
):
"""
Client API operation adapter/wrapper
"""
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
return
None
from
milestones
import
api
as
milestones_api
return
milestones_api
.
add_course_content_milestone
(
course_id
,
content_id
,
relationship
,
milestone
)
def
get_course_content_milestones
(
course_id
,
content_id
,
relationship
):
"""
Client API operation adapter/wrapper
"""
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
return
[]
from
milestones
import
api
as
milestones_api
return
milestones_api
.
get_course_content_milestones
(
course_id
,
content_id
,
relationship
)
def
remove_content_references
(
content_id
):
"""
Client API operation adapter/wrapper
"""
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
return
None
from
milestones
import
api
as
milestones_api
return
milestones_api
.
remove_content_references
(
content_id
)
def
get_course_milestones_fulfillment_paths
(
course_id
,
user_id
):
"""
Client API operation adapter/wrapper
"""
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
return
None
from
milestones
import
api
as
milestones_api
return
milestones_api
.
get_course_milestones_fulfillment_paths
(
course_id
,
user_id
)
def
add_user_milestone
(
user
,
milestone
):
"""
Client API operation adapter/wrapper
"""
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
return
None
from
milestones
import
api
as
milestones_api
return
milestones_api
.
add_user_milestone
(
user
,
milestone
)
common/djangoapps/util/tests/test_milestones_helpers.py
0 → 100644
View file @
cd053913
"""
Tests for the milestones helpers library, which is the integration point for the edx_milestones API
"""
from
mock
import
patch
from
util
import
milestones_helpers
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'MILESTONES_APP'
:
False
})
class
MilestonesHelpersTestCase
(
ModuleStoreTestCase
):
"""
Main test suite for Milestones API client library
"""
def
setUp
(
self
):
"""
Test case scaffolding
"""
super
(
MilestonesHelpersTestCase
,
self
)
.
setUp
(
create_user
=
False
)
self
.
course
=
CourseFactory
.
create
(
metadata
=
{
'entrance_exam_enabled'
:
True
,
}
)
self
.
user
=
{
'id'
:
'123'
}
self
.
milestone
=
{
'name'
:
'Test Milestone'
,
'namespace'
:
'doesnt.matter'
,
'description'
:
'Testing Milestones Helpers Library'
,
}
def
test_add_milestone_returns_none_when_app_disabled
(
self
):
response
=
milestones_helpers
.
add_milestone
(
milestone_data
=
self
.
milestone
)
self
.
assertIsNone
(
response
)
def
test_get_milestones_returns_none_when_app_disabled
(
self
):
response
=
milestones_helpers
.
get_milestones
(
namespace
=
"whatever"
)
self
.
assertEqual
(
len
(
response
),
0
)
def
test_get_milestone_relationship_types_returns_none_when_app_disabled
(
self
):
response
=
milestones_helpers
.
get_milestone_relationship_types
()
self
.
assertEqual
(
len
(
response
),
0
)
def
test_add_course_milestone_returns_none_when_app_disabled
(
self
):
response
=
milestones_helpers
.
add_course_milestone
(
unicode
(
self
.
course
.
id
),
'requires'
,
self
.
milestone
)
self
.
assertIsNone
(
response
)
def
test_get_course_milestones_returns_none_when_app_disabled
(
self
):
response
=
milestones_helpers
.
get_course_milestones
(
unicode
(
self
.
course
.
id
))
self
.
assertEqual
(
len
(
response
),
0
)
def
test_add_course_content_milestone_returns_none_when_app_disabled
(
self
):
response
=
milestones_helpers
.
add_course_content_milestone
(
unicode
(
self
.
course
.
id
),
'i4x://any/content/id'
,
'requires'
,
self
.
milestone
)
self
.
assertIsNone
(
response
)
def
test_get_course_content_milestones_returns_none_when_app_disabled
(
self
):
response
=
milestones_helpers
.
get_course_content_milestones
(
unicode
(
self
.
course
.
id
),
'i4x://doesnt/matter/for/this/test'
,
'requires'
)
self
.
assertEqual
(
len
(
response
),
0
)
def
test_remove_content_references_returns_none_when_app_disabled
(
self
):
response
=
milestones_helpers
.
remove_content_references
(
"i4x://any/content/id/will/do"
)
self
.
assertIsNone
(
response
)
def
test_get_namespace_choices_returns_values_when_app_disabled
(
self
):
response
=
milestones_helpers
.
get_namespace_choices
()
self
.
assertIn
(
'ENTRANCE_EXAM'
,
response
)
def
test_get_course_milestones_fulfillment_paths_returns_none_when_app_disabled
(
self
):
response
=
milestones_helpers
.
get_course_milestones_fulfillment_paths
(
unicode
(
self
.
course
.
id
),
self
.
user
)
self
.
assertIsNone
(
response
)
def
test_add_user_milestone_returns_none_when_app_disabled
(
self
):
response
=
milestones_helpers
.
add_user_milestone
(
self
.
user
,
self
.
milestone
)
self
.
assertIsNone
(
response
)
lms/djangoapps/courseware/module_render.py
View file @
cd053913
...
@@ -63,10 +63,8 @@ from xmodule.x_module import XModuleDescriptor
...
@@ -63,10 +63,8 @@ from xmodule.x_module import XModuleDescriptor
from
xblock_django.user_service
import
DjangoXBlockUserService
from
xblock_django.user_service
import
DjangoXBlockUserService
from
util.json_request
import
JsonResponse
from
util.json_request
import
JsonResponse
from
util.sandboxing
import
can_execute_unsafe_code
,
get_python_lib_zip
from
util.sandboxing
import
can_execute_unsafe_code
,
get_python_lib_zip
if
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
from
util
import
milestones_helpers
from
milestones
import
api
as
milestones_api
from
util.module_utils
import
yield_dynamic_descriptor_descendents
from
util.milestones_helpers
import
calculate_entrance_exam_score
,
get_required_content
from
util.module_utils
import
yield_dynamic_descriptor_descendents
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -136,14 +134,14 @@ def toc_for_course(request, course, active_chapter, active_section, field_data_c
...
@@ -136,14 +134,14 @@ def toc_for_course(request, course, active_chapter, active_section, field_data_c
return
None
return
None
# Check to see if the course is gated on milestone-required content (such as an Entrance Exam)
# Check to see if the course is gated on milestone-required content (such as an Entrance Exam)
required_content
=
get_required_content
(
course
,
request
.
user
)
required_content
=
milestones_helpers
.
get_required_content
(
course
,
request
.
user
)
chapters
=
list
()
chapters
=
list
()
for
chapter
in
course_module
.
get_display_items
():
for
chapter
in
course_module
.
get_display_items
():
# Only show required content, if there is required content
# Only show required content, if there is required content
# chapter.hide_from_toc is read-only (boo)
# chapter.hide_from_toc is read-only (boo)
local_hide_from_toc
=
False
local_hide_from_toc
=
False
if
len
(
required_content
)
:
if
required_content
:
if
unicode
(
chapter
.
location
)
not
in
required_content
:
if
unicode
(
chapter
.
location
)
not
in
required_content
:
local_hide_from_toc
=
True
local_hide_from_toc
=
True
...
@@ -375,7 +373,7 @@ def get_module_system_for_user(user, field_data_cache,
...
@@ -375,7 +373,7 @@ def get_module_system_for_user(user, field_data_cache,
inner_get_module
inner_get_module
)
)
exam_modules
=
[
module
for
module
in
exam_module_generators
]
exam_modules
=
[
module
for
module
in
exam_module_generators
]
exam_score
=
calculate_entrance_exam_score
(
user
,
course_descriptor
,
exam_modules
)
exam_score
=
milestones_helpers
.
calculate_entrance_exam_score
(
user
,
course_descriptor
,
exam_modules
)
return
exam_score
return
exam_score
def
_fulfill_content_milestones
(
user
,
course_key
,
content_key
):
def
_fulfill_content_milestones
(
user
,
course_key
,
content_key
):
...
@@ -394,8 +392,8 @@ def get_module_system_for_user(user, field_data_cache,
...
@@ -394,8 +392,8 @@ def get_module_system_for_user(user, field_data_cache,
exam_pct
=
_calculate_entrance_exam_score
(
user
,
course
)
exam_pct
=
_calculate_entrance_exam_score
(
user
,
course
)
if
exam_pct
>=
course
.
entrance_exam_minimum_score_pct
:
if
exam_pct
>=
course
.
entrance_exam_minimum_score_pct
:
exam_key
=
UsageKey
.
from_string
(
course
.
entrance_exam_id
)
exam_key
=
UsageKey
.
from_string
(
course
.
entrance_exam_id
)
relationship_types
=
milestones_
api
.
get_milestone_relationship_types
()
relationship_types
=
milestones_
helpers
.
get_milestone_relationship_types
()
content_milestones
=
milestones_
api
.
get_course_content_milestones
(
content_milestones
=
milestones_
helpers
.
get_course_content_milestones
(
course_key
,
course_key
,
exam_key
,
exam_key
,
relationship
=
relationship_types
[
'FULFILLS'
]
relationship
=
relationship_types
[
'FULFILLS'
]
...
@@ -403,7 +401,7 @@ def get_module_system_for_user(user, field_data_cache,
...
@@ -403,7 +401,7 @@ def get_module_system_for_user(user, field_data_cache,
# Add each milestone to the user's set...
# Add each milestone to the user's set...
user
=
{
'id'
:
user
.
id
}
user
=
{
'id'
:
user
.
id
}
for
milestone
in
content_milestones
:
for
milestone
in
content_milestones
:
milestones_
api
.
add_user_milestone
(
user
,
milestone
)
milestones_
helpers
.
add_user_milestone
(
user
,
milestone
)
def
handle_grade_event
(
block
,
event_type
,
event
):
# pylint: disable=unused-argument
def
handle_grade_event
(
block
,
event_type
,
event
):
# pylint: disable=unused-argument
"""
"""
...
...
lms/djangoapps/courseware/tabs.py
View file @
cd053913
...
@@ -9,9 +9,7 @@ from courseware.access import has_access
...
@@ -9,9 +9,7 @@ from courseware.access import has_access
from
student.models
import
CourseEnrollment
,
EntranceExamConfiguration
from
student.models
import
CourseEnrollment
,
EntranceExamConfiguration
from
xmodule.tabs
import
CourseTabList
from
xmodule.tabs
import
CourseTabList
if
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
from
util
import
milestones_helpers
from
milestones.api
import
get_course_milestones_fulfillment_paths
from
util.milestones_helpers
import
serialize_user
def
get_course_tab_list
(
course
,
user
):
def
get_course_tab_list
(
course
,
user
):
...
@@ -33,9 +31,9 @@ def get_course_tab_list(course, user):
...
@@ -33,9 +31,9 @@ def get_course_tab_list(course, user):
entrance_exam_mode
=
False
entrance_exam_mode
=
False
if
settings
.
FEATURES
.
get
(
'ENTRANCE_EXAMS'
,
False
):
if
settings
.
FEATURES
.
get
(
'ENTRANCE_EXAMS'
,
False
):
if
getattr
(
course
,
'entrance_exam_enabled'
,
False
):
if
getattr
(
course
,
'entrance_exam_enabled'
,
False
):
course_milestones_paths
=
get_course_milestones_fulfillment_paths
(
course_milestones_paths
=
milestones_helpers
.
get_course_milestones_fulfillment_paths
(
unicode
(
course
.
id
),
unicode
(
course
.
id
),
serialize_user
(
user
)
milestones_helpers
.
serialize_user
(
user
)
)
)
for
__
,
value
in
course_milestones_paths
.
iteritems
():
for
__
,
value
in
course_milestones_paths
.
iteritems
():
if
len
(
value
.
get
(
'content'
,
[])):
if
len
(
value
.
get
(
'content'
,
[])):
...
...
lms/djangoapps/courseware/tests/test_entrance_exam.py
View file @
cd053913
"""
"""
Tests use cases related to LMS Entrance Exam behavior, such as gated content access (TOC)
Tests use cases related to LMS Entrance Exam behavior, such as gated content access (TOC)
"""
"""
from
django.conf
import
settings
from
django.test.client
import
RequestFactory
from
django.test.client
import
RequestFactory
from
django.test.utils
import
override_settings
from
django.test.utils
import
override_settings
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
...
@@ -9,12 +10,11 @@ from courseware.model_data import FieldDataCache
...
@@ -9,12 +10,11 @@ from courseware.model_data import FieldDataCache
from
courseware.module_render
import
get_module
,
toc_for_course
from
courseware.module_render
import
get_module
,
toc_for_course
from
courseware.tests.factories
import
UserFactory
,
InstructorFactory
from
courseware.tests.factories
import
UserFactory
,
InstructorFactory
from
courseware.courses
import
get_entrance_exam_content_info
,
get_entrance_exam_score
from
courseware.courses
import
get_entrance_exam_content_info
,
get_entrance_exam_score
from
milestones
import
api
as
milestones_api
from
milestones.models
import
MilestoneRelationshipType
from
milestones.models
import
MilestoneRelationshipType
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
,
TEST_DATA_MOCK_MODULESTORE
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
,
TEST_DATA_MOCK_MODULESTORE
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
util
.milestones_helpers
import
generate_milestone_namespace
,
NAMESPACE_CHOICES
from
util
import
milestones_helpers
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
mock
import
patch
from
mock
import
patch
import
mock
import
mock
...
@@ -23,8 +23,10 @@ import mock
...
@@ -23,8 +23,10 @@ import mock
class
EntranceExamTestCases
(
ModuleStoreTestCase
):
class
EntranceExamTestCases
(
ModuleStoreTestCase
):
"""
"""
Check that content is properly gated. Create a test course from scratch to mess with.
Check that content is properly gated. Create a test course from scratch to mess with.
We typically assume that the Entrance Exam feature flag is set to True in test.py
However, the tests below are designed to execute workflows regardless of the setting
If set to False, we are essentially confirming that the workflows do not cause exceptions
"""
"""
def
setUp
(
self
):
def
setUp
(
self
):
"""
"""
Test case scaffolding
Test case scaffolding
...
@@ -109,30 +111,31 @@ class EntranceExamTestCases(ModuleStoreTestCase):
...
@@ -109,30 +111,31 @@ class EntranceExamTestCases(ModuleStoreTestCase):
category
=
"problem"
,
category
=
"problem"
,
display_name
=
"Exam Problem - Problem 3"
display_name
=
"Exam Problem - Problem 3"
)
)
milestone_namespace
=
generate_milestone_namespace
(
if
settings
.
FEATURES
.
get
(
'ENTRANCE_EXAMS'
,
False
):
NAMESPACE_CHOICES
[
'ENTRANCE_EXAM'
],
namespace_choices
=
milestones_helpers
.
get_namespace_choices
()
self
.
course
.
id
milestone_namespace
=
milestones_helpers
.
generate_milestone_namespace
(
)
namespace_choices
.
get
(
'ENTRANCE_EXAM'
),
self
.
milestone
=
{
self
.
course
.
id
'name'
:
'Test Milestone'
,
)
'namespace'
:
milestone_namespace
,
self
.
milestone
=
{
'description'
:
'Testing Courseware Entrance Exam Chapter'
,
'name'
:
'Test Milestone'
,
}
'namespace'
:
milestone_namespace
,
MilestoneRelationshipType
.
objects
.
create
(
name
=
'requires'
,
active
=
True
)
'description'
:
'Testing Courseware Entrance Exam Chapter'
,
MilestoneRelationshipType
.
objects
.
create
(
name
=
'fulfills'
,
active
=
True
)
}
self
.
milestone_relationship_types
=
milestones_api
.
get_milestone_relationship_types
()
milestones_helpers
.
seed_milestone_relationship_types
()
self
.
milestone
=
milestones_api
.
add_milestone
(
self
.
milestone
)
self
.
milestone_relationship_types
=
milestones_helpers
.
get_milestone_relationship_types
()
milestones_api
.
add_course_milestone
(
self
.
milestone
=
milestones_helpers
.
add_milestone
(
self
.
milestone
)
unicode
(
self
.
course
.
id
),
milestones_helpers
.
add_course_milestone
(
self
.
milestone_relationship_types
[
'REQUIRES'
],
unicode
(
self
.
course
.
id
),
self
.
milestone
self
.
milestone_relationship_types
[
'REQUIRES'
],
)
self
.
milestone
milestones_api
.
add_course_content_milestone
(
)
unicode
(
self
.
course
.
id
),
milestones_helpers
.
add_course_content_milestone
(
unicode
(
self
.
entrance_exam
.
location
),
unicode
(
self
.
course
.
id
),
self
.
milestone_relationship_types
[
'FULFILLS'
],
unicode
(
self
.
entrance_exam
.
location
),
self
.
milestone
self
.
milestone_relationship_types
[
'FULFILLS'
],
)
self
.
milestone
)
user
=
UserFactory
()
user
=
UserFactory
()
self
.
request
=
RequestFactory
()
self
.
request
=
RequestFactory
()
self
.
request
.
user
=
user
self
.
request
.
user
=
user
...
@@ -241,7 +244,8 @@ class EntranceExamTestCases(ModuleStoreTestCase):
...
@@ -241,7 +244,8 @@ class EntranceExamTestCases(ModuleStoreTestCase):
'section'
:
self
.
exam_1
.
location
.
name
'section'
:
self
.
exam_1
.
location
.
name
})
})
resp
=
self
.
client
.
get
(
url
)
resp
=
self
.
client
.
get
(
url
)
self
.
assertRedirects
(
resp
,
expected_url
,
status_code
=
302
,
target_status_code
=
200
)
if
settings
.
FEATURES
.
get
(
'ENTRANCE_EXAMS'
,
False
):
self
.
assertRedirects
(
resp
,
expected_url
,
status_code
=
302
,
target_status_code
=
200
)
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'ENTRANCE_EXAMS'
:
False
})
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'ENTRANCE_EXAMS'
:
False
})
def
test_entrance_exam_content_absence
(
self
):
def
test_entrance_exam_content_absence
(
self
):
...
@@ -261,7 +265,6 @@ class EntranceExamTestCases(ModuleStoreTestCase):
...
@@ -261,7 +265,6 @@ class EntranceExamTestCases(ModuleStoreTestCase):
self
.
assertNotIn
(
'Exam Problem - Problem 1'
,
resp
.
content
)
self
.
assertNotIn
(
'Exam Problem - Problem 1'
,
resp
.
content
)
self
.
assertNotIn
(
'Exam Problem - Problem 2'
,
resp
.
content
)
self
.
assertNotIn
(
'Exam Problem - Problem 2'
,
resp
.
content
)
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'ENTRANCE_EXAMS'
:
True
})
def
test_entrance_exam_content_presence
(
self
):
def
test_entrance_exam_content_presence
(
self
):
"""
"""
Unit Test: If entrance exam is enabled then its content e.g. problems should be loaded and redirection will
Unit Test: If entrance exam is enabled then its content e.g. problems should be loaded and redirection will
...
@@ -275,41 +278,44 @@ class EntranceExamTestCases(ModuleStoreTestCase):
...
@@ -275,41 +278,44 @@ class EntranceExamTestCases(ModuleStoreTestCase):
'section'
:
self
.
exam_1
.
location
.
name
'section'
:
self
.
exam_1
.
location
.
name
})
})
resp
=
self
.
client
.
get
(
url
)
resp
=
self
.
client
.
get
(
url
)
self
.
assertRedirects
(
resp
,
expected_url
,
status_code
=
302
,
target_status_code
=
200
)
if
settings
.
FEATURES
.
get
(
'ENTRANCE_EXAMS'
,
False
):
resp
=
self
.
client
.
get
(
expected_url
)
self
.
assertRedirects
(
resp
,
expected_url
,
status_code
=
302
,
target_status_code
=
200
)
self
.
assertIn
(
'Exam Problem - Problem 1'
,
resp
.
content
)
resp
=
self
.
client
.
get
(
expected_url
)
self
.
assertIn
(
'Exam Problem - Problem 2'
,
resp
.
content
)
self
.
assertIn
(
'Exam Problem - Problem 1'
,
resp
.
content
)
self
.
assertIn
(
'Exam Problem - Problem 2'
,
resp
.
content
)
def
test_entrance_exam_content_info
(
self
):
def
test_entrance_exam_content_info
(
self
):
"""
"""
test entrance exam content info method
test entrance exam content info method
"""
"""
exam_chapter
,
is_exam_passed
=
get_entrance_exam_content_info
(
self
.
request
,
self
.
course
)
exam_chapter
,
is_exam_passed
=
get_entrance_exam_content_info
(
self
.
request
,
self
.
course
)
self
.
assertEqual
(
exam_chapter
.
url_name
,
self
.
entrance_exam
.
url_name
)
if
settings
.
FEATURES
.
get
(
'ENTRANCE_EXAMS'
,
False
):
self
.
assertEqual
(
is_exam_passed
,
False
)
self
.
assertEqual
(
exam_chapter
.
url_name
,
self
.
entrance_exam
.
url_name
)
self
.
assertEqual
(
is_exam_passed
,
False
)
# Pass the entrance exam
# Pass the entrance exam
# pylint: disable=maybe-no-member,no-member
# pylint: disable=maybe-no-member,no-member
grade_dict
=
{
'value'
:
1
,
'max_value'
:
1
,
'user_id'
:
self
.
request
.
user
.
id
}
grade_dict
=
{
'value'
:
1
,
'max_value'
:
1
,
'user_id'
:
self
.
request
.
user
.
id
}
field_data_cache
=
FieldDataCache
.
cache_for_descriptor_descendents
(
field_data_cache
=
FieldDataCache
.
cache_for_descriptor_descendents
(
self
.
course
.
id
,
self
.
course
.
id
,
self
.
request
.
user
,
self
.
request
.
user
,
self
.
course
,
self
.
course
,
depth
=
2
depth
=
2
)
)
# pylint: disable=protected-access
# pylint: disable=protected-access
module
=
get_module
(
module
=
get_module
(
self
.
request
.
user
,
self
.
request
.
user
,
self
.
request
,
self
.
request
,
self
.
problem_1
.
scope_ids
.
usage_id
,
self
.
problem_1
.
scope_ids
.
usage_id
,
field_data_cache
,
field_data_cache
,
)
.
_xmodule
)
.
_xmodule
module
.
system
.
publish
(
self
.
problem_1
,
'grade'
,
grade_dict
)
module
.
system
.
publish
(
self
.
problem_1
,
'grade'
,
grade_dict
)
exam_chapter
,
is_exam_passed
=
get_entrance_exam_content_info
(
self
.
request
,
self
.
course
)
exam_chapter
,
is_exam_passed
=
get_entrance_exam_content_info
(
self
.
request
,
self
.
course
)
self
.
assertEqual
(
exam_chapter
,
None
)
self
.
assertEqual
(
exam_chapter
,
None
)
self
.
assertEqual
(
is_exam_passed
,
True
)
self
.
assertEqual
(
is_exam_passed
,
True
)
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'ENTRANCE_EXAMS'
:
True
})
def
test_entrance_exam_score
(
self
):
def
test_entrance_exam_score
(
self
):
"""
"""
test entrance exam score. we will hit the method get_entrance_exam_score to verify exam score.
test entrance exam score. we will hit the method get_entrance_exam_score to verify exam score.
...
@@ -352,8 +358,9 @@ class EntranceExamTestCases(ModuleStoreTestCase):
...
@@ -352,8 +358,9 @@ class EntranceExamTestCases(ModuleStoreTestCase):
}
}
)
)
resp
=
self
.
client
.
get
(
url
)
resp
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
if
settings
.
FEATURES
.
get
(
'ENTRANCE_EXAMS'
,
False
):
self
.
assertIn
(
'To access course materials, you must score'
,
resp
.
content
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertIn
(
'To access course materials, you must score'
,
resp
.
content
)
def
test_entrance_exam_requirement_message_hidden
(
self
):
def
test_entrance_exam_requirement_message_hidden
(
self
):
"""
"""
...
@@ -369,8 +376,9 @@ class EntranceExamTestCases(ModuleStoreTestCase):
...
@@ -369,8 +376,9 @@ class EntranceExamTestCases(ModuleStoreTestCase):
)
)
resp
=
self
.
client
.
get
(
url
)
resp
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertNotIn
(
'To access course materials, you must score'
,
resp
.
content
)
if
settings
.
FEATURES
.
get
(
'ENTRANCE_EXAMS'
,
False
):
self
.
assertNotIn
(
'You have passed the entrance exam.'
,
resp
.
content
)
self
.
assertNotIn
(
'To access course materials, you must score'
,
resp
.
content
)
self
.
assertNotIn
(
'You have passed the entrance exam.'
,
resp
.
content
)
def
test_entrance_exam_passed_message_and_course_content
(
self
):
def
test_entrance_exam_passed_message_and_course_content
(
self
):
"""
"""
...
@@ -404,10 +412,12 @@ class EntranceExamTestCases(ModuleStoreTestCase):
...
@@ -404,10 +412,12 @@ class EntranceExamTestCases(ModuleStoreTestCase):
module
.
system
.
publish
(
self
.
problem_1
,
'grade'
,
grade_dict
)
module
.
system
.
publish
(
self
.
problem_1
,
'grade'
,
grade_dict
)
resp
=
self
.
client
.
get
(
url
)
resp
=
self
.
client
.
get
(
url
)
self
.
assertNotIn
(
'To access course materials, you must score'
,
resp
.
content
)
if
settings
.
FEATURES
.
get
(
'ENTRANCE_EXAMS'
,
False
):
self
.
assertIn
(
'You have passed the entrance exam.'
,
resp
.
content
)
self
.
assertNotIn
(
'To access course materials, you must score'
,
resp
.
content
)
self
.
assertIn
(
'Lesson 1'
,
resp
.
content
)
self
.
assertIn
(
'You have passed the entrance exam.'
,
resp
.
content
)
self
.
assertIn
(
'Lesson 1'
,
resp
.
content
)
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'ENTRANCE_EXAMS'
:
True
})
def
test_entrance_exam_gating
(
self
):
def
test_entrance_exam_gating
(
self
):
"""
"""
Unit Test: test_entrance_exam_gating
Unit Test: test_entrance_exam_gating
...
@@ -477,6 +487,7 @@ class EntranceExamTestCases(ModuleStoreTestCase):
...
@@ -477,6 +487,7 @@ class EntranceExamTestCases(ModuleStoreTestCase):
for
toc_section
in
self
.
expected_unlocked_toc
:
for
toc_section
in
self
.
expected_unlocked_toc
:
self
.
assertIn
(
toc_section
,
unlocked_toc
)
self
.
assertIn
(
toc_section
,
unlocked_toc
)
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'ENTRANCE_EXAMS'
:
True
})
def
test_skip_entrance_exame_gating
(
self
):
def
test_skip_entrance_exame_gating
(
self
):
"""
"""
Tests gating is disabled if skip entrance exam is set for a user.
Tests gating is disabled if skip entrance exam is set for a user.
...
...
lms/djangoapps/courseware/tests/test_tabs.py
View file @
cd053913
...
@@ -15,16 +15,14 @@ from xmodule import tabs
...
@@ -15,16 +15,14 @@ from xmodule import tabs
from
xmodule.modulestore.tests.django_utils
import
(
from
xmodule.modulestore.tests.django_utils
import
(
TEST_DATA_MIXED_TOY_MODULESTORE
,
TEST_DATA_MIXED_CLOSED_MODULESTORE
TEST_DATA_MIXED_TOY_MODULESTORE
,
TEST_DATA_MIXED_CLOSED_MODULESTORE
)
)
from
courseware.tabs
import
get_course_tab_list
from
courseware.views
import
get_static_tab_contents
,
static_tab
from
courseware.views
import
get_static_tab_contents
,
static_tab
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
from
util
import
milestones_helpers
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
if
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
from
courseware.tabs
import
get_course_tab_list
from
milestones
import
api
as
milestones_api
from
milestones.models
import
MilestoneRelationshipType
class
StaticTabDateTestCase
(
LoginEnrollmentTestCase
,
ModuleStoreTestCase
):
class
StaticTabDateTestCase
(
LoginEnrollmentTestCase
,
ModuleStoreTestCase
):
"""Test cases for Static Tab Dates."""
"""Test cases for Static Tab Dates."""
...
@@ -140,9 +138,8 @@ class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
...
@@ -140,9 +138,8 @@ class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
self
.
setup_user
()
self
.
setup_user
()
self
.
enroll
(
self
.
course
)
self
.
enroll
(
self
.
course
)
self
.
user
.
is_staff
=
True
self
.
user
.
is_staff
=
True
self
.
relationship_types
=
milestones_api
.
get_milestone_relationship_types
()
self
.
relationship_types
=
milestones_helpers
.
get_milestone_relationship_types
()
MilestoneRelationshipType
.
objects
.
create
(
name
=
'requires'
)
milestones_helpers
.
seed_milestone_relationship_types
()
MilestoneRelationshipType
.
objects
.
create
(
name
=
'fulfills'
)
def
test_get_course_tabs_list_entrance_exam_enabled
(
self
):
def
test_get_course_tabs_list_entrance_exam_enabled
(
self
):
"""
"""
...
@@ -160,13 +157,13 @@ class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
...
@@ -160,13 +157,13 @@ class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
}
}
self
.
course
.
entrance_exam_enabled
=
True
self
.
course
.
entrance_exam_enabled
=
True
self
.
course
.
entrance_exam_id
=
unicode
(
entrance_exam
.
location
)
self
.
course
.
entrance_exam_id
=
unicode
(
entrance_exam
.
location
)
milestone
=
milestones_
api
.
add_milestone
(
milestone
)
milestone
=
milestones_
helpers
.
add_milestone
(
milestone
)
milestones_
api
.
add_course_milestone
(
milestones_
helpers
.
add_course_milestone
(
unicode
(
self
.
course
.
id
),
unicode
(
self
.
course
.
id
),
self
.
relationship_types
[
'REQUIRES'
],
self
.
relationship_types
[
'REQUIRES'
],
milestone
milestone
)
)
milestones_
api
.
add_course_content_milestone
(
milestones_
helpers
.
add_course_content_milestone
(
unicode
(
self
.
course
.
id
),
unicode
(
self
.
course
.
id
),
unicode
(
entrance_exam
.
location
),
unicode
(
entrance_exam
.
location
),
self
.
relationship_types
[
'FULFILLS'
],
self
.
relationship_types
[
'FULFILLS'
],
...
...
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