diff --git a/cms/envs/common.py b/cms/envs/common.py index cfd0664618..428a84799a 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -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 diff --git a/common/djangoapps/lang_pref/api.py b/common/djangoapps/lang_pref/api.py new file mode 100644 index 0000000000..18b98c1e5a --- /dev/null +++ b/common/djangoapps/lang_pref/api.py @@ -0,0 +1,81 @@ +# -*- 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 diff --git a/common/djangoapps/lang_pref/tests/test_api.py b/common/djangoapps/lang_pref/tests/test_api.py new file mode 100644 index 0000000000..2362fbc0ea --- /dev/null +++ b/common/djangoapps/lang_pref/tests/test_api.py @@ -0,0 +1,30 @@ +# -*- 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') diff --git a/common/djangoapps/user_api/api/account.py b/common/djangoapps/user_api/api/account.py index aa17e0bf91..4281326860 100644 --- a/common/djangoapps/user_api/api/account.py +++ b/common/djangoapps/user_api/api/account.py @@ -414,4 +414,3 @@ def _validate_email(email): raise AccountEmailInvalid( u"Email '{email}' format is not valid".format(email=email) ) - diff --git a/common/djangoapps/user_api/api/profile.py b/common/djangoapps/user_api/api/profile.py index b9b4c934ff..b985d3c039 100644 --- a/common/djangoapps/user_api/api/profile.py +++ b/common/djangoapps/user_api/api/profile.py @@ -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) diff --git a/common/djangoapps/user_api/tests/test_profile_api.py b/common/djangoapps/user_api/tests/test_profile_api.py index 7421457a44..823985588c 100644 --- a/common/djangoapps/user_api/tests/test_profile_api.py +++ b/common/djangoapps/user_api/tests/test_profile_api.py @@ -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 diff --git a/lms/djangoapps/student_account/test/test_views.py b/lms/djangoapps/student_account/test/test_views.py index c3e522e18e..adb8b8b7e2 100644 --- a/lms/djangoapps/student_account/test/test_views.py +++ b/lms/djangoapps/student_account/test/test_views.py @@ -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 diff --git a/lms/djangoapps/student_account/views.py b/lms/djangoapps/student_account/views.py index 4fba5f7c8a..d1344e881a 100644 --- a/lms/djangoapps/student_account/views.py +++ b/lms/djangoapps/student_account/views.py @@ -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 diff --git a/lms/djangoapps/student_profile/test/test_views.py b/lms/djangoapps/student_profile/test/test_views.py index ffb4ab6404..c933242c1d 100644 --- a/lms/djangoapps/student_profile/test/test_views.py +++ b/lms/djangoapps/student_profile/test/test_views.py @@ -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' ) diff --git a/lms/djangoapps/student_profile/urls.py b/lms/djangoapps/student_profile/urls.py index 113e2e89ed..81344a0be3 100644 --- a/lms/djangoapps/student_profile/urls.py +++ b/lms/djangoapps/student_profile/urls.py @@ -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'), ) diff --git a/lms/djangoapps/student_profile/views.py b/lms/djangoapps/student_profile/views.py index 078183c54c..c0ec2f57cd 100644 --- a/lms/djangoapps/student_profile/views.py +++ b/lms/djangoapps/student_profile/views.py @@ -1,15 +1,17 @@ """ 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) diff --git a/lms/envs/common.py b/lms/envs/common.py index 33dce4c86b..abd6d2e31b 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -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 diff --git a/lms/static/js/student_profile/profile.js b/lms/static/js/student_profile/profile.js index 9a7665878a..a453dceb45 100644 --- a/lms/static/js/student_profile/profile.js +++ b/lms/static/js/student_profile/profile.js @@ -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 : '' }); } }, diff --git a/lms/templates/modal/_modal-settings-language.html b/lms/templates/modal/_modal-settings-language.html index b7fd01f38e..9846d1f259 100644 --- a/lms/templates/modal/_modal-settings-language.html +++ b/lms/templates/modal/_modal-settings-language.html @@ -54,7 +54,12 @@