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
ec241774
Commit
ec241774
authored
Mar 24, 2017
by
Andy Armstrong
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add the staff preview bar to the new course home page
LEARNER-75
parent
b1b950c6
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
317 additions
and
150 deletions
+317
-150
common/lib/xmodule/xmodule/tabs.py
+3
-0
lms/djangoapps/courseware/tabs.py
+1
-0
lms/djangoapps/courseware/tests/test_masquerade.py
+1
-1
lms/djangoapps/courseware/views/index.py
+2
-2
lms/djangoapps/courseware/views/views.py
+38
-19
lms/static/sass/course/layout/_courseware_preview.scss
+2
-2
lms/static/sass/shared-v2/_components.scss
+57
-0
lms/static/sass/shared-v2/_header.scss
+1
-1
lms/static/sass/shared-v2/_layouts.scss
+2
-5
lms/static/sass/shared-v2/_variables.scss
+3
-0
lms/templates/courseware/course_navigation.html
+4
-73
lms/templates/main.html
+1
-0
lms/templates/preview_menu.html
+81
-0
lms/templates/user_dropdown.html
+1
-1
openedx/core/djangoapps/plugin_api/views.py
+0
-1
openedx/features/course_bookmarks/views/course_bookmarks.py
+1
-0
openedx/features/course_experience/templates/course_experience/course-home-fragment.html
+0
-21
openedx/features/course_experience/tests/views/test_course_outline.py
+85
-10
openedx/features/course_experience/urls.py
+6
-1
openedx/features/course_experience/views/course_home.py
+25
-10
openedx/features/course_experience/views/course_outline.py
+3
-3
No files found.
common/lib/xmodule/xmodule/tabs.py
View file @
ec241774
...
...
@@ -72,6 +72,9 @@ class CourseTab(object):
# True if this tab should be displayed only for instructors
course_staff_only
=
False
# True if this tab supports showing staff users a preview menu
supports_preview_menu
=
False
def
__init__
(
self
,
tab_dict
):
"""
Initializes class members with values passed in by subclasses.
...
...
lms/djangoapps/courseware/tabs.py
View file @
ec241774
...
...
@@ -36,6 +36,7 @@ class CoursewareTab(EnrolledTab):
view_name
=
'courseware'
is_movable
=
False
is_default
=
False
supports_preview_menu
=
True
@staticmethod
def
main_course_url_name
(
request
):
...
...
lms/djangoapps/courseware/tests/test_masquerade.py
View file @
ec241774
...
...
@@ -395,7 +395,7 @@ class TestStaffMasqueradeAsSpecificStudent(StaffMasqueradeTestCase, ProblemSubmi
def
test_masquerade_as_specific_student_progress
(
self
):
"""
Test masque
s
rading as a specific user for progress page.
Test masquerading as a specific user for progress page.
"""
# Give the student some correct answers, check their progress page
self
.
login_student
()
...
...
lms/djangoapps/courseware/views/index.py
View file @
ec241774
...
...
@@ -412,9 +412,9 @@ class CoursewareIndex(View):
'init'
:
''
,
'fragment'
:
Fragment
(),
'staff_access'
:
self
.
is_staff
,
'studio_url'
:
get_studio_url
(
self
.
course
,
'course'
),
'masquerade'
:
self
.
masquerade
,
'real_user'
:
self
.
real_user
,
'supports_preview_menu'
:
True
,
'studio_url'
:
get_studio_url
(
self
.
course
,
'course'
),
'xqa_server'
:
settings
.
FEATURES
.
get
(
'XQA_SERVER'
,
"http://your_xqa_server.com"
),
'bookmarks_api_url'
:
reverse
(
'bookmarks'
),
'language_preference'
:
self
.
_get_language_preference
(),
...
...
lms/djangoapps/courseware/views/views.py
View file @
ec241774
...
...
@@ -51,6 +51,7 @@ import survey.views
from
certificates
import
api
as
certs_api
from
certificates.models
import
CertificateStatuses
from
openedx.core.djangoapps.models.course_details
import
CourseDetails
from
openedx.core.djangoapps.plugin_api.views
import
EdxFragmentView
from
commerce.utils
import
EcommerceService
from
enrollment.api
import
add_enrollment
from
course_modes.models
import
CourseMode
...
...
@@ -383,10 +384,11 @@ def course_info(request, course_id):
'course'
:
course
,
'staff_access'
:
staff_access
,
'masquerade'
:
masquerade
,
'supports_preview_menu'
:
True
,
'studio_url'
:
studio_url
,
'show_enroll_banner'
:
show_enroll_banner
,
'url_to_enroll'
:
url_to_enroll
,
'upgrade_link'
:
upgrade_link
'upgrade_link'
:
upgrade_link
,
}
# Get the URL of the user's last position in order to display the 'where you were last' message
...
...
@@ -449,7 +451,7 @@ def get_last_accessed_courseware(course, request, user):
return
(
None
,
None
)
class
StaticCourseTabView
(
FragmentView
):
class
StaticCourseTabView
(
Edx
FragmentView
):
"""
View that displays a static course tab with a given name.
"""
...
...
@@ -486,7 +488,7 @@ class StaticCourseTabView(FragmentView):
})
class
CourseTabView
(
FragmentView
):
class
CourseTabView
(
Edx
FragmentView
):
"""
View that displays a course tab page.
"""
...
...
@@ -499,29 +501,46 @@ class CourseTabView(FragmentView):
course_key
=
CourseKey
.
from_string
(
course_id
)
course
=
get_course_with_access
(
request
.
user
,
'load'
,
course_key
)
tab
=
CourseTabList
.
get_tab_by_type
(
course
.
tabs
,
tab_type
)
return
super
(
CourseTabView
,
self
)
.
get
(
request
,
course
=
course
,
tab
=
tab
,
**
kwargs
)
page_context
=
self
.
create_page_context
(
request
,
course
=
course
,
tab
=
tab
,
**
kwargs
)
return
super
(
CourseTabView
,
self
)
.
get
(
request
,
course
=
course
,
page_context
=
page_context
,
**
kwargs
)
def
render_to_fragment
(
self
,
request
,
course
=
None
,
tab
=
None
,
**
kwargs
):
def
create_page_context
(
self
,
request
,
course
=
None
,
tab
=
None
,
**
kwargs
):
"""
Creates the context for the fragment's template.
"""
staff_access
=
has_access
(
request
.
user
,
'staff'
,
course
)
supports_preview_menu
=
tab
.
get
(
'supports_preview_menu'
,
False
)
if
supports_preview_menu
:
masquerade
,
masquerade_user
=
setup_masquerade
(
request
,
course
.
id
,
staff_access
,
reset_masquerade_data
=
True
)
request
.
user
=
masquerade_user
else
:
masquerade
=
None
return
{
'course'
:
course
,
'tab'
:
tab
,
'active_page'
:
tab
.
get
(
'type'
,
None
),
'staff_access'
:
staff_access
,
'masquerade'
:
masquerade
,
'supports_preview_menu'
:
supports_preview_menu
,
'uses_pattern_library'
:
True
,
'disable_courseware_js'
:
True
,
}
def
render_to_fragment
(
self
,
request
,
course
=
None
,
page_context
=
None
,
**
kwargs
):
"""
Renders the course tab to a fragment.
"""
tab
=
page_context
[
'tab'
]
return
tab
.
render_to_fragment
(
request
,
course
,
**
kwargs
)
def
render_to_standalone_html
(
self
,
request
,
fragment
,
course
=
None
,
tab
=
None
,
**
kwargs
):
def
render_to_standalone_html
(
self
,
request
,
fragment
,
course
=
None
,
tab
=
None
,
page_context
=
None
,
**
kwargs
):
"""
Renders this course tab's fragment to HTML for a standalone page.
"""
return
render_to_string
(
'courseware/tab-view.html'
,
{
'course'
:
course
,
'active_page'
:
tab
[
'type'
],
'tab'
:
tab
,
'fragment'
:
fragment
,
'uses_pattern_library'
:
True
,
'disable_courseware_js'
:
True
,
},
)
if
not
page_context
:
page_context
=
self
.
create_page_context
(
request
,
course
=
course
,
tab
=
tab
,
**
kwargs
)
page_context
[
'fragment'
]
=
fragment
return
render_to_string
(
'courseware/tab-view.html'
,
page_context
)
@ensure_csrf_cookie
...
...
@@ -871,11 +890,12 @@ def _progress(request, course_key, student_id):
'studio_url'
:
studio_url
,
'grade_summary'
:
grade_summary
,
'staff_access'
:
staff_access
,
'masquerade'
:
masquerade
,
'supports_preview_menu'
:
True
,
'student'
:
student
,
'passed'
:
is_course_passed
(
course
,
grade_summary
),
'credit_course_requirements'
:
_credit_course_requirements
(
course_key
,
student
),
'certificate_data'
:
_get_cert_data
(
student
,
course
,
course_key
,
is_active
,
enrollment_mode
),
'masquerade'
:
masquerade
}
with
outer_atomic
():
...
...
@@ -1397,7 +1417,6 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True):
'disable_header'
:
True
,
'disable_footer'
:
True
,
'disable_window_wrap'
:
True
,
'disable_preview_menu'
:
True
,
'staff_access'
:
bool
(
has_access
(
request
.
user
,
'staff'
,
course
)),
'xqa_server'
:
settings
.
FEATURES
.
get
(
'XQA_SERVER'
,
'http://your_xqa_server.com'
),
}
...
...
lms/static/sass/course/layout/_courseware_preview.scss
View file @
ec241774
...
...
@@ -21,14 +21,14 @@
display
:
inline-block
;
.action-preview-label
{
@include
margin-right
(
$baseline
/
2
);
display
:
inline-block
;
margin-right
:
(
$baseline
/
2
);
margin-bottom
:
0
;
vertical-align
:
middle
;
}
.action-preview-select
{
margin-right
:
$baseline
;
@include
margin-right
(
$baseline
)
;
}
.action-preview-username-container
{
...
...
lms/static/sass/shared-v2/_components.scss
View file @
ec241774
...
...
@@ -92,3 +92,60 @@
}
}
}
.wrapper-preview-menu
{
@include
clearfix
();
@include
box-sizing
(
border-box
);
margin
:
0
auto
0
;
padding
:
(
$baseline
*
0
.75
);
background-color
:
$lms-preview-menu-color
;
@media
print
{
display
:
none
;
}
.preview-menu
{
max-width
:
$lms-max-width
;
width
:
auto
;
margin
:
0
auto
;
}
.preview-actions
{
@include
margin-left
(
0
);
display
:
inline-block
;
margin-bottom
:
0
;
.action-preview
{
display
:
inline-block
;
.action-preview-label
{
@include
margin-right
(
$baseline
/
2
);
display
:
inline-block
;
margin-bottom
:
0
;
vertical-align
:
middle
;
}
.action-preview-select
{
@include
margin-right
(
$baseline
);
}
.action-preview-username-container
{
display
:
none
;
.action-preview-username
{
vertical-align
:
middle
;
height
:
25px
;
}
}
}
}
.preview-specific-student-notice
{
margin-top
:
(
$baseline
/
2
);
font-size
:
90%
;
>
p
{
margin-bottom
:
0
;
}
}
}
lms/static/sass/shared-v2/_header.scss
View file @
ec241774
...
...
@@ -19,7 +19,7 @@
margin
:
0
auto
;
padding
:
10px
10px
0
;
width
:
100%
;
max-width
:
1180px
;
max-width
:
$lms-max-width
;
.left
{
@include
float
(
left
);
...
...
lms/static/sass/shared-v2/_layouts.scss
View file @
ec241774
// LMS layouts
.content-wrapper
{
max-width
:
1180px
;
padding
:
{
top
:
$baseline
/
2
;
bottom
:
$baseline
*
2
;
}
max-width
:
$lms-max-width
;
padding-bottom
:
$baseline
*
2
;
.container
{
@include
clearfix
();
...
...
lms/static/sass/shared-v2/_variables.scss
View file @
ec241774
// LMS variables
$lms-max-width
:
1180px
;
$lms-gray
:
palette
(
grayscale
,
base
);
$lms-background-color
:
palette
(
grayscale
,
x-back
);
$lms-container-background-color
:
$white
;
$lms-border-color
:
palette
(
grayscale
,
back
);
$lms-label-color
:
palette
(
grayscale
,
black
);
$lms-active-color
:
palette
(
primary
,
base
);
$lms-preview-menu-color
:
#c8c8c8
;
$white-transparent
:
rgba
(
255
,
255
,
255
,
0
);
$white-opacity-40
:
rgba
(
255
,
255
,
255
,
0
.4
);
...
...
lms/templates/courseware/course_navigation.html
View file @
ec241774
## mako
<
%
page
args=
"active_page=None"
expression_filter=
"h"
/>
<
%
namespace
name=
'static'
file=
'/static_content.html'
/>
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
from
courseware
.
tabs
import
get_course_tab_list
from
django
.
core
.
urlresolvers
import
reverse
from
django
.
conf
import
settings
from
openedx
.
core
.
djangoapps
.
course_groups
.
partition_scheme
import
get_cohorted_user_partition
from
openedx
.
core
.
djangolib
.
js_utils
import
dump_js_escaped_json
from
openedx
.
core
.
djangolib
.
markup
import
HTML
,
Text
from
student
.
models
import
CourseEnrollment
%
>
<
%
if
active_page
is
None
and
active_page_context
is
not
UNDEFINED:
#
If
active_page
is
not
passed
in
as
an
argument
,
it
may
be
in
the
context
as
active_page_context
active_page =
active_page_context
def
selected
(
is_selected
)
:
return
"
selected
"
if
is_selected
else
""
#
If
active_page
is
not
passed
in
as
an
argument
,
it
may
be
in
the
context
as
active_page_context
active_page =
active_page_context
show_preview_menu =
not
disable_preview_menu
and
staff_access
and
active_page
in
["
courseware
",
"
info
",
"
progress
"]
cohorted_user_partition =
get_cohorted_user_partition(course)
masquerade_user_name =
masquerade.user_name
if
masquerade
else
None
masquerade_group_id =
masquerade.group_id
if
masquerade
else
None
staff_selected =
selected(not
masquerade
or
masquerade
.
role
!=
"
student
")
specific_student_selected =
selected(not
staff_selected
and
masquerade
.
user_name
)
student_selected =
selected(not
staff_selected
and
not
specific_student_selected
and
not
masquerade_group_id
)
include_special_exams =
settings.FEATURES.get('ENABLE_SPECIAL_EXAMS',
False
)
and
(
course
.
enable_proctored_exams
or
course
.
enable_timed_exams
)
%
>
...
...
@@ -39,47 +26,6 @@ include_special_exams = settings.FEATURES.get('ENABLE_SPECIAL_EXAMS', False) and
% endfor
<div
class=
"proctored_exam_status"
></div>
% endif
% if show_preview_menu:
<nav
class=
"wrapper-preview-menu"
aria-label=
"${_('Course View')}"
>
<div
class=
"preview-menu"
>
<ol
class=
"preview-actions"
>
<li
class=
"action-preview"
>
<form
action=
"#"
class=
"action-preview-form"
method=
"post"
>
<label
for=
"action-preview-select"
class=
"action-preview-label"
>
${_("View this course as:")}
</label>
<select
class=
"action-preview-select"
id=
"action-preview-select"
name=
"select"
>
<option
value=
"staff"
${
staff_selected
}
>
${_("Staff")}
</option>
<option
value=
"student"
${
student_selected
}
>
${_("Student")}
</option>
<option
value=
"specific student"
${
specific_student_selected
}
>
${_("Specific student")}
</option>
% if cohorted_user_partition:
% for group in sorted(cohorted_user_partition.groups, key=lambda group: group.name):
<option
value=
"group.id"
data-group-id=
"${group.id}"
${
selected
(
masquerade_group_id =
=
group
.
id
)}
>
${_("Student in {content_group}").format(content_group=group.name)}
</option>
% endfor
% endif
</select>
<div
class=
"action-preview-username-container"
>
<label
for=
"action-preview-username"
class=
"action-preview-label"
>
${_("Username or email:")}
</label>
<input
type=
"text"
class=
"action-preview-username"
id=
"action-preview-username"
>
</div>
<button
type=
"submit"
class=
"sr"
name=
"submit"
value=
"submit"
>
${_("Set preview mode")}
</button>
</form>
</li>
</ol>
% if specific_student_selected:
<div
class=
"preview-specific-student-notice"
>
<p>
${Text(_("You are now viewing the course as {i_start}{user_name}{i_end}.")).format(
user_name=masquerade_user_name,
i_start=HTML(u'
<i>
'),
i_end=HTML(u'
</i>
'),
)}
</p>
</div>
% endif
</div>
</nav>
% endif
% if disable_tabs is UNDEFINED or not disable_tabs:
<nav
class=
"${active_page} wrapper-course-material"
aria-label=
"${_('Course Material')}"
>
...
...
@@ -95,18 +41,3 @@ include_special_exams = settings.FEATURES.get('ENABLE_SPECIAL_EXAMS', False) and
</div>
</nav>
%endif
% if show_preview_menu:
<
%
preview_options =
{
"
courseId
"
:
course
.
id
,
"
disableStudentAccess
"
:
disable_student_access
if
disable_student_access
is
not
UNDEFINED
else
False
,
"
specificStudentSelected
"
:
specific_student_selected
,
"
cohortedUserPartitionId
"
:
cohorted_user_partition
.
id
if
cohorted_user_partition
else
None
,
"
masqueradeUsername
"
:
masquerade_user_name
if
masquerade_user_name
is
not
UNDEFINED
else
None
,
}
%
>
<
%
static:require_module_async
module_name=
"lms/js/preview/preview_factory"
class_name=
"PreviewFactory"
>
PreviewFactory(${preview_options | n, dump_js_escaped_json});
</
%
static:require
_module_async
>
% endif
lms/templates/main.html
View file @
ec241774
...
...
@@ -134,6 +134,7 @@ from pipeline_mako import render_require_js_path_overrides
% if not disable_header:
<
%
include
file=
"${static.get_template_path('header.html')}"
args=
"online_help_token=online_help_token"
/>
<
%
include
file=
"/preview_menu.html"
/>
% endif
<div
class=
"content-wrapper"
id=
"content"
>
...
...
lms/templates/preview_menu.html
0 → 100644
View file @
ec241774
## mako
<
%
page
args=
"active_page=None"
expression_filter=
"h"
/>
<
%
namespace
name=
'static'
file=
'/static_content.html'
/>
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
from
django
.
conf
import
settings
from
openedx
.
core
.
djangoapps
.
course_groups
.
partition_scheme
import
get_cohorted_user_partition
from
openedx
.
core
.
djangolib
.
js_utils
import
dump_js_escaped_json
from
openedx
.
core
.
djangolib
.
markup
import
HTML
,
Text
%
>
<
%
show_preview_menu =
course
and
staff_access
and
supports_preview_menu
%
>
% if show_preview_menu:
<
%
def
selected
(
is_selected
)
:
return
"
selected
"
if
is_selected
else
""
cohorted_user_partition =
get_cohorted_user_partition(course)
masquerade_user_name =
masquerade.user_name
if
masquerade
else
None
masquerade_group_id =
masquerade.group_id
if
masquerade
else
None
staff_selected =
selected(not
masquerade
or
masquerade
.
role
!=
"
student
")
specific_student_selected =
selected(not
staff_selected
and
masquerade
.
user_name
)
student_selected =
selected(not
staff_selected
and
not
specific_student_selected
and
not
masquerade_group_id
)
%
>
<nav
class=
"wrapper-preview-menu"
aria-label=
"${_('Course View')}"
>
<div
class=
"preview-menu"
>
<ol
class=
"preview-actions"
>
<li
class=
"action-preview"
>
<form
action=
"#"
class=
"action-preview-form"
method=
"post"
>
<label
for=
"action-preview-select"
class=
"action-preview-label"
>
${_("View this course as:")}
</label>
<select
class=
"action-preview-select"
id=
"action-preview-select"
name=
"select"
>
<option
value=
"staff"
${
staff_selected
}
>
${_("Staff")}
</option>
<option
value=
"student"
${
student_selected
}
>
${_("Student")}
</option>
<option
value=
"specific student"
${
specific_student_selected
}
>
${_("Specific student")}
</option>
% if cohorted_user_partition:
% for group in sorted(cohorted_user_partition.groups, key=lambda group: group.name):
<option
value=
"group.id"
data-group-id=
"${group.id}"
${
selected
(
masquerade_group_id =
=
group
.
id
)}
>
${_("Student in {content_group}").format(content_group=group.name)}
</option>
% endfor
% endif
</select>
<div
class=
"action-preview-username-container"
>
<label
for=
"action-preview-username"
class=
"action-preview-label"
>
${_("Username or email:")}
</label>
<input
type=
"text"
class=
"action-preview-username"
id=
"action-preview-username"
>
</div>
<button
type=
"submit"
class=
"sr"
name=
"submit"
value=
"submit"
>
${_("Set preview mode")}
</button>
</form>
</li>
</ol>
% if specific_student_selected:
<div
class=
"preview-specific-student-notice"
>
<p>
${Text(_("You are now viewing the course as {i_start}{user_name}{i_end}.")).format(
user_name=masquerade_user_name,
i_start=HTML(u'
<i>
'),
i_end=HTML(u'
</i>
'),
)}
</p>
</div>
% endif
</div>
</nav>
<
%
preview_options =
{
"
courseId
"
:
course
.
id
,
"
disableStudentAccess
"
:
disable_student_access
if
disable_student_access
is
not
UNDEFINED
else
False
,
"
specificStudentSelected
"
:
specific_student_selected
,
"
cohortedUserPartitionId
"
:
cohorted_user_partition
.
id
if
cohorted_user_partition
else
None
,
"
masqueradeUsername
"
:
masquerade_user_name
if
masquerade_user_name
is
not
UNDEFINED
else
None
,
}
%
>
<
%
static:require_module_async
module_name=
"lms/js/preview/preview_factory"
class_name=
"PreviewFactory"
>
PreviewFactory(${preview_options | n, dump_js_escaped_json});
</
%
static:require
_module_async
>
% endif
lms/templates/user_dropdown.html
View file @
ec241774
...
...
@@ -4,7 +4,7 @@
## This template should not use the target student's details when masquerading, see TNL-4895
<
%
self
.
real_user =
real_user
if
real_user
!=
UNDEFINED
else
user
self
.
real_user =
getattr(user,
'
real_user
',
user
)
%
>
<
%!
...
...
openedx/core/djangoapps/plugin_api/views.py
View file @
ec241774
...
...
@@ -91,6 +91,5 @@ class EdxFragmentView(FragmentView):
'disable_header'
:
True
,
'disable_footer'
:
True
,
'disable_window_wrap'
:
True
,
'disable_preview_menu'
:
True
,
}
return
render_to_response
(
settings
.
STANDALONE_FRAGMENT_VIEW_TEMPLATE
,
context
)
openedx/features/course_bookmarks/views/course_bookmarks.py
View file @
ec241774
...
...
@@ -49,6 +49,7 @@ class CourseBookmarksView(View):
context
=
{
'csrf'
:
csrf
(
request
)[
'csrf_token'
],
'course'
:
course
,
'supports_preview_menu'
:
True
,
'course_url'
:
course_url
,
'bookmarks_fragment'
:
bookmarks_fragment
,
'disable_courseware_js'
:
True
,
...
...
openedx/features/course_experience/templates/course_experience/course-home.html
→
openedx/features/course_experience/templates/course_experience/course-home
-fragment
.html
View file @
ec241774
## mako
<
%!
main_css =
"style-main-v2"
%
>
<
%
page
expression_filter=
"h"
/>
<
%
inherit
file=
"../main.html"
/>
<
%
namespace
name=
'static'
file=
'../static_content.html'
/>
<
%
def
name=
"online_help_token()"
><
%
return
"
courseware
"
%
></
%
def>
<
%
def
name=
"course_name()"
>
<
%
return
_
("{
course_number
}
Courseware
").
format
(
course_number=
course.display_number_with_default)
%
>
</
%
def>
<
%!
import
json
...
...
@@ -21,20 +14,6 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
from
openedx
.
core
.
djangolib
.
markup
import
HTML
%
>
<
%
block
name=
"bodyclass"
>
course
</
%
block>
<
%
block
name=
"pagetitle"
>
${course_name()}
</
%
block>
<
%
include
file=
"../courseware/course_navigation.html"
args=
"active_page='courseware'"
/>
<
%
block
name=
"headextra"
>
${HTML(outline_fragment.head_html())}
</
%
block>
<
%
block
name=
"js_extra"
>
${HTML(outline_fragment.foot_html())}
</
%
block>
<
%
block
name=
"content"
>
<div
class=
"course-view container"
id=
"course-container"
>
<header
class=
"page-header has-secondary"
>
...
...
openedx/features/course_experience/tests/views/test_course_outline.py
View file @
ec241774
"""
Tests for the Course Outline view and supporting views.
"""
import
datetime
from
mock
import
patch
import
json
from
django.core.urlresolvers
import
reverse
from
courseware.tests.factories
import
StaffFactory
from
student.models
import
CourseEnrollment
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
TEST_PASSWORD
=
'test'
def
course_home_url
(
course
):
"""
Returns the URL for the course's home page
"""
return
reverse
(
'edx.course_experience.course_home'
,
kwargs
=
{
'course_id'
:
unicode
(
course
.
id
),
}
)
class
TestCourseOutlinePage
(
SharedModuleStoreTestCase
):
"""
...
...
@@ -43,8 +60,7 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
@classmethod
def
setUpTestData
(
cls
):
"""Set up and enroll our fake user in the course."""
cls
.
password
=
'test'
cls
.
user
=
UserFactory
(
password
=
cls
.
password
)
cls
.
user
=
UserFactory
(
password
=
TEST_PASSWORD
)
for
course
in
cls
.
courses
:
CourseEnrollment
.
enroll
(
cls
.
user
,
course
.
id
)
...
...
@@ -53,18 +69,13 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
Set up for the tests.
"""
super
(
TestCourseOutlinePage
,
self
)
.
setUp
()
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
self
.
password
)
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
TEST_PASSWORD
)
@patch
(
'openedx.features.course_experience.views.course_outline.get_last_accessed_courseware'
)
def
test_render
(
self
,
patched_get_last_accessed
):
for
course
in
self
.
courses
:
patched_get_last_accessed
.
return_value
=
(
None
,
course
.
last_accessed
)
url
=
reverse
(
'edx.course_experience.course_home'
,
kwargs
=
{
'course_id'
:
unicode
(
course
.
id
),
}
)
url
=
course_home_url
(
course
)
response
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
response
.
status_code
,
200
)
response_content
=
response
.
content
.
decode
(
"utf-8"
)
...
...
@@ -79,3 +90,67 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
self
.
assertIn
(
section
.
display_name
,
response_content
)
for
vertical
in
section
.
children
:
self
.
assertNotIn
(
vertical
.
display_name
,
response_content
)
class
TestCourseOutlinePreview
(
SharedModuleStoreTestCase
):
"""
Unit tests for staff preview of the course outline.
"""
def
update_masquerade
(
self
,
course
,
role
,
group_id
=
None
,
user_name
=
None
):
"""
Toggle masquerade state.
"""
masquerade_url
=
reverse
(
'masquerade_update'
,
kwargs
=
{
'course_key_string'
:
unicode
(
course
.
id
),
}
)
response
=
self
.
client
.
post
(
masquerade_url
,
json
.
dumps
({
'role'
:
role
,
'group_id'
:
group_id
,
'user_name'
:
user_name
}),
'application/json'
)
self
.
assertEqual
(
response
.
status_code
,
200
)
return
response
def
test_preview
(
self
):
"""
Verify the behavior of preview for the course outline.
"""
course
=
CourseFactory
.
create
(
start
=
datetime
.
datetime
.
now
()
-
datetime
.
timedelta
(
days
=
30
)
)
staff_user
=
StaffFactory
(
course_key
=
course
.
id
,
password
=
TEST_PASSWORD
)
CourseEnrollment
.
enroll
(
staff_user
,
course
.
id
)
future_date
=
datetime
.
datetime
.
now
()
+
datetime
.
timedelta
(
days
=
30
)
with
self
.
store
.
bulk_operations
(
course
.
id
):
chapter
=
ItemFactory
.
create
(
category
=
'chapter'
,
parent_location
=
course
.
location
,
display_name
=
'First Chapter'
,
)
section
=
ItemFactory
.
create
(
category
=
'sequential'
,
parent_location
=
chapter
.
location
)
ItemFactory
.
create
(
category
=
'vertical'
,
parent_location
=
section
.
location
)
chapter
=
ItemFactory
.
create
(
category
=
'chapter'
,
parent_location
=
course
.
location
,
display_name
=
'Future Chapter'
,
due
=
future_date
,
)
section
=
ItemFactory
.
create
(
category
=
'sequential'
,
parent_location
=
chapter
.
location
)
ItemFactory
.
create
(
category
=
'vertical'
,
parent_location
=
section
.
location
)
# Verify that a staff user sees a chapter with a due date in the future
self
.
client
.
login
(
username
=
staff_user
.
username
,
password
=
'test'
)
url
=
course_home_url
(
course
)
response
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertContains
(
response
,
'Future Chapter'
)
# Verify that staff masquerading as a learner does not see the future chapter.
self
.
update_masquerade
(
course
,
role
=
'student'
)
response
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertNotContains
(
response
,
'Future Chapter'
)
openedx/features/course_experience/urls.py
View file @
ec241774
...
...
@@ -4,7 +4,7 @@ Defines URLs for the course experience.
from
django.conf.urls
import
url
from
views.course_home
import
CourseHomeView
from
views.course_home
import
CourseHomeView
,
CourseHomeFragmentView
from
views.course_outline
import
CourseOutlineFragmentView
urlpatterns
=
[
...
...
@@ -14,6 +14,11 @@ urlpatterns = [
name
=
'edx.course_experience.course_home'
,
),
url
(
r'^home_fragment$'
,
CourseHomeFragmentView
.
as_view
(),
name
=
'edx.course_experience.course_home_fragment_view'
,
),
url
(
r'^outline_fragment$'
,
CourseOutlineFragmentView
.
as_view
(),
name
=
'edx.course_experience.course_outline_fragment_view'
,
...
...
openedx/features/course_experience/views/course_home.py
View file @
ec241774
...
...
@@ -4,20 +4,22 @@ Views for the course home page.
from
django.contrib.auth.decorators
import
login_required
from
django.core.context_processors
import
csrf
from
django.
shortcuts
import
render_to_response
from
django.
template.loader
import
render_to_string
from
django.utils.decorators
import
method_decorator
from
django.views.decorators.cache
import
cache_control
from
django.views.decorators.csrf
import
ensure_csrf_cookie
from
django.views.generic
import
View
from
courseware.courses
import
get_course_with_access
from
lms.djangoapps.courseware.views.views
import
CourseTabView
from
opaque_keys.edx.keys
import
CourseKey
from
openedx.core.djangoapps.plugin_api.views
import
EdxFragmentView
from
util.views
import
ensure_valid_course_key
from
web_fragments.fragment
import
Fragment
from
course_outline
import
CourseOutlineFragmentView
class
CourseHomeView
(
View
):
class
CourseHomeView
(
CourseTab
View
):
"""
The home page for a course.
"""
...
...
@@ -25,21 +27,33 @@ class CourseHomeView(View):
@method_decorator
(
ensure_csrf_cookie
)
@method_decorator
(
cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
))
@method_decorator
(
ensure_valid_course_key
)
def
get
(
self
,
request
,
course_id
):
def
get
(
self
,
request
,
course_id
,
**
kwargs
):
"""
Displays the home page for the specified course.
"""
return
super
(
CourseHomeView
,
self
)
.
get
(
request
,
course_id
,
'courseware'
,
**
kwargs
)
def
render_to_fragment
(
self
,
request
,
course
=
None
,
tab
=
None
,
**
kwargs
):
course_id
=
unicode
(
course
.
id
)
home_fragment_view
=
CourseHomeFragmentView
()
return
home_fragment_view
.
render_to_fragment
(
request
,
course_id
=
course_id
,
**
kwargs
)
Arguments:
request: HTTP request
course_id (unicode): course id
class
CourseHomeFragmentView
(
EdxFragmentView
):
"""
A fragment to render the home page for a course.
"""
def
render_to_fragment
(
self
,
request
,
course_id
=
None
,
**
kwargs
):
"""
Renders the course's home page as a fragment.
"""
course_key
=
CourseKey
.
from_string
(
course_id
)
course
=
get_course_with_access
(
request
.
user
,
'load'
,
course_key
,
check_if_enrolled
=
True
)
# Render the outline as a fragment
outline_fragment
=
CourseOutlineFragmentView
()
.
render_to_fragment
(
request
,
course_id
=
course_id
)
outline_fragment
=
CourseOutlineFragmentView
()
.
render_to_fragment
(
request
,
course_id
=
course_id
,
**
kwargs
)
# Render the
entire unified course view
# Render the
course home fragment
context
=
{
'csrf'
:
csrf
(
request
)[
'csrf_token'
],
'course'
:
course
,
...
...
@@ -47,4 +61,5 @@ class CourseHomeView(View):
'disable_courseware_js'
:
True
,
'uses_pattern_library'
:
True
,
}
return
render_to_response
(
'course_experience/course-home.html'
,
context
)
html
=
render_to_string
(
'course_experience/course-home-fragment.html'
,
context
)
return
Fragment
(
html
)
openedx/features/course_experience/views/course_outline.py
View file @
ec241774
...
...
@@ -9,12 +9,12 @@ from courseware.courses import get_course_with_access
from
lms.djangoapps.courseware.views.views
import
get_last_accessed_courseware
from
lms.djangoapps.course_api.blocks.api
import
get_blocks
from
opaque_keys.edx.keys
import
CourseKey
from
openedx.core.djangoapps.plugin_api.views
import
EdxFragmentView
from
web_fragments.fragment
import
Fragment
from
web_fragments.views
import
FragmentView
from
xmodule.modulestore.django
import
modulestore
class
CourseOutlineFragmentView
(
FragmentView
):
class
CourseOutlineFragmentView
(
Edx
FragmentView
):
"""
Course outline fragment to be shown in the unified course view.
"""
...
...
@@ -35,7 +35,7 @@ class CourseOutlineFragmentView(FragmentView):
return
block
def
render_to_fragment
(
self
,
request
,
course_id
=
None
,
**
kwargs
):
def
render_to_fragment
(
self
,
request
,
course_id
=
None
,
page_context
=
None
,
**
kwargs
):
"""
Renders the course outline as a fragment.
"""
...
...
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