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
5346ee25
Commit
5346ee25
authored
Jul 27, 2016
by
Kevin Kim
Committed by
GitHub
Jul 27, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #12974 from edx/kkim/gate_banner
Gated Subsection Staff Banner
parents
99eb795a
a39d6c2e
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 @
5346ee25
...
...
@@ -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 @
5346ee25
...
...
@@ -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 @
5346ee25
...
...
@@ -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 @
5346ee25
...
...
@@ -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 @
5346ee25
...
...
@@ -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 @
5346ee25
...
...
@@ -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 @
5346ee25
...
...
@@ -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 @
5346ee25
...
...
@@ -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 @
5346ee25
...
...
@@ -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