Allow changing of language preference from profile page
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
81
common/djangoapps/lang_pref/api.py
Normal file
81
common/djangoapps/lang_pref/api.py
Normal file
@@ -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
|
||||
30
common/djangoapps/lang_pref/tests/test_api.py
Normal file
30
common/djangoapps/lang_pref/tests/test_api.py
Normal file
@@ -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')
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user