Commit fe884029 by Andy Armstrong

Merge branch 'master' into alasdair/fedx-118-pattern-library-styles-with-current-partials

parents c42b36cc 56d003e4
......@@ -485,7 +485,6 @@ STATIC_ROOT = ENV_ROOT / "staticfiles" / EDX_PLATFORM_REVISION
STATICFILES_DIRS = [
COMMON_ROOT / "static",
PROJECT_ROOT / "static",
LMS_ROOT / "static",
# This is how you would use the textbook images locally
# ("book", ENV_ROOT / "book_images"),
......
......@@ -54,7 +54,7 @@ from openedx.core.djangolib.js_utils import (
<body class="${static.dir_rtl()} <%block name='bodyclass'></%block> lang_${LANGUAGE_CODE}">
<%block name="view_notes"></%block>
<a class="nav-skip" href="#content">${_("Skip to main content")}</a>
<a class="nav-skip" href="#main">${_("Skip to main content")}</a>
<script type="text/javascript">
window.baseUrl = "${settings.STATIC_URL | n, js_escaped_string}";
......@@ -72,9 +72,11 @@ from openedx.core.djangolib.js_utils import (
<%block name="page_alert"></%block>
</div>
<div id="content" tabindex="-1">
<%block name="content"></%block>
</div>
<main id="main" aria-label="Content" tabindex="-1">
<div id="content">
<%block name="content"></%block>
</div>
</main>
% if user.is_authenticated():
<%include file="widgets/sock.html" args="online_help_token=online_help_token" />
......
......@@ -22,7 +22,7 @@ from openedx.core.djangolib.js_utils import js_escaped_string
</%block>
<%block name="content">
<div id="content" tabindex="-1">
<div id="content">
<div class="wrapper-mast wrapper">
<header class="mast mast-wizard has-actions">
<h1 class="page-header">
......
......@@ -374,6 +374,8 @@ class VideoEventTransformer(EventTransformer):
u'edx.video.seeked': u'seek_video',
u'edx.video.transcript.shown': u'show_transcript',
u'edx.video.transcript.hidden': u'hide_transcript',
u'edx.video.language_menu.shown': u'video_show_cc_menu',
u'edx.video.language_menu.hidden': u'video_hide_cc_menu',
}
is_legacy_event = True
......
......@@ -117,17 +117,17 @@
});
});
it('can emit "video_show_cc_menu" event', function () {
it('can emit "edx.video.language_menu.shown" event', function () {
state.el.trigger('language_menu:show');
expect(Logger.log).toHaveBeenCalledWith('video_show_cc_menu', {
expect(Logger.log).toHaveBeenCalledWith('edx.video.language_menu.shown', {
id: 'id',
code: 'html5'
});
});
it('can emit "video_hide_cc_menu" event', function () {
it('can emit "edx.video.language_menu.hidden" event', function () {
state.el.trigger('language_menu:hide');
expect(Logger.log).toHaveBeenCalledWith('video_hide_cc_menu', {
expect(Logger.log).toHaveBeenCalledWith('edx.video.language_menu.hidden', {
id: 'id',
code: 'html5',
language: 'en'
......@@ -135,7 +135,7 @@
});
it('can emit "show_transcript" event', function () {
state.el.trigger('captions:show');
state.el.trigger('transcript:show');
expect(Logger.log).toHaveBeenCalledWith('show_transcript', {
id: 'id',
code: 'html5',
......@@ -144,7 +144,7 @@
});
it('can emit "hide_transcript" event', function () {
state.el.trigger('captions:hide');
state.el.trigger('transcript:hide');
expect(Logger.log).toHaveBeenCalledWith('hide_transcript', {
id: 'id',
code: 'html5',
......@@ -152,6 +152,24 @@
});
});
it('can emit "edx.video.closed_captions.shown" event', function () {
state.el.trigger('captions:show');
expect(Logger.log).toHaveBeenCalledWith('edx.video.closed_captions.shown', {
id: 'id',
code: 'html5',
current_time: 10
});
});
it('can emit "edx.video.closed_captions.hidden" event', function () {
state.el.trigger('captions:hide');
expect(Logger.log).toHaveBeenCalledWith('edx.video.closed_captions.hidden', {
id: 'id',
code: 'html5',
current_time: 10
});
});
it('can destroy itself', function () {
var plugin = state.videoEventsPlugin;
spyOn($.fn, 'off').and.callThrough();
......@@ -167,6 +185,8 @@
'speedchange': plugin.onSpeedChange,
'language_menu:show': plugin.onShowLanguageMenu,
'language_menu:hide': plugin.onHideLanguageMenu,
'transcript:show': plugin.onShowTranscript,
'transcript:hide': plugin.onHideTranscript,
'captions:show': plugin.onShowCaptions,
'captions:hide': plugin.onHideCaptions,
'destroy': plugin.destroy
......
......@@ -17,7 +17,9 @@ define('video/09_events_plugin.js', [], function() {
_.bindAll(this, 'onReady', 'onPlay', 'onPause', 'onEnded', 'onSeek',
'onSpeedChange', 'onShowLanguageMenu', 'onHideLanguageMenu', 'onSkip',
'onShowCaptions', 'onHideCaptions', 'destroy');
'onShowTranscript', 'onHideTranscript', 'onShowCaptions', 'onHideCaptions',
'destroy');
this.state = state;
this.options = _.extend({}, options);
this.state.videoEventsPlugin = this;
......@@ -45,6 +47,8 @@ define('video/09_events_plugin.js', [], function() {
'speedchange': this.onSpeedChange,
'language_menu:show': this.onShowLanguageMenu,
'language_menu:hide': this.onHideLanguageMenu,
'transcript:show': this.onShowTranscript,
'transcript:hide': this.onHideTranscript,
'captions:show': this.onShowCaptions,
'captions:hide': this.onHideCaptions,
'destroy': this.destroy
......@@ -101,21 +105,29 @@ define('video/09_events_plugin.js', [], function() {
},
onShowLanguageMenu: function () {
this.log('video_show_cc_menu');
this.log('edx.video.language_menu.shown');
},
onHideLanguageMenu: function () {
this.log('video_hide_cc_menu', { language: this.getCurrentLanguage() });
this.log('edx.video.language_menu.hidden', { language: this.getCurrentLanguage() });
},
onShowCaptions: function () {
onShowTranscript: function () {
this.log('show_transcript', {current_time: this.getCurrentTime()});
},
onHideCaptions: function () {
onHideTranscript: function () {
this.log('hide_transcript', {current_time: this.getCurrentTime()});
},
onShowCaptions: function () {
this.log('edx.video.closed_captions.shown', {current_time: this.getCurrentTime()});
},
onHideCaptions: function () {
this.log('edx.video.closed_captions.hidden', {current_time: this.getCurrentTime()});
},
getCurrentTime: function () {
var player = this.state.videoPlayer;
return player ? player.currentTime : 0;
......
......@@ -398,7 +398,7 @@
// present instead of on the container hover, since it wraps
// the "CC" and "Transcript" buttons as well.
if ($(event.currentTarget).find('.lang').length) {
this.state.el.trigger('language_menu:show');
this.state.el.trigger('language_menu:hide');
}
},
......@@ -1131,6 +1131,8 @@
this.captionDisplayEl
.text(gettext('(Caption will be displayed when you start playing the video.)'));
}
this.state.el.trigger('captions:show');
},
hideClosedCaptions: function() {
......@@ -1144,6 +1146,8 @@
.removeClass('is-active')
.find('.control-text')
.text(gettext('Turn on closed captioning'));
this.state.el.trigger('captions:hide');
},
updateCaptioningCookie: function(method) {
......@@ -1191,7 +1195,7 @@
state.el.addClass('closed');
text = gettext('Turn on transcripts');
if (trigger_event) {
this.state.el.trigger('captions:hide');
this.state.el.trigger('transcript:hide');
}
transcriptControlEl
......@@ -1204,7 +1208,7 @@
this.scrollCaption();
text = gettext('Turn off transcripts');
if (trigger_event) {
this.state.el.trigger('captions:show');
this.state.el.trigger('transcript:show');
}
transcriptControlEl
......
......@@ -3,6 +3,7 @@ Acceptance tests for Studio.
"""
from bok_choy.web_app_test import WebAppTest
from flaky import flaky
from ...pages.studio.asset_index import AssetIndexPage
from ...pages.studio.auto_auth import AutoAuthPage
......@@ -94,6 +95,7 @@ class CoursePagesTest(StudioCourseTest):
self.dashboard_page.visit()
self.assertEqual(self.browser.current_url.strip('/').rsplit('/')[-1], 'home')
@flaky # TODO: FEDX-88
def test_page_existence(self):
"""
Make sure that all these pages are accessible once you have a course.
......
<section class="container">
<div class="wrapper-student-notes">
<div class="student-notes">
<main id="main" aria-label="Content" tabindex="-1">
<div class="title-search-container">
<div class="wrapper-title">
<h1 class="page-title">
......@@ -42,7 +42,7 @@
<span class="copy">Loading</span>
</div>
</section>
</main>
</div>
</div>
</section>
......@@ -26,13 +26,21 @@
&.request-pending {
border-top: 2px solid $orange;
}
&.request-denied {
border-top: 2px solid $red;
}
&.request-approved {
border-top: 2px solid $green;
}
}
#api-access-status {
@extend %t-copy-base;
}
#api-access-request {
.api-management-form {
padding: 0 $baseline $baseline $baseline;
......@@ -91,4 +99,17 @@
text-transform: none;
}
}
.application-info {
margin: $baseline 0;
p {
@extend %t-copy-base;
margin: $baseline/2 0;
.application-label {
@extend %t-weight4;
}
}
}
}
......@@ -12,7 +12,7 @@
${_("{platform_name} API Access Request").format(platform_name=settings.PLATFORM_NAME)}
</h1>
<form action="" method="post" id="api-access-request">
<form action="" method="post" class="api-management-form">
<input type="hidden" id="csrf_token" name="csrfmiddlewaretoken" value="${csrf_token}">
${form.as_p() | n}
<input id="api-access-submit" type="submit" value="${_('Request API Access')}"/>
......
......@@ -11,17 +11,52 @@ from openedx.core.djangolib.markup import Text, HTML
<div id="api-access-wrapper">
<h1 id="api-access-request-header">${_("{platform_name} API Access Request").format(platform_name=settings.PLATFORM_NAME)}</h1>
<div class="request-status request-${status}">
% if status == ApiAccessRequest.PENDING:
## Translators: "platform_name" is the name of this Open edX installation. "link_start" and "link_end" are the HTML for a link to the API documentation. "api_support_email_link" is HTML for a link to email the API support staff.
<p id="api-access-status">${Text(_('Your request to access the {platform_name} Course Catalog API is being processed. You will receive a message at the email address in your profile when processing is complete. You can also return to this page to see the status of your API access request. To learn more about the {platform_name} Course Catalog API, visit {link_start}our API documentation page{link_end}. For questions about using this API, visit our FAQ page or contact {api_support_email_link}.')).format(
platform_name=Text(settings.PLATFORM_NAME),
link_start=HTML('<a href="{}">').format(Text(api_support_link)),
link_end=HTML('</a>'),
api_support_email_link=HTML('<a href="mailto:{email}">{email}</a>').format(email=Text(api_support_email))
)}</p>
% endif
<p id="api-access-status">
% if status == ApiAccessRequest.PENDING:
## Translators: "platform_name" is the name of this Open edX installation.
${Text(_('Your request to access the {platform_name} Course Catalog API is being processed. You will receive a message at the email address in your profile when processing is complete. You can also return to this page to see the status of your API access request.')).format(
platform_name=Text(settings.PLATFORM_NAME)
)}
## TODO (ECOM-3946): Add status text for 'active' and 'denied', as well as API client creation.
% elif status == ApiAccessRequest.DENIED:
## Translators: "platform_name" is the name of this Open edX installation. "api_support_email_link" is HTML for a link to email the API support staff.
${Text(_('Your request to access the {platform_name} Course Catalog API has been denied. If you think this is an error, or for other questions about using this API, contact {api_support_email_link}.')).format(
platform_name=Text(settings.PLATFORM_NAME),
api_support_email_link=HTML('<a href="mailto:{email}">{email}</a>').format(email=Text(api_support_email))
)}
% elif status == ApiAccessRequest.APPROVED:
${Text(_('Your request to access the {platform_name} Course Catalog API has been approved.')).format(
platform_name=Text(settings.PLATFORM_NAME)
)}
% if application:
<div class="application-info">
<p><span class="application-label">${_("Application Name") + ":"}</span> ${application.name}</p>
<p><span class="application-label">${_("API Client ID") + ":"}</span> ${application.client_id}</p>
<p><span class="application-label">${_("API Client Secret") + ":"}</span> ${application.client_secret}</p>
<p><span class="application-label">${_("Redirect URLs") + ":"}</span> ${application.redirect_uris}</p>
</div>
<p>${_('If you would like to regenerate your API client information, please use the form below.')}</p>
% endif
<form id="api-form-fields" method="post" class="api-management-form">
<input type="hidden" id="csrf_token" name="csrfmiddlewaretoken" value="${csrf_token}">
${form.as_p() | n}
<input id="api-access-submit" type="submit" value="${_('Generate API client credentials')}"/>
</form>
% endif
</p>
<p>
## Translators: "platform_name" is the name of this Open edX installation. "link_start" and "link_end" are the HTML for a link to the API documentation. "api_support_email_link" is HTML for a link to email the API support staff.
${Text(_('To learn more about the {platform_name} Course Catalog API, visit {link_start}our API documentation page{link_end}. For questions about using this API, contact {api_support_email_link}.')).format(
platform_name=Text(settings.PLATFORM_NAME),
link_start=HTML('<a href="{}">').format(Text(api_support_link)),
link_end=HTML('</a>'),
api_support_email_link=HTML('<a href="mailto:{email}">{email}</a>').format(email=Text(api_support_email))
)}
</p>
</div>
</div>
......@@ -10,7 +10,6 @@ from openedx.core.djangolib.js_utils import (
%>
<%block name="pagetitle">${_("CCX Coach Dashboard")}</%block>
<%block name="nav_skip">#ccx-coach-dashboard-content</%block>
<%block name="headextra">
<%static:css group='style-course-vendor'/>
......@@ -23,66 +22,68 @@ from openedx.core.djangolib.js_utils import (
<section class="container">
<div class="instructor-dashboard-wrapper-2">
<section class="instructor-dashboard-content-2" id="ccx-coach-dashboard-content">
<h1>${_("CCX Coach Dashboard")}</h1>
<main id="main" aria-label="Content" tabindex="-1">
<section class="instructor-dashboard-content-2" id="ccx-coach-dashboard-content">
<h1>${_("CCX Coach Dashboard")}</h1>
%if not ccx:
% if messages:
<ul class="messages">
% for message in messages:
% if message.tags:
<li class="${message.tags}">${message}</li>
% else:
<li>${message}</li>
% endif
% endfor
%if not ccx:
% if messages:
<ul class="messages">
% for message in messages:
% if message.tags:
<li class="${message.tags}">${message}</li>
% else:
<li>${message}</li>
% endif
% endfor
</ul>
% endif
<section>
<p class="request-response-error" id="ccx-create-message"></p>
<form action="${create_ccx_url}" class="ccx-form" method="POST" onsubmit="return validateForm(this)">
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}"/>
<div class="field">
<label class="sr" for="ccx_name">${_('Name your CCX')}</label>
<input name="name" id="ccx_name" placeholder="${_('Name your CCX')}"/><br/>
</div>
<div class="field">
<button id="create-ccx" type="submit">${_('Create a new Custom Course for edX')}</button>
</div>
</form>
</section>
%endif
%if ccx:
<ul class="instructor-nav">
<li class="nav-item">
<a href="#" data-section="membership">${_("Enrollment")}</a>
</li>
<li class="nav-item">
<a href="#" data-section="schedule">${_("Schedule")}</a>
</li>
<li class="nav-item">
<a href="#" data-section="student_admin">${_("Student Admin")}</a>
</li>
<li class="nav-item">
<a href="#" data-section="grading_policy">${_("Grading Policy")}</a>
</li>
</ul>
% endif
<section>
<p class="request-response-error" id="ccx-create-message"></p>
<form action="${create_ccx_url}" class="ccx-form" method="POST" onsubmit="return validateForm(this)">
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}"/>
<div class="field">
<label class="sr" for="ccx_name">${_('Name your CCX')}</label>
<input name="name" id="ccx_name" placeholder="${_('Name your CCX')}"/><br/>
</div>
<div class="field">
<button id="create-ccx" type="submit">${_('Create a new Custom Course for edX')}</button>
</div>
</form>
</section>
%endif
<section id="membership" class="idash-section">
<%include file="enrollment.html" args="" />
</section>
<section id="schedule" class="idash-section">
<%include file="schedule.html" args="" />
</section>
<section id="student_admin" class="idash-section">
<%include file="student_admin.html" args="" />
</section>
<section id="grading_policy" class="idash-section">
<%include file="grading_policy.html" args="" />
</section>
%endif
%if ccx:
<ul class="instructor-nav">
<li class="nav-item">
<a href="#" data-section="membership">${_("Enrollment")}</a>
</li>
<li class="nav-item">
<a href="#" data-section="schedule">${_("Schedule")}</a>
</li>
<li class="nav-item">
<a href="#" data-section="student_admin">${_("Student Admin")}</a>
</li>
<li class="nav-item">
<a href="#" data-section="grading_policy">${_("Grading Policy")}</a>
</li>
</ul>
<section id="membership" class="idash-section">
<%include file="enrollment.html" args="" />
</section>
<section id="schedule" class="idash-section">
<%include file="schedule.html" args="" />
</section>
<section id="student_admin" class="idash-section">
<%include file="student_admin.html" args="" />
</section>
<section id="grading_policy" class="idash-section">
<%include file="grading_policy.html" args="" />
</section>
%endif
</section>
</main>
</div>
</section>
......
......@@ -28,48 +28,50 @@
<%block name="pagetitle">${_("Courses")}</%block>
<section class="find-courses">
<section class="courses-container">
% if course_discovery_enabled:
<div id="discovery-form" role="search" aria-label="course" class="wrapper-search-context">
<div id="discovery-message" class="search-status-label"></div>
<form class="wrapper-search-input">
<label for="discovery-input" class="sr">${_('Search for a course')}</label>
<input id="discovery-input" class="discovery-input" placeholder="${_('Search for a course')}" type="text"/>
<button type="submit" class="button postfix discovery-submit" aria-label="${_('Search')}">
<i class="icon fa fa-search" aria-hidden="true"></i>
<div aria-live="polite" aria-relevant="all">
<div id="loading-indicator" class="loading-spinner hidden">
<i class="icon fa fa-spinner fa-spin"></i>
<span class="sr">${_('Loading')}</span>
</div>
</div>
</button>
</form>
</div>
<main id="main" aria-label="Content" tabindex="-1">
<section class="find-courses">
<section class="courses-container">
% if course_discovery_enabled:
<div id="discovery-form" role="search" aria-label="course" class="wrapper-search-context">
<div id="discovery-message" class="search-status-label"></div>
<form class="wrapper-search-input">
<label for="discovery-input" class="sr">${_('Search for a course')}</label>
<input id="discovery-input" class="discovery-input" placeholder="${_('Search for a course')}" type="text"/>
<button type="submit" class="button postfix discovery-submit" aria-label="${_('Search')}">
<i class="icon fa fa-search" aria-hidden="true"></i>
<div aria-live="polite" aria-relevant="all">
<div id="loading-indicator" class="loading-spinner hidden">
<i class="icon fa fa-spinner fa-spin"></i>
<span class="sr">${_('Loading')}</span>
</div>
</div>
</button>
</form>
</div>
<div id="filter-bar" class="filters hide-phone is-collapsed">
</div>
% endif
<div id="filter-bar" class="filters hide-phone is-collapsed">
</div>
% endif
<div class="courses${'' if course_discovery_enabled else ' no-course-discovery'}" role="region" aria-label="${_('List of Courses')}">
<ul class="courses-listing">
%for course in courses:
<li class="courses-listing-item">
<%include file="../course.html" args="course=course" />
</li>
%endfor
</ul>
</div>
<div class="courses${'' if course_discovery_enabled else ' no-course-discovery'}" role="region" aria-label="${_('List of Courses')}">
<ul class="courses-listing">
%for course in courses:
<li class="courses-listing-item">
<%include file="../course.html" args="course=course" />
</li>
%endfor
</ul>
</div>
% if course_discovery_enabled:
<aside aria-label="${_('Refine Your Search')}" class="search-facets phone-menu">
<h2 class="header-search-facets">${_('Refine Your Search')}</h2>
<section class="search-facets-lists">
</section>
</aside>
% endif
% if course_discovery_enabled:
<aside aria-label="${_('Refine Your Search')}" class="search-facets phone-menu">
<h2 class="header-search-facets">${_('Refine Your Search')}</h2>
<section class="search-facets-lists">
</section>
</aside>
% endif
</section>
</section>
</section>
</section>
</main>
......@@ -37,8 +37,6 @@ ${static.get_page_title_breadcrumbs(course_name())}
<%static:css group='style-student-notes'/>
% endif
<%block name="nav_skip">${"#content" if section_title else "#content"}</%block>
<%include file="../discussion/_js_head_dependencies.html" />
${fragment.head_html()}
</%block>
......
......@@ -57,8 +57,6 @@ ${static.get_page_title_breadcrumbs(course_name())}
<%static:css group='style-student-notes'/>
% endif
<%block name="nav_skip">${"#content" if section_title else "#content"}</%block>
<%include file="../discussion/_js_head_dependencies.html" />
${fragment.head_html()}
</%block>
......@@ -170,7 +168,8 @@ ${fragment.foot_html()}
</div>
% endif
<section class="course-content" id="course-content" role="main" aria-label="Content">
<section class="course-content" id="course-content">
<main id="main" aria-label="Content" tabindex="-1">
% if getattr(course, 'entrance_exam_enabled') and \
getattr(course, 'entrance_exam_minimum_score_pct') and \
entrance_exam_current_score is not UNDEFINED:
......@@ -199,9 +198,10 @@ ${fragment.foot_html()}
)}
</p>
% endif
% endif
% endif
${fragment.body_html()}
${fragment.body_html()}
</main>
</section>
<section class="courseware-results-wrapper">
......
......@@ -52,57 +52,60 @@ from openedx.core.djangolib.markup import Text, HTML
</%block>
<%block name="bodyclass">view-in-course view-course-info ${course.css_class or ''}</%block>
<section class="container">
<div class="home">
<div class="page-header-main">
<h1 class="page-title">${_("Welcome to {org}'s {course_name}!").format(org=course.display_org_with_default, course_name=course.display_number_with_default)}</h1>
<h2 class="page-subtitle">${course.display_name_with_default}</h2>
</div>
% if last_accessed_courseware_url:
<div class="page-header-secondary">
<a href="${last_accessed_courseware_url}" class="last-accessed-link">${_("Resume Course")}</a>
</div>
% endif
</div>
<div class="info-wrapper">
% if user.is_authenticated():
<section class="updates">
% if studio_url is not None and masquerade and masquerade.role == 'staff':
<div class="wrap-instructor-info studio-view">
<a class="instructor-info-action" href="${studio_url}">
${_("View Updates in Studio")}
</a>
<main id="main" aria-label="Content" tabindex="-1">
<section class="container">
<div class="home">
<div class="page-header-main">
<h1 class="page-title">${_("Welcome to {org}'s {course_name}!").format(org=course.display_org_with_default, course_name=course.display_number_with_default)}</h1>
<h2 class="page-subtitle">${course.display_name_with_default}</h2>
</div>
% if last_accessed_courseware_url:
<div class="page-header-secondary">
<a href="${last_accessed_courseware_url}" class="last-accessed-link">${_("Resume Course")}</a>
</div>
% endif
</div>
% endif
<div class="info-wrapper">
% if user.is_authenticated():
<section class="updates">
% if studio_url is not None and masquerade and masquerade.role == 'staff':
<div class="wrap-instructor-info studio-view">
<a class="instructor-info-action" href="${studio_url}">
${_("View Updates in Studio")}
</a>
</div>
% endif
<h1>${_("Course Updates and News")}</h1>
${HTML(get_course_info_section(request, masquerade_user, course, 'updates'))}
<h1>${_("Course Updates and News")}</h1>
${HTML(get_course_info_section(request, masquerade_user, course, 'updates'))}
## CourseTalk widget
% if show_coursetalk_widget:
<div class="coursetalk-write-reviews">
<div id="ct-custom-read-review-widget" data-provider="${platform_key}" data-course="${course_review_key}"></div>
</div>
% endif
</section>
<section aria-label="${_('Handout Navigation')}" class="handouts">
% if SelfPacedConfiguration.current().enable_course_home_improvements:
<h1 class="handouts-header">${_("Important Course Dates")}</h1>
${HTML(get_course_date_summary(course, user))}
% endif
## CourseTalk widget
% if show_coursetalk_widget:
<div class="coursetalk-write-reviews">
<div id="ct-custom-read-review-widget" data-provider="${platform_key}" data-course="${course_review_key}"></div>
</div>
% endif
</section>
<section aria-label="${_('Handout Navigation')}" class="handouts">
% if SelfPacedConfiguration.current().enable_course_home_improvements:
<h1 class="handouts-header">${_("Important Course Dates")}</h1>
${HTML(get_course_date_summary(course, user))}
% endif
<h1 class="handouts-header">${_(course.info_sidebar_name)}</h1>
${HTML(get_course_info_section(request, masquerade_user, course, 'handouts'))}
</section>
% else:
<section class="updates">
<h1 class="handouts-header">${_("Course Updates and News")}</h1>
${HTML(get_course_info_section(request, masquerade_user, course, 'guest_updates'))}
</section>
<section aria-label="${_('Handout Navigation')}" class="handouts">
<h1 class="handouts-header">${_("Course Handouts")}</h1>
${HTML(get_course_info_section(request, masquerade_user, course, 'guest_handouts'))}
<h1 class="handouts-header">${_(course.info_sidebar_name)}</h1>
${HTML(get_course_info_section(request, masquerade_user, course, 'handouts'))}
</section>
% else:
<section class="updates">
<h1 class="handouts-header">${_("Course Updates and News")}</h1>
${HTML(get_course_info_section(request, masquerade_user, course, 'guest_updates'))}
</section>
<section aria-label="${_('Handout Navigation')}" class="handouts">
<h1 class="handouts-header">${_("Course Handouts")}</h1>
${HTML(get_course_info_section(request, masquerade_user, course, 'guest_handouts'))}
</section>
% endif
</div>
</section>
% endif
</div>
</section>
</main>
......@@ -16,8 +16,10 @@
<%include file="/courseware/course_navigation.html" args="active_page='static_tab_{0}'.format(tab['url_slug'])" />
<section class="container">
<div class="static_tab_wrapper">
${tab_contents}
</div>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="container">
<div class="static_tab_wrapper">
${tab_contents}
</div>
</section>
</main>
......@@ -8,7 +8,6 @@ from django.core.urlresolvers import reverse
<%block name="bodyclass">discussion</%block>
<%block name="pagetitle">${_("Discussion - {course_number}").format(course_number=course.display_number_with_default) | h}</%block>
<%block name="nav_skip">#discussion-container</%block>
<%block name="headextra">
<%static:css group='style-course-vendor'/>
......@@ -39,13 +38,14 @@ from django.core.urlresolvers import reverse
data-sort-preference="${sort_preference | h}"
data-flag-moderator="${flag_moderator | h}"
data-user-cohort-id="${user_cohort | h}"
data-course-settings="${course_settings | h}"
tabindex="-1">
data-course-settings="${course_settings | h}">
<div class="discussion-body">
<div class="forum-nav" role="complementary" aria-label="${_("Discussion thread list")}"></div>
<div class="discussion-column" role="main" aria-label="Discussion" id="discussion-column">
<article class="new-post-article" style="display: none" tabindex="-1" aria-label="${_("New topic form")}"></article>
<div class="forum-content"></div>
<div class="discussion-column" id="discussion-column">
<main id="main" aria-label="Content" tabindex="-1">
<article class="new-post-article" style="display: none" tabindex="-1" aria-label="${_("New topic form")}"></article>
<div class="forum-content"></div>
</main>
</div>
</div>
</section>
......
......@@ -20,7 +20,7 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
<section class="container">
<div class="wrapper-student-notes">
<div class="student-notes">
<main id="main" aria-label="Content" tabindex="-1">
<div class="title-search-container">
<div class="wrapper-title">
<h1 class="page-title">
......@@ -93,7 +93,7 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
</section>
% endif
</section>
</main>
</div>
</div>
</section>
......
......@@ -5,48 +5,50 @@ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
%>
<section class="home">
<header>
<div class="outer-wrapper">
<div class="title">
<div class="heading-group">
% if homepage_overlay_html:
${homepage_overlay_html}
% else:
## Translators: 'Open edX' is a brand, please keep this untranslated. See http://openedx.org for more information.
<h1>${_("Welcome to Open edX!")}</h1>
## Translators: 'Open edX' is a brand, please keep this untranslated. See http://openedx.org for more information.
<p>${_("It works! This is the default homepage for this Open edX instance.")}</p>
% endif
</div>
% if settings.FEATURES.get('ENABLE_COURSE_DISCOVERY'):
<div class="course-search">
<form method="get" action="/courses">
<label><span class="sr">${_("Search for a course")}</span>
<input class="search-input" name="search_query" type="text" placeholder="${_("Search for a course")}"></input>
</label>
<button class="search-button" type="submit">
<i class="icon fa fa-search" aria-hidden="true"></i><span class="sr">${_("Search")}</span>
</button>
</form>
</div>
% endif
</div>
<main id="main" aria-label="Content" tabindex="-1">
<section class="home">
<header>
<div class="outer-wrapper">
<div class="title">
<div class="heading-group">
% if homepage_overlay_html:
${homepage_overlay_html}
% else:
## Translators: 'Open edX' is a brand, please keep this untranslated. See http://openedx.org for more information.
<h1>${_("Welcome to Open edX!")}</h1>
## Translators: 'Open edX' is a brand, please keep this untranslated. See http://openedx.org for more information.
<p>${_("It works! This is the default homepage for this Open edX instance.")}</p>
% endif
</div>
% if settings.FEATURES.get('ENABLE_COURSE_DISCOVERY'):
<div class="course-search">
<form method="get" action="/courses">
<label><span class="sr">${_("Search for a course")}</span>
<input class="search-input" name="search_query" type="text" placeholder="${_("Search for a course")}"></input>
</label>
<button class="search-button" type="submit">
<i class="icon fa fa-search" aria-hidden="true"></i><span class="sr">${_("Search")}</span>
</button>
</form>
</div>
% endif
% if show_homepage_promo_video:
<a href="#video-modal" class="media" rel="leanModal">
<div class="hero">
<div class="play-intro"></div>
</div>
</a>
% endif
</div>
</header>
<%include file="${courses_list}" />
% if show_homepage_promo_video:
<a href="#video-modal" class="media" rel="leanModal">
<div class="hero">
<div class="play-intro"></div>
</div>
</a>
% endif
</div>
</header>
<%include file="${courses_list}" />
</section>
</section>
</main>
% if show_homepage_promo_video:
<section id="video-modal" class="modal home-page-video-modal video-modal">
......
......@@ -20,7 +20,6 @@ from django.core.urlresolvers import reverse
## 6. And tests go in lms/djangoapps/instructor/tests/
<%block name="pagetitle">${_("Instructor Dashboard")}</%block>
<%block name="nav_skip">#content</%block>
<%block name="headextra">
<%static:css group='style-course-vendor'/>
......@@ -86,41 +85,42 @@ from django.core.urlresolvers import reverse
<section class="container">
<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
</div>
<h1>${_("Instructor Dashboard")}</h1>
%if analytics_dashboard_message:
<div class="wrapper-msg urgency-low is-shown">
<p>${analytics_dashboard_message}</p>
</div>
%endif
## links which are tied to idash-sections below.
## the links are activated and handled in instructor_dashboard.coffee
## when the javascript loads, it clicks on the first section
<ul class="instructor-nav">
% for section_data in sections:
## This is necessary so we don't scrape 'section_display_name' as a string.
<% dname = section_data['section_display_name'] %>
<li class="nav-item"><a href="" data-section="${ section_data['section_key'] }">${_(dname)}</a></li>
% endfor
</ul>
## 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>
<main id="main" aria-label="Content" tabindex="-1">
<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
</div>
<h1>${_("Instructor Dashboard")}</h1>
%if analytics_dashboard_message:
<div class="wrapper-msg urgency-low is-shown">
<p>${analytics_dashboard_message}</p>
</div>
%endif
## links which are tied to idash-sections below.
## the links are activated and handled in instructor_dashboard.coffee
## when the javascript loads, it clicks on the first section
<ul class="instructor-nav">
% for section_data in sections:
## This is necessary so we don't scrape 'section_display_name' as a string.
<% dname = section_data['section_display_name'] %>
<li class="nav-item"><a href="" data-section="${ section_data['section_key'] }">${_(dname)}</a></li>
% endfor
</ul>
## 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>
</main>
</div>
</section>
......@@ -20,8 +20,9 @@ ProgramListFactory({
</%block>
<%block name="pagetitle">${_("Programs")}</%block>
<div class="program-list-wrapper">
<div class="program-cards-container"></div>
<div class="sidebar"></div>
</div>
<main id="main" aria-label="Content" tabindex="-1">
<div class="program-list-wrapper">
<div class="program-cards-container"></div>
<div class="sidebar"></div>
</div>
</main>
......@@ -127,13 +127,13 @@ from pipeline_mako import render_require_js_path_overrides
% if not disable_window_wrap:
<div class="window-wrap" dir="${static.dir_rtl()}">
% endif
<a class="nav-skip" href="<%block name="nav_skip">#content</%block>">${_("Skip to main content")}</a>
<a class="nav-skip" href="#main">${_("Skip to main content")}</a>
% if not disable_header:
<%include file="${static.get_template_path('header.html')}" />
% endif
<div role="main" class="content-wrapper" id="content" tabindex="-1">
<div class="content-wrapper" id="content">
${self.body()}
<%block name="bodyextra"/>
</div>
......
......@@ -24,11 +24,11 @@
<body class="{% block bodyclass %}{% endblock %} lang_{{LANGUAGE_CODE}}">
<div class="window-wrap" dir="${static.dir_rtl()}">
<a class="nav-skip" href="{% block nav_skip %}#content{% endblock %}">{% trans "Skip to main content" %}</a>
<a class="nav-skip" href="#main">{% trans "Skip to main content" %}</a>
{% with course=request.course %}
{% include "header.html"|microsite_template_path %}
{% endwith %}
<div role="main" class="content-wrapper" id="content" tabindex="-1">
<div class="content-wrapper" id="content">
{% block body %}{% endblock %}
{% block bodyextra %}{% endblock %}
</div>
......
......@@ -8,11 +8,13 @@ from openedx.core.djangolib.markup import Text, HTML
<%block name="pagetitle">${_("Page Not Found")}</%block>
<section class="outside-app">
<h1>${_("Page not found")}</h1>
<p>${Text(_('The page that you were looking for was not found. Go back to the {link_start}homepage{link_end} or let us know about any pages that may have been moved at {email}.')).format(
link_start=HTML('<a href="/">'),
link_end=HTML('</a>'),
email=HTML('<a href="mailto:{email}">{email}</a>').format(email=Text(static.get_tech_support_email_address()))
)}</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="outside-app">
<h1>${_("Page not found")}</h1>
<p>${Text(_('The page that you were looking for was not found. Go back to the {link_start}homepage{link_end} or let us know about any pages that may have been moved at {email}.')).format(
link_start=HTML('<a href="/">'),
link_end=HTML('</a>'),
email=HTML('<a href="mailto:{email}">{email}</a>').format(email=Text(static.get_tech_support_email_address()))
)}</p>
</section>
</main>
......@@ -4,7 +4,9 @@
<%block name="pagetitle">${_("About")}</%block>
<section class="container about">
<h1>${_("About")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="container about">
<h1>${_("About")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
</main>
......@@ -4,7 +4,9 @@
<%block name="pagetitle">${_("Blog")}</%block>
<section class="container about">
<h1>${_("Blog")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="container about">
<h1>${_("Blog")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
</main>
......@@ -4,7 +4,9 @@
<%block name="pagetitle">${_("Contact")}</%block>
<section class="container about">
<h1>${_("Contact")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="container about">
<h1>${_("Contact")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
</main>
......@@ -4,7 +4,9 @@
<%block name="pagetitle">${_("Donate")}</%block>
<section class="container about">
<h1>${_("Donate")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="container about">
<h1>${_("Donate")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
</main>
......@@ -7,14 +7,16 @@ from openedx.core.djangolib.markup import Text, HTML
<%block name="pagetitle">${_("This Course Unavailable In Your Country")}</%block>
<section class="outside-app">
<p>
${Text(_("Our system indicates that you are trying to access this {platform_name} "
"course from a country or region currently subject to U.S. economic and trade "
"sanctions. Unfortunately, at this time {platform_name} must comply with "
"export controls, and we cannot allow you to access this course."
)).format(
platform_name=Text(settings.PLATFORM_NAME),
)}
</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="outside-app">
<p>
${Text(_("Our system indicates that you are trying to access this {platform_name} "
"course from a country or region currently subject to U.S. economic and trade "
"sanctions. Unfortunately, at this time {platform_name} must comply with "
"export controls, and we cannot allow you to access this course."
)).format(
platform_name=Text(settings.PLATFORM_NAME),
)}
</p>
</section>
</main>
......@@ -4,7 +4,9 @@
<%block name="pagetitle">${_("FAQ")}</%block>
<section class="container about">
<h1>${_("FAQ")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="container about">
<h1>${_("FAQ")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
</main>
......@@ -4,7 +4,9 @@
<%block name="pagetitle">${_("Help")}</%block>
<section class="container about">
<h1>${_("Help")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="container about">
<h1>${_("Help")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
</main>
......@@ -4,7 +4,9 @@
<%block name="pagetitle">${_("Honor Code")}</%block>
<section class="container about">
<h1>${_("Honor Code")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="container about">
<h1>${_("Honor Code")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
</main>
......@@ -4,7 +4,9 @@
<%block name="pagetitle">${_("Jobs")}</%block>
<section class="container about">
<h1>${_("Jobs")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="container about">
<h1>${_("Jobs")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
</main>
......@@ -4,7 +4,9 @@
<%block name="pagetitle">${_("Media Kit")}</%block>
<section class="container about">
<h1>${_("Media Kit")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
<main id="main" aria-label="Cntent" tabindex="-1">
<section class="container about">
<h1>${_("Media Kit")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
</main>
......@@ -5,7 +5,9 @@
<%block name="pagetitle">${_("In the Press")}</%block>
<section class="container about">
<h1>${_("In the Press")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="container about">
<h1>${_("In the Press")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
</main>
......@@ -5,7 +5,9 @@
<%block name="pagetitle">${_("In the Press")}</%block>
<section class="container about">
<h1>${_("In the Press")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="container about">
<h1>${_("In the Press")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
</main>
......@@ -5,7 +5,9 @@
<%block name="pagetitle">${_("Privacy Policy")}</%block>
<section class="container about">
<h1>${_("Privacy Policy")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="container about">
<h1>${_("Privacy Policy")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
</main>
<%page expression_filter="h"/>
<%!
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext as _
from openedx.core.djangolib.markup import Text, HTML
%>
<%inherit file="../main.html" />
<section class="outside-app">
<h1>
${Text(_("Currently the {platform_name} servers are down")).format(
platform_name=HTML(u"<em>{}</em>").format(Text(settings.PLATFORM_NAME))
)}
</h1>
<p>
${Text(_("Our staff is currently working to get the site back up as soon as possible. "
"Please email us at {tech_support_email} to report any problems or downtime.")).format(
tech_support_email=HTML('<a href="mailto:{0}">{0}</a>').format(Text(settings.TECH_SUPPORT_EMAIL))
)}</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="outside-app">
<h1>
${Text(_("Currently the {platform_name} servers are down")).format(
platform_name=HTML(u"<em>{}</em>").format(Text(settings.PLATFORM_NAME))
)}
</h1>
<p>
${Text(_("Our staff is currently working to get the site back up as soon as possible. "
"Please email us at {tech_support_email} to report any problems or downtime.")).format(
tech_support_email=HTML('<a href="mailto:{0}">{0}</a>').format(Text(settings.TECH_SUPPORT_EMAIL))
)}</p>
</section>
</main>
......@@ -6,17 +6,19 @@ from openedx.core.djangolib.markup import Text, HTML
%>
<%inherit file="../main.html" />
<section class="outside-app">
<h1>
${Text(_(u"There has been a 500 error on the {platform_name} servers")).format(
platform_name=HTML("<em>{platform_name}</em>").format(platform_name=Text(static.get_platform_name()))
)}
</h1>
<p>
${Text(_('Please wait a few seconds and then reload the page. If the problem persists, please email us at {email}.')).format(
email=HTML('<a href="mailto:{email}">{email}</a>').format(
email=Text(static.get_tech_support_email_address())
)
)}
</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="outside-app">
<h1>
${Text(_(u"There has been a 500 error on the {platform_name} servers")).format(
platform_name=HTML("<em>{platform_name}</em>").format(platform_name=Text(static.get_platform_name()))
)}
</h1>
<p>
${Text(_('Please wait a few seconds and then reload the page. If the problem persists, please email us at {email}.')).format(
email=HTML('<a href="mailto:{email}">{email}</a>').format(
email=Text(static.get_tech_support_email_address())
)
)}
</p>
</section>
</main>
......@@ -5,16 +5,18 @@ from openedx.core.djangolib.markup import Text, HTML
%>
<%inherit file="../main.html" />
<section class="outside-app">
<h1>
${Text(_("Currently the {platform_name} servers are overloaded")).format(
platform_name=HTML("<em>{}</em>").format(platform_name=Text(settings.PLATFORM_NAME))
)}
</h1>
<p>
${Text(_("Our staff is currently working to get the site back up as soon as possible. "
"Please email us at {tech_support_email} to report any problems or downtime.")).format(
tech_support_email=HTML('<a href="mailto:{0}">{0}</a>').format(tech_support_email=Text(settings.TECH_SUPPORT_EMAIL))
)}
</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="outside-app">
<h1>
${Text(_("Currently the {platform_name} servers are overloaded")).format(
platform_name=HTML("<em>{}</em>").format(platform_name=Text(settings.PLATFORM_NAME))
)}
</h1>
<p>
${Text(_("Our staff is currently working to get the site back up as soon as possible. "
"Please email us at {tech_support_email} to report any problems or downtime.")).format(
tech_support_email=HTML('<a href="mailto:{0}">{0}</a>').format(tech_support_email=Text(settings.TECH_SUPPORT_EMAIL))
)}
</p>
</section>
</main>
......@@ -4,7 +4,9 @@
<%block name="pagetitle">${_("Terms of Service")}</%block>
<section class="container about">
<h1>${_("Terms of Service")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
<main id="main" aria-label="Content" tabindex="-1">
<section class="container about">
<h1>${_("Terms of Service")}</h1>
<p>${_("This page left intentionally blank. It is not used by edx.org but is left here for possible use by installations of Open edX.")}</p>
</section>
</main>
......@@ -16,7 +16,6 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
<%namespace name='static' file='/static_content.html'/>
<%block name="pagetitle">${_("Account Settings")}</%block>
<%block name="nav_skip">#content</%block>
% if duplicate_provider:
<section>
......@@ -37,7 +36,7 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
var platformName = '${ static.get_platform_name() | n, js_escaped_string }';
AccountSettingsFactory(
fieldsData,
fieldsData,
authData,
'${ user_accounts_api_url | n, js_escaped_string }',
'${ user_preferences_api_url | n, js_escaped_string }',
......
<main id="main" aria-label="Content" tabindex="-1">
<div class="account-settings-container">
<div class="wrapper-header">
<h2 class="header-title"><%- gettext("Account Settings") %></h2>
......@@ -22,3 +23,4 @@
<% }); %>
</div>
</div>
</main>
......@@ -33,5 +33,7 @@
</%block>
<div class="section-bkg-wrapper">
<div id="login-and-registration-container" class="login-register" />
<main id="main" aria-label="Content" tabindex="-1">
<div id="login-and-registration-container" class="login-register" />
</main>
</div>
......@@ -9,16 +9,17 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json
%>
<%block name="pagetitle">${_("Learner Profile")}</%block>
<%block name="nav_skip">#content</%block>
<%block name="bodyclass">view-profile</%block>
<div class="message-banner" aria-live="polite"></div>
<div class="wrapper-profile">
<div class="ui-loading-indicator">
<p><span class="spin"><i class="icon fa fa-refresh" aria-hidden="true"></i></span> <span class="copy">${_("Loading")}</span></p>
<main id="main" aria-label="Content" tabindex="-1">
<div class="wrapper-profile">
<div class="ui-loading-indicator">
<p><span class="spin"><i class="icon fa fa-refresh" aria-hidden="true"></i></span> <span class="copy">${_("Loading")}</span></p>
</div>
</div>
</div>
</main>
<%block name="headextra">
<%static:css group='style-course'/>
</%block>
......
......@@ -7,22 +7,23 @@
{% block wiki_breadcrumbs %}
{% include "wiki/includes/breadcrumbs.html" %}
{% endblock %}
{% block nav_skip %}#main-article{% endblock %}
{% block wiki_contents %}
<div class="article-wrapper">
<article class="main-article" id="main-article" tabindex="-1">
{% if selected_tab != "edit" %}
<h1>{{ article.current_revision.title }}</h1>
<main id="main" aria-label="Content" tabindex="-1">
<article class="main-article" id="main-article">
{% if selected_tab != "edit" %}
<h1>{{ article.current_revision.title }}</h1>
{% endif %}
{% endif %}
{% block wiki_contents_tab %}
{% wiki_render article %}
{% endblock %}
</article>
{% block wiki_contents_tab %}
{% wiki_render article %}
{% endblock %}
</article>
</main>
<div class="article-functions">
<ul class="nav nav-tabs">
......
......@@ -47,8 +47,6 @@
{% endblock %}
{% block nav_skip %}#wiki-content{% endblock %}
{% block body %}
{% if request.course %}
{% with course=request.course %}
......@@ -56,6 +54,7 @@
{% endwith %}
{% endif %}
<main id="main" aria-label="Content" tabindex="-1">
<section class="container wiki {{ selected_tab }}" id="wiki-content">
<div class="wiki-wrapper">
{% block wiki_body %}
......@@ -77,5 +76,6 @@
</div>
</section>
</main>
{% endblock %}
"""Decorators for API access management."""
from functools import wraps
from django.core.urlresolvers import reverse
from django.http import HttpResponseNotFound
from django.shortcuts import redirect
from openedx.core.djangoapps.api_admin.models import ApiAccessConfig
from openedx.core.djangoapps.api_admin.models import ApiAccessRequest, ApiAccessConfig
def api_access_enabled_or_404(view):
def api_access_enabled_or_404(view_func):
"""If API access management feature is not enabled, return a 404."""
@wraps(view)
def wrapped_view(request, *args, **kwargs):
@wraps(view_func)
def wrapped_view(view_obj, *args, **kwargs):
"""Wrapper for the view function."""
if ApiAccessConfig.current().enabled:
return view(request, *args, **kwargs)
return view_func(view_obj, *args, **kwargs)
return HttpResponseNotFound()
return wrapped_view
def require_api_access(view_func):
"""If the requesting user does not have API access, bounce them to the request form."""
@wraps(view_func)
def wrapped_view(view_obj, *args, **kwargs):
"""Wrapper for the view function."""
if ApiAccessRequest.has_api_access(args[0].user):
return view_func(view_obj, *args, **kwargs)
return redirect(reverse('api_admin:api-request'))
return wrapped_view
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('api_admin', '0004_auto_20160412_1506'),
]
operations = [
migrations.AlterField(
model_name='apiaccessrequest',
name='user',
field=models.OneToOneField(related_name='api_access_request', to=settings.AUTH_USER_MODEL),
),
]
......@@ -32,7 +32,7 @@ class ApiAccessRequest(TimeStampedModel):
(DENIED, _('Denied')),
(APPROVED, _('Approved')),
)
user = models.OneToOneField(User)
user = models.OneToOneField(User, related_name='api_access_request')
status = models.CharField(
max_length=255,
choices=STATUS_CHOICES,
......
"""Factories for API management."""
import factory
from factory.django import DjangoModelFactory
from oauth2_provider.models import get_application_model
from microsite_configuration.tests.factories import SiteFactory
from openedx.core.djangoapps.api_admin.models import ApiAccessRequest
from student.tests.factories import UserFactory
Application = get_application_model() # pylint: disable=invalid-name
class ApiAccessRequestFactory(DjangoModelFactory):
"""Factory for ApiAccessRequest objects."""
class Meta(object):
......@@ -14,3 +18,12 @@ class ApiAccessRequestFactory(DjangoModelFactory):
user = factory.SubFactory(UserFactory)
site = factory.SubFactory(SiteFactory)
class ApplicationFactory(DjangoModelFactory):
"""Factory for OAuth Application objects."""
class Meta(object):
model = Application
authorization_grant_type = Application.GRANT_CLIENT_CREDENTIALS
client_type = Application.CLIENT_CONFIDENTIAL
#pylint: disable=missing-docstring
import unittest
import ddt
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.test.utils import override_settings
from oauth2_provider.models import get_application_model
from openedx.core.djangoapps.api_admin.models import ApiAccessRequest, ApiAccessConfig
from openedx.core.djangoapps.api_admin.tests.factories import ApiAccessRequestFactory
from openedx.core.djangoapps.api_admin.tests.factories import ApiAccessRequestFactory, ApplicationFactory
from openedx.core.djangoapps.api_admin.tests.utils import VALID_DATA
from student.tests.factories import UserFactory
Application = get_application_model() # pylint: disable=invalid-name
class ApiAdminTest(TestCase):
def setUp(self):
......@@ -86,6 +92,8 @@ class ApiRequestViewTest(ApiAdminTest):
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@override_settings(PLATFORM_NAME='edX')
@ddt.ddt
class ApiRequestStatusViewTest(ApiAdminTest):
def setUp(self):
......@@ -103,14 +111,35 @@ class ApiRequestStatusViewTest(ApiAdminTest):
response = self.client.get(self.url)
self.assertRedirects(response, reverse('api_admin:api-request'))
def test_get_with_request(self):
@ddt.data(
(ApiAccessRequest.APPROVED, 'Your request to access the edX Course Catalog API has been approved.'),
(ApiAccessRequest.PENDING, 'Your request to access the edX Course Catalog API is being processed.'),
(ApiAccessRequest.DENIED, 'Your request to access the edX Course Catalog API has been denied.'),
)
@ddt.unpack
def test_get_with_request(self, status, expected):
"""
Verify that users who have requested access can see a message
regarding their request status.
"""
ApiAccessRequestFactory(user=self.user)
ApiAccessRequestFactory(user=self.user, status=status)
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertIn(expected, response.content)
def test_get_with_existing_application(self):
"""
Verify that if the user has created their client credentials, they
are shown on the status page.
"""
ApiAccessRequestFactory(user=self.user, status=ApiAccessRequest.APPROVED)
application = ApplicationFactory(user=self.user)
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
unicode_content = response.content.decode('utf-8')
self.assertIn(application.client_secret, unicode_content) # pylint: disable=no-member
self.assertIn(application.client_id, unicode_content) # pylint: disable=no-member
self.assertIn(application.redirect_uris, unicode_content) # pylint: disable=no-member
def test_get_anonymous(self):
"""Verify that users must be logged in to see the page."""
......@@ -124,6 +153,49 @@ class ApiRequestStatusViewTest(ApiAdminTest):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 404)
@ddt.data(
(ApiAccessRequest.APPROVED, True, True),
(ApiAccessRequest.DENIED, True, False),
(ApiAccessRequest.PENDING, True, False),
(ApiAccessRequest.APPROVED, False, True),
(ApiAccessRequest.DENIED, False, False),
(ApiAccessRequest.PENDING, False, False),
)
@ddt.unpack
def test_post(self, status, application_exists, new_application_created):
"""
Verify that posting the form creates an application if the user is
approved, and does not otherwise. Also ensure that if the user
already has an application, it is deleted before a new
application is created.
"""
if application_exists:
old_application = ApplicationFactory(user=self.user)
ApiAccessRequestFactory(user=self.user, status=status)
self.client.post(self.url, {
'name': 'test.com',
'redirect_uris': 'http://example.com'
})
applications = Application.objects.filter(user=self.user)
if application_exists and new_application_created:
self.assertEqual(applications.count(), 1)
self.assertNotEqual(old_application, applications[0])
elif application_exists:
self.assertEqual(applications.count(), 1)
self.assertEqual(old_application, applications[0])
elif new_application_created:
self.assertEqual(applications.count(), 1)
else:
self.assertEqual(applications.count(), 0)
def test_post_with_errors(self):
ApiAccessRequestFactory(user=self.user, status=ApiAccessRequest.APPROVED)
response = self.client.post(self.url, {
'name': 'test.com',
'redirect_uris': 'not a url'
})
self.assertIn('Enter a valid URL.', response.content)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class ApiTosViewTest(ApiAdminTest):
......
......@@ -9,13 +9,19 @@ from django.utils.translation import ugettext as _
from django.views.generic import View
from django.views.generic.base import TemplateView
from django.views.generic.edit import CreateView
from edxmako.shortcuts import render_to_response
from oauth2_provider.generators import generate_client_secret, generate_client_id
from oauth2_provider.models import get_application_model
from oauth2_provider.views import ApplicationRegistration
from edxmako.shortcuts import render_to_response
from openedx.core.djangoapps.api_admin.decorators import require_api_access
from openedx.core.djangoapps.api_admin.forms import ApiAccessRequestForm
from openedx.core.djangoapps.api_admin.models import ApiAccessRequest
log = logging.getLogger(__name__)
Application = get_application_model() # pylint: disable=invalid-name
class ApiRequestView(CreateView):
"""Form view for requesting API access."""
......@@ -38,22 +44,71 @@ class ApiRequestView(CreateView):
return super(ApiRequestView, self).form_valid(form)
class ApiRequestStatusView(View):
class ApiRequestStatusView(ApplicationRegistration):
"""View for confirming our receipt of an API request."""
def get(self, request):
success_url = reverse_lazy('api_admin:api-status')
def get(self, request, form=None): # pylint: disable=arguments-differ
"""
If the user has not created an API request, redirect them to the
request form. Otherwise, display the status of their API request.
request form. Otherwise, display the status of their API
request. We take `form` as an optional argument so that we can
display validation errors correctly on the page.
"""
status = ApiAccessRequest.api_access_status(request.user)
if status is None:
if form is None:
form = self.get_form_class()()
user = request.user
try:
api_request = ApiAccessRequest.objects.get(user=user)
except ApiAccessRequest.DoesNotExist:
return redirect(reverse('api_admin:api-request'))
try:
application = Application.objects.get(user=user)
except Application.DoesNotExist:
application = None
# We want to fill in a few fields ourselves, so remove them
# from the form so that the user doesn't see them.
for field in ('client_type', 'client_secret', 'client_id', 'authorization_grant_type'):
form.fields.pop(field)
return render_to_response('api_admin/status.html', {
'status': status,
'api_support_link': _('TODO'),
'status': api_request.status,
'api_support_link': settings.API_DOCUMENTATION_URL,
'api_support_email': settings.API_ACCESS_MANAGER_EMAIL,
'form': form,
'application': application,
})
def get_form(self, form_class=None):
form = super(ApiRequestStatusView, self).get_form(form_class)
# Copy the data, since it's an immutable QueryDict.
copied_data = form.data.copy()
# Now set the fields that were removed earlier. We give them
# confidential client credentials, and generate their client
# ID and secret.
copied_data.update({
'authorization_grant_type': Application.GRANT_CLIENT_CREDENTIALS,
'client_type': Application.CLIENT_CONFIDENTIAL,
'client_secret': generate_client_secret(),
'client_id': generate_client_id(),
})
form.data = copied_data
return form
def form_valid(self, form):
# Delete any existing applications if the user has decided to regenerate their credentials
Application.objects.filter(user=self.request.user).delete()
return super(ApiRequestStatusView, self).form_valid(form)
def form_invalid(self, form):
return self.get(self.request, form)
@require_api_access
def post(self, request):
return super(ApiRequestStatusView, self).post(request)
class ApiTosView(TemplateView):
......
......@@ -152,8 +152,8 @@ def pa11ycrawler(options):
opts = parse_bokchoy_opts(options)
opts['report_dir'] = Env.PA11YCRAWLER_REPORT_DIR
opts['coveragerc'] = Env.PA11YCRAWLER_COVERAGERC
opts['should_fetch_course'] = getattr(options, 'should_fetch_course', None)
opts['course_key'] = getattr(options, 'course-key', None)
opts['should_fetch_course'] = getattr(options, 'should_fetch_course', not opts['fasttest'])
opts['course_key'] = getattr(options, 'course-key', "course-v1:edX+Test101+course")
test_suite = Pa11yCrawler('a11y_crawler', **opts)
test_suite.run()
......
......@@ -200,7 +200,7 @@ class TestPaverPa11yCrawlerCmd(unittest.TestCase):
'--pa11y-reporter="1.0-json" '
'--depth-limit=6 '
).format(
start_urls=start_urls,
start_urls=' '.join(start_urls),
report_dir=report_dir,
)
return expected_statement
......
......@@ -268,7 +268,7 @@ class Pa11yCrawler(BokChoyTestSuite):
def __init__(self, *args, **kwargs):
super(Pa11yCrawler, self).__init__(*args, **kwargs)
self.course_key = kwargs.get('course_key', "course-v1:edX+Test101+course")
self.course_key = kwargs.get('course_key')
if self.imports_dir:
# If imports_dir has been specified, assume the files are
# already there -- no need to fetch them from github. This
......@@ -279,7 +279,7 @@ class Pa11yCrawler(BokChoyTestSuite):
# Otherwise, obey `--skip-fetch` command and use the default
# test course. Note that the fetch will also be skipped when
# using `--fast`.
self.should_fetch_course = kwargs.get('should_fetch_course', not self.fasttest)
self.should_fetch_course = kwargs.get('should_fetch_course')
self.imports_dir = path('test_root/courses/')
self.pa11y_report_dir = os.path.join(self.report_dir, 'pa11ycrawler_reports')
......@@ -360,7 +360,7 @@ class Pa11yCrawler(BokChoyTestSuite):
'--pa11y-reporter="{reporter}" '
'--depth-limit={depth} '
).format(
start_urls=self.start_urls,
start_urls=' '.join(self.start_urls),
allowed_domains='localhost',
report_dir=self.pa11y_report_dir,
reporter="1.0-json",
......
......@@ -20,7 +20,6 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
<%block name="pagetitle">${_("Dashboard")}</%block>
<%block name="bodyclass">view-dashboard is-authenticated</%block>
<%block name="nav_skip">#my-courses</%block>
<%block name="header_extras">
% for template_name in ["donation"]:
......@@ -77,7 +76,8 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
</div>
<section class="container dashboard" id="dashboard-main">
<section class="my-courses" id="my-courses" role="main" aria-label="Content" tabindex="-1">
<main id="main" aria-label="Content" tabindex="-1">
<section class="my-courses" id="my-courses">
<header class="wrapper-header-courses">
<h2 class="header-courses">${_("My Courses")}</h2>
</header>
......@@ -132,6 +132,7 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
</div>
% endif
</section>
</main>
% if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'):
<div id="dashboard-search-bar" class="search-bar dashboard-search-bar" role="search" aria-label="Dashboard">
......
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