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
9d747fa6
Commit
9d747fa6
authored
Jul 20, 2016
by
Nimisha Asthagiri
Committed by
GitHub
Jul 20, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #12975 from edx/tnl/hide_content
LMS Hide Subsection Content
parents
3302b26e
37b8e636
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
562 additions
and
170 deletions
+562
-170
common/lib/xmodule/xmodule/seq_module.py
+139
-71
common/lib/xmodule/xmodule/tests/test_sequence.py
+64
-16
lms/djangoapps/course_api/blocks/api.py
+2
-1
lms/djangoapps/course_blocks/api.py
+0
-3
lms/djangoapps/course_blocks/transformers/hidden_content.py
+102
-0
lms/djangoapps/course_blocks/transformers/start_date.py
+12
-32
lms/djangoapps/course_blocks/transformers/tests/helpers.py
+20
-18
lms/djangoapps/course_blocks/transformers/tests/test_hidden_content.py
+83
-0
lms/djangoapps/course_blocks/transformers/utils.py
+100
-4
lms/djangoapps/course_blocks/transformers/visibility.py
+9
-22
lms/djangoapps/courseware/views/index.py
+2
-1
lms/templates/hidden_content.html
+26
-0
lms/templates/seq_module.html
+2
-2
setup.py
+1
-0
No files found.
common/lib/xmodule/xmodule/seq_module.py
View file @
9d747fa6
...
@@ -4,6 +4,8 @@ xModule implementation of a learning sequence
...
@@ -4,6 +4,8 @@ xModule implementation of a learning sequence
# pylint: disable=abstract-method
# pylint: disable=abstract-method
import
collections
import
collections
from
datetime
import
datetime
from
django.utils.timezone
import
UTC
import
json
import
json
import
logging
import
logging
from
pkg_resources
import
resource_string
from
pkg_resources
import
resource_string
...
@@ -38,13 +40,22 @@ class SequenceFields(object):
...
@@ -38,13 +40,22 @@ class SequenceFields(object):
# NOTE: Position is 1-indexed. This is silly, but there are now student
# NOTE: Position is 1-indexed. This is silly, but there are now student
# positions saved on prod, so it's not easy to fix.
# positions saved on prod, so it's not easy to fix.
position
=
Integer
(
help
=
"Last tab viewed in this sequence"
,
scope
=
Scope
.
user_state
)
position
=
Integer
(
help
=
"Last tab viewed in this sequence"
,
scope
=
Scope
.
user_state
)
due
=
Date
(
due
=
Date
(
display_name
=
_
(
"Due Date"
),
display_name
=
_
(
"Due Date"
),
help
=
_
(
"Enter the date by which problems are due."
),
help
=
_
(
"Enter the date by which problems are due."
),
scope
=
Scope
.
settings
,
scope
=
Scope
.
settings
,
)
)
# Entrance Exam flag -- see cms/contentstore/views/entrance_exam.py for usage
hide_after_due
=
Boolean
(
display_name
=
_
(
"Hide sequence content After Due Date"
),
help
=
_
(
"If set, the sequence content is hidden for non-staff users after the due date has passed."
),
default
=
False
,
scope
=
Scope
.
settings
,
)
is_entrance_exam
=
Boolean
(
is_entrance_exam
=
Boolean
(
display_name
=
_
(
"Is Entrance Exam"
),
display_name
=
_
(
"Is Entrance Exam"
),
help
=
_
(
help
=
_
(
...
@@ -97,16 +108,6 @@ class ProctoringFields(object):
...
@@ -97,16 +108,6 @@ class ProctoringFields(object):
scope
=
Scope
.
settings
,
scope
=
Scope
.
settings
,
)
)
hide_after_due
=
Boolean
(
display_name
=
_
(
"Hide Exam Results After Due Date"
),
help
=
_
(
"This setting overrides the default behavior of showing exam results after the due date has passed."
" Currently only supported for timed exams."
),
default
=
False
,
scope
=
Scope
.
settings
,
)
is_practice_exam
=
Boolean
(
is_practice_exam
=
Boolean
(
display_name
=
_
(
"Is Practice Exam"
),
display_name
=
_
(
"Is Practice Exam"
),
help
=
_
(
help
=
_
(
...
@@ -177,90 +178,157 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
...
@@ -177,90 +178,157 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
raise
NotFoundError
(
'Unexpected dispatch type'
)
raise
NotFoundError
(
'Unexpected dispatch type'
)
@classmethod
def
verify_current_content_visibility
(
cls
,
due
,
hide_after_due
):
"""
Returns whether the content visibility policy passes
for the given due date and hide_after_due values and
the current date-time.
"""
return
(
not
due
or
not
hide_after_due
or
datetime
.
now
(
UTC
())
<
due
)
def
student_view
(
self
,
context
):
def
student_view
(
self
,
context
):
context
=
context
or
{}
self
.
_capture_basic_metrics
()
banner_text
=
None
special_html_view
=
self
.
_hidden_content_student_view
(
context
)
or
self
.
_special_exam_student_view
()
if
special_html_view
:
masquerading_as_specific_student
=
context
.
get
(
'specific_masquerade'
,
False
)
banner_text
,
special_html
=
special_html_view
if
special_html
and
not
masquerading_as_specific_student
:
return
Fragment
(
special_html
)
return
self
.
_student_view
(
context
,
banner_text
)
def
_special_exam_student_view
(
self
):
"""
Checks whether this sequential is a special exam. If so, returns
a banner_text or the fragment to display depending on whether
staff is masquerading.
"""
if
self
.
is_time_limited
:
special_exam_html
=
self
.
_time_limited_student_view
()
if
special_exam_html
:
banner_text
=
_
(
"This exam is hidden from the learner."
)
return
banner_text
,
special_exam_html
def
_hidden_content_student_view
(
self
,
context
):
"""
Checks whether the content of this sequential is hidden from the
runtime user. If so, returns a banner_text or the fragment to
display depending on whether staff is masquerading.
"""
if
not
self
.
_can_user_view_content
():
subsection_format
=
(
self
.
format
or
_
(
"subsection"
))
.
lower
()
# pylint: disable=no-member
# Translators: subsection_format refers to the assignment
# type of the subsection, such as Homework, Lab, Exam, etc.
banner_text
=
_
(
"Because the due date has passed, "
"this {subsection_format} is hidden from the learner."
)
.
format
(
subsection_format
=
subsection_format
)
hidden_content_html
=
self
.
system
.
render_template
(
'hidden_content.html'
,
{
'subsection_format'
:
subsection_format
,
'progress_url'
:
context
.
get
(
'progress_url'
),
}
)
return
banner_text
,
hidden_content_html
def
_can_user_view_content
(
self
):
"""
Returns whether the runtime user can view the content
of this sequential.
"""
return
(
self
.
runtime
.
user_is_staff
or
self
.
verify_current_content_visibility
(
self
.
due
,
self
.
hide_after_due
)
)
def
_student_view
(
self
,
context
,
banner_text
=
None
):
"""
Returns the rendered student view of the content of this
sequential. If banner_text is given, it is added to the
content.
"""
display_items
=
self
.
get_display_items
()
display_items
=
self
.
get_display_items
()
self
.
_update_position
(
context
,
len
(
display_items
))
fragment
=
Fragment
()
params
=
{
'items'
:
self
.
_render_student_view_for_items
(
context
,
display_items
,
fragment
),
'element_id'
:
self
.
location
.
html_id
(),
'item_id'
:
self
.
location
.
to_deprecated_string
(),
'position'
:
self
.
position
,
'tag'
:
self
.
location
.
category
,
'ajax_url'
:
self
.
system
.
ajax_url
,
'next_url'
:
context
.
get
(
'next_url'
),
'prev_url'
:
context
.
get
(
'prev_url'
),
'banner_text'
:
banner_text
,
}
fragment
.
add_content
(
self
.
system
.
render_template
(
"seq_module.html"
,
params
))
self
.
_capture_full_seq_item_metrics
(
display_items
)
self
.
_capture_current_unit_metrics
(
display_items
)
return
fragment
def
_update_position
(
self
,
context
,
number_of_display_items
):
"""
Update the user's sequential position given the context and the
number_of_display_items
"""
# If we're rendering this sequence, but no position is set yet,
# If we're rendering this sequence, but no position is set yet,
# or exceeds the length of the displayable items,
# or exceeds the length of the displayable items,
# default the position to the first element
# default the position to the first element
if
context
.
get
(
'requested_child'
)
==
'first'
:
if
context
.
get
(
'requested_child'
)
==
'first'
:
self
.
position
=
1
self
.
position
=
1
elif
context
.
get
(
'requested_child'
)
==
'last'
:
elif
context
.
get
(
'requested_child'
)
==
'last'
:
self
.
position
=
len
(
display_items
)
or
1
self
.
position
=
number_of_display_items
or
1
elif
self
.
position
is
None
or
self
.
position
>
len
(
display_items
)
:
elif
self
.
position
is
None
or
self
.
position
>
number_of_display_items
:
self
.
position
=
1
self
.
position
=
1
## Returns a set of all types of all sub-children
def
_render_student_view_for_items
(
self
,
context
,
display_items
,
fragment
):
contents
=
[]
"""
Updates the given fragment with rendered student views of the given
fragment
=
Fragment
()
display_items. Returns a list of dict objects with information about
context
=
context
or
{}
the given display_items.
"""
bookmarks_service
=
self
.
runtime
.
service
(
self
,
"bookmarks"
)
bookmarks_service
=
self
.
runtime
.
service
(
self
,
"bookmarks"
)
context
[
"username"
]
=
self
.
runtime
.
service
(
self
,
"user"
)
.
get_current_user
()
.
opt_attrs
[
'edx-platform.username'
]
context
[
"username"
]
=
self
.
runtime
.
service
(
self
,
"user"
)
.
get_current_user
()
.
opt_attrs
[
'edx-platform.username'
]
parent_module
=
self
.
get_parent
()
display_names
=
[
display_names
=
[
parent_module
.
display_name_with_default
,
self
.
get_parent
()
.
display_name_with_default
,
self
.
display_name_with_default
self
.
display_name_with_default
]
]
contents
=
[]
# We do this up here because proctored exam functionality could bypass
for
item
in
display_items
:
# rendering after this section.
is_bookmarked
=
bookmarks_service
.
is_bookmarked
(
usage_key
=
item
.
scope_ids
.
usage_id
)
self
.
_capture_basic_metrics
()
# Is this sequential part of a timed or proctored exam?
masquerading
=
context
.
get
(
'specific_masquerade'
,
False
)
special_exam_html
=
None
if
self
.
is_time_limited
:
special_exam_html
=
self
.
_time_limited_student_view
(
context
)
# Do we have an applicable alternate rendering
# from the edx_proctoring subsystem?
if
special_exam_html
and
not
masquerading
:
fragment
.
add_content
(
special_exam_html
)
return
fragment
for
child
in
display_items
:
is_bookmarked
=
bookmarks_service
.
is_bookmarked
(
usage_key
=
child
.
scope_ids
.
usage_id
)
context
[
"bookmarked"
]
=
is_bookmarked
context
[
"bookmarked"
]
=
is_bookmarked
progress
=
child
.
get_progress
()
progress
=
item
.
get_progress
()
rendered_
child
=
child
.
render
(
STUDENT_VIEW
,
context
)
rendered_
item
=
item
.
render
(
STUDENT_VIEW
,
context
)
fragment
.
add_frag_resources
(
rendered_
child
)
fragment
.
add_frag_resources
(
rendered_
item
)
child
info
=
{
item
info
=
{
'content'
:
rendered_
child
.
content
,
'content'
:
rendered_
item
.
content
,
'page_title'
:
getattr
(
child
,
'tooltip_title'
,
''
),
'page_title'
:
getattr
(
item
,
'tooltip_title'
,
''
),
'progress_status'
:
Progress
.
to_js_status_str
(
progress
),
'progress_status'
:
Progress
.
to_js_status_str
(
progress
),
'progress_detail'
:
Progress
.
to_js_detail_str
(
progress
),
'progress_detail'
:
Progress
.
to_js_detail_str
(
progress
),
'type'
:
child
.
get_icon_class
(),
'type'
:
item
.
get_icon_class
(),
'id'
:
child
.
scope_ids
.
usage_id
.
to_deprecated_string
(),
'id'
:
item
.
scope_ids
.
usage_id
.
to_deprecated_string
(),
'bookmarked'
:
is_bookmarked
,
'bookmarked'
:
is_bookmarked
,
'path'
:
" > "
.
join
(
display_names
+
[
child
.
display_name_with_default
]),
'path'
:
" > "
.
join
(
display_names
+
[
item
.
display_name_with_default
]),
}
}
contents
.
append
(
child
info
)
contents
.
append
(
item
info
)
params
=
{
return
contents
'items'
:
contents
,
'element_id'
:
self
.
location
.
html_id
(),
'item_id'
:
self
.
location
.
to_deprecated_string
(),
'position'
:
self
.
position
,
'tag'
:
self
.
location
.
category
,
'ajax_url'
:
self
.
system
.
ajax_url
,
'next_url'
:
context
.
get
(
'next_url'
),
'prev_url'
:
context
.
get
(
'prev_url'
),
'override_hidden_exam'
:
masquerading
and
special_exam_html
is
not
None
,
}
fragment
.
add_content
(
self
.
system
.
render_template
(
"seq_module.html"
,
params
))
self
.
_capture_full_seq_item_metrics
(
display_items
)
self
.
_capture_current_unit_metrics
(
display_items
)
# Get all descendant XBlock types and counts
return
fragment
def
_locations_in_subtree
(
self
,
node
):
def
_locations_in_subtree
(
self
,
node
):
"""
"""
...
@@ -328,7 +396,7 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
...
@@ -328,7 +396,7 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
for
block_type
,
count
in
curr_block_counts
.
items
():
for
block_type
,
count
in
curr_block_counts
.
items
():
newrelic
.
agent
.
add_custom_parameter
(
'seq.current.block_counts.{}'
.
format
(
block_type
),
count
)
newrelic
.
agent
.
add_custom_parameter
(
'seq.current.block_counts.{}'
.
format
(
block_type
),
count
)
def
_time_limited_student_view
(
self
,
context
):
def
_time_limited_student_view
(
self
):
"""
"""
Delegated rendering of a student view when in a time
Delegated rendering of a student view when in a time
limited view. This ultimately calls down into edx_proctoring
limited view. This ultimately calls down into edx_proctoring
...
...
common/lib/xmodule/xmodule/tests/test_sequence.py
View file @
9d747fa6
...
@@ -2,6 +2,10 @@
...
@@ -2,6 +2,10 @@
Tests for sequence module.
Tests for sequence module.
"""
"""
# pylint: disable=no-member
# pylint: disable=no-member
from
datetime
import
timedelta
import
ddt
from
django.utils.timezone
import
now
from
freezegun
import
freeze_time
from
mock
import
Mock
from
mock
import
Mock
from
xblock.reference.user_service
import
XBlockUser
,
UserService
from
xblock.reference.user_service
import
XBlockUser
,
UserService
from
xmodule.tests
import
get_test_system
from
xmodule.tests
import
get_test_system
...
@@ -24,10 +28,15 @@ class StubUserService(UserService):
...
@@ -24,10 +28,15 @@ class StubUserService(UserService):
return
user
return
user
@ddt.ddt
class
SequenceBlockTestCase
(
XModuleXmlImportTest
):
class
SequenceBlockTestCase
(
XModuleXmlImportTest
):
"""
"""
Tests for the Sequence Module.
Tests for the Sequence Module.
"""
"""
TODAY
=
now
()
TOMORROW
=
TODAY
+
timedelta
(
days
=
1
)
DAY_AFTER_TOMORROW
=
TOMORROW
+
timedelta
(
days
=
1
)
@classmethod
@classmethod
def
setUpClass
(
cls
):
def
setUpClass
(
cls
):
super
(
SequenceBlockTestCase
,
cls
)
.
setUpClass
()
super
(
SequenceBlockTestCase
,
cls
)
.
setUpClass
()
...
@@ -54,13 +63,16 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
...
@@ -54,13 +63,16 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
chapter_1
=
xml
.
ChapterFactory
.
build
(
parent
=
course
)
# has 2 child sequences
chapter_1
=
xml
.
ChapterFactory
.
build
(
parent
=
course
)
# has 2 child sequences
xml
.
ChapterFactory
.
build
(
parent
=
course
)
# has 0 child sequences
xml
.
ChapterFactory
.
build
(
parent
=
course
)
# has 0 child sequences
chapter_3
=
xml
.
ChapterFactory
.
build
(
parent
=
course
)
# has 1 child sequence
chapter_3
=
xml
.
ChapterFactory
.
build
(
parent
=
course
)
# has 1 child sequence
chapter_4
=
xml
.
ChapterFactory
.
build
(
parent
=
course
)
# has
2 child sequences
chapter_4
=
xml
.
ChapterFactory
.
build
(
parent
=
course
)
# has
1 child sequence, with hide_after_due
xml
.
SequenceFactory
.
build
(
parent
=
chapter_1
)
xml
.
SequenceFactory
.
build
(
parent
=
chapter_1
)
xml
.
SequenceFactory
.
build
(
parent
=
chapter_1
)
xml
.
SequenceFactory
.
build
(
parent
=
chapter_1
)
sequence_3_1
=
xml
.
SequenceFactory
.
build
(
parent
=
chapter_3
)
# has 3 verticals
sequence_3_1
=
xml
.
SequenceFactory
.
build
(
parent
=
chapter_3
)
# has 3 verticals
xml
.
SequenceFactory
.
build
(
parent
=
chapter_4
)
xml
.
SequenceFactory
.
build
(
# sequence_4_1
xml
.
SequenceFactory
.
build
(
parent
=
chapter_4
)
parent
=
chapter_4
,
hide_after_due
=
str
(
True
),
due
=
str
(
cls
.
TOMORROW
),
)
for
_
in
range
(
3
):
for
_
in
range
(
3
):
xml
.
VerticalFactory
.
build
(
parent
=
sequence_3_1
)
xml
.
VerticalFactory
.
build
(
parent
=
sequence_3_1
)
...
@@ -98,9 +110,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
...
@@ -98,9 +110,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
def
test_render_student_view
(
self
):
def
test_render_student_view
(
self
):
html
=
self
.
_get_rendered_student_view
(
html
=
self
.
_get_rendered_student_view
(
self
.
sequence_3_1
,
self
.
sequence_3_1
,
requested_child
=
None
,
extra_context
=
dict
(
next_url
=
'NextSequential'
,
prev_url
=
'PrevSequential'
),
next_url
=
'NextSequential'
,
prev_url
=
'PrevSequential'
)
)
self
.
_assert_view_at_position
(
html
,
expected_position
=
1
)
self
.
_assert_view_at_position
(
html
,
expected_position
=
1
)
self
.
assertIn
(
unicode
(
self
.
sequence_3_1
.
location
),
html
)
self
.
assertIn
(
unicode
(
self
.
sequence_3_1
.
location
),
html
)
...
@@ -115,20 +125,15 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
...
@@ -115,20 +125,15 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
html
=
self
.
_get_rendered_student_view
(
self
.
sequence_3_1
,
requested_child
=
'last'
)
html
=
self
.
_get_rendered_student_view
(
self
.
sequence_3_1
,
requested_child
=
'last'
)
self
.
_assert_view_at_position
(
html
,
expected_position
=
3
)
self
.
_assert_view_at_position
(
html
,
expected_position
=
3
)
def
_get_rendered_student_view
(
self
,
sequence
,
requested_child
,
next_url
=
None
,
prev_url
=
None
):
def
_get_rendered_student_view
(
self
,
sequence
,
requested_child
=
None
,
extra_context
=
None
):
"""
"""
Returns the rendered student view for the given sequence and the
Returns the rendered student view for the given sequence and the
requested_child parameter.
requested_child parameter.
"""
"""
return
sequence
.
xmodule_runtime
.
render
(
context
=
{
'requested_child'
:
requested_child
}
sequence
,
if
extra_context
:
STUDENT_VIEW
,
context
.
update
(
extra_context
)
{
return
sequence
.
xmodule_runtime
.
render
(
sequence
,
STUDENT_VIEW
,
context
)
.
content
'requested_child'
:
requested_child
,
'next_url'
:
next_url
,
'prev_url'
:
prev_url
,
},
)
.
content
def
_assert_view_at_position
(
self
,
rendered_html
,
expected_position
):
def
_assert_view_at_position
(
self
,
rendered_html
,
expected_position
):
"""
"""
...
@@ -140,3 +145,46 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
...
@@ -140,3 +145,46 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
html
=
self
.
_get_rendered_student_view
(
self
.
sequence_3_1
,
requested_child
=
None
)
html
=
self
.
_get_rendered_student_view
(
self
.
sequence_3_1
,
requested_child
=
None
)
for
child
in
self
.
sequence_3_1
.
children
:
for
child
in
self
.
sequence_3_1
.
children
:
self
.
assertIn
(
"'page_title': '{}'"
.
format
(
child
.
name
),
html
)
self
.
assertIn
(
"'page_title': '{}'"
.
format
(
child
.
name
),
html
)
def
test_hidden_content_before_due
(
self
):
html
=
self
.
_get_rendered_student_view
(
self
.
sequence_4_1
)
self
.
assertIn
(
"seq_module.html"
,
html
)
self
.
assertIn
(
"'banner_text': None"
,
html
)
@freeze_time
(
DAY_AFTER_TOMORROW
)
@ddt.data
(
(
None
,
'subsection'
),
(
'Homework'
,
'homework'
),
)
@ddt.unpack
def
test_hidden_content_past_due
(
self
,
format_type
,
expected_text
):
progress_url
=
'http://test_progress_link'
self
.
_set_sequence_format
(
self
.
sequence_4_1
,
format_type
)
html
=
self
.
_get_rendered_student_view
(
self
.
sequence_4_1
,
extra_context
=
dict
(
progress_url
=
progress_url
),
)
self
.
assertIn
(
"hidden_content.html"
,
html
)
self
.
assertIn
(
progress_url
,
html
)
self
.
assertIn
(
"'subsection_format': '{}'"
.
format
(
expected_text
),
html
)
@freeze_time
(
DAY_AFTER_TOMORROW
)
def
test_masquerade_hidden_content_past_due
(
self
):
self
.
_set_sequence_format
(
self
.
sequence_4_1
,
"Homework"
)
html
=
self
.
_get_rendered_student_view
(
self
.
sequence_4_1
,
extra_context
=
dict
(
specific_masquerade
=
True
),
)
self
.
assertIn
(
"seq_module.html"
,
html
)
self
.
assertIn
(
"'banner_text': 'Because the due date has passed, "
"this homework is hidden from the learner.'"
,
html
)
def
_set_sequence_format
(
self
,
sequence
,
format_type
):
"""
Sets the format field on the given sequence to the
given value.
"""
sequence
.
_xmodule
.
format
=
format_type
# pylint: disable=protected-access
lms/djangoapps/course_api/blocks/api.py
View file @
9d747fa6
...
@@ -3,6 +3,7 @@ API function for retrieving course blocks data
...
@@ -3,6 +3,7 @@ API function for retrieving course blocks data
"""
"""
from
lms.djangoapps.course_blocks.api
import
get_course_blocks
,
COURSE_BLOCK_ACCESS_TRANSFORMERS
from
lms.djangoapps.course_blocks.api
import
get_course_blocks
,
COURSE_BLOCK_ACCESS_TRANSFORMERS
from
lms.djangoapps.course_blocks.transformers.hidden_content
import
HiddenContentTransformer
from
openedx.core.lib.block_structure.transformers
import
BlockStructureTransformers
from
openedx.core.lib.block_structure.transformers
import
BlockStructureTransformers
from
.transformers.blocks_api
import
BlocksAPITransformer
from
.transformers.blocks_api
import
BlocksAPITransformer
...
@@ -51,7 +52,7 @@ def get_blocks(
...
@@ -51,7 +52,7 @@ def get_blocks(
# create ordered list of transformers, adding BlocksAPITransformer at end.
# create ordered list of transformers, adding BlocksAPITransformer at end.
transformers
=
BlockStructureTransformers
()
transformers
=
BlockStructureTransformers
()
if
user
is
not
None
:
if
user
is
not
None
:
transformers
+=
COURSE_BLOCK_ACCESS_TRANSFORMERS
+
[
ProctoredExamTransformer
()]
transformers
+=
COURSE_BLOCK_ACCESS_TRANSFORMERS
+
[
ProctoredExamTransformer
()
,
HiddenContentTransformer
()
]
transformers
+=
[
transformers
+=
[
BlocksAPITransformer
(
BlocksAPITransformer
(
block_counts
,
block_counts
,
...
...
lms/djangoapps/course_blocks/api.py
View file @
9d747fa6
...
@@ -2,11 +2,8 @@
...
@@ -2,11 +2,8 @@
API entry point to the course_blocks app with top-level
API entry point to the course_blocks app with top-level
get_course_blocks function.
get_course_blocks function.
"""
"""
from
django.core.cache
import
cache
from
openedx.core.djangoapps.content.block_structure.api
import
get_block_structure_manager
from
openedx.core.djangoapps.content.block_structure.api
import
get_block_structure_manager
from
openedx.core.lib.block_structure.manager
import
BlockStructureManager
from
openedx.core.lib.block_structure.transformers
import
BlockStructureTransformers
from
openedx.core.lib.block_structure.transformers
import
BlockStructureTransformers
from
xmodule.modulestore.django
import
modulestore
from
.transformers
import
(
from
.transformers
import
(
library_content
,
library_content
,
...
...
lms/djangoapps/course_blocks/transformers/hidden_content.py
0 → 100644
View file @
9d747fa6
"""
Visibility Transformer implementation.
"""
from
datetime
import
datetime
from
pytz
import
utc
from
openedx.core.lib.block_structure.transformer
import
BlockStructureTransformer
,
FilteringTransformerMixin
from
xmodule.seq_module
import
SequenceModule
from
.utils
import
collect_merged_boolean_field
,
collect_merged_date_field
MAXIMUM_DATE
=
utc
.
localize
(
datetime
.
max
)
class
HiddenContentTransformer
(
FilteringTransformerMixin
,
BlockStructureTransformer
):
"""
A transformer that enforces the hide_after_due field on
blocks by removing children blocks from the block structure for
which the user does not have access. The due and hide_after_due
fields on a block is percolated down to its descendants, so that
all blocks enforce the hidden content settings from their ancestors.
For a block with multiple parents, access is denied only if
access is denied from all its parents.
Staff users are exempted from hidden content rules.
"""
VERSION
=
1
MERGED_DUE_DATE
=
'merged_due_date'
MERGED_HIDE_AFTER_DUE
=
'merged_hide_after_due'
@classmethod
def
name
(
cls
):
"""
Unique identifier for the transformer's class;
same identifier used in setup.py.
"""
return
"hidden_content"
@classmethod
def
_get_merged_hide_after_due
(
cls
,
block_structure
,
block_key
):
"""
Returns whether the block with the given block_key in the
given block_structure should be visible to staff only per
computed value from ancestry chain.
"""
return
block_structure
.
get_transformer_block_field
(
block_key
,
cls
,
cls
.
MERGED_HIDE_AFTER_DUE
,
False
)
@classmethod
def
_get_merged_due_date
(
cls
,
block_structure
,
block_key
):
"""
Returns the merged value for the start date for the block with
the given block_key in the given block_structure.
"""
return
block_structure
.
get_transformer_block_field
(
block_key
,
cls
,
cls
.
MERGED_DUE_DATE
,
False
)
@classmethod
def
collect
(
cls
,
block_structure
):
"""
Collects any information that's necessary to execute this
transformer's transform method.
"""
collect_merged_boolean_field
(
block_structure
,
transformer
=
cls
,
xblock_field_name
=
'hide_after_due'
,
merged_field_name
=
cls
.
MERGED_HIDE_AFTER_DUE
,
)
collect_merged_date_field
(
block_structure
,
transformer
=
cls
,
xblock_field_name
=
'due'
,
merged_field_name
=
cls
.
MERGED_DUE_DATE
,
default_date
=
MAXIMUM_DATE
,
func_merge_parents
=
max
,
func_merge_ancestors
=
min
,
)
def
transform_block_filters
(
self
,
usage_info
,
block_structure
):
# Users with staff access bypass the Visibility check.
if
usage_info
.
has_staff_access
:
return
[
block_structure
.
create_universal_filter
()]
return
[
block_structure
.
create_removal_filter
(
lambda
block_key
:
self
.
_is_block_hidden
(
block_structure
,
block_key
),
),
]
def
_is_block_hidden
(
self
,
block_structure
,
block_key
):
"""
Returns whether the block with the given block_key should
be hidden, given the current time.
"""
due
=
self
.
_get_merged_due_date
(
block_structure
,
block_key
)
hide_after_due
=
self
.
_get_merged_hide_after_due
(
block_structure
,
block_key
)
return
not
SequenceModule
.
verify_current_content_visibility
(
due
,
hide_after_due
)
lms/djangoapps/course_blocks/transformers/start_date.py
View file @
9d747fa6
...
@@ -5,7 +5,7 @@ from openedx.core.lib.block_structure.transformer import BlockStructureTransform
...
@@ -5,7 +5,7 @@ from openedx.core.lib.block_structure.transformer import BlockStructureTransform
from
lms.djangoapps.courseware.access_utils
import
check_start_date
from
lms.djangoapps.courseware.access_utils
import
check_start_date
from
xmodule.course_metadata_utils
import
DEFAULT_START_DATE
from
xmodule.course_metadata_utils
import
DEFAULT_START_DATE
from
.utils
import
get_field_on_block
from
.utils
import
collect_merged_date_field
class
StartDateTransformer
(
FilteringTransformerMixin
,
BlockStructureTransformer
):
class
StartDateTransformer
(
FilteringTransformerMixin
,
BlockStructureTransformer
):
...
@@ -36,7 +36,7 @@ class StartDateTransformer(FilteringTransformerMixin, BlockStructureTransformer)
...
@@ -36,7 +36,7 @@ class StartDateTransformer(FilteringTransformerMixin, BlockStructureTransformer)
return
"start_date"
return
"start_date"
@classmethod
@classmethod
def
get_merged_start_date
(
cls
,
block_structure
,
block_key
):
def
_
get_merged_start_date
(
cls
,
block_structure
,
block_key
):
"""
"""
Returns the merged value for the start date for the block with
Returns the merged value for the start date for the block with
the given block_key in the given block_structure.
the given block_key in the given block_structure.
...
@@ -53,35 +53,15 @@ class StartDateTransformer(FilteringTransformerMixin, BlockStructureTransformer)
...
@@ -53,35 +53,15 @@ class StartDateTransformer(FilteringTransformerMixin, BlockStructureTransformer)
"""
"""
block_structure
.
request_xblock_fields
(
'days_early_for_beta'
)
block_structure
.
request_xblock_fields
(
'days_early_for_beta'
)
for
block_key
in
block_structure
.
topological_traversal
():
collect_merged_date_field
(
block_structure
,
# compute merged value of start date from all parents
transformer
=
cls
,
parents
=
block_structure
.
get_parents
(
block_key
)
xblock_field_name
=
'start'
,
min_all_parents_start_date
=
min
(
merged_field_name
=
cls
.
MERGED_START_DATE
,
cls
.
get_merged_start_date
(
block_structure
,
parent_key
)
default_date
=
DEFAULT_START_DATE
,
for
parent_key
in
parents
func_merge_parents
=
min
,
)
if
parents
else
None
func_merge_ancestors
=
max
,
)
# set the merged value for this block
block_start
=
get_field_on_block
(
block_structure
.
get_xblock
(
block_key
),
'start'
)
if
min_all_parents_start_date
is
None
:
# no parents so just use value on block or default
merged_start_value
=
block_start
or
DEFAULT_START_DATE
elif
not
block_start
:
# no value on this block so take value from parents
merged_start_value
=
min_all_parents_start_date
else
:
# max of merged-start-from-all-parents and this block
merged_start_value
=
max
(
min_all_parents_start_date
,
block_start
)
block_structure
.
set_transformer_block_field
(
block_key
,
cls
,
cls
.
MERGED_START_DATE
,
merged_start_value
)
def
transform_block_filters
(
self
,
usage_info
,
block_structure
):
def
transform_block_filters
(
self
,
usage_info
,
block_structure
):
# Users with staff access bypass the Start Date check.
# Users with staff access bypass the Start Date check.
...
@@ -91,7 +71,7 @@ class StartDateTransformer(FilteringTransformerMixin, BlockStructureTransformer)
...
@@ -91,7 +71,7 @@ class StartDateTransformer(FilteringTransformerMixin, BlockStructureTransformer)
removal_condition
=
lambda
block_key
:
not
check_start_date
(
removal_condition
=
lambda
block_key
:
not
check_start_date
(
usage_info
.
user
,
usage_info
.
user
,
block_structure
.
get_xblock_field
(
block_key
,
'days_early_for_beta'
),
block_structure
.
get_xblock_field
(
block_key
,
'days_early_for_beta'
),
self
.
get_merged_start_date
(
block_structure
,
block_key
),
self
.
_
get_merged_start_date
(
block_structure
,
block_key
),
usage_info
.
course_key
,
usage_info
.
course_key
,
)
)
return
[
block_structure
.
create_removal_filter
(
removal_condition
)]
return
[
block_structure
.
create_removal_filter
(
removal_condition
)]
lms/djangoapps/course_blocks/transformers/tests/helpers.py
View file @
9d747fa6
...
@@ -255,7 +255,7 @@ class BlockParentsMapTestCase(TransformerRegistryTestMixin, ModuleStoreTestCase)
...
@@ -255,7 +255,7 @@ class BlockParentsMapTestCase(TransformerRegistryTestMixin, ModuleStoreTestCase)
self
,
self
,
test_user
,
test_user
,
expected_user_accessible_blocks
,
expected_user_accessible_blocks
,
blocks_with_differing_access
,
blocks_with_differing_access
=
None
,
transformers
=
None
,
transformers
=
None
,
):
):
"""
"""
...
@@ -272,7 +272,8 @@ class BlockParentsMapTestCase(TransformerRegistryTestMixin, ModuleStoreTestCase)
...
@@ -272,7 +272,8 @@ class BlockParentsMapTestCase(TransformerRegistryTestMixin, ModuleStoreTestCase)
blocks_with_differing_access (set(int)): Set of
blocks_with_differing_access (set(int)): Set of
blocks (indices) whose access will differ from the
blocks (indices) whose access will differ from the
transformers result and the current implementation of
transformers result and the current implementation of
has_access.
has_access. If not provided, does not compare with
has_access results.
transformers (BlockStructureTransformers): An optional collection
transformers (BlockStructureTransformers): An optional collection
of transformers that are to be executed. If not
of transformers that are to be executed. If not
...
@@ -312,7 +313,6 @@ class BlockParentsMapTestCase(TransformerRegistryTestMixin, ModuleStoreTestCase)
...
@@ -312,7 +313,6 @@ class BlockParentsMapTestCase(TransformerRegistryTestMixin, ModuleStoreTestCase)
# compute access results of the block
# compute access results of the block
block_structure_result
=
xblock_key
in
block_structure
block_structure_result
=
xblock_key
in
block_structure
has_access_result
=
bool
(
has_access
(
user
,
'load'
,
self
.
get_block
(
i
),
course_key
=
self
.
course
.
id
))
# compare with expected value
# compare with expected value
self
.
assertEquals
(
self
.
assertEquals
(
...
@@ -323,23 +323,25 @@ class BlockParentsMapTestCase(TransformerRegistryTestMixin, ModuleStoreTestCase)
...
@@ -323,23 +323,25 @@ class BlockParentsMapTestCase(TransformerRegistryTestMixin, ModuleStoreTestCase)
)
)
)
)
# compare with has_access_result
if
blocks_with_differing_access
:
if
i
in
blocks_with_differing_access
:
# compare with has_access_result
self
.
assertNotEqual
(
has_access_result
=
bool
(
has_access
(
user
,
'load'
,
self
.
get_block
(
i
),
course_key
=
self
.
course
.
id
))
block_structure_result
,
if
i
in
blocks_with_differing_access
:
has_access_result
,
self
.
assertNotEqual
(
"block structure ({0}) & has_access ({1}) results are equal for block {2} for user {3}"
.
format
(
block_structure_result
,
block_structure_result
,
has_access_result
,
i
,
user
.
username
has_access_result
,
"block structure ({0}) & has_access ({1}) results are equal for block {2} for user {3}"
.
format
(
block_structure_result
,
has_access_result
,
i
,
user
.
username
)
)
)
)
else
:
else
:
self
.
assertEquals
(
self
.
assertEquals
(
block_structure_result
,
block_structure
_result
,
has_access
_result
,
has_access_result
,
"block structure ({0}) & has_access ({1}) results not equal for block {2} for user {3}"
.
format
(
"block structure ({0}) & has_access ({1}) results not equal for block {2} for user {3}"
.
format
(
block_structure_result
,
has_access_result
,
i
,
user
.
username
block_structure_result
,
has_access_result
,
i
,
user
.
username
)
)
)
)
self
.
client
.
logout
()
self
.
client
.
logout
()
...
...
lms/djangoapps/course_blocks/transformers/tests/test_hidden_content.py
0 → 100644
View file @
9d747fa6
"""
Tests for HiddenContentTransformer.
"""
from
datetime
import
timedelta
import
ddt
from
django.utils.timezone
import
now
from
nose.plugins.attrib
import
attr
from
..hidden_content
import
HiddenContentTransformer
from
.helpers
import
BlockParentsMapTestCase
,
update_block
@attr
(
'shard_3'
)
@ddt.ddt
class
HiddenContentTransformerTestCase
(
BlockParentsMapTestCase
):
"""
VisibilityTransformer Test
"""
TRANSFORMER_CLASS_TO_TEST
=
HiddenContentTransformer
ALL_BLOCKS
=
{
0
,
1
,
2
,
3
,
4
,
5
,
6
}
class
DueDateType
(
object
):
"""
Use constant enum types for deterministic ddt test method names (rather than dynamically generated timestamps)
"""
none
=
1
,
future
=
2
,
past
=
3
TODAY
=
now
()
PAST_DATE
=
TODAY
-
timedelta
(
days
=
30
)
FUTURE_DATE
=
TODAY
+
timedelta
(
days
=
30
)
@classmethod
def
due
(
cls
,
enum_value
):
"""
Returns a start date for the given enum value
"""
if
enum_value
==
cls
.
future
:
return
cls
.
FUTURE_DATE
elif
enum_value
==
cls
.
past
:
return
cls
.
PAST_DATE
else
:
return
None
# Following test cases are based on BlockParentsMapTestCase.parents_map
@ddt.data
(
({},
ALL_BLOCKS
),
({
0
:
DueDateType
.
none
},
ALL_BLOCKS
),
({
0
:
DueDateType
.
future
},
ALL_BLOCKS
),
({
1
:
DueDateType
.
none
},
ALL_BLOCKS
),
({
1
:
DueDateType
.
future
},
ALL_BLOCKS
),
({
4
:
DueDateType
.
none
},
ALL_BLOCKS
),
({
4
:
DueDateType
.
future
},
ALL_BLOCKS
),
({
0
:
DueDateType
.
past
},
{}),
({
1
:
DueDateType
.
past
},
ALL_BLOCKS
-
{
1
,
3
,
4
}),
({
2
:
DueDateType
.
past
},
ALL_BLOCKS
-
{
2
,
5
}),
({
4
:
DueDateType
.
past
},
ALL_BLOCKS
-
{
4
}),
({
1
:
DueDateType
.
past
,
2
:
DueDateType
.
past
},
{
0
}),
({
1
:
DueDateType
.
none
,
2
:
DueDateType
.
past
},
ALL_BLOCKS
-
{
2
,
5
}),
({
1
:
DueDateType
.
past
,
2
:
DueDateType
.
none
},
ALL_BLOCKS
-
{
1
,
3
,
4
}),
)
@ddt.unpack
def
test_hidden_content
(
self
,
hide_due_values
,
expected_visible_blocks
,
):
for
idx
,
due_date_type
in
hide_due_values
.
iteritems
():
block
=
self
.
get_block
(
idx
)
block
.
due
=
self
.
DueDateType
.
due
(
due_date_type
)
block
.
hide_after_due
=
True
update_block
(
block
)
self
.
assert_transform_results
(
self
.
student
,
expected_visible_blocks
,
blocks_with_differing_access
=
None
,
transformers
=
self
.
transformers
,
)
lms/djangoapps/course_blocks/transformers/utils.py
View file @
9d747fa6
...
@@ -10,7 +10,103 @@ def get_field_on_block(block, field_name, default_value=None):
...
@@ -10,7 +10,103 @@ def get_field_on_block(block, field_name, default_value=None):
returns value from only a single parent chain
returns value from only a single parent chain
(e.g., doesn't take a union in DAGs).
(e.g., doesn't take a union in DAGs).
"""
"""
if
block
.
fields
[
field_name
]
.
is_set_on
(
block
):
try
:
return
getattr
(
block
,
field_name
)
if
block
.
fields
[
field_name
]
.
is_set_on
(
block
):
else
:
return
getattr
(
block
,
field_name
)
return
default_value
except
KeyError
:
pass
return
default_value
def
collect_merged_boolean_field
(
block_structure
,
transformer
,
xblock_field_name
,
merged_field_name
,
):
"""
Collects a boolean xBlock field of name xblock_field_name
for the given block_structure and transformer. The boolean
value is percolated down the hierarchy of the block_structure
and stored as a value of merged_field_name in the
block_structure.
Assumes that the boolean field is False, by default. So,
the value is ANDed across all parents for blocks with
multiple parents and ORed across all ancestors down a single
hierarchy chain.
"""
for
block_key
in
block_structure
.
topological_traversal
():
# compute merged value of the boolean field from all parents
parents
=
block_structure
.
get_parents
(
block_key
)
all_parents_merged_value
=
all
(
# pylint: disable=invalid-name
block_structure
.
get_transformer_block_field
(
parent_key
,
transformer
,
merged_field_name
,
False
,
)
for
parent_key
in
parents
)
if
parents
else
False
# set the merged value for this block
block_structure
.
set_transformer_block_field
(
block_key
,
transformer
,
merged_field_name
,
(
all_parents_merged_value
or
get_field_on_block
(
block_structure
.
get_xblock
(
block_key
),
xblock_field_name
,
False
,
)
)
)
def
collect_merged_date_field
(
block_structure
,
transformer
,
xblock_field_name
,
merged_field_name
,
default_date
,
func_merge_parents
=
min
,
func_merge_ancestors
=
max
,
):
"""
Collects a date xBlock field of name xblock_field_name
for the given block_structure and transformer. The date
value is percolated down the hierarchy of the block_structure
and stored as a value of merged_field_name in the
block_structure.
"""
for
block_key
in
block_structure
.
topological_traversal
():
parents
=
block_structure
.
get_parents
(
block_key
)
block_date
=
get_field_on_block
(
block_structure
.
get_xblock
(
block_key
),
xblock_field_name
)
if
not
parents
:
# no parents so just use value on block or default
merged_date_value
=
block_date
or
default_date
else
:
# compute merged value of date from all parents
merged_all_parents_date
=
func_merge_parents
(
block_structure
.
get_transformer_block_field
(
parent_key
,
transformer
,
merged_field_name
,
default_date
,
)
for
parent_key
in
parents
)
if
not
block_date
:
# no value on this block so take value from parents
merged_date_value
=
merged_all_parents_date
else
:
# compute merged date of the block and the parent
merged_date_value
=
func_merge_ancestors
(
merged_all_parents_date
,
block_date
)
block_structure
.
set_transformer_block_field
(
block_key
,
transformer
,
merged_field_name
,
merged_date_value
)
lms/djangoapps/course_blocks/transformers/visibility.py
View file @
9d747fa6
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
Visibility Transformer implementation.
Visibility Transformer implementation.
"""
"""
from
openedx.core.lib.block_structure.transformer
import
BlockStructureTransformer
,
FilteringTransformerMixin
from
openedx.core.lib.block_structure.transformer
import
BlockStructureTransformer
,
FilteringTransformerMixin
from
.utils
import
collect_merged_boolean_field
class
VisibilityTransformer
(
FilteringTransformerMixin
,
BlockStructureTransformer
):
class
VisibilityTransformer
(
FilteringTransformerMixin
,
BlockStructureTransformer
):
...
@@ -30,7 +31,7 @@ class VisibilityTransformer(FilteringTransformerMixin, BlockStructureTransformer
...
@@ -30,7 +31,7 @@ class VisibilityTransformer(FilteringTransformerMixin, BlockStructureTransformer
return
"visibility"
return
"visibility"
@classmethod
@classmethod
def
get_visible_to_staff_only
(
cls
,
block_structure
,
block_key
):
def
_
get_visible_to_staff_only
(
cls
,
block_structure
,
block_key
):
"""
"""
Returns whether the block with the given block_key in the
Returns whether the block with the given block_key in the
given block_structure should be visible to staff only per
given block_structure should be visible to staff only per
...
@@ -46,26 +47,12 @@ class VisibilityTransformer(FilteringTransformerMixin, BlockStructureTransformer
...
@@ -46,26 +47,12 @@ class VisibilityTransformer(FilteringTransformerMixin, BlockStructureTransformer
Collects any information that's necessary to execute this
Collects any information that's necessary to execute this
transformer's transform method.
transformer's transform method.
"""
"""
for
block_key
in
block_structure
.
topological_traversal
():
collect_merged_boolean_field
(
block_structure
,
# compute merged value of visible_to_staff_only from all parents
transformer
=
cls
,
parents
=
block_structure
.
get_parents
(
block_key
)
xblock_field_name
=
'visible_to_staff_only'
,
all_parents_visible_to_staff_only
=
all
(
# pylint: disable=invalid-name
merged_field_name
=
cls
.
MERGED_VISIBLE_TO_STAFF_ONLY
,
cls
.
get_visible_to_staff_only
(
block_structure
,
parent_key
)
)
for
parent_key
in
parents
)
if
parents
else
False
# set the merged value for this block
block_structure
.
set_transformer_block_field
(
block_key
,
cls
,
cls
.
MERGED_VISIBLE_TO_STAFF_ONLY
,
# merge visible_to_staff_only from all parents and this block
(
all_parents_visible_to_staff_only
or
block_structure
.
get_xblock
(
block_key
)
.
visible_to_staff_only
)
)
def
transform_block_filters
(
self
,
usage_info
,
block_structure
):
def
transform_block_filters
(
self
,
usage_info
,
block_structure
):
# Users with staff access bypass the Visibility check.
# Users with staff access bypass the Visibility check.
...
@@ -74,6 +61,6 @@ class VisibilityTransformer(FilteringTransformerMixin, BlockStructureTransformer
...
@@ -74,6 +61,6 @@ class VisibilityTransformer(FilteringTransformerMixin, BlockStructureTransformer
return
[
return
[
block_structure
.
create_removal_filter
(
block_structure
.
create_removal_filter
(
lambda
block_key
:
self
.
get_visible_to_staff_only
(
block_structure
,
block_key
),
lambda
block_key
:
self
.
_
get_visible_to_staff_only
(
block_structure
,
block_key
),
)
)
]
]
lms/djangoapps/courseware/views/index.py
View file @
9d747fa6
...
@@ -447,7 +447,7 @@ class CoursewareIndex(View):
...
@@ -447,7 +447,7 @@ class CoursewareIndex(View):
return
"{url}?child={requested_child}"
.
format
(
return
"{url}?child={requested_child}"
.
format
(
url
=
reverse
(
url
=
reverse
(
'courseware_section'
,
'courseware_section'
,
args
=
[
unicode
(
self
.
course
.
id
),
section_info
[
'chapter_url_name'
],
section_info
[
'url_name'
]],
args
=
[
unicode
(
self
.
course
_key
),
section_info
[
'chapter_url_name'
],
section_info
[
'url_name'
]],
),
),
requested_child
=
requested_child
,
requested_child
=
requested_child
,
)
)
...
@@ -455,6 +455,7 @@ class CoursewareIndex(View):
...
@@ -455,6 +455,7 @@ class CoursewareIndex(View):
section_context
=
{
section_context
=
{
'activate_block_id'
:
self
.
request
.
GET
.
get
(
'activate_block_id'
),
'activate_block_id'
:
self
.
request
.
GET
.
get
(
'activate_block_id'
),
'requested_child'
:
self
.
request
.
GET
.
get
(
"child"
),
'requested_child'
:
self
.
request
.
GET
.
get
(
"child"
),
'progress_url'
:
reverse
(
'progress'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course_key
)}),
}
}
if
previous_of_active_section
:
if
previous_of_active_section
:
section_context
[
'prev_url'
]
=
_compute_section_url
(
previous_of_active_section
,
'last'
)
section_context
[
'prev_url'
]
=
_compute_section_url
(
previous_of_active_section
,
'last'
)
...
...
lms/templates/hidden_content.html
0 → 100644
View file @
9d747fa6
<
%
page
expression_filter=
"h"
/>
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
from
openedx
.
core
.
djangolib
.
markup
import
HTML
,
Text
%
>
<div
class=
"sequence hidden-content proctored-exam completed"
>
<h3>
${_("The due date for this {subsection_format} has passed.").format(
subsection_format=subsection_format,
)}
</h3>
<hr>
<p>
${Text(_(
"Because the due date has passed, this {subsection_format} "
"is no longer available.{line_break}If you have completed this {subsection_format}, "
"your grade is available on the {link_start}progress page{link_end}."
)).format(
subsection_format=subsection_format,
line_break=HTML("
<br>
"),
link_start=HTML("
<a
href=
'{}'
>
").format(progress_url),
link_end=HTML("
</a>
"),
)}
</p>
</div>
lms/templates/seq_module.html
View file @
9d747fa6
...
@@ -3,12 +3,12 @@
...
@@ -3,12 +3,12 @@
<div
id=
"sequence_${element_id}"
class=
"sequence"
data-id=
"${item_id}"
data-position=
"${position}"
data-ajax-url=
"${ajax_url}"
data-next-url=
"${next_url}"
data-prev-url=
"${prev_url}"
>
<div
id=
"sequence_${element_id}"
class=
"sequence"
data-id=
"${item_id}"
data-position=
"${position}"
data-ajax-url=
"${ajax_url}"
data-next-url=
"${next_url}"
data-prev-url=
"${prev_url}"
>
<div
class=
"path"
></div>
<div
class=
"path"
></div>
% if
override_hidden_exam
:
% if
banner_text
:
<div
class=
"pattern-library-shim alert alert-information subsection-header"
tabindex=
"-1"
>
<div
class=
"pattern-library-shim alert alert-information subsection-header"
tabindex=
"-1"
>
<span
class=
"pattern-library-shim icon alert-icon fa fa-bullhorn"
aria-hidden=
"true"
></span>
<span
class=
"pattern-library-shim icon alert-icon fa fa-bullhorn"
aria-hidden=
"true"
></span>
<div
class=
"pattern-library-shim alert-message"
>
<div
class=
"pattern-library-shim alert-message"
>
<p
class=
"pattern-library-shim alert-copy"
>
<p
class=
"pattern-library-shim alert-copy"
>
${
_("This exam is hidden from the learner.")
}
${
banner_text
}
</p>
</p>
</div>
</div>
</div>
</div>
...
...
setup.py
View file @
9d747fa6
...
@@ -49,6 +49,7 @@ setup(
...
@@ -49,6 +49,7 @@ setup(
"start_date = lms.djangoapps.course_blocks.transformers.start_date:StartDateTransformer"
,
"start_date = lms.djangoapps.course_blocks.transformers.start_date:StartDateTransformer"
,
"user_partitions = lms.djangoapps.course_blocks.transformers.user_partitions:UserPartitionTransformer"
,
"user_partitions = lms.djangoapps.course_blocks.transformers.user_partitions:UserPartitionTransformer"
,
"visibility = lms.djangoapps.course_blocks.transformers.visibility:VisibilityTransformer"
,
"visibility = lms.djangoapps.course_blocks.transformers.visibility:VisibilityTransformer"
,
"hidden_content = lms.djangoapps.course_blocks.transformers.hidden_content:HiddenContentTransformer"
,
"course_blocks_api = lms.djangoapps.course_api.blocks.transformers.blocks_api:BlocksAPITransformer"
,
"course_blocks_api = lms.djangoapps.course_api.blocks.transformers.blocks_api:BlocksAPITransformer"
,
"proctored_exam = lms.djangoapps.course_api.blocks.transformers.proctored_exam:ProctoredExamTransformer"
,
"proctored_exam = lms.djangoapps.course_api.blocks.transformers.proctored_exam:ProctoredExamTransformer"
,
"grades = lms.djangoapps.courseware.transformers.grades:GradesTransformer"
,
"grades = lms.djangoapps.courseware.transformers.grades:GradesTransformer"
,
...
...
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