diff --git a/common/lib/xmodule/xmodule/tabs.py b/common/lib/xmodule/xmodule/tabs.py index 8e2f83b8b4..de7838fc8f 100644 --- a/common/lib/xmodule/xmodule/tabs.py +++ b/common/lib/xmodule/xmodule/tabs.py @@ -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. diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 6c2826d5c3..d0fa746437 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -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): diff --git a/lms/djangoapps/courseware/tests/test_masquerade.py b/lms/djangoapps/courseware/tests/test_masquerade.py index 97ddb7601f..9d16e08be6 100644 --- a/lms/djangoapps/courseware/tests/test_masquerade.py +++ b/lms/djangoapps/courseware/tests/test_masquerade.py @@ -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() diff --git a/lms/djangoapps/courseware/views/index.py b/lms/djangoapps/courseware/views/index.py index 935cc2bd5b..33c0a076d3 100644 --- a/lms/djangoapps/courseware/views/index.py +++ b/lms/djangoapps/courseware/views/index.py @@ -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(), diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 4da81c0e25..03c1d0c5e3 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -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'), } diff --git a/lms/static/sass/course/layout/_courseware_preview.scss b/lms/static/sass/course/layout/_courseware_preview.scss index 8c4ebcb73f..2261747188 100644 --- a/lms/static/sass/course/layout/_courseware_preview.scss +++ b/lms/static/sass/course/layout/_courseware_preview.scss @@ -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 { diff --git a/lms/static/sass/shared-v2/_components.scss b/lms/static/sass/shared-v2/_components.scss index cd484b0ba2..549298f9c0 100644 --- a/lms/static/sass/shared-v2/_components.scss +++ b/lms/static/sass/shared-v2/_components.scss @@ -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; + } + } +} diff --git a/lms/static/sass/shared-v2/_header.scss b/lms/static/sass/shared-v2/_header.scss index 69a28e70e7..de3ceaa6fc 100644 --- a/lms/static/sass/shared-v2/_header.scss +++ b/lms/static/sass/shared-v2/_header.scss @@ -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); diff --git a/lms/static/sass/shared-v2/_layouts.scss b/lms/static/sass/shared-v2/_layouts.scss index 5045a9210d..b9031a1bab 100644 --- a/lms/static/sass/shared-v2/_layouts.scss +++ b/lms/static/sass/shared-v2/_layouts.scss @@ -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(); diff --git a/lms/static/sass/shared-v2/_variables.scss b/lms/static/sass/shared-v2/_variables.scss index 0c0b6d01df..9d09fa220e 100644 --- a/lms/static/sass/shared-v2/_variables.scss +++ b/lms/static/sass/shared-v2/_variables.scss @@ -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); diff --git a/lms/templates/courseware/course_navigation.html b/lms/templates/courseware/course_navigation.html index 6e3b041412..a77fe168bb 100644 --- a/lms/templates/courseware/course_navigation.html +++ b/lms/templates/courseware/course_navigation.html @@ -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
% endif -% if show_preview_menu: - -% endif % if disable_tabs is UNDEFINED or not disable_tabs: %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}); - -% endif diff --git a/lms/templates/main.html b/lms/templates/main.html index 5c3d01e2ff..621b3e0c5a 100644 --- a/lms/templates/main.html +++ b/lms/templates/main.html @@ -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
diff --git a/lms/templates/preview_menu.html b/lms/templates/preview_menu.html new file mode 100644 index 0000000000..52953cbe6a --- /dev/null +++ b/lms/templates/preview_menu.html @@ -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) + %> + + + <% + 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}); + +% endif diff --git a/lms/templates/user_dropdown.html b/lms/templates/user_dropdown.html index dd0c14046c..ec029ac27f 100644 --- a/lms/templates/user_dropdown.html +++ b/lms/templates/user_dropdown.html @@ -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) %> <%! diff --git a/openedx/core/djangoapps/plugin_api/views.py b/openedx/core/djangoapps/plugin_api/views.py index ba0b34796c..27532085fb 100644 --- a/openedx/core/djangoapps/plugin_api/views.py +++ b/openedx/core/djangoapps/plugin_api/views.py @@ -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) diff --git a/openedx/features/course_bookmarks/views/course_bookmarks.py b/openedx/features/course_bookmarks/views/course_bookmarks.py index 9715fede27..f44f05770f 100644 --- a/openedx/features/course_bookmarks/views/course_bookmarks.py +++ b/openedx/features/course_bookmarks/views/course_bookmarks.py @@ -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, diff --git a/openedx/features/course_experience/templates/course_experience/course-home.html b/openedx/features/course_experience/templates/course_experience/course-home-fragment.html similarity index 75% rename from openedx/features/course_experience/templates/course_experience/course-home.html rename to openedx/features/course_experience/templates/course_experience/course-home-fragment.html index ee1ba91ca6..2d14b7be1a 100644 --- a/openedx/features/course_experience/templates/course_experience/course-home.html +++ b/openedx/features/course_experience/templates/course_experience/course-home-fragment.html @@ -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 name="course_name()"> -<% return _("{course_number} Courseware").format(course_number=course.display_number_with_default) %> - <%! 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 name="pagetitle">${course_name()} - -<%include file="../courseware/course_navigation.html" args="active_page='courseware'" /> - -<%block name="headextra"> -${HTML(outline_fragment.head_html())} - - -<%block name="js_extra"> -${HTML(outline_fragment.foot_html())} - - <%block name="content">