Add the staff preview bar to the new course home page
LEARNER-75
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -395,7 +395,7 @@ class TestStaffMasqueradeAsSpecificStudent(StaffMasqueradeTestCase, ProblemSubmi
|
||||
|
||||
def test_masquerade_as_specific_student_progress(self):
|
||||
"""
|
||||
Test masquesrading 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()
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(EdxFragmentView):
|
||||
"""
|
||||
View that displays a static course tab with a given name.
|
||||
"""
|
||||
@@ -486,7 +488,7 @@ class StaticCourseTabView(FragmentView):
|
||||
})
|
||||
|
||||
|
||||
class CourseTabView(FragmentView):
|
||||
class CourseTabView(EdxFragmentView):
|
||||
"""
|
||||
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'),
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
// 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();
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// 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);
|
||||
|
||||
@@ -1,32 +1,19 @@
|
||||
## 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
|
||||
# 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 ""
|
||||
|
||||
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
|
||||
|
||||
@@ -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">
|
||||
|
||||
81
lms/templates/preview_menu.html
Normal file
81
lms/templates/preview_menu.html
Normal file
@@ -0,0 +1,81 @@
|
||||
## 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
|
||||
@@ -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)
|
||||
%>
|
||||
|
||||
<%!
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
## 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">
|
||||
@@ -1,15 +1,32 @@
|
||||
"""
|
||||
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')
|
||||
|
||||
@@ -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 = [
|
||||
@@ -13,6 +13,11 @@ urlpatterns = [
|
||||
CourseHomeView.as_view(),
|
||||
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(),
|
||||
|
||||
@@ -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(CourseTabView):
|
||||
"""
|
||||
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)
|
||||
|
||||
Arguments:
|
||||
request: HTTP request
|
||||
course_id (unicode): course id
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@@ -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(EdxFragmentView):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user