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
a39d6c2e
Commit
a39d6c2e
authored
Jul 11, 2016
by
Kevin Kim
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add gated content banner for staff in lms
parent
1809f88d
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
135 additions
and
28 deletions
+135
-28
common/djangoapps/util/milestones_helpers.py
+14
-1
common/djangoapps/util/tests/test_milestones_helpers.py
+5
-0
common/lib/xmodule/xmodule/seq_module.py
+17
-0
common/test/acceptance/pages/lms/courseware.py
+6
-0
common/test/acceptance/tests/lms/test_lms_gating.py
+66
-10
lms/djangoapps/courseware/module_render.py
+1
-0
openedx/core/lib/gating/api.py
+13
-9
openedx/core/lib/gating/tests/test_api.py
+12
-7
requirements/edx/github.txt
+1
-1
No files found.
common/djangoapps/util/milestones_helpers.py
View file @
a39d6c2e
...
...
@@ -2,7 +2,6 @@
"""
Utility library for working with the edx-milestones app
"""
from
django.conf
import
settings
from
django.utils.translation
import
ugettext
as
_
...
...
@@ -12,6 +11,7 @@ from opaque_keys.edx.keys import CourseKey
from
milestones
import
api
as
milestones_api
from
milestones.exceptions
import
InvalidMilestoneRelationshipTypeException
from
milestones.models
import
MilestoneRelationshipType
from
milestones.services
import
MilestonesService
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
xmodule.modulestore.django
import
modulestore
import
request_cache
...
...
@@ -418,3 +418,16 @@ def remove_user_milestone(user, milestone):
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
):
return
None
return
milestones_api
.
remove_user_milestone
(
user
,
milestone
)
def
get_service
():
"""
Returns MilestonesService instance if feature flag enabled;
else returns None.
Note: MilestonesService only has access to the functions
explicitly requested in the MilestonesServices class
"""
if
not
settings
.
FEATURES
.
get
(
'MILESTONES_APP'
,
False
):
return
None
return
MilestonesService
()
common/djangoapps/util/tests/test_milestones_helpers.py
View file @
a39d6c2e
...
...
@@ -110,6 +110,11 @@ class MilestonesHelpersTestCase(ModuleStoreTestCase):
response
=
milestones_helpers
.
add_user_milestone
(
self
.
user
,
self
.
milestone
)
self
.
assertIsNone
(
response
)
def
test_get_service_returns_none_when_app_disabled
(
self
):
"""MilestonesService is None when app disabled"""
response
=
milestones_helpers
.
get_service
()
self
.
assertIsNone
(
response
)
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'MILESTONES_APP'
:
True
})
def
test_any_unfulfilled_milestones
(
self
):
""" Tests any_unfulfilled_milestones for invalid arguments """
...
...
common/lib/xmodule/xmodule/seq_module.py
View file @
a39d6c2e
...
...
@@ -137,6 +137,7 @@ class ProctoringFields(object):
@XBlock.wants
(
'proctoring'
)
@XBlock.wants
(
'milestones'
)
@XBlock.wants
(
'credit'
)
@XBlock.needs
(
"user"
)
@XBlock.needs
(
"bookmarks"
)
...
...
@@ -209,6 +210,8 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
banner_text
,
special_html
=
special_html_view
if
special_html
and
not
masquerading_as_specific_student
:
return
Fragment
(
special_html
)
else
:
banner_text
=
self
.
_gated_content_staff_banner
()
return
self
.
_student_view
(
context
,
banner_text
)
def
_special_exam_student_view
(
self
):
...
...
@@ -249,6 +252,20 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
return
banner_text
,
hidden_content_html
def
_gated_content_staff_banner
(
self
):
"""
Checks whether the content is gated for learners. If so,
returns a banner_text depending on whether user is staff.
"""
milestones_service
=
self
.
runtime
.
service
(
self
,
'milestones'
)
if
milestones_service
:
content_milestones
=
milestones_service
.
get_course_content_milestones
(
self
.
course_id
,
self
.
location
,
'requires'
)
banner_text
=
_
(
'This subsection is unlocked for learners when they meet the prerequisite requirements.'
)
if
content_milestones
and
self
.
runtime
.
user_is_staff
:
return
banner_text
def
_can_user_view_content
(
self
):
"""
Returns whether the runtime user can view the content
...
...
common/test/acceptance/pages/lms/courseware.py
View file @
a39d6c2e
...
...
@@ -229,6 +229,12 @@ class CoursewarePage(CoursePage):
return
self
.
entrance_exam_message_selector
.
is_present
()
\
and
"You have passed the entrance exam"
in
self
.
entrance_exam_message_selector
.
text
[
0
]
def
has_banner
(
self
):
"""
Returns boolean indicating presence of banner
"""
return
self
.
q
(
css
=
'.pattern-library-shim'
)
.
is_present
()
@property
def
is_timer_bar_present
(
self
):
"""
...
...
common/test/acceptance/tests/lms/test_lms_gating.py
View file @
a39d6c2e
...
...
@@ -9,6 +9,7 @@ from ...pages.studio.auto_auth import AutoAuthPage
from
...pages.studio.overview
import
CourseOutlinePage
from
...pages.lms.courseware
import
CoursewarePage
from
...pages.lms.problem
import
ProblemPage
from
...pages.lms.staff_view
import
StaffPage
from
...pages.common.logout
import
LogoutPage
from
...fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
...
...
@@ -17,8 +18,11 @@ class GatingTest(UniqueCourseTest):
"""
Test gating feature in LMS.
"""
USERNAME
=
"STUDENT_TESTER"
EMAIL
=
"student101@example.com"
STAFF_USERNAME
=
"STAFF_TESTER"
STAFF_EMAIL
=
"staff101@example.com"
STUDENT_USERNAME
=
"STUDENT_TESTER"
STUDENT_EMAIL
=
"student101@example.com"
def
setUp
(
self
):
super
(
GatingTest
,
self
)
.
setUp
()
...
...
@@ -82,7 +86,7 @@ class GatingTest(UniqueCourseTest):
Make the first subsection a prerequisite
"""
# Login as staff
self
.
_auto_auth
(
"STAFF_TESTER"
,
"staff101@example.com"
,
True
)
self
.
_auto_auth
(
self
.
STAFF_USERNAME
,
self
.
STAFF_EMAIL
,
True
)
# Make the first subsection a prerequisite
self
.
course_outline
.
visit
()
...
...
@@ -95,7 +99,7 @@ class GatingTest(UniqueCourseTest):
Gate the second subsection on the first subsection
"""
# Login as staff
self
.
_auto_auth
(
"STAFF_TESTER"
,
"staff101@example.com"
,
True
)
self
.
_auto_auth
(
self
.
STAFF_USERNAME
,
self
.
STAFF_EMAIL
,
True
)
# Gate the second subsection based on the score achieved in the first subsection
self
.
course_outline
.
visit
()
...
...
@@ -103,6 +107,15 @@ class GatingTest(UniqueCourseTest):
self
.
course_outline
.
select_advanced_tab
(
desired_item
=
'gated_content'
)
self
.
course_outline
.
add_prerequisite_to_subsection
(
"80"
)
def
_fulfill_prerequisite
(
self
):
"""
Fulfill the prerequisite needed to see gated content
"""
problem_page
=
ProblemPage
(
self
.
browser
)
self
.
assertEqual
(
problem_page
.
wait_for_page
()
.
problem_name
,
'HEIGHT OF EIFFEL TOWER'
)
problem_page
.
click_choice
(
'choice_1'
)
problem_page
.
click_check
()
def
test_subsection_gating_in_studio
(
self
):
"""
Given that I am a staff member
...
...
@@ -132,7 +145,7 @@ class GatingTest(UniqueCourseTest):
self
.
assertTrue
(
self
.
course_outline
.
gating_prerequisites_dropdown_is_visible
())
self
.
assertTrue
(
self
.
course_outline
.
gating_prerequisite_min_score_is_visible
())
def
test_gated_subsection_in_lms
(
self
):
def
test_gated_subsection_in_lms
_for_student
(
self
):
"""
Given that I am a student
When I visit the LMS Courseware
...
...
@@ -143,15 +156,58 @@ class GatingTest(UniqueCourseTest):
self
.
_setup_prereq
()
self
.
_setup_gated_subsection
()
self
.
_auto_auth
(
self
.
USERNAME
,
self
.
EMAIL
,
False
)
self
.
_auto_auth
(
self
.
STUDENT_USERNAME
,
self
.
STUDENT_
EMAIL
,
False
)
self
.
courseware_page
.
visit
()
self
.
assertEqual
(
self
.
courseware_page
.
num_subsections
,
1
)
# Fulfill prerequisite and verify that gated subsection is shown
problem_page
=
ProblemPage
(
self
.
browser
)
self
.
assertEqual
(
problem_page
.
wait_for_page
()
.
problem_name
,
'HEIGHT OF EIFFEL TOWER'
)
problem_page
.
click_choice
(
'choice_1'
)
problem_page
.
click_check
()
self
.
_fulfill_prerequisite
()
self
.
courseware_page
.
visit
()
self
.
assertEqual
(
self
.
courseware_page
.
num_subsections
,
2
)
def
test_gated_subsection_in_lms_for_staff
(
self
):
"""
Given that I am a staff member
When I visit the LMS Courseware
Then I can see all gated subsections
Displayed along with notification banners
Then if I masquerade as a student
Then I cannot see a gated subsection
When I fufill the gating prerequisite
Then I can see the gated subsection (without a banner)
"""
self
.
_setup_prereq
()
self
.
_setup_gated_subsection
()
# Fulfill prerequisites for specific student
self
.
_auto_auth
(
self
.
STUDENT_USERNAME
,
self
.
STUDENT_EMAIL
,
False
)
self
.
courseware_page
.
visit
()
self
.
_fulfill_prerequisite
()
self
.
_auto_auth
(
self
.
STAFF_USERNAME
,
self
.
STAFF_EMAIL
,
True
)
self
.
courseware_page
.
visit
()
staff_page
=
StaffPage
(
self
.
browser
,
self
.
course_id
)
self
.
assertEqual
(
staff_page
.
staff_view_mode
,
'Staff'
)
self
.
assertEqual
(
self
.
courseware_page
.
num_subsections
,
2
)
# Click on gated section and check for banner
self
.
courseware_page
.
q
(
css
=
'.chapter-content-container a'
)
.
nth
(
1
)
.
click
()
self
.
courseware_page
.
wait_for_page
()
self
.
assertTrue
(
self
.
courseware_page
.
has_banner
())
self
.
courseware_page
.
q
(
css
=
'.chapter-content-container a'
)
.
nth
(
0
)
.
click
()
self
.
courseware_page
.
wait_for_page
()
staff_page
.
set_staff_view_mode
(
'Student'
)
self
.
assertEqual
(
self
.
courseware_page
.
num_subsections
,
1
)
self
.
assertFalse
(
self
.
courseware_page
.
has_banner
())
staff_page
.
set_staff_view_mode_specific_student
(
self
.
STUDENT_USERNAME
)
self
.
assertEqual
(
self
.
courseware_page
.
num_subsections
,
2
)
self
.
courseware_page
.
q
(
css
=
'.chapter-content-container a'
)
.
nth
(
1
)
.
click
()
self
.
courseware_page
.
wait_for_page
()
self
.
assertFalse
(
self
.
courseware_page
.
has_banner
())
lms/djangoapps/courseware/module_render.py
View file @
a39d6c2e
...
...
@@ -755,6 +755,7 @@ def get_module_system_for_user(user, student_data, # TODO # pylint: disable=to
'user'
:
DjangoXBlockUserService
(
user
,
user_is_staff
=
user_is_staff
),
"reverification"
:
ReverificationService
(),
'proctoring'
:
ProctoringService
(),
'milestones'
:
milestones_helpers
.
get_service
(),
'credit'
:
CreditService
(),
'bookmarks'
:
BookmarksService
(
user
=
user
),
},
...
...
openedx/core/lib/gating/api.py
View file @
a39d6c2e
...
...
@@ -4,6 +4,7 @@ API for the gating djangoapp
import
logging
from
django.utils.translation
import
ugettext
as
_
from
lms.djangoapps.courseware.access
import
_has_access_to_course
from
milestones
import
api
as
milestones_api
from
opaque_keys.edx.keys
import
UsageKey
from
xmodule.modulestore.django
import
modulestore
...
...
@@ -285,12 +286,15 @@ def get_gated_content(course, user):
Returns:
list: The list of gated content usage keys for the given course
"""
# Get the unfulfilled gating milestones for this course, for this user
return
[
m
[
'content_id'
]
for
m
in
find_gating_milestones
(
course
.
id
,
None
,
'requires'
,
{
'id'
:
user
.
id
}
)
]
if
_has_access_to_course
(
user
,
'staff'
,
course
.
id
):
return
[]
else
:
# Get the unfulfilled gating milestones for this course, for this user
return
[
m
[
'content_id'
]
for
m
in
find_gating_milestones
(
course
.
id
,
None
,
'requires'
,
{
'id'
:
user
.
id
}
)
]
openedx/core/lib/gating/tests/test_api.py
View file @
a39d6c2e
...
...
@@ -10,6 +10,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, TEST_DAT
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
openedx.core.lib.gating
import
api
as
gating_api
from
openedx.core.lib.gating.exceptions
import
GatingValidationError
from
student.tests.factories
import
UserFactory
@attr
(
'shard_2'
)
...
...
@@ -154,19 +155,23 @@ class TestGatingApi(ModuleStoreTestCase, MilestonesTestCaseMixin):
self
.
assertIsNone
(
min_score
)
def
test_get_gated_content
(
self
):
""" Test test_get_gated_content """
"""
Verify staff bypasses gated content and student gets list of unfulfilled prerequisites.
"""
mock_user
=
MagicMock
(
)
mock_user
.
id
.
return_value
=
1
staff
=
UserFactory
(
is_staff
=
True
)
student
=
UserFactory
(
is_staff
=
False
)
self
.
assertEqual
(
gating_api
.
get_gated_content
(
self
.
course
,
mock_user
),
[])
self
.
assertEqual
(
gating_api
.
get_gated_content
(
self
.
course
,
staff
),
[])
self
.
assertEqual
(
gating_api
.
get_gated_content
(
self
.
course
,
student
),
[])
gating_api
.
add_prerequisite
(
self
.
course
.
id
,
self
.
seq1
.
location
)
gating_api
.
set_required_content
(
self
.
course
.
id
,
self
.
seq2
.
location
,
self
.
seq1
.
location
,
100
)
milestone
=
milestones_api
.
get_course_content_milestones
(
self
.
course
.
id
,
self
.
seq2
.
location
,
'requires'
)[
0
]
self
.
assertEqual
(
gating_api
.
get_gated_content
(
self
.
course
,
mock_user
),
[
unicode
(
self
.
seq2
.
location
)])
self
.
assertEqual
(
gating_api
.
get_gated_content
(
self
.
course
,
staff
),
[])
self
.
assertEqual
(
gating_api
.
get_gated_content
(
self
.
course
,
student
),
[
unicode
(
self
.
seq2
.
location
)])
milestones_api
.
add_user_milestone
({
'id'
:
mock_user
.
id
},
milestone
)
milestones_api
.
add_user_milestone
({
'id'
:
student
.
id
},
milestone
)
# pylint: disable=no-member
self
.
assertEqual
(
gating_api
.
get_gated_content
(
self
.
course
,
mock_user
),
[])
self
.
assertEqual
(
gating_api
.
get_gated_content
(
self
.
course
,
student
),
[])
requirements/edx/github.txt
View file @
a39d6c2e
...
...
@@ -84,7 +84,7 @@ git+https://github.com/pmitros/RecommenderXBlock.git@v1.1#egg=recommender-xblock
git+https://github.com/solashirai/crowdsourcehinter.git@518605f0a95190949fe77bd39158450639e2e1dc#egg=crowdsourcehinter-xblock==0.1
-e git+https://github.com/pmitros/RateXBlock.git@367e19c0f6eac8a5f002fd0f1559555f8e74bfff#egg=rate-xblock
-e git+https://github.com/pmitros/DoneXBlock.git@857bf365f19c904d7e48364428f6b93ff153fabd#egg=done-xblock
git+https://github.com/edx/edx-milestones.git@v0.1.
8#egg=edx-milestones==0.1.8
git+https://github.com/edx/edx-milestones.git@v0.1.
9#egg=edx-milestones==0.1.9
git+https://github.com/edx/xblock-utils.git@v1.0.2#egg=xblock-utils==1.0.2
-e git+https://github.com/edx-solutions/xblock-google-drive.git@138e6fa0bf3a2013e904a085b9fed77dab7f3f21#egg=xblock-google-drive
-e git+https://github.com/edx/edx-reverification-block.git@0.0.5#egg=edx-reverification-block==0.0.5
...
...
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