Commit 3456c117 by Giulio Gratta

Added "Edit in CMS" links throughout courseware

parent 6292e559
......@@ -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
......
......@@ -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)
......
......@@ -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,
......
......@@ -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
......@@ -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
......
......@@ -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',
......
......@@ -63,7 +63,7 @@ class TestStaffMasqueradeAsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
def test_staff_debug_for_staff(self):
resp = self.get_cw_section()
sdebug = '<div aria-hidden="true"><a href="#i4x_edX_graded_problem_H1P1_debug" id="i4x_edX_graded_problem_H1P1_trig">Staff Debug Info</a></div>'
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 = '<div><a href="#i4x_edX_graded_problem_H1P1_debug" id="i4x_edX_graded_problem_H1P1_trig">Staff Debug Info</a></div>'
sdebug = 'Staff Debug Info'
self.assertFalse(sdebug in resp.content)
......
......@@ -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))
......
"""
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
......
......@@ -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
......
......@@ -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))
......
......@@ -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
......
......@@ -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)
......
......@@ -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;
......
......@@ -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);
......
......@@ -201,6 +201,12 @@
<section class="container">
<section class="details">
% if staff_access and studio_url is not None:
<div class="wrap-instructor-info studio-view">
<a class="instructor-info-action" href="${studio_url}">${_("View About Page in studio")}</a>
</div>
% endif
<nav>
<a href="#" class="active">${_("Overview")}</a>
## <a href="#">${_("FAQ")}</a>
......
......@@ -49,7 +49,7 @@ def url_class(is_active):
<%block name="extratabs" />
% if masquerade is not UNDEFINED:
% if staff_access and masquerade is not None:
<li style="float:right"><a href="#" id="staffstatus">${_("Staff view")}</a></li>
<li style="float:right"><a href="#" id="staffstatus">${_("Staff view")}</a></li>
% endif
% endif
</ol>
......
......@@ -29,6 +29,14 @@ $(document).ready(function(){
<div class="info-wrapper">
% if user.is_authenticated():
<section class="updates">
% if staff_access and masquerade is not UNDEFINED and studio_url is not None:
% if masquerade == 'staff':
<div class="wrap-instructor-info studio-view">
<a class="instructor-info-action" href="${studio_url}">${_("View Updates in Studio")}</a>
</div>
% endif
% endif
<h1>${_("Course Updates &amp; News")}</h1>
${get_course_info_section(request, course, 'updates')}
</section>
......
......@@ -117,16 +117,16 @@ function goto( mode)
<section class="container">
<div class="instructor-dashboard-wrapper">
%if settings.FEATURES.get('ENABLE_INSTRUCTOR_BETA_DASHBOARD'):
<div class="beta-button-wrapper"><a href="${ beta_dashboard_url }">${_("Try New Beta Dashboard")}</a></div>
%endif
%if studio_url:
## not checking access because if user can see this, they are at least course staff (with studio edit access)
<div class="studio-edit-link"><a href="${studio_url}" target="_blank">${_('Edit Course In Studio')}</a></div>
%endif
<section class="instructor-dashboard-content" id="instructor-dashboard-content">
<div class="wrap-instructor-info studio-view beta-button-wrapper">
%if studio_url:
<a class="instructor-info-action" href="${studio_url}">${_("View Course in Studio")}</a>
%endif
%if settings.FEATURES.get('ENABLE_INSTRUCTOR_BETA_DASHBOARD'):
<a class="instructor-info-action beta-button" href="${ beta_dashboard_url }">${_("Try New Beta Dashboard")}</a>
%endif
</div>
<h1>${_("Instructor Dashboard")}</h1>
<h2 class="navbar">[ <a href="#" onclick="goto('Grades');" class="${modeflag.get('Grades')}">Grades</a> |
......
......@@ -26,19 +26,26 @@ from django.conf import settings
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.stack.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.symbol.js')}"></script>
<script>
${progress_graph.body(grade_summary, course.grade_cutoffs, "grade-detail-graph", not course.no_grade, not course.no_grade)}
${progress_graph.body(grade_summary, course.grade_cutoffs, "grade-detail-graph", not course.no_grade, not course.no_grade)}
</script>
</%block>
<%include file="/dashboard/_dashboard_prompt_midcourse_reverify.html" />
<%include file="/courseware/course_navigation.html" args="active_page='progress'" />
<div class="container">
<div class="profile-wrapper">
<div class="course-info" id="course-info-progress" aria-label="${_('Course Progress')}">
% if staff_access and studio_url is not None:
<div class="wrap-instructor-info">
<a class="instructor-info-action studio-view" href="${studio_url}">${_("View Grading in studio")}</a>
</div>
% endif
<header>
<h1>${_("Course Progress for Student '{username}' ({email})").format(username=student.username, email=student.email)}</h1>
<h1>${_("Course Progress for Student '{username}' ({email})").format(username=student.username, email=student.email)}</h1>
</header>
%if not course.disable_progress_graph:
......
<%! from django.utils.translation import ugettext as _ %>
<div class="wrap-instructor-info studio-view">
<a class="instructor-info-action" href="${edit_link}">${_("View Unit in Studio")}</a>
</div>
${frag_content}
\ No newline at end of file
......@@ -53,34 +53,35 @@
<script language="JavaScript" type="text/javascript"></script>
<section class="container">
<div class="instructor-dashboard-wrapper-2">
<div class="olddash-button-wrapper"><a href="${ old_dashboard_url }"> ${_("Back to Standard Dashboard")} </a></div>
%if studio_url:
## not checking access because if user can see this, they are at least course staff (with studio edit access)
<div class="studio-edit-link"><a href="${studio_url}" target="_blank">${_('Edit Course In Studio')}</a></div>
%endif
<section class="instructor-dashboard-content-2" id="instructor-dashboard-content">
<h1>${_("Instructor Dashboard")}</h1>
<hr />
## links which are tied to idash-sections below.
## the links are acativated and handled in instructor_dashboard.coffee
## when the javascript loads, it clicks on the first section
<h2 class="instructor-nav">
% for section_data in sections:
<a href="" data-section="${ section_data['section_key'] }">${_(section_data['section_display_name'])}</a>
% endfor
</h2>
## each section corresponds to a section_data sub-dictionary provided by the view
## to keep this short, sections can be pulled out into their own files
% for section_data in sections:
<section id="${ section_data['section_key'] }" class="idash-section">
<%include file="${ section_data['section_key'] }.html" args="section_data=section_data" />
<div class="instructor-dashboard-wrapper-2">
<section class="instructor-dashboard-content-2" id="instructor-dashboard-content">
<div class="wrap-instructor-info studio-view">
%if studio_url:
<a class="instructor-info-action" href="${studio_url}">${_("View Course in Studio")}</a>
%endif
<a class="instructor-info-action" href="${ old_dashboard_url }"> ${_("Back to Standard Dashboard")} </a>
</div>
<h1>${_("Instructor Dashboard")}</h1>
<hr />
## links which are tied to idash-sections below.
## the links are acativated and handled in instructor_dashboard.coffee
## when the javascript loads, it clicks on the first section
<h2 class="instructor-nav">
% for section_data in sections:
<a href="" data-section="${ section_data['section_key'] }">${_(section_data['section_display_name'])}</a>
% endfor
</h2>
## each section corresponds to a section_data sub-dictionary provided by the view
## to keep this short, sections can be pulled out into their own files
% for section_data in sections:
<section id="${ section_data['section_key'] }" class="idash-section">
<%include file="${ section_data['section_key'] }.html" args="section_data=section_data" />
</section>
% endfor
</section>
% endfor
</section>
</div>
</div>
</section>
......@@ -4,24 +4,26 @@
${block_content}
%if location.category in ['problem','video','html','combinedopenended','graphical_slider_tool']:
% if edit_link:
<div>
<a href="${edit_link}">Edit</a>
% if xqa_key:
/ <a href="#${element_id}_xqa-modal" onclick="javascript:getlog('${element_id}', {
'location': '${location}',
'xqa_key': '${xqa_key}',
'category': '${category}',
'user': '${user}'
})" id="${element_id}_xqa_log">QA</a>
% endif
</div>
<div>
<a href="${edit_link}">Edit</a>
% if xqa_key:
/ <a href="#${element_id}_xqa-modal" onclick="javascript:getlog('${element_id}', {
'location': '${location}',
'xqa_key': '${xqa_key}',
'category': '${category}',
'user': '${user}'
})" id="${element_id}_xqa_log">QA</a>
% endif
</div>
% endif
<div aria-hidden="true"><a href="#${element_id}_debug" id="${element_id}_trig">${_("Staff Debug Info")}</a></div>
<div aria-hidden="true" class="wrap-instructor-info">
<a class="instructor-info-action" href="#${element_id}_debug" id="${element_id}_trig">${_("Staff Debug Info")}</a>
% if settings.FEATURES.get('ENABLE_STUDENT_HISTORY_VIEW') and \
location.category == 'problem':
<div aria-hidden="true"><a href="#${element_id}_history" id="${element_id}_history_trig">${_("Submission history")}</a></div>
% endif
% if settings.FEATURES.get('ENABLE_STUDENT_HISTORY_VIEW') and \
location.category == 'problem':
<a class="instructor-info-action" href="#${element_id}_history" id="${element_id}_history_trig">${_("Submission history")}</a>
% endif
</div>
<section aria-hidden="true" id="${element_id}_xqa-modal" class="modal xqa-modal" style="width:80%; left:20%; height:80%; overflow:auto" >
<div class="inner-wrapper">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment