Commit 74bf7f23 by Usman Khalid Committed by Andy Armstrong

Account settings page.

TNL-1499
parent 3a2c527c
...@@ -15,14 +15,15 @@ from django.core.urlresolvers import reverse ...@@ -15,14 +15,15 @@ from django.core.urlresolvers import reverse
from django.core import mail from django.core import mail
from django.test.utils import override_settings 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 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.api import activate_account, create_account
from openedx.core.djangoapps.user_api.accounts import EMAIL_MAX_LENGTH 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.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
from student.tests.factories import CourseModeFactory
@ddt.ddt @ddt.ddt
...@@ -499,3 +500,45 @@ class StudentAccountLoginAndRegistrationTest(UrlResetMixin, ModuleStoreTestCase) ...@@ -499,3 +500,45 @@ class StudentAccountLoginAndRegistrationTest(UrlResetMixin, ModuleStoreTestCase)
url=reverse("social:begin", kwargs={"backend": backend_name}), url=reverse("social:begin", kwargs={"backend": backend_name}),
params=urlencode(params) 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'): ...@@ -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'^register/$', 'login_and_registration_form', {'initial_mode': 'register'}, name='account_register'),
url(r'^password$', 'password_change_request_handler', name='password_change_request'), 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 ...@@ -5,30 +5,37 @@ import json
from ipware.ip import get_ip from ipware.ip import get_ip
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.http import ( from django.http import (
HttpResponse, HttpResponseBadRequest, HttpResponseForbidden HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
) )
from django.shortcuts import redirect from django.shortcuts import redirect
from django.http import HttpRequest from django.http import HttpRequest
from django_countries import countries
from django.core.urlresolvers import reverse, resolve from django.core.urlresolvers import reverse, resolve
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_http_methods 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 import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from edxmako.shortcuts import render_to_response from edxmako.shortcuts import render_to_response
from microsite_configuration import microsite from microsite_configuration import microsite
from embargo import api as embargo_api from embargo import api as embargo_api
import third_party_auth
from external_auth.login_and_register import ( from external_auth.login_and_register import (
login as external_auth_login, login as external_auth_login,
register as external_auth_register register as external_auth_register
) )
from student.models import UserProfile
from student.views import ( from student.views import (
signin_user as old_login_view, signin_user as old_login_view,
register_user as old_register_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.accounts.api import request_password_change
from openedx.core.djangoapps.user_api.errors import UserNotFound from openedx.core.djangoapps.user_api.errors import UserNotFound
...@@ -294,3 +301,68 @@ def _external_auth_intercept(request, mode): ...@@ -294,3 +301,68 @@ def _external_auth_intercept(request, mode):
return external_auth_login(request) return external_auth_login(request)
elif mode == "register": elif mode == "register":
return external_auth_register(request) 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);
...@@ -28,14 +28,9 @@ ...@@ -28,14 +28,9 @@
@include animation(rotateCW $tmg-s1 linear infinite); @include animation(rotateCW $tmg-s1 linear infinite);
} }
.ui-loading { .ui-loading-base {
@include animation(fadeIn $tmg-f2 linear 1); @include animation(fadeIn $tmg-f2 linear 1);
@extend %ui-well;
@extend %t-copy-base; @extend %t-copy-base;
opacity: .6;
background-color: $white;
padding: ($baseline*1.5) $baseline;
text-align: center;
.spin { .spin {
@extend %anim-rotateCW; @extend %anim-rotateCW;
...@@ -46,3 +41,12 @@ ...@@ -46,3 +41,12 @@
padding-left: ($baseline/4); 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 @@ ...@@ -45,6 +45,7 @@
@import 'elements/system-feedback'; @import 'elements/system-feedback';
// base - specific views // base - specific views
@import "views/account-settings";
@import 'views/login-register'; @import 'views/login-register';
@import 'views/verification'; @import 'views/verification';
@import 'views/decoupled-verification'; @import 'views/decoupled-verification';
......
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
@import 'elements/controls'; @import 'elements/controls';
// shared - course // shared - course
@import 'shared/fields';
@import 'shared/forms'; @import 'shared/forms';
@import 'shared/footer'; @import 'shared/footer';
@import 'shared/header'; @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) ...@@ -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> <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"> <ul class="dropdown-menu" aria-label="More Options" role="menu">
<%block name="navigation_dropdown_menu_links" > <%block name="navigation_dropdown_menu_links" >
% if settings.MKTG_URL_LINK_MAP.get('FAQ'): <li><a href="${reverse('account_settings')}">${_("Account Settings")}</a></li>
<li><a href="${marketing_link('FAQ')}">${_("Help")}</a></li>
% endif
</%block> </%block>
<li><a href="${reverse('logout')}" role="menuitem">${_("Sign out")}</a></li> <li><a href="${reverse('logout')}" role="menuitem">${_("Sign out")}</a></li>
</ul> </ul>
......
...@@ -90,9 +90,7 @@ site_status_msg = get_site_status_msg(course_id) ...@@ -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> <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"> <ul class="dropdown-menu" aria-label="More Options" role="menu">
<%block name="navigation_dropdown_menu_links" > <%block name="navigation_dropdown_menu_links" >
% if settings.MKTG_URL_LINK_MAP.get('FAQ'): <li><a href="${reverse('account_settings')}">${_("Account Settings")}</a></li>
<li><a href="${marketing_link('FAQ')}">${_("Help")}</a></li>
% endif
</%block> </%block>
<li><a href="${reverse('logout')}" role="menuitem">${_("Sign out")}</a></li> <li><a href="${reverse('logout')}" role="menuitem">${_("Sign out")}</a></li>
</ul> </ul>
......
<%! import json %>
<%! from django.core.urlresolvers import reverse %>
<%! from django.utils.translation import ugettext as _ %>
<!--<%namespace name='static' file='/static_content.html'/>-->
<%inherit file="/main.html" />
<%namespace name='static' file='/static_content.html'/>
<%block name="pagetitle">${_("Account Settings")}</%block>
<%block name="header_extras">
<script type="text/template" id="account_settings-tpl">
<%static:include path="student_account/account_settings.underscore" />
</script>
% for template_name in ["field_dropdown", "field_link", "field_readonly", "field_text"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="fields/${template_name}.underscore" />
</script>
% endfor
</%block>
<div class="wrapper-account-settings"></div>
<%block name="headextra">
<%static:css group='style-course'/>
<script>
(function (require) {
require(['js/student_account/views/account_settings_factory'], function(setupAccountSettingsSection) {
var fieldsData = ${ json.dumps(fields) };
setupAccountSettingsSection(fieldsData, '${user_accounts_api_url}', '${user_preferences_api_url}');
});
}).call(this, require || RequireJS.require);
</script>
</%block>
<div class="account-settings-container">
<div class="wrapper-header">
<h2 class="header-title"><%- gettext("Account Settings") %></h2>
<small class="account-settings-header-subtitle"><%- gettext("These settings include basic information about your account. You can also specify additional information and see your linked social accounts on this page.") %></small>
</div>
<div class="account-settings-sections">
<% _.each(sections, function(section) { %>
<div class="section">
<h3 class="section-header"><%- gettext(section.title) %></h3>
<div class="account-settings-section-body">
<div class="ui-loading-indicator">
<span class="spin"><i class="icon fa fa-refresh" aria-hidden=true></i></span>
<span class="copy"><%- gettext("Loading") %></span>
</div>
<div class="ui-loading-error is-hidden">
<i class="fa fa-exclamation-triangle message-error" aria-hidden=true></i>
<span class="copy"><%- gettext("An error occurred. Please reload the page.") %></span>
</div>
</div>
</div>
<% }); %>
</div>
</div>
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