Skip to content
Snippets Groups Projects
Commit 74bf7f23 authored by Usman Khalid's avatar Usman Khalid Committed by Andy Armstrong
Browse files

Account settings page.

TNL-1499
parent 3a2c527c
No related merge requests found
Showing
with 967 additions and 17 deletions
......@@ -15,14 +15,15 @@ from django.core.urlresolvers import reverse
from django.core import mail
from django.test.utils import override_settings
from util.testing import UrlResetMixin
from third_party_auth.tests.testutil import simulate_running_pipeline
from embargo.test_utils import restrict_course
from openedx.core.djangoapps.user_api.accounts.api import activate_account, create_account
from openedx.core.djangoapps.user_api.accounts import EMAIL_MAX_LENGTH
from student.tests.factories import CourseModeFactory, UserFactory
from student_account.views import account_settings_context
from third_party_auth.tests.testutil import simulate_running_pipeline
from util.testing import UrlResetMixin
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from student.tests.factories import CourseModeFactory
@ddt.ddt
......@@ -499,3 +500,45 @@ class StudentAccountLoginAndRegistrationTest(UrlResetMixin, ModuleStoreTestCase)
url=reverse("social:begin", kwargs={"backend": backend_name}),
params=urlencode(params)
)
class AccountSettingsViewTest(TestCase):
""" Tests for the account settings view. """
USERNAME = 'student'
PASSWORD = 'password'
FIELDS = [
'country',
'gender',
'language',
'level_of_education',
'password',
'year_of_birth',
'preferred_language',
]
def setUp(self):
super(AccountSettingsViewTest, self).setUp()
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
self.client.login(username=self.USERNAME, password=self.PASSWORD)
def test_context(self):
context = account_settings_context(self.user)
user_accounts_api_url = reverse("accounts_api", kwargs={'username': self.user.username})
self.assertEqual(context['user_accounts_api_url'], user_accounts_api_url)
user_preferences_api_url = reverse('preferences_api', kwargs={'username': self.user.username})
self.assertEqual(context['user_preferences_api_url'], user_preferences_api_url)
for attribute in self.FIELDS:
self.assertIn(attribute, context['fields'])
def test_view(self):
view_path = reverse('account_settings')
response = self.client.get(path=view_path)
for attribute in self.FIELDS:
self.assertIn(attribute, response.content)
......@@ -11,3 +11,8 @@ if settings.FEATURES.get('ENABLE_COMBINED_LOGIN_REGISTRATION'):
url(r'^register/$', 'login_and_registration_form', {'initial_mode': 'register'}, name='account_register'),
url(r'^password$', 'password_change_request_handler', name='password_change_request'),
)
urlpatterns += patterns(
'student_account.views',
url(r'^settings$', 'account_settings', name='account_settings'),
)
......@@ -5,30 +5,37 @@ import json
from ipware.ip import get_ip
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.http import (
HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
)
from django.shortcuts import redirect
from django.http import HttpRequest
from django_countries import countries
from django.core.urlresolvers import reverse, resolve
from django.utils.translation import ugettext as _
from django_future.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_http_methods
from opaque_keys.edx.keys import CourseKey
from lang_pref.api import released_languages
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from edxmako.shortcuts import render_to_response
from microsite_configuration import microsite
from embargo import api as embargo_api
import third_party_auth
from external_auth.login_and_register import (
login as external_auth_login,
register as external_auth_register
)
from student.models import UserProfile
from student.views import (
signin_user as old_login_view,
register_user as old_register_view
)
from student_account.helpers import auth_pipeline_urls
import third_party_auth
from util.bad_request_rate_limiter import BadRequestRateLimiter
from openedx.core.djangoapps.user_api.accounts.api import request_password_change
from openedx.core.djangoapps.user_api.errors import UserNotFound
......@@ -294,3 +301,68 @@ def _external_auth_intercept(request, mode):
return external_auth_login(request)
elif mode == "register":
return external_auth_register(request)
@login_required
@require_http_methods(['GET'])
def account_settings(request):
"""Render the current user's account settings page.
Args:
request (HttpRequest)
Returns:
HttpResponse: 200 if the page was sent successfully
HttpResponse: 302 if not logged in (redirect to login page)
HttpResponse: 405 if using an unsupported HTTP method
Example usage:
GET /account/settings
"""
return render_to_response('student_account/account_settings.html', account_settings_context(request.user))
def account_settings_context(user):
""" Context for the account settings page.
Args:
user (User): The user for whom the context is required.
Returns:
dict
"""
country_options = [
(country_code, unicode(country_name))
for country_code, country_name in sorted(
countries.countries, key=lambda(__, name): unicode(name)
)
]
year_of_birth_options = [(unicode(year), unicode(year)) for year in UserProfile.VALID_YEARS]
context = {
'user_accounts_api_url': reverse("accounts_api", kwargs={'username': user.username}),
'user_preferences_api_url': reverse('preferences_api', kwargs={'username': user.username}),
'fields': {
'country': {
'options': country_options,
}, 'gender': {
'options': UserProfile.GENDER_CHOICES,
}, 'language': {
'options': released_languages(),
}, 'level_of_education': {
'options': UserProfile.LEVEL_OF_EDUCATION_CHOICES,
}, 'password': {
'url': reverse('password_reset'),
}, 'year_of_birth': {
'options': year_of_birth_options,
}, 'preferred_language': {
'options': settings.ALL_LANGUAGES,
}
}
}
return context
;(function (define, undefined) {
'use strict';
define([
'gettext', 'underscore', 'backbone',
], function (gettext, _, Backbone) {
var UserAccountModel = Backbone.Model.extend({
idAttribute: 'username',
defaults: {
username: '',
name: '',
email: '',
password: '',
language: null,
country: null,
date_joined: "",
gender: null,
goals: "",
level_of_education: null,
mailing_address: "",
year_of_birth: null,
language_proficiencies: []
}
});
return UserAccountModel;
})
}).call(this, define || RequireJS.define);
;(function (define, undefined) {
'use strict';
define([
'gettext', 'underscore', 'backbone',
], function (gettext, _, Backbone) {
var UserPreferencesModel = Backbone.Model.extend({
idAttribute: 'account_privacy',
defaults: {
account_privacy: 'private'
}
});
return UserPreferencesModel;
})
}).call(this, define || RequireJS.define);
\ No newline at end of file
;(function (define, undefined) {
'use strict';
define([
'gettext', 'jquery', 'underscore', 'backbone',
'js/views/fields',
'js/student_account/models/user_account_model',
'js/student_account/models/user_preferences_model',
'js/student_account/views/account_settings_fields',
'js/student_account/views/account_settings_view',
], function (gettext, $, _, Backbone, FieldViews, UserAccountModel, UserPreferencesModel,
AccountSettingsFieldViews, AccountSettingsView) {
return function (fieldsData, userAccountsApiUrl, userPreferencesApiUrl) {
var accountSettingsElement = $('.wrapper-account-settings');
var userAccountModel = new UserAccountModel();
userAccountModel.url = userAccountsApiUrl;
var userPreferencesModel = new UserPreferencesModel();
userPreferencesModel.url = userPreferencesApiUrl;
var sectionsData = [
{
title: gettext('Basic Account Information (required)'),
fields: [
{
view: new FieldViews.ReadonlyFieldView({
model: userAccountModel,
title: gettext('Username'),
valueAttribute: 'username',
helpMessage: ''
})
},
{
view: new FieldViews.TextFieldView({
model: userAccountModel,
title: gettext('Full Name'),
valueAttribute: 'name',
helpMessage: gettext('The name that appears on your edX certificates.')
})
},
{
view: new AccountSettingsFieldViews.EmailFieldView({
model: userAccountModel,
title: gettext('Email'),
valueAttribute: 'email',
helpMessage: gettext('The email address you use to sign in to edX. Communications from edX and your courses are sent to this address.')
})
},
{
view: new AccountSettingsFieldViews.PasswordFieldView({
model: userAccountModel,
title: gettext('Password'),
valueAttribute: 'password',
emailAttribute: 'email',
linkTitle: gettext('Reset Password'),
linkHref: fieldsData['password']['url'],
helpMessage: gettext('When you click "Reset Password", a message will be sent to your email address. Click the link in the message to reset your password.')
})
},
{
view: new AccountSettingsFieldViews.LanguagePreferenceFieldView({
model: userPreferencesModel,
title: 'Language',
valueAttribute: 'pref-lang',
required: true,
refreshPageOnSave: true,
helpMessage: gettext('The language used for the edX site. The site is currently available in a limited number of languages.'),
options: fieldsData['language']['options']
})
}
]
},
{
title: gettext('Additional Information (optional)'),
fields: [
{
view: new FieldViews.DropdownFieldView({
model: userAccountModel,
title: gettext('Education Completed'),
valueAttribute: 'level_of_education',
options: fieldsData['level_of_education']['options']
})
},
{
view: new FieldViews.DropdownFieldView({
model: userAccountModel,
title: gettext('Gender'),
valueAttribute: 'gender',
options: fieldsData['gender']['options']
})
},
{
view: new FieldViews.DropdownFieldView({
model: userAccountModel,
title: gettext('Year of Birth'),
valueAttribute: 'year_of_birth',
options: fieldsData['year_of_birth']['options']
})
},
{
view: new FieldViews.DropdownFieldView({
model: userAccountModel,
title: gettext('Country or Region'),
valueAttribute: 'country',
options: fieldsData['country']['options']
})
},
{
view: new AccountSettingsFieldViews.LanguageProficienciesFieldView({
model: userAccountModel,
title: gettext('Preferred Language'),
valueAttribute: 'language_proficiencies',
options: fieldsData['preferred_language']['options']
})
}
]
},
{
title: gettext('Connected Accounts'),
fields: [
{
view: new FieldViews.LinkFieldView({
model: userAccountModel,
title: gettext('Facebook'),
valueAttribute: 'auth-facebook',
linkTitle: gettext('Link'),
helpMessage: gettext('Coming soon')
})
},
{
view: new FieldViews.LinkFieldView({
model: userAccountModel,
title: gettext('Google'),
valueAttribute: 'auth-google',
linkTitle: gettext('Link'),
helpMessage: gettext('Coming soon')
})
}
]
}
];
var accountSettingsView = new AccountSettingsView({
el: accountSettingsElement,
sectionsData: sectionsData
});
accountSettingsView.render();
var showLoadingError = function (model, response, options) {
accountSettingsView.showLoadingError();
};
userAccountModel.fetch({
success: function (model, response, options) {
userPreferencesModel.fetch({
success: function (model, response, options) {
accountSettingsView.renderFields();
},
error: showLoadingError
})
},
error: showLoadingError
});
return {
userAccountModel: userAccountModel,
userPreferencesModel: userPreferencesModel,
accountSettingsView: accountSettingsView
};
};
})
}).call(this, define || RequireJS.define);
;(function (define, undefined) {
'use strict';
define([
'gettext', 'jquery', 'underscore', 'backbone', 'js/mustache', 'js/views/fields'
], function (gettext, $, _, Backbone, RequireMustache, FieldViews) {
var Mustache = window.Mustache || RequireMustache;
var AccountSettingsFieldViews = {};
AccountSettingsFieldViews.EmailFieldView = FieldViews.TextFieldView.extend({
successMessage: function() {
return this.indicators['success'] + interpolate_text(
gettext('We\'ve sent a confirmation message to {new_email_address}. Click the link in the message to update your email address.'),
{'new_email_address': this.fieldValue()}
);
}
});
AccountSettingsFieldViews.LanguagePreferenceFieldView = FieldViews.DropdownFieldView.extend({
saveSucceeded: function () {
var data = {
'language': this.modelValue()
};
var view = this;
$.ajax({
type: 'POST',
url: '/i18n/setlang/',
data: data,
dataType: 'html',
success: function (data, status, xhr) {
view.showSuccessMessage();
},
error: function (xhr, status, error) {
view.message(
view.indicators['error'] + gettext('You must sign out of edX and sign back in before your language changes take effect.')
);
}
});
}
});
AccountSettingsFieldViews.PasswordFieldView = FieldViews.LinkFieldView.extend({
initialize: function (options) {
this._super(options);
_.bindAll(this, 'resetPassword');
},
linkClicked: function (event) {
event.preventDefault();
this.resetPassword(event)
},
resetPassword: function (event) {
var data = {};
data[this.options.emailAttribute] = this.model.get(this.options.emailAttribute);
var view = this;
$.ajax({
type: 'POST',
url: view.options.linkHref,
data: data,
success: function (data, status, xhr) {
view.showSuccessMessage()
},
error: function (xhr, status, error) {
view.showErrorMessage(xhr);
}
});
},
successMessage: function () {
return this.indicators['success'] + interpolate_text(
gettext('We\'ve sent a message to {email_address}. Click the link in the message to reset your password.'),
{'email_address': this.model.get(this.options.emailAttribute)}
);
},
});
AccountSettingsFieldViews.LanguageProficienciesFieldView = FieldViews.DropdownFieldView.extend({
modelValue: function () {
var modelValue = this.model.get(this.options.valueAttribute);
if (_.isArray(modelValue) && modelValue.length > 0) {
return modelValue[0].code
} else {
return '';
}
},
saveValue: function () {
var attributes = {};
var value = this.fieldValue() ? [{'code': this.fieldValue()}] : [];
attributes[this.options.valueAttribute] = value;
this.saveAttributes(attributes);
}
});
return AccountSettingsFieldViews;
})
}).call(this, define || RequireJS.define);
;(function (define, undefined) {
'use strict';
define([
'gettext', 'jquery', 'underscore', 'backbone',
], function (gettext, $, _, Backbone) {
var AccountSettingsView = Backbone.View.extend({
initialize: function (options) {
this.template = _.template($('#account_settings-tpl').text());
_.bindAll(this, 'render', 'renderFields', 'showLoadingError');
},
render: function () {
this.$el.html(this.template({
sections: this.options.sectionsData
}));
return this;
},
renderFields: function () {
this.$('.ui-loading-indicator').addClass('is-hidden');
var view = this;
_.each(this.$('.account-settings-section-body'), function (sectionEl, index) {
_.each(view.options.sectionsData[index].fields, function (field, index) {
$(sectionEl).append(field.view.render().el);
});
});
return this;
},
showLoadingError: function () {
this.$('.ui-loading-indicator').addClass('is-hidden');
this.$('.ui-loading-error').removeClass('is-hidden');
}
});
return AccountSettingsView;
})
}).call(this, define || RequireJS.define);
;(function (define, undefined) {
'use strict';
define([
'gettext', 'jquery', 'underscore', 'backbone', 'js/mustache', 'backbone-super'
], function (gettext, $, _, Backbone, RequireMustache) {
var Mustache = window.Mustache || RequireMustache;
var messageRevertDelay = 4000;
var FieldViews = {};
FieldViews.FieldView = Backbone.View.extend({
fieldType: 'generic',
className: function () {
return 'u-field' + ' u-field-' + this.fieldType + ' u-field-' + this.options.valueAttribute;
},
tagName: 'div',
indicators: {
'error': '<i class="fa fa-exclamation-triangle message-error"></i>',
'validationError': '<i class="fa fa-exclamation-triangle message-validation-error"></i>',
'inProgress': '<i class="fa fa-spinner message-in-progress"></i>',
'success': '<i class="fa fa-check message-success"></i>'
},
messages: {
'error': gettext('An error occurred. Please try again.'),
'validationError': '',
'inProgress': gettext('Saving'),
'success': gettext('Your changes have been saved.')
},
initialize: function (options) {
this.template = _.template($(this.templateSelector).text()),
this.helpMessage = this.options.helpMessage || '';
this.showMessages = _.isUndefined(this.options.showMessages) ? true : this.options.showMessages;
_.bindAll(this, 'modelValue', 'saveAttributes', 'saveSucceeded', 'getMessage',
'message', 'showHelpMessage', 'showInProgressMessage', 'showSuccessMessage', 'showErrorMessage');
},
modelValue: function () {
return this.model.get(this.options.valueAttribute);
},
saveAttributes: function (attributes, options) {
var view = this;
var defaultOptions = {
contentType: 'application/merge-patch+json',
patch: true,
wait: true,
success: function (model, response, options) {
view.saveSucceeded()
},
error: function (model, xhr, options) {
view.showErrorMessage(xhr)
}
};
this.showInProgressMessage();
this.model.save(attributes, _.extend(defaultOptions, options));
},
saveSucceeded: function () {
this.showSuccessMessage();
},
message: function (message) {
return this.$('.u-field-message').html(message);
},
getMessage: function(message_status) {
if ((message_status + 'Message') in this) {
return this[message_status + 'Message'].call(this);
} else if (this.showMessages) {
return this.indicators[message_status] + this.messages[message_status];
}
return this.indicators[message_status];
},
showHelpMessage: function () {
this.message(this.helpMessage);
},
showInProgressMessage: function () {
this.message(this.getMessage('inProgress'));
},
showSuccessMessage: function () {
var successMessage = this.getMessage('success');
this.message(successMessage);
if (this.options.refreshPageOnSave) {
document.location.reload();
}
var view = this;
var context = Date.now();
this.lastSuccessMessageContext = context;
setTimeout(function () {
if ((context === view.lastSuccessMessageContext) && (view.message().html() == successMessage)) {
view.showHelpMessage();
}
}, messageRevertDelay);
},
showErrorMessage: function (xhr) {
if (xhr.status === 400) {
try {
var errors = JSON.parse(xhr.responseText);
var validationErrorMessage = Mustache.escapeHtml(errors['field_errors'][this.options.valueAttribute]['user_message']);
var message = this.indicators['validationError'] + validationErrorMessage;
this.message(message);
} catch (error) {
this.message(this.getMessage('error'));
}
} else {
this.message(this.getMessage('error'));
}
}
});
FieldViews.ReadonlyFieldView = FieldViews.FieldView.extend({
fieldType: 'readonly',
templateSelector: '#field_readonly-tpl',
initialize: function (options) {
this._super(options);
_.bindAll(this, 'render', 'fieldValue', 'updateValueInField');
this.listenTo(this.model, "change:" + this.options.valueAttribute, this.updateValueInField);
},
render: function () {
this.$el.html(this.template({
id: this.options.valueAttribute,
title: this.options.title,
value: this.modelValue(),
message: this.helpMessage
}));
return this;
},
fieldValue: function () {
return this.$('.u-field-value input').val();
},
updateValueInField: function () {
this.$('.u-field-value input').val(Mustache.escapeHtml(this.modelValue()));
}
});
FieldViews.TextFieldView = FieldViews.FieldView.extend({
fieldType: 'text',
templateSelector: '#field_text-tpl',
events: {
'change input': 'saveValue'
},
initialize: function (options) {
this._super(options);
_.bindAll(this, 'render', 'fieldValue', 'updateValueInField', 'saveValue');
this.listenTo(this.model, "change:" + this.options.valueAttribute, this.updateValueInField);
},
render: function () {
this.$el.html(this.template({
id: this.options.valueAttribute,
title: this.options.title,
value: this.modelValue(),
message: this.helpMessage
}));
return this;
},
fieldValue: function () {
return this.$('.u-field-value input').val();
},
updateValueInField: function () {
var value = (_.isUndefined(this.modelValue()) || _.isNull(this.modelValue())) ? '' : this.modelValue();
this.$('.u-field-value input').val(Mustache.escapeHtml(value));
},
saveValue: function (event) {
var attributes = {};
attributes[this.options.valueAttribute] = this.fieldValue();
this.saveAttributes(attributes);
}
});
FieldViews.DropdownFieldView = FieldViews.FieldView.extend({
fieldType: 'dropdown',
templateSelector: '#field_dropdown-tpl',
events: {
'change select': 'saveValue'
},
initialize: function (options) {
this._super(options);
_.bindAll(this, 'render', 'fieldValue', 'updateValueInField', 'saveValue');
this.listenTo(this.model, "change:" + this.options.valueAttribute, this.updateValueInField);
},
render: function () {
this.$el.html(this.template({
id: this.options.valueAttribute,
title: this.options.title,
required: this.options.required,
selectOptions: this.options.options,
message: this.helpMessage,
}));
this.updateValueInField()
return this;
},
fieldValue: function () {
return this.$('.u-field-value select').val();
},
updateValueInField: function () {
var value = (_.isUndefined(this.modelValue()) || _.isNull(this.modelValue())) ? '' : this.modelValue();
this.$('.u-field-value select').val(Mustache.escapeHtml(value));
},
saveValue: function () {
var attributes = {};
attributes[this.options.valueAttribute] = this.fieldValue();
this.saveAttributes(attributes);
}
});
FieldViews.LinkFieldView = FieldViews.FieldView.extend({
fieldType: 'link',
templateSelector: '#field_link-tpl',
events: {
'click a': 'linkClicked'
},
initialize: function (options) {
this._super(options);
_.bindAll(this, 'render', 'linkClicked');
},
render: function () {
this.$el.html(this.template({
id: this.options.valueAttribute,
title: this.options.title,
linkTitle: this.options.linkTitle,
linkHref: this.options.linkHref,
message: this.helpMessage,
}));
return this;
},
linkClicked: function () {
event.preventDefault();
}
});
return FieldViews;
})
}).call(this, define || RequireJS.define);
......@@ -28,14 +28,9 @@
@include animation(rotateCW $tmg-s1 linear infinite);
}
.ui-loading {
.ui-loading-base {
@include animation(fadeIn $tmg-f2 linear 1);
@extend %ui-well;
@extend %t-copy-base;
opacity: .6;
background-color: $white;
padding: ($baseline*1.5) $baseline;
text-align: center;
.spin {
@extend %anim-rotateCW;
......@@ -46,3 +41,12 @@
padding-left: ($baseline/4);
}
}
.ui-loading {
@extend .ui-loading-base;
@extend %ui-well;
opacity: 0.6;
background-color: $white;
padding: ($baseline*1.5) $baseline;
text-align: center;
}
......@@ -45,6 +45,7 @@
@import 'elements/system-feedback';
// base - specific views
@import "views/account-settings";
@import 'views/login-register';
@import 'views/verification';
@import 'views/decoupled-verification';
......
......@@ -43,6 +43,7 @@
@import 'elements/controls';
// shared - course
@import 'shared/fields';
@import 'shared/forms';
@import 'shared/footer';
@import 'shared/header';
......
// lms - shared - fields
// ====================
.u-field {
padding: $baseline 0;
border-bottom: 1px solid $gray-l5;
.message-error {
color: $alert-color;
}
.message-validation-error {
color: $warning-color;
}
.message-in-progress {
color: $gray-d2;
}
.message-success {
color: $success-color;
}
}
.u-field-readonly {
input[type="text"],
input[type="text"]:focus {
background-color: transparent;
padding: 0px;
border: none;
box-shadow: none;
}
}
.u-field-title {
width: flex-grid(3, 12);
display: inline-block;
color: $dark-gray1;
vertical-align: top;
margin-bottom: 0;
label {
@include margin-left($baseline/2);
}
}
.u-field-value {
width: flex-grid(3, 12);
display: inline-block;
vertical-align: top;
select, input {
width: 100%;
}
}
.u-field-message {
@extend small;
@include padding-left($baseline/2);
width: flex-grid(6, 12);
display: inline-block;
vertical-align: top;
color: $dark-gray1;
i {
@include margin-right($baseline/4);
}
}
// lms - application - account settings
// ====================
// Table of Contents
// * +Container - Account Settings
// * +Main - Header
// * +Settings Section
// +Container - Account Settings
.wrapper-account-settings {
@extend .container;
padding-top: ($baseline*2);
.account-settings-container {
padding: 0;
}
.ui-loading-indicator,
.ui-loading-error {
@extend .ui-loading-base;
// center horizontally
@include margin-left(auto);
@include margin-right(auto);
padding: ($baseline*3);
text-align: center;
.message-error {
color: $alert-color;
}
}
}
// +Main - Header
.wrapper-account-settings {
.wrapper-header {
.header-title {
@extend %t-title4;
margin-bottom: ($baseline/2);
}
.header-subtitle {
color: $gray-l2;
}
}
}
// +Settings Section
.account-settings-sections {
.section-header {
@extend %t-title6;
@extend %t-strong;
padding-bottom: ($baseline/2);
border-bottom: 1px solid $gray-l4;
}
.section {
background-color: $white;
padding: $baseline;
margin-top: $baseline;
border: 1px solid $gray-l4;
box-shadow: 0 0 1px 1px $shadow-l2;
border-radius: 5px;
}
}
<label class="u-field-title" for="u-field-select-<%- id %>">
<%- gettext(title) %>
</label>
<span class="u-field-value">
<select name="select" id="u-field-select-<%- id %>" aria-describedby="u-field-message-<%- id %>">
<% if (!required) { %>
<option value=""></option>
<% } %>
<% _.each(selectOptions, function(selectOption) { %>
<option value="<%- selectOption[0] %>"><%- selectOption[1] %></option>
<% }); %>
</select>
</span>
<span class="u-field-message" id="u-field-message-<%- id %>">
<%- gettext(message) %>
</span>
<label class="u-field-title" for="u-field-link-<%- id %>">
<%- gettext(title) %>
</label>
<span class="u-field-value">
<a id="u-field-link-<%- id %>" href="<%- gettext(linkHref) %>" aria-describedby="u-field-message-<%- id %>">
<%- gettext(linkTitle) %>
</a>
</span>
<span class="u-field-message" id="u-field-message-<%- id %>">
<%- gettext(message) %>
</span>
<label class="u-field-title" for="u-field-input-<%- id %>">
<%- gettext(title) %>
</label>
<span class="u-field-value">
<input id="u-field-input-<%- id %>" aria-describedby="u-field-message-<%- id %>" type="text" name="input" readonly=true value="<%- gettext(value) %>">
</span>
<span class="u-field-message" id="u-field-message-<%- id %>">
<%- gettext(message) %>
</span>
<label class="u-field-title" for="u-field-input-<%- id %>">
<%- gettext(title) %>
</label>
<span class="u-field-value">
<input id="u-field-input-<%- id %>" aria-describedby="u-field-message-<%- id %>" type="text" name="input" value="<%- gettext(value) %>">
</span>
<span class="u-field-message" id="u-field-message-<%- id %>">
<%- gettext(message) %>
</span>
......@@ -82,9 +82,7 @@ site_status_msg = get_site_status_msg(course_id)
<a href="#" class="dropdown" aria-haspopup="true" aria-expanded="false"><span class="sr">${_("More options dropdown")}</span> &#9662;</a>
<ul class="dropdown-menu" aria-label="More Options" role="menu">
<%block name="navigation_dropdown_menu_links" >
% if settings.MKTG_URL_LINK_MAP.get('FAQ'):
<li><a href="${marketing_link('FAQ')}">${_("Help")}</a></li>
% endif
<li><a href="${reverse('account_settings')}">${_("Account Settings")}</a></li>
</%block>
<li><a href="${reverse('logout')}" role="menuitem">${_("Sign out")}</a></li>
</ul>
......
......@@ -90,9 +90,7 @@ site_status_msg = get_site_status_msg(course_id)
<a href="#" class="dropdown" aria-haspopup="true" aria-expanded="false"><span class="sr">${_("More options dropdown")}</span><i class="fa fa-sort-desc" aria-hidden="true"></i></a>
<ul class="dropdown-menu" aria-label="More Options" role="menu">
<%block name="navigation_dropdown_menu_links" >
% if settings.MKTG_URL_LINK_MAP.get('FAQ'):
<li><a href="${marketing_link('FAQ')}">${_("Help")}</a></li>
% endif
<li><a href="${reverse('account_settings')}">${_("Account Settings")}</a></li>
</%block>
<li><a href="${reverse('logout')}" role="menuitem">${_("Sign out")}</a></li>
</ul>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment