Commit be189e60 by Diana Huang Committed by Julia Hansbrough

Store user preferences for languages

Also, refactor tests such that we're not deleting users or prefs
parent 77cce3aa
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
Middleware for UserPreferences Middleware for UserPreferences
""" """
from django.utils.translation.trans_real import parse_accept_lang_header
from user_api.models import UserPreference, LANGUAGE_KEY from user_api.models import UserPreference, LANGUAGE_KEY
...@@ -17,11 +15,10 @@ class UserPreferenceMiddleware(object): ...@@ -17,11 +15,10 @@ class UserPreferenceMiddleware(object):
def process_request(self, request): def process_request(self, request):
""" """
If a user's UserPreference contains a language preference, If a user's UserPreference contains a language preference and there is
stick that preference in the session. no language set on the session, use the user's preference.
""" """
if 'django_language' not in request.session and request.user.is_authenticated():
query = UserPreference.objects.filter(user=request.user, key=LANGUAGE_KEY) user_pref = UserPreference.get_preference(request.user, LANGUAGE_KEY)
if query.exists(): if user_pref:
# there should only be one result for query request.session['django_language'] = user_pref
request.session['django_language'] = query[0].value
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
LANGUAGE_KEY = 'pref-lang'
class UserPreference(models.Model): class UserPreference(models.Model):
"""A user's preference, stored as generic text to be processed by client""" """A user's preference, stored as generic text to be processed by client"""
...@@ -10,3 +12,26 @@ class UserPreference(models.Model): ...@@ -10,3 +12,26 @@ class UserPreference(models.Model):
class Meta: class Meta:
unique_together = ("user", "key") unique_together = ("user", "key")
@classmethod
def set_preference(cls, user, preference_key, preference_value):
"""
Sets the user preference for a given key
"""
user_pref, _ = cls.objects.get_or_create(user=user, key=preference_key)
user_pref.value = preference_value
user_pref.save()
@classmethod
def get_preference(cls, user, preference_key):
"""
Gets the user preference value for a given key
Returns None if there isn't a preference for the given key
"""
try:
user_pref = cls.objects.get(user=user, key=preference_key)
return user_pref.value
except cls.DoesNotExist:
return None
from django.test import TestCase
from django.test.client import RequestFactory
from django.contrib.sessions.middleware import SessionMiddleware
from user_api.middleware import UserPreferenceMiddleware
from user_api.models import UserPreference, LANGUAGE_KEY
from student.tests.factories import UserFactory
class TestUserPreferenceMiddleware(TestCase):
"""
Tests to make sure user preferences are getting properly set in the middleware
"""
def setUp(self):
self.middleware = UserPreferenceMiddleware()
self.request_factory = RequestFactory()
self.session_middleware = SessionMiddleware()
self.user = UserFactory.create()
self.request = self.request_factory.get('/somewhere')
self.request.user = self.user
self.session_middleware.process_request(self.request)
def test_no_language_set_in_session_or_prefs(self):
# nothing set in the session or the prefs
self.middleware.process_request(self.request)
self.assertNotIn('django_language', self.request.session)
def test_language_in_user_prefs(self):
# language set in the user preferences and not the session
UserPreference.set_preference(self.user, LANGUAGE_KEY, 'eo')
self.middleware.process_request(self.request)
self.assertEquals(self.request.session['django_language'], 'eo')
def test_language_in_session(self):
# language set in both the user preferences and session,
# session should get precedence
self.request.session['django_language'] = 'en'
UserPreference.set_preference(self.user, LANGUAGE_KEY, 'eo')
self.middleware.process_request(self.request)
self.assertEquals(self.request.session['django_language'], 'en')
...@@ -2,6 +2,7 @@ from django.db import IntegrityError ...@@ -2,6 +2,7 @@ from django.db import IntegrityError
from django.test import TestCase from django.test import TestCase
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from user_api.tests.factories import UserPreferenceFactory from user_api.tests.factories import UserPreferenceFactory
from user_api.models import UserPreference
class UserPreferenceModelTest(TestCase): class UserPreferenceModelTest(TestCase):
...@@ -26,3 +27,18 @@ class UserPreferenceModelTest(TestCase): ...@@ -26,3 +27,18 @@ class UserPreferenceModelTest(TestCase):
key="testkey3", key="testkey3",
value="\xe8\xbf\x99\xe6\x98\xaf\xe4\xb8\xad\xe5\x9b\xbd\xe6\x96\x87\xe5\xad\x97'" value="\xe8\xbf\x99\xe6\x98\xaf\xe4\xb8\xad\xe5\x9b\xbd\xe6\x96\x87\xe5\xad\x97'"
) )
def test_get_set_preference(self):
user = UserFactory.create()
key = 'testkey'
value = 'testvalue'
# does a round trip
UserPreference.set_preference(user, key, value)
pref = UserPreference.get_preference(user, key)
self.assertEqual(pref, value)
# get preference for key that doesn't exist for user
pref = UserPreference.get_preference(user, 'testkey_none')
self.assertIsNone(pref)
import base64 import base64
from django.contrib.auth.models import User
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.core.urlresolvers import reverse
import json import json
import re import re
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from unittest import SkipTest from unittest import SkipTest
from user_api.models import UserPreference from user_api.models import UserPreference, LANGUAGE_KEY
from user_api.tests.factories import UserPreferenceFactory from user_api.tests.factories import UserPreferenceFactory
...@@ -17,21 +17,9 @@ USER_PREFERENCE_LIST_URI = "/user_api/v1/user_prefs/" ...@@ -17,21 +17,9 @@ USER_PREFERENCE_LIST_URI = "/user_api/v1/user_prefs/"
@override_settings(EDX_API_KEY=TEST_API_KEY) @override_settings(EDX_API_KEY=TEST_API_KEY)
class UserApiTestCase(TestCase): class ApiTestCase(TestCase):
def setUp(self):
super(UserApiTestCase, self).setUp() LIST_URI = USER_LIST_URI
self.users = [
UserFactory.create(
email="test{0}@test.org".format(i),
profile__name="Test {0}".format(i)
)
for i in range(5)
]
self.prefs = [
UserPreferenceFactory.create(user=self.users[0], key="key0"),
UserPreferenceFactory.create(user=self.users[0], key="key1"),
UserPreferenceFactory.create(user=self.users[1], key="key0")
]
def basic_auth(self, username, password): def basic_auth(self, username, password):
return {'HTTP_AUTHORIZATION': 'Basic ' + base64.b64encode('%s:%s' % (username, password))} return {'HTTP_AUTHORIZATION': 'Basic ' + base64.b64encode('%s:%s' % (username, password))}
...@@ -100,6 +88,32 @@ class UserApiTestCase(TestCase): ...@@ -100,6 +88,32 @@ class UserApiTestCase(TestCase):
self.assertEqual(response.status_code, 405) self.assertEqual(response.status_code, 405)
class NoUserApiTestCase(ApiTestCase):
def test_get_list_empty(self):
result = self.get_json(self.LIST_URI)
self.assertEqual(result["count"], 0)
self.assertIsNone(result["next"])
self.assertIsNone(result["previous"])
self.assertEqual(result["results"], [])
class UserApiTestCase(ApiTestCase):
def setUp(self):
super(UserApiTestCase, self).setUp()
self.users = [
UserFactory.create(
email="test{0}@test.org".format(i),
profile__name="Test {0}".format(i)
)
for i in range(5)
]
self.prefs = [
UserPreferenceFactory.create(user=self.users[0], key="key0"),
UserPreferenceFactory.create(user=self.users[0], key="key1"),
UserPreferenceFactory.create(user=self.users[1], key="key0")
]
class UserViewSetTest(UserApiTestCase): class UserViewSetTest(UserApiTestCase):
LIST_URI = USER_LIST_URI LIST_URI = USER_LIST_URI
...@@ -137,17 +151,10 @@ class UserViewSetTest(UserApiTestCase): ...@@ -137,17 +151,10 @@ class UserViewSetTest(UserApiTestCase):
def test_basic_auth(self): def test_basic_auth(self):
# ensure that having basic auth headers in the mix does not break anything # ensure that having basic auth headers in the mix does not break anything
self.assertHttpOK( self.assertHttpOK(
self.request_with_auth("get", self.LIST_URI, **self.basic_auth('someuser', 'somepass'))) self.request_with_auth("get", self.LIST_URI,
**self.basic_auth('someuser', 'somepass')))
self.assertHttpForbidden( self.assertHttpForbidden(
self.client.get(self.LIST_URI, **self.basic_auth('someuser', 'somepass'))) self.client.get(self.LIST_URI, **self.basic_auth('someuser', 'somepass')))
def test_get_list_empty(self):
User.objects.all().delete()
result = self.get_json(self.LIST_URI)
self.assertEqual(result["count"], 0)
self.assertIsNone(result["next"])
self.assertIsNone(result["previous"])
self.assertEqual(result["results"], [])
def test_get_list_nonempty(self): def test_get_list_nonempty(self):
result = self.get_json(self.LIST_URI) result = self.get_json(self.LIST_URI)
...@@ -245,14 +252,6 @@ class UserPreferenceViewSetTest(UserApiTestCase): ...@@ -245,14 +252,6 @@ class UserPreferenceViewSetTest(UserApiTestCase):
def test_debug_auth(self): def test_debug_auth(self):
self.assertHttpOK(self.client.get(self.LIST_URI)) self.assertHttpOK(self.client.get(self.LIST_URI))
def test_get_list_empty(self):
UserPreference.objects.all().delete()
result = self.get_json(self.LIST_URI)
self.assertEqual(result["count"], 0)
self.assertIsNone(result["next"])
self.assertIsNone(result["previous"])
self.assertEqual(result["results"], [])
def test_get_list_nonempty(self): def test_get_list_nonempty(self):
result = self.get_json(self.LIST_URI) result = self.get_json(self.LIST_URI)
self.assertEqual(result["count"], 3) self.assertEqual(result["count"], 3)
...@@ -357,3 +356,29 @@ class UserPreferenceViewSetTest(UserApiTestCase): ...@@ -357,3 +356,29 @@ class UserPreferenceViewSetTest(UserApiTestCase):
"url": uri, "url": uri,
} }
) )
class TestLanguageSetting(TestCase):
"""
Test setting languages
"""
def test_set_preference_happy(self):
user = UserFactory.create()
self.client.login(username=user.username, password='test')
lang = 'en'
response = self.client.post(reverse('user_api_set_language'), {'language': lang})
self.assertEqual(response.status_code, 200)
user_pref = UserPreference.get_preference(user, LANGUAGE_KEY)
self.assertEqual(user_pref, lang)
def test_set_preference_missing_lang(self):
user = UserFactory.create()
self.client.login(username=user.username, password='test')
response = self.client.post(reverse('user_api_set_language'))
self.assertEqual(response.status_code, 400)
self.assertIsNone(UserPreference.get_preference(user, LANGUAGE_KEY))
...@@ -9,4 +9,5 @@ user_api_router.register(r'user_prefs', user_api_views.UserPreferenceViewSet) ...@@ -9,4 +9,5 @@ user_api_router.register(r'user_prefs', user_api_views.UserPreferenceViewSet)
urlpatterns = patterns( urlpatterns = patterns(
'', '',
url(r'^v1/', include(user_api_router.urls)), url(r'^v1/', include(user_api_router.urls)),
url(r'^setlang/', 'user_api.views.set_language', name='user_api_set_language')
) )
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseBadRequest
from rest_framework import authentication from rest_framework import authentication
from rest_framework import filters from rest_framework import filters
from rest_framework import permissions from rest_framework import permissions
from rest_framework import viewsets from rest_framework import viewsets
from user_api.models import UserPreference from user_api.models import UserPreference, LANGUAGE_KEY
from user_api.serializers import UserSerializer, UserPreferenceSerializer from user_api.serializers import UserSerializer, UserPreferenceSerializer
...@@ -43,3 +45,18 @@ class UserPreferenceViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -43,3 +45,18 @@ class UserPreferenceViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = UserPreferenceSerializer serializer_class = UserPreferenceSerializer
paginate_by = 10 paginate_by = 10
paginate_by_param = "page_size" paginate_by_param = "page_size"
@login_required
def set_language(request):
"""
This view is called when the user would like to set a language preference
"""
user = request.user
lang_pref = request.POST.get('language', None)
if lang_pref:
UserPreference.set_preference(user, LANGUAGE_KEY, lang_pref)
return HttpResponse('{"success": true}')
return HttpResponseBadRequest('no language provided')
...@@ -698,6 +698,10 @@ MIDDLEWARE_CLASSES = ( ...@@ -698,6 +698,10 @@ MIDDLEWARE_CLASSES = (
# Allows us to dark-launch particular languages # Allows us to dark-launch particular languages
'dark_lang.middleware.DarkLangMiddleware', 'dark_lang.middleware.DarkLangMiddleware',
# Allows us to set user preferences
# should be after DarkLangMiddleware
'user_api.middleware.UserPreferenceMiddleware',
# Detects user-requested locale from 'accept-language' header in http request # Detects user-requested locale from 'accept-language' header in http request
'django.middleware.locale.LocaleMiddleware', 'django.middleware.locale.LocaleMiddleware',
......
...@@ -83,15 +83,17 @@ ...@@ -83,15 +83,17 @@
}); });
}); });
$("#change_language_form").submit(function(event, xhr) { $("#submit-lang").click(function(event, xhr) {
$.post('/i18n/setlang/', event.preventDefault();
{"language": language,}, $.post('/user_api/setlang/',
function(data) { {"language": $('#settings-language-value').val()})
if (data.success){ .done(
location.reload(); function(data){
} // submit form as normal
}) $('.settings-language-form').submit();
}); }
);
});
$("#change_email_form").submit(function(){ $("#change_email_form").submit(function(){
var new_email = $('#new_email_field').val(); var new_email = $('#new_email_field').val();
......
<%! from django.utils.translation import ugettext as _ %> <%! from django.utils.translation import ugettext as _ %>
<%!
from django.core.urlresolvers import reverse
%>
<%namespace name='static' file='../static_content.html'/> <%namespace name='static' file='../static_content.html'/>
......
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