Commit 254a0ce6 by Renzo Lucioni

Allow changing of language preference from profile page

parent c5530b75
......@@ -308,6 +308,8 @@ TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_
LANGUAGE_CODE = 'en' # http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGES = lms.envs.common.LANGUAGES
LANGUAGE_DICT = dict(LANGUAGES)
USE_I18N = True
USE_L10N = True
......
# -*- coding: utf-8 -*-
""" Python API for language and translation management. """
from collections import namedtuple
from django.conf import settings
from django.utils.translation import get_language
from dark_lang.models import DarkLangConfig
# Named tuples can be referenced using object-like variable
# deferencing, making the use of tuples more readable by
# eliminating the need to see the context of the tuple packing.
Language = namedtuple('Language', 'code name')
def released_languages():
"""Retrieve the list of released languages.
Constructs a list of Language tuples by intersecting the
list of valid language tuples with the list of released
language codes.
Returns:
list of Language: Languages in which full translations are available.
Example:
>>> print released_languages()
[Language(code='en', name=u'English'), Language(code='fr', name=u'Français')]
"""
released_language_codes = DarkLangConfig.current().released_languages_list
default_language_code = settings.LANGUAGE_CODE
if default_language_code not in released_language_codes:
released_language_codes.append(default_language_code)
released_language_codes.sort()
# Intersect the list of valid language tuples with the list
# of release language codes
released_languages = [
Language(tuple[0], tuple[1])
for tuple in settings.LANGUAGES
if tuple[0] in released_language_codes
]
return released_languages
def preferred_language(preferred_language_code):
"""Retrieve the name of the user's preferred language.
Note:
The preferred_language_code may be None. If this is the case,
the if/else block will handle it by returning either the active
language or the default language.
Args:
preferred_language_code (str): The ISO 639 code corresponding
to the user's preferred language.
Returns:
unicode: The name of the user's preferred language.
"""
active_language_code = get_language()
if preferred_language_code in settings.LANGUAGE_DICT:
# If the user has indicated a preference for a valid
# language, record their preferred language
preferred_language = settings.LANGUAGE_DICT[preferred_language_code]
elif active_language_code in settings.LANGUAGE_DICT:
# Otherwise, set the language used in the current thread
# as the preferred language
preferred_language = settings.LANGUAGE_DICT[active_language_code]
else:
# Otherwise, use the default language
preferred_language = settings.LANGUAGE_DICT[settings.LANGUAGE_CODE]
return preferred_language
# -*- coding: utf-8 -*-
""" Tests for the language API. """
from django.test import TestCase
import ddt
from lang_pref import api as language_api
@ddt.ddt
class LanguageApiTest(TestCase):
INVALID_LANGUAGE_CODES = ['', 'foo']
def test_released_languages(self):
released_languages = language_api.released_languages()
self.assertGreaterEqual(len(released_languages), 1)
def test_preferred_language(self):
preferred_language = language_api.preferred_language('fr')
self.assertEqual(preferred_language, u'Français')
@ddt.data(*INVALID_LANGUAGE_CODES)
def test_invalid_preferred_language(self, language_code):
preferred_language = language_api.preferred_language(language_code)
self.assertEqual(preferred_language, u'English')
def test_no_preferred_language(self):
preferred_language = language_api.preferred_language(None)
self.assertEqual(preferred_language, u'English')
......@@ -414,4 +414,3 @@ def _validate_email(email):
raise AccountEmailInvalid(
u"Email '{email}' format is not valid".format(email=email)
)
......@@ -5,7 +5,8 @@ but does NOT include basic account information such as username, password, and
email address.
"""
from user_api.models import UserProfile
from user_api.models import User, UserProfile, UserPreference
from user_api.helpers import intercept_errors
......@@ -43,13 +44,13 @@ FULL_NAME_MAX_LENGTH = 255
@intercept_errors(ProfileInternalError, ignore_errors=[ProfileRequestError])
def profile_info(username):
"""Retrieve a user's profile information
"""Retrieve a user's profile information.
Searches either by username or email.
At least one of the keyword args must be provided.
Arguments:
Args:
username (unicode): The username of the account to retrieve.
Returns:
......@@ -78,7 +79,7 @@ def update_profile(username, full_name=None):
Args:
username (unicode): The username associated with the account.
Keyword Arguments:
Keyword Args:
full_name (unicode): If provided, set the user's full name to this value.
Returns:
......@@ -102,31 +103,48 @@ def update_profile(username, full_name=None):
@intercept_errors(ProfileInternalError, ignore_errors=[ProfileRequestError])
def preference_info(username, preference_name):
def preference_info(username):
"""Retrieve information about a user's preferences.
Arguments:
Args:
username (unicode): The username of the account to retrieve.
preference_name (unicode): The name of the preference to retrieve.
Returns:
The JSON-deserialized value.
dict: Empty if there is no user
"""
pass
preferences = UserPreference.objects.filter(user__username=username)
preferences_dict = {}
for preference in preferences:
preferences_dict[preference.key] = preference.value
return preferences_dict
@intercept_errors(ProfileInternalError, ignore_errors=[ProfileRequestError])
def update_preference(username, preference_name, preference_value):
"""Update a user's preference.
def update_preferences(username, **kwargs):
"""Update a user's preferences.
Arguments:
Sets the provided preferences for the given user.
Args:
username (unicode): The username of the account to retrieve.
preference_name (unicode): The name of the preference to set.
preference_value (JSON-serializable): The new value for the preference.
Keyword Args:
**kwargs (unicode): Arbitrary key-value preference pairs
Returns:
None
Raises:
ProfileUserNotFound
"""
pass
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise ProfileUserNotFound
else:
for key, value in kwargs.iteritems():
UserPreference.set_preference(user, key, value)
......@@ -5,6 +5,7 @@ from django.test import TestCase
import ddt
from nose.tools import raises
from dateutil.parser import parse as parse_datetime
from user_api.api import account as account_api
from user_api.api import profile as profile_api
from user_api.models import UserProfile
......@@ -13,9 +14,9 @@ from user_api.models import UserProfile
@ddt.ddt
class ProfileApiTest(TestCase):
USERNAME = u"frank-underwood"
PASSWORD = u"ṕáśśẃőŕd"
EMAIL = u"frank+underwood@example.com"
USERNAME = u'frank-underwood'
PASSWORD = u'ṕáśśẃőŕd'
EMAIL = u'frank+underwood@example.com'
def test_create_profile(self):
# Create a new account, which should have an empty profile by default.
......@@ -31,9 +32,9 @@ class ProfileApiTest(TestCase):
def test_update_full_name(self):
account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
profile_api.update_profile(self.USERNAME, full_name=u"ȻħȺɍłɇs")
profile = profile_api.profile_info(username=self.USERNAME)
self.assertEqual(profile['full_name'], u"ȻħȺɍłɇs")
profile_api.update_profile(self.USERNAME, full_name=u'ȻħȺɍłɇs')
profile = profile_api.profile_info(self.USERNAME)
self.assertEqual(profile['full_name'], u'ȻħȺɍłɇs')
@raises(profile_api.ProfileInvalidField)
@ddt.data('', 'a' * profile_api.FULL_NAME_MAX_LENGTH + 'a')
......@@ -43,10 +44,10 @@ class ProfileApiTest(TestCase):
@raises(profile_api.ProfileUserNotFound)
def test_update_profile_no_user(self):
profile_api.update_profile(self.USERNAME, full_name="test")
profile_api.update_profile(self.USERNAME, full_name='test')
def test_retrieve_profile_no_user(self):
profile = profile_api.profile_info("does not exist")
profile = profile_api.profile_info('does not exist')
self.assertIs(profile, None)
def test_record_name_change_history(self):
......@@ -55,30 +56,53 @@ class ProfileApiTest(TestCase):
# Change the name once
# Since the original name was an empty string, expect that the list
# of old names is empty
profile_api.update_profile(self.USERNAME, full_name="new name")
profile_api.update_profile(self.USERNAME, full_name='new name')
meta = UserProfile.objects.get(user__username=self.USERNAME).get_meta()
self.assertEqual(meta, {})
# Change the name again and expect the new name is stored in the history
profile_api.update_profile(self.USERNAME, full_name="another new name")
profile_api.update_profile(self.USERNAME, full_name='another new name')
meta = UserProfile.objects.get(user__username=self.USERNAME).get_meta()
self.assertEqual(len(meta['old_names']), 1)
name, rationale, timestamp = meta['old_names'][0]
self.assertEqual(name, "new name")
self.assertEqual(rationale, u"")
self.assertEqual(name, 'new name')
self.assertEqual(rationale, u'')
self._assert_is_datetime(timestamp)
# Change the name a third time and expect both names are stored in the history
profile_api.update_profile(self.USERNAME, full_name="yet another new name")
profile_api.update_profile(self.USERNAME, full_name='yet another new name')
meta = UserProfile.objects.get(user__username=self.USERNAME).get_meta()
self.assertEqual(len(meta['old_names']), 2)
name, rationale, timestamp = meta['old_names'][1]
self.assertEqual(name, "another new name")
self.assertEqual(rationale, u"")
self.assertEqual(name, 'another new name')
self.assertEqual(rationale, u'')
self._assert_is_datetime(timestamp)
def test_update_and_retrieve_preference_info(self):
account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
profile_api.update_preferences(self.USERNAME, preference_key='preference_value')
preferences = profile_api.preference_info(self.USERNAME)
self.assertEqual(preferences['preference_key'], 'preference_value')
@raises(profile_api.ProfileUserNotFound)
def test_retrieve_and_update_preference_info_no_user(self):
preferences = profile_api.preference_info(self.USERNAME)
self.assertEqual(preferences, {})
profile_api.update_preferences(self.USERNAME, preference_key='preference_value')
def test_update_and_retrieve_preference_info_unicode(self):
account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
profile_api.update_preferences(self.USERNAME, **{u'ⓟⓡⓔⓕⓔⓡⓔⓝⓒⓔ_ⓚⓔⓨ': u'ǝnןɐʌ_ǝɔuǝɹǝɟǝɹd'})
preferences = profile_api.preference_info(self.USERNAME)
self.assertEqual(preferences[u'ⓟⓡⓔⓕⓔⓡⓔⓝⓒⓔ_ⓚⓔⓨ'], u'ǝnןɐʌ_ǝɔuǝɹǝɟǝɹd')
def _assert_is_datetime(self, timestamp):
if not timestamp:
return False
......
......@@ -56,24 +56,6 @@ class StudentAccountViewTest(UrlResetMixin, TestCase):
result = self.client.login(username=self.USERNAME, password=self.PASSWORD)
self.assertTrue(result)
def _change_email(self, new_email, password):
"""Request to change the user's email. """
data = {}
if new_email is not None:
data['new_email'] = new_email
if password is not None:
# We can't pass a Unicode object to urlencode, so we encode the Unicode object
data['password'] = password.encode('utf-8')
response = self.client.put(
path=reverse('email_change_request'),
data=urlencode(data),
content_type='application/x-www-form-urlencoded'
)
return response
def test_index(self):
response = self.client.get(reverse('account_index'))
self.assertContains(response, "Student Account")
......@@ -153,7 +135,7 @@ class StudentAccountViewTest(UrlResetMixin, TestCase):
response = self._change_email(invalid_email, self.PASSWORD)
self.assertEquals(response.status_code, 400)
def test_email_change_confirmation_handler(self):
def test_email_change_confirmation(self):
# Get an email change activation key
activation_key = account_api.request_email_change(self.USERNAME, self.NEW_EMAIL, self.PASSWORD)
......@@ -242,3 +224,21 @@ class StudentAccountViewTest(UrlResetMixin, TestCase):
self.assertEqual(email.to, expected_to)
self.assertIn(expected_subject, email.subject)
self.assertIn(expected_body, email.body)
def _change_email(self, new_email, password):
"""Request to change the user's email. """
data = {}
if new_email is not None:
data['new_email'] = new_email
if password is not None:
# We can't pass a Unicode object to urlencode, so we encode the Unicode object
data['password'] = password.encode('utf-8')
response = self.client.put(
path=reverse('email_change_request'),
data=urlencode(data),
content_type='application/x-www-form-urlencoded'
)
return response
......@@ -11,6 +11,7 @@ from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from edxmako.shortcuts import render_to_response, render_to_string
from microsite_configuration import microsite
from user_api.api import account as account_api
from user_api.api import profile as profile_api
......
......@@ -2,6 +2,8 @@
""" Tests for student profile views. """
from urllib import urlencode
from collections import namedtuple
from mock import patch
import ddt
from django.test import TestCase
......@@ -11,16 +13,26 @@ from django.core.urlresolvers import reverse
from util.testing import UrlResetMixin
from user_api.api import account as account_api
from user_api.api import profile as profile_api
from lang_pref import LANGUAGE_KEY
@ddt.ddt
class StudentProfileViewTest(UrlResetMixin, TestCase):
""" Tests for the student profile views. """
USERNAME = u"heisenberg"
PASSWORD = u"ḅḷüëṡḳÿ"
EMAIL = u"walt@savewalterwhite.com"
FULL_NAME = u"𝖂𝖆𝖑𝖙𝖊𝖗 𝖂𝖍𝖎𝖙𝖊"
USERNAME = u'heisenberg'
PASSWORD = u'ḅḷüëṡḳÿ'
EMAIL = u'walt@savewalterwhite.com'
FULL_NAME = u'𝖂𝖆𝖑𝖙𝖊𝖗 𝖂𝖍𝖎𝖙𝖊'
Language = namedtuple('Language', 'code name')
NEW_LANGUAGE = Language('fr', u'Français')
INVALID_LANGUAGE_CODES = [
'',
'foo',
'en@pirate',
]
@patch.dict(settings.FEATURES, {'ENABLE_NEW_DASHBOARD': True})
def setUp(self):
......@@ -37,38 +49,77 @@ class StudentProfileViewTest(UrlResetMixin, TestCase):
def test_index(self):
response = self.client.get(reverse('profile_index'))
self.assertContains(response, "Student Profile")
self.assertContains(response, "Change My Name")
self.assertContains(response, "Change Preferred Language")
self.assertContains(response, "Connected Accounts")
def test_name_change_handler(self):
def test_name_change(self):
# Verify that the name on the account is blank
profile_info = profile_api.profile_info(self.USERNAME)
self.assertEquals(profile_info['full_name'], '')
self.assertEqual(profile_info['full_name'], '')
response = self._change_name(self.FULL_NAME)
self.assertEquals(response.status_code, 204)
self.assertEqual(response.status_code, 204)
# Verify that the name on the account has been changed
profile_info = profile_api.profile_info(self.USERNAME)
self.assertEquals(profile_info['full_name'], self.FULL_NAME)
self.assertEqual(profile_info['full_name'], self.FULL_NAME)
def test_name_change_invalid(self):
# Name cannot be an empty string
response = self._change_name('')
self.assertEquals(response.status_code, 400)
self.assertEqual(response.status_code, 400)
def test_name_change_missing_params(self):
response = self._change_name(None)
self.assertEquals(response.status_code, 400)
self.assertEqual(response.status_code, 400)
@patch('student_profile.views.profile_api.update_profile')
def test_name_change_internal_error(self, mock_call):
def test_name_change_internal_error(self, mock_update_profile):
# This can't happen if the user is logged in, but test it anyway
mock_call.side_effect = profile_api.ProfileUserNotFound
mock_update_profile.side_effect = profile_api.ProfileUserNotFound
response = self._change_name(self.FULL_NAME)
self.assertEqual(response.status_code, 500)
@patch('student_profile.views.language_api.released_languages')
def test_language_change(self, mock_released_languages):
mock_released_languages.return_value = [self.NEW_LANGUAGE]
# Set French as the user's preferred language
response = self._change_language(self.NEW_LANGUAGE.code)
self.assertEqual(response.status_code, 204)
# Verify that French is now the user's preferred language
preferences = profile_api.preference_info(self.USERNAME)
self.assertEqual(preferences[LANGUAGE_KEY], self.NEW_LANGUAGE.code)
# Verify that the page reloads in French
response = self.client.get(reverse('profile_index'))
self.assertContains(response, "Merci de choisir la langue")
@ddt.data(*INVALID_LANGUAGE_CODES)
def test_change_to_invalid_or_unreleased_language(self, language_code):
response = self._change_language(language_code)
self.assertEqual(response.status_code, 400)
def test_change_to_missing_language(self):
response = self._change_language(None)
self.assertEqual(response.status_code, 400)
@patch('student_profile.views.profile_api.update_preferences')
@patch('student_profile.views.language_api.released_languages')
def test_language_change_missing_profile(self, mock_released_languages, mock_update_preferences):
# This can't happen if the user is logged in, but test it anyway
mock_released_languages.return_value = [self.NEW_LANGUAGE]
mock_update_preferences.side_effect = profile_api.ProfileUserNotFound
response = self._change_language(self.NEW_LANGUAGE.code)
self.assertEqual(response.status_code, 500)
@ddt.data(
('get', 'profile_index'),
('put', 'name_change')
('put', 'name_change'),
('put', 'language_change')
)
@ddt.unpack
def test_require_login(self, method, url_name):
......@@ -83,7 +134,8 @@ class StudentProfileViewTest(UrlResetMixin, TestCase):
@ddt.data(
('get', 'profile_index'),
('put', 'name_change')
('put', 'name_change'),
('put', 'language_change')
)
@ddt.unpack
def test_require_http_method(self, correct_method, url_name):
......@@ -109,5 +161,22 @@ class StudentProfileViewTest(UrlResetMixin, TestCase):
return self.client.put(
path=reverse('name_change'),
data=urlencode(data),
content_type= 'application/x-www-form-urlencoded'
content_type='application/x-www-form-urlencoded'
)
def _change_language(self, new_language):
"""Request a language change.
Returns:
HttpResponse
"""
data = {}
if new_language is not None:
data['new_language'] = new_language
return self.client.put(
path=reverse('language_change'),
data=urlencode(data),
content_type='application/x-www-form-urlencoded'
)
......@@ -4,4 +4,5 @@ urlpatterns = patterns(
'student_profile.views',
url(r'^$', 'index', name='profile_index'),
url(r'^name_change$', 'name_change_handler', name='name_change'),
url(r'^language_change$', 'language_change_handler', name='language_change'),
)
""" Views for a student's profile information. """
from django.conf import settings
from django.http import (
QueryDict, HttpResponse,
HttpResponseBadRequest, HttpResponseServerError
)
from django.conf import settings
from django_future.csrf import ensure_csrf_cookie
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from edxmako.shortcuts import render_to_response
from user_api.api import profile as profile_api
from lang_pref import LANGUAGE_KEY, api as language_api
from third_party_auth import pipeline
......@@ -26,15 +28,22 @@ def index(request):
HttpResponse: 302 if not logged in (redirect to login page)
HttpResponse: 405 if using an unsupported HTTP method
Example usage:
Example:
GET /profile
"""
user = request.user
released_languages = language_api.released_languages()
preferred_language_code = profile_api.preference_info(user.username).get(LANGUAGE_KEY)
preferred_language = language_api.preferred_language(preferred_language_code)
context = {
'disable_courseware_js': True
'disable_courseware_js': True,
'released_languages': released_languages,
'preferred_language': preferred_language,
}
if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
......@@ -59,7 +68,7 @@ def name_change_handler(request):
HttpResponse: 405 if using an unsupported HTTP method
HttpResponse: 500 if an unexpected error occurs.
Example usage:
Example:
PUT /profile/name_change
......@@ -82,3 +91,51 @@ def name_change_handler(request):
# A 204 is intended to allow input for actions to take place
# without causing a change to the user agent's active document view.
return HttpResponse(status=204)
@login_required
@require_http_methods(['PUT'])
@ensure_csrf_cookie
def language_change_handler(request):
"""Change the user's language preference.
Args:
request (HttpRequest)
Returns:
HttpResponse: 204 if successful
HttpResponse: 302 if not logged in (redirect to login page)
HttpResponse: 400 if no language is provided, or an unreleased
language is provided
HttpResponse: 405 if using an unsupported HTTP method
HttpResponse: 500 if an unexpected error occurs.
Example:
PUT /profile/language_change
"""
put = QueryDict(request.body)
username = request.user.username
new_language = put.get('new_language')
if new_language is None:
return HttpResponseBadRequest("Missing param 'new_language'")
# Check that the provided language code corresponds to a released language
released_languages = language_api.released_languages()
if new_language in [language.code for language in released_languages]:
try:
profile_api.update_preferences(username, **{LANGUAGE_KEY: new_language})
request.session['django_language'] = new_language
except profile_api.ProfileUserNotFound:
return HttpResponseServerError()
else:
return HttpResponseBadRequest(
"Provided language code corresponds to an unreleased language"
)
# A 204 is intended to allow input for actions to take place
# without causing a change to the user agent's active document view.
return HttpResponse(status=204)
......@@ -687,7 +687,7 @@ LANGUAGE_CODE = 'en' # http://www.i18nguy.com/unicode/language-identifiers.html
# Sourced from http://www.localeplanet.com/icu/ and wikipedia
LANGUAGES = (
('en', u'English'),
('eo', u'Dummy Language (Esperanto)'), # Dummy languaged used for testing
('eo', u'Dummy Language (Esperanto)'), # Dummy language used for testing
('fake2', u'Fake translations'), # Another dummy language for testing (not pushed to prod)
('am', u'አማርኛ'), # Amharic
......@@ -778,6 +778,9 @@ LOCALE_PATHS = (REPO_ROOT + '/conf/locale',) # edx-platform/conf/locale/
# Messages
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
# Guidelines for translators
TRANSLATORS_GUIDE = 'https://github.com/edx/edx-platform/blob/master/docs/en_us/developers/source/i18n_translators_guide.rst'
#################################### GITHUB #######################################
# gitreload is used in LMS-workflow to pull content from github
# gitreload requests are only allowed from these IP addresses, which are
......
......@@ -16,22 +16,42 @@ var edx = edx || {};
eventHandlers: {
init: function() {
_fn.eventHandlers.submit();
_fn.eventHandlers.click();
},
submit: function() {
$("#name-change-form").submit( _fn.form.submit );
$('#name-change-form').on( 'submit', _fn.update.name );
},
click: function() {
$('#language-change-form .submit-button').on( 'click', _fn.update.language );
}
},
update: {
name: function( event ) {
_fn.form.submit( event, '#new-name', 'new_name', 'name_change' );
},
language: function( event ) {
/**
* The onSuccess argument here means: take `window.location.reload`
* and return a function that will use `window.location` as the
* `this` reference inside `reload()`.
*/
_fn.form.submit( event, '#new-language', 'new_language', 'language_change', window.location.reload.bind(window.location) );
}
},
form: {
submit: function( event ) {
var $newName = $('#new-name');
var data = {
new_name: $newName.val()
};
submit: function( event, idSelector, key, url, onSuccess ) {
var $selection = $(idSelector),
data = {};
data[key] = $selection.val();
event.preventDefault();
_fn.ajax.put( 'name_change', data );
_fn.ajax.put( url, data, onSuccess );
}
},
......@@ -40,7 +60,7 @@ var edx = edx || {};
var csrftoken = _fn.cookie.get( 'csrftoken' );
$.ajaxSetup({
beforeSend: function(xhr, settings) {
beforeSend: function( xhr, settings ) {
if ( settings.type === 'PUT' ) {
xhr.setRequestHeader( 'X-CSRFToken', csrftoken );
}
......@@ -48,11 +68,12 @@ var edx = edx || {};
});
},
put: function( url, data ) {
put: function( url, data, onSuccess ) {
$.ajax({
url: url,
type: 'PUT',
data: data
data: data,
success: onSuccess ? onSuccess : ''
});
}
},
......
......@@ -54,7 +54,12 @@
<ul class="list list-actions actions-supplemental">
<li class="list-actions-item">
${_("Don't see your preferred language? {link_start}Volunteer to become a translator!{link_end}").format(link_start='<a class=" action action-volunteer" rel="external" target="_blank" href="https://github.com/edx/edx-platform/blob/master/docs/en_us/developers/source/i18n_translators_guide.rst">', link_end="</a>")}
${_("Don't see your preferred language? {link_start}Volunteer to become a translator!{link_end}").format(
link_start='<a class=" action action-volunteer" rel="external" target="_blank" href={translators_guide}>'.format(
translators_guide=settings.TRANSLATORS_GUIDE
),
link_end="</a>"
)}
</li>
</ul>
</div>
......
......@@ -16,14 +16,46 @@
<form id="name-change-form">
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}">
<label for="new-name">${_('Full Name')}</label>
<label for="new-name">${_("Full Name")}</label>
<input id="new-name" type="text" name="new-name" value="" placeholder="Xsy" />
<div class="submit-button">
<input type="submit" id="name-change-submit" value="${_('Change My Name')}">
<input type="submit" id="name-change-submit" value="${_("Change My Name")}">
</div>
</form>
<div id="language-change-body">
<form id="language-change-form">
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}">
<label for="new-language">${_("Please choose your preferred language")}</label>
<select id="new-language" name="language">
% for language in released_languages:
% if language.name is preferred_language:
<option value="${language.code}" selected="selected">${language.name}</option>
% else:
<option value="${language.code}">${language.name}</option>
% endif
% endfor
</select>
<div class="submit-button">
<input type="submit" id="language-change-submit" value="${_("Change Preferred Language")}" />
</div>
</form>
<ul class="list list-actions actions-supplemental">
<li class="list-actions-item">
${_("Don't see your preferred language? {link_start}Volunteer to become a translator!{link_end}").format(
link_start='<a class=" action action-volunteer" rel="external" target="_blank" href={translators_guide}>'.format(
translators_guide=settings.TRANSLATORS_GUIDE
),
link_end="</a>"
)}
</li>
</ul>
</div>
% if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
<%include file="third_party_auth.html" />
% endif
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