From 3456c1173b1c539626304f7b46153ea2e870f893 Mon Sep 17 00:00:00 2001 From: Giulio Gratta Date: Fri, 7 Feb 2014 14:17:18 -0800 Subject: [PATCH] Added "Edit in CMS" links throughout courseware --- common/djangoapps/xmodule_modifiers.py | 28 ++++- common/lib/xmodule/xmodule/course_module.py | 1 + .../xmodule/modulestore/inheritance.py | 4 + lms/djangoapps/courseware/courses.py | 26 +++- lms/djangoapps/courseware/module_render.py | 4 +- .../courseware/tests/test_courses.py | 22 ++-- .../courseware/tests/test_masquerade.py | 4 +- .../courseware/tests/test_module_render.py | 118 +++++++++++++++++- lms/djangoapps/courseware/views.py | 37 +++++- .../instructor/features/bulk_email.py | 2 +- lms/djangoapps/instructor/features/common.py | 2 +- .../instructor/views/instructor_dashboard.py | 3 +- lms/djangoapps/instructor/views/legacy.py | 1 - .../sass/course/courseware/_courseware.scss | 29 +++++ .../sass/multicourse/_course_about.scss | 26 +++- lms/templates/courseware/course_about.html | 6 + .../courseware/course_navigation.html | 2 +- lms/templates/courseware/info.html | 8 ++ .../courseware/instructor_dashboard.html | 18 +-- lms/templates/courseware/progress.html | 11 +- lms/templates/edit_unit_link.html | 6 + .../instructor_dashboard_2.html | 59 ++++----- lms/templates/staff_problem_info.html | 34 ++--- 23 files changed, 361 insertions(+), 90 deletions(-) create mode 100644 lms/templates/edit_unit_link.html diff --git a/common/djangoapps/xmodule_modifiers.py b/common/djangoapps/xmodule_modifiers.py index 55160f766f..77d590777f 100644 --- a/common/djangoapps/xmodule_modifiers.py +++ b/common/djangoapps/xmodule_modifiers.py @@ -17,6 +17,8 @@ from xmodule.seq_module import SequenceModule from xmodule.vertical_module import VerticalModule from xmodule.x_module import shim_xmodule_js, XModuleDescriptor, XModule from lms.lib.xblock.runtime import quote_slashes +from xmodule.modulestore import MONGO_MODULESTORE_TYPE +from xmodule.modulestore.django import modulestore, loc_mapper log = logging.getLogger(__name__) @@ -152,17 +154,33 @@ def grade_histogram(module_id): return grades -def add_staff_debug_info(user, block, view, frag, context): # pylint: disable=unused-argument +def add_staff_markup(user, block, view, frag, context): # pylint: disable=unused-argument """ Updates the supplied module with a new get_html function that wraps the output of the old get_html function with additional information - for admin users only, including a histogram of student answers and the - definition of the xmodule + for admin users only, including a histogram of student answers, the + definition of the xmodule, and a link to view the module in Studio + if it is a Studio edited, mongo stored course. - Does nothing if module is a SequenceModule or a VerticalModule. + Does nothing if module is a SequenceModule. """ # TODO: make this more general, eg use an XModule attribute instead - if isinstance(block, (SequenceModule, VerticalModule)): + if isinstance(block, VerticalModule): + # check that the course is a mongo backed Studio course before doing work + is_mongo_course = modulestore().get_modulestore_type(block.course_id) == MONGO_MODULESTORE_TYPE + is_studio_course = block.course_edit_method == "Studio" + + if is_studio_course and is_mongo_course: + # get relative url/location of unit in Studio + locator = loc_mapper().translate_location(block.course_id, block.location, False, True) + # build edit link to unit in CMS + edit_link = "//" + settings.CMS_BASE + locator.url_reverse('unit', '') + # return edit link in rendered HTML for display + return wrap_fragment(frag, render_to_string("edit_unit_link.html", {'frag_content': frag.content, 'edit_link': edit_link})) + else: + return frag + + if isinstance(block, SequenceModule): return frag block_id = block.id diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 7462babf4e..3afc44b85b 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -224,6 +224,7 @@ class CourseFields(object): scope=Scope.content) show_calculator = Boolean(help="Whether to show the calculator in this course", default=False, scope=Scope.settings) display_name = String(help="Display name for this module", default="Empty", display_name="Display Name", scope=Scope.settings) + course_edit_method = String(help="Method with which this course is edited.", default="Studio", scope=Scope.settings) show_chat = Boolean(help="Whether to show the chat widget in this course", default=False, scope=Scope.settings) tabs = CourseTabList(help="List of tabs to enable in this course", scope=Scope.settings, default=[]) end_of_course_survey_url = String(help="Url for the end-of-course survey", scope=Scope.settings) diff --git a/common/lib/xmodule/xmodule/modulestore/inheritance.py b/common/lib/xmodule/xmodule/modulestore/inheritance.py index 0a20716e30..ce5682665f 100644 --- a/common/lib/xmodule/xmodule/modulestore/inheritance.py +++ b/common/lib/xmodule/xmodule/modulestore/inheritance.py @@ -36,6 +36,10 @@ class InheritanceMixin(XBlockMixin): default=None, scope=Scope.user_state, ) + course_edit_method = String( + help="Method with which this course is edited.", + default="Studio", scope=Scope.settings + ) giturl = String( help="url root for course data git repository", scope=Scope.settings, diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index ea9536a387..d5cfc47977 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -9,7 +9,7 @@ from django.conf import settings from edxmako.shortcuts import render_to_string from xmodule.course_module import CourseDescriptor -from xmodule.modulestore import Location, XML_MODULESTORE_TYPE +from xmodule.modulestore import Location, XML_MODULESTORE_TYPE, MONGO_MODULESTORE_TYPE from xmodule.modulestore.django import modulestore, loc_mapper from xmodule.contentstore.content import StaticContent from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationError @@ -341,3 +341,27 @@ def get_cms_course_link(course): course.location.course_id, course.location, False, True ) return "//" + settings.CMS_BASE + locator.url_reverse('course/', '') + + +def get_cms_block_link(block, page): + """ + Returns a link to block_index for editing the course in cms, + assuming that the block is actually cms-backed. + """ + locator = loc_mapper().translate_location( + block.location.course_id, block.location, False, True + ) + return "//" + settings.CMS_BASE + locator.url_reverse(page, '') + + +def get_studio_url(course_id, page): + """ + Get the Studio URL of the page that is passed in. + """ + course = get_course_by_id(course_id) + is_studio_course = course.course_edit_method == "Studio" + is_mongo_course = modulestore().get_modulestore_type(course_id) == MONGO_MODULESTORE_TYPE + studio_link = None + if is_studio_course and is_mongo_course: + studio_link = get_cms_block_link(course, page) + return studio_link diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 9e6f246688..704247acaf 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -37,7 +37,7 @@ from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore, ModuleI18nService from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.util.duedate import get_extended_due_date -from xmodule_modifiers import replace_course_urls, replace_jump_to_id_urls, replace_static_urls, add_staff_debug_info, wrap_xblock +from xmodule_modifiers import replace_course_urls, replace_jump_to_id_urls, replace_static_urls, add_staff_markup, wrap_xblock from xmodule.lti_module import LTIModule from xmodule.x_module import XModuleDescriptor @@ -371,7 +371,7 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours if settings.FEATURES.get('DISPLAY_DEBUG_INFO_TO_STAFF'): if has_access(user, descriptor, 'staff', course_id): - block_wrappers.append(partial(add_staff_debug_info, user)) + block_wrappers.append(partial(add_staff_markup, user)) # These modules store data using the anonymous_student_id as a key. # To prevent loss of data, we will continue to provide old modules with diff --git a/lms/djangoapps/courseware/tests/test_courses.py b/lms/djangoapps/courseware/tests/test_courses.py index e15fefa4c3..32eb3c242f 100644 --- a/lms/djangoapps/courseware/tests/test_courses.py +++ b/lms/djangoapps/courseware/tests/test_courses.py @@ -14,8 +14,13 @@ from xmodule.tests.xml import factories as xml from xmodule.tests.xml import XModuleXmlImportTest from courseware.courses import ( - get_course_by_id, get_course, get_cms_course_link, course_image_url, - get_course_info_section, get_course_about_section + get_course_by_id, + get_course, + get_cms_course_link, + get_cms_block_link, + course_image_url, + get_course_info_section, + get_course_about_section ) from courseware.tests.helpers import get_request_for_user from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE, TEST_DATA_MIXED_MODULESTORE @@ -52,21 +57,18 @@ class CoursesTest(ModuleStoreTestCase): @override_settings( MODULESTORE=TEST_DATA_MONGO_MODULESTORE, CMS_BASE=CMS_BASE_TEST ) - def test_get_cms_course_link(self): + def test_get_cms_course_block_link(self): """ - Tests that get_cms_course_link_by_id returns the right thing + Tests that get_cms_course_link_by_id and get_cms_block_link_by_id return the right thing """ + cms_url = u"//{}/course/org.num.name/branch/draft/block/name".format(CMS_BASE_TEST) self.course = CourseFactory.create( org='org', number='num', display_name='name' ) - self.assertEqual( - u"//{}/course/org.num.name/branch/draft/block/name".format( - CMS_BASE_TEST - ), - get_cms_course_link(self.course) - ) + self.assertEqual(cms_url, get_cms_course_link(self.course)) + self.assertEqual(cms_url, get_cms_block_link(self.course, 'course')) @mock.patch( 'xmodule.modulestore.django.get_current_request_hostname', diff --git a/lms/djangoapps/courseware/tests/test_masquerade.py b/lms/djangoapps/courseware/tests/test_masquerade.py index abdbccf889..8767186f34 100644 --- a/lms/djangoapps/courseware/tests/test_masquerade.py +++ b/lms/djangoapps/courseware/tests/test_masquerade.py @@ -63,7 +63,7 @@ class TestStaffMasqueradeAsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase) def test_staff_debug_for_staff(self): resp = self.get_cw_section() - sdebug = '' + sdebug = 'Staff Debug Info' self.assertTrue(sdebug in resp.content) @@ -82,7 +82,7 @@ class TestStaffMasqueradeAsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase) self.assertEqual(togresp.content, '{"status": "student"}', '') resp = self.get_cw_section() - sdebug = '
Staff Debug Info
' + sdebug = 'Staff Debug Info' self.assertFalse(sdebug in resp.content) diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index ef7ae4cab3..55e26619fb 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -27,9 +27,12 @@ from xmodule.x_module import XModuleDescriptor from courseware import module_render as render from courseware.courses import get_course_with_access, course_image_url, get_course_info_section from courseware.model_data import FieldDataCache -from courseware.tests.factories import StudentModuleFactory, UserFactory +from courseware.tests.factories import StudentModuleFactory, UserFactory, GlobalStaffFactory from courseware.tests.tests import LoginEnrollmentTestCase + from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE +from courseware.tests.modulestore_config import TEST_DATA_MONGO_MODULESTORE +from courseware.tests.modulestore_config import TEST_DATA_XML_MODULESTORE from lms.lib.xblock.runtime import quote_slashes @@ -509,6 +512,119 @@ class TestHtmlModifiers(ModuleStoreTestCase): ) +class ViewInStudioTest(ModuleStoreTestCase): + """Tests for the 'View in Studio' link visiblity.""" + + def setUp(self): + """ Set up the user and request that will be used. """ + self.staff_user = GlobalStaffFactory.create() + self.request = RequestFactory().get('/') + self.request.user = self.staff_user + self.request.session = {} + self.module = None + + def _get_module(self, course_id, descriptor, location): + """ + Get the module from the course from which to pattern match (or not) the 'View in Studio' buttons + """ + field_data_cache = FieldDataCache.cache_for_descriptor_descendents( + course_id, + self.staff_user, + descriptor + ) + + self.module = render.get_module( + self.staff_user, + self.request, + location, + field_data_cache, + course_id, + ) + + def setup_mongo_course(self, course_edit_method='Studio'): + """ Create a mongo backed course. """ + course = CourseFactory.create( + course_edit_method=course_edit_method + ) + + descriptor = ItemFactory.create( + category='vertical', + ) + + self._get_module(course.id, descriptor, descriptor.location) + + def setup_xml_course(self): + """ + Define the XML backed course to use. + Toy courses are already loaded in XML and mixed modulestores. + """ + course_id = 'edX/toy/2012_Fall' + location = Location('i4x', 'edX', 'toy', 'chapter', 'Overview') + descriptor = modulestore().get_instance(course_id, location) + + self._get_module(course_id, descriptor, location) + + +@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) +class MongoViewInStudioTest(ViewInStudioTest): + """Test the 'View in Studio' link visibility in a mongo backed course.""" + + def setUp(self): + super(MongoViewInStudioTest, self).setUp() + + def test_view_in_studio_link_studio_course(self): + """Regular Studio courses should see 'View in Studio' links.""" + self.setup_mongo_course() + result_fragment = self.module.render('student_view') + self.assertIn('View Unit in Studio', result_fragment.content) + + def test_view_in_studio_link_xml_authored(self): + """Courses that change 'course_edit_method' setting can hide 'View in Studio' links.""" + self.setup_mongo_course(course_edit_method='XML') + result_fragment = self.module.render('student_view') + self.assertNotIn('View Unit in Studio', result_fragment.content) + + +@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) +class MixedViewInStudioTest(ViewInStudioTest): + """Test the 'View in Studio' link visibility in a mixed mongo backed course.""" + + def setUp(self): + super(MixedViewInStudioTest, self).setUp() + + def test_view_in_studio_link_mongo_backed(self): + """Mixed mongo courses that are mongo backed should see 'View in Studio' links.""" + self.setup_mongo_course() + result_fragment = self.module.render('student_view') + self.assertIn('View Unit in Studio', result_fragment.content) + + def test_view_in_studio_link_xml_authored(self): + """Courses that change 'course_edit_method' setting can hide 'View in Studio' links.""" + self.setup_mongo_course(course_edit_method='XML') + result_fragment = self.module.render('student_view') + self.assertNotIn('View Unit in Studio', result_fragment.content) + + def test_view_in_studio_link_xml_backed(self): + """Course in XML only modulestore should not see 'View in Studio' links.""" + self.setup_xml_course() + result_fragment = self.module.render('student_view') + self.assertNotIn('View Unit in Studio', result_fragment.content) + + +@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) +class XmlViewInStudioTest(ViewInStudioTest): + """Test the 'View in Studio' link visibility in an xml backed course.""" + + def setUp(self): + super(XmlViewInStudioTest, self).setUp() + + def test_view_in_studio_link_xml_backed(self): + """Course in XML only modulestore should not see 'View in Studio' links.""" + self.setup_xml_course() + result_fragment = self.module.render('student_view') + self.assertNotIn('View Unit in Studio', result_fragment.content) + + @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) @patch.dict('django.conf.settings.FEATURES', {'DISPLAY_DEBUG_INFO_TO_STAFF': True, 'DISPLAY_HISTOGRAMS_TO_STAFF': True}) @patch('courseware.module_render.has_access', Mock(return_value=True)) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 1ea573a8b5..cb34b55de2 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -1,3 +1,7 @@ +""" +Courseware views functions +""" + import logging import urllib @@ -20,7 +24,8 @@ from markupsafe import escape from courseware import grades from courseware.access import has_access -from courseware.courses import get_courses, get_course_with_access, sort_by_announcement +from courseware.courses import get_courses, get_course_with_access, get_studio_url, sort_by_announcement + from courseware.masquerade import setup_masquerade from courseware.model_data import FieldDataCache from .module_render import toc_for_course, get_module_for_descriptor, get_module @@ -46,6 +51,7 @@ log = logging.getLogger("edx.courseware") template_imports = {'urllib': urllib} + def user_groups(user): """ TODO (vshnayder): This is not used. When we have a new plan for groups, adjust appropriately. @@ -95,7 +101,7 @@ def render_accordion(request, course, chapter, section, field_data_cache): # grab the table of contents user = User.objects.prefetch_related("groups").get(id=request.user.id) - request.user = user # keep just one instance of User + request.user = user # keep just one instance of User toc = toc_for_course(user, request, course, chapter, section, field_data_cache) context = dict([ @@ -257,6 +263,8 @@ def index(request, course_id, chapter=None, section=None, u' far, should have gotten a course module for this user') return redirect(reverse('about_course', args=[course.id])) + studio_url = get_studio_url(course_id, 'course') + if chapter is None: return redirect_to_course_position(course_module) @@ -268,6 +276,7 @@ def index(request, course_id, chapter=None, section=None, 'init': '', 'fragment': Fragment(), 'staff_access': staff_access, + 'studio_url': studio_url, 'masquerade': masq, 'xqa_server': settings.FEATURES.get('USE_XQA_SERVER', 'http://xqa:server@content-qa.mitx.mit.edu/xqa'), 'reverifications': fetch_reverify_banner_info(request, course_id), @@ -294,7 +303,7 @@ def index(request, course_id, chapter=None, section=None, chapter_module = course_module.get_child_by(lambda m: m.url_name == chapter) if chapter_module is None: # User may be trying to access a chapter that isn't live yet - if masq=='student': # if staff is masquerading as student be kinder, don't 404 + if masq == 'student': # if staff is masquerading as student be kinder, don't 404 log.debug('staff masq as student: no chapter %s' % chapter) return redirect(reverse('courseware', args=[course.id])) raise Http404 @@ -303,7 +312,7 @@ def index(request, course_id, chapter=None, section=None, section_descriptor = chapter_descriptor.get_child_by(lambda m: m.url_name == section) if section_descriptor is None: # Specifically asked-for section doesn't exist - if masq=='student': # if staff is masquerading as student be kinder, don't 404 + if masq == 'student': # if staff is masquerading as student be kinder, don't 404 log.debug('staff masq as student: no section %s' % section) return redirect(reverse('courseware', args=[course.id])) raise Http404 @@ -317,7 +326,8 @@ def index(request, course_id, chapter=None, section=None, section_field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course_id, user, section_descriptor, depth=None) - section_module = get_module_for_descriptor(request.user, + section_module = get_module_for_descriptor( + request.user, request, section_descriptor, section_field_data_cache, @@ -336,6 +346,7 @@ def index(request, course_id, chapter=None, section=None, context['section_title'] = section_descriptor.display_name_with_default else: # section is none, so display a message + studio_url = get_studio_url(course_id, 'course') prev_section = get_current_child(chapter_module) if prev_section is None: # Something went wrong -- perhaps this chapter has no sections visible to the user @@ -347,6 +358,7 @@ def index(request, course_id, chapter=None, section=None, 'courseware/welcome-back.html', { 'course': course, + 'studio_url': studio_url, 'chapter_module': chapter_module, 'prev_section': prev_section, 'prev_section_url': prev_section_url @@ -461,6 +473,7 @@ def course_info(request, course_id): course = get_course_with_access(request.user, course_id, 'load') staff_access = has_access(request.user, course, 'staff') masq = setup_masquerade(request, staff_access) # allow staff to toggle masquerade on info page + studio_url = get_studio_url(course_id, 'course_info') reverifications = fetch_reverify_banner_info(request, course_id) context = { @@ -470,6 +483,7 @@ def course_info(request, course_id): 'course': course, 'staff_access': staff_access, 'masquerade': masq, + 'studio_url': studio_url, 'reverifications': reverifications, } @@ -537,6 +551,11 @@ def registered_for_course(course, user): @ensure_csrf_cookie @cache_if_anonymous def course_about(request, course_id): + """ + Display the course's about page. + + Assumes the course_id is in a valid format. + """ if microsite.get_value( 'ENABLE_MKTG_SITE', @@ -546,6 +565,8 @@ def course_about(request, course_id): course = get_course_with_access(request.user, course_id, 'see_exists') registered = registered_for_course(course, request.user) + staff_access = has_access(request.user, course, 'staff') + studio_url = get_studio_url(course_id, 'settings/details') if has_access(request.user, course, 'load'): course_target = reverse('info', args=[course.id]) @@ -575,6 +596,8 @@ def course_about(request, course_id): return render_to_response('courseware/course_about.html', { 'course': course, + 'staff_access': staff_access, + 'studio_url': studio_url, 'registered': registered, 'course_target': course_target, 'registration_price': registration_price, @@ -664,7 +687,7 @@ def _progress(request, course_id, student_id): student = User.objects.prefetch_related("groups").get(id=student.id) courseware_summary = grades.progress_summary(student, request, course) - + studio_url = get_studio_url(course_id, 'settings/grading') grade_summary = grades.grade(student, request, course) if courseware_summary is None: @@ -674,6 +697,7 @@ def _progress(request, course_id, student_id): context = { 'course': course, 'courseware_summary': courseware_summary, + 'studio_url': studio_url, 'grade_summary': grade_summary, 'staff_access': staff_access, 'student': student, @@ -701,6 +725,7 @@ def fetch_reverify_banner_info(request, course_id): reverifications[info.status].append(info) return reverifications + @login_required def submission_history(request, course_id, student_username, location): """Render an HTML fragment (meant for inclusion elsewhere) that renders a diff --git a/lms/djangoapps/instructor/features/bulk_email.py b/lms/djangoapps/instructor/features/bulk_email.py index 4d207e696f..821e6c9f73 100644 --- a/lms/djangoapps/instructor/features/bulk_email.py +++ b/lms/djangoapps/instructor/features/bulk_email.py @@ -117,7 +117,7 @@ def when_i_send_an_email(step, recipient): # pylint: disable=unused-argument # Go to the email section of the instructor dash world.visit('/courses/edx/888/Bulk_Email_Test_Course') world.css_click('a[href="/courses/edx/888/Bulk_Email_Test_Course/instructor"]') - world.css_click('div.beta-button-wrapper>a') + world.css_click('div.beta-button-wrapper>a.beta-button') world.css_click('a[data-section="send_email"]') # Select the recipient diff --git a/lms/djangoapps/instructor/features/common.py b/lms/djangoapps/instructor/features/common.py index c12273030a..d6c23a1518 100644 --- a/lms/djangoapps/instructor/features/common.py +++ b/lms/djangoapps/instructor/features/common.py @@ -77,7 +77,7 @@ def go_to_section(section_name): # course_info, membership, student_admin, data_download, analytics, send_email world.visit('/courses/edx/999/Test_Course') world.css_click('a[href="/courses/edx/999/Test_Course/instructor"]') - world.css_click('div.beta-button-wrapper>a') + world.css_click('div.beta-button-wrapper>a.beta-button') world.css_click('a[data-section="{0}"]'.format(section_name)) diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 33a385beec..3a0dbd50cf 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -60,8 +60,7 @@ def instructor_dashboard_2(request, course_id): sections.insert(3, _section_extensions(course)) # Gate access to course email by feature flag & by course-specific authorization - if settings.FEATURES['ENABLE_INSTRUCTOR_EMAIL'] and \ - is_studio_course and CourseAuthorization.instructor_email_enabled(course_id): + if settings.FEATURES['ENABLE_INSTRUCTOR_EMAIL'] and is_studio_course and CourseAuthorization.instructor_email_enabled(course_id): sections.append(_section_send_email(course_id, access, course)) # Gate access to Metrics tab by featue flag and staff authorization diff --git a/lms/djangoapps/instructor/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py index b3e932bbcc..f80d6ef617 100644 --- a/lms/djangoapps/instructor/views/legacy.py +++ b/lms/djangoapps/instructor/views/legacy.py @@ -851,7 +851,6 @@ def instructor_dashboard(request, course_id): # determine if this is a studio-backed course so we can provide a link to edit this course in studio is_studio_course = modulestore().get_modulestore_type(course_id) != XML_MODULESTORE_TYPE - studio_url = None if is_studio_course: studio_url = get_cms_course_link(course) diff --git a/lms/static/sass/course/courseware/_courseware.scss b/lms/static/sass/course/courseware/_courseware.scss index 5daaef6885..051b98f088 100644 --- a/lms/static/sass/course/courseware/_courseware.scss +++ b/lms/static/sass/course/courseware/_courseware.scss @@ -11,7 +11,36 @@ html.video-fullscreen{ } } +.wrap-instructor-info { + margin: ($baseline/2) ($baseline/4) 0 0; + overflow: hidden; + + &.studio-view { + position: relative; + top: -($baseline/2); + margin: 0; + } + + .instructor-info-action { + @extend %t-copy-sub2; + float: right; + margin-left: ($baseline/2); + padding: ($baseline/4) ($baseline/2); + border-radius: ($baseline/4); + background-color: $shadow-l2; + text-align: right; + text-transform: uppercase; + color: $lighter-base-font-color; + + &:hover { + background-color: $link-hover; + color: $white; + } + } +} + div.course-wrapper { + position: relative; section.course-content { @extend .content; diff --git a/lms/static/sass/multicourse/_course_about.scss b/lms/static/sass/multicourse/_course_about.scss index e6f30986df..1fcbf2a225 100644 --- a/lms/static/sass/multicourse/_course_about.scss +++ b/lms/static/sass/multicourse/_course_about.scss @@ -233,7 +233,31 @@ .container { @include clearfix; - + + .wrap-instructor-info { + &.studio-view { + position: relative; + margin: ($baseline/2) 0 0 0; + overflow: hidden; + } + + .instructor-info-action { + @extend %t-copy-sub2; + float: right; + padding: ($baseline/4) ($baseline/2); + border-radius: ($baseline/4); + background-color: $shadow-l2; + text-align: right; + text-transform: uppercase; + color: $lighter-base-font-color; + + &:hover { + background-color: $link-hover; + color: $white; + } + } + } + nav { border-bottom: 1px solid $border-color-2; @include box-sizing(border-box); diff --git a/lms/templates/courseware/course_about.html b/lms/templates/courseware/course_about.html index 7c1d098f22..b542eca149 100644 --- a/lms/templates/courseware/course_about.html +++ b/lms/templates/courseware/course_about.html @@ -201,6 +201,12 @@
+ % if staff_access and studio_url is not None: + + % endif +