Merge pull request #2419 from edx/talbs/lms-languagemenu

LMS: Adds Language Selection Menu
This commit is contained in:
Julia Hansbrough
2014-02-14 18:32:12 -05:00
39 changed files with 643 additions and 84 deletions

View File

@@ -19,6 +19,8 @@ class DarkLangConfig(ConfigurationModel):
def released_languages_list(self):
"""
``released_languages`` as a list of language codes.
Example: ['it', 'de-at', 'es', 'pt-br']
"""
if not self.released_languages.strip(): # pylint: disable=no-member
return []

View File

@@ -0,0 +1,6 @@
"""
Useful information for setting the language preference
"""
# this is the UserPreference key for the user's preferred language
LANGUAGE_KEY = 'pref-lang'

View File

@@ -0,0 +1,25 @@
"""
Middleware for Language Preferences
"""
from user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
class LanguagePreferenceMiddleware(object):
"""
Middleware for user preferences.
Ensures that, once set, a user's preferences are reflected in the page
whenever they are logged in.
"""
def process_request(self, request):
"""
If a user's UserPreference contains a language preference and there is
no language set on the session (i.e. from dark language overrides), use the user's preference.
"""
if request.user.is_authenticated() and 'django_language' not in request.session:
user_pref = UserPreference.get_preference(request.user, LANGUAGE_KEY)
if user_pref:
request.session['django_language'] = user_pref

View File

@@ -0,0 +1,42 @@
from django.test import TestCase
from django.test.client import RequestFactory
from django.contrib.sessions.middleware import SessionMiddleware
from lang_pref.middleware import LanguagePreferenceMiddleware
from user_api.models import UserPreference
from lang_pref import 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 = LanguagePreferenceMiddleware()
self.session_middleware = SessionMiddleware()
self.user = UserFactory.create()
self.request = RequestFactory().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')

View File

@@ -0,0 +1,34 @@
"""
Tests for the language setting view
"""
from django.core.urlresolvers import reverse
from django.test import TestCase
from student.tests.factories import UserFactory
from user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
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('lang_pref_set_language'), {'language': lang})
self.assertEquals(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('lang_pref_set_language'))
self.assertEquals(response.status_code, 400)
self.assertIsNone(UserPreference.get_preference(user, LANGUAGE_KEY))

View File

@@ -0,0 +1,10 @@
"""
Urls for managing language preferences
"""
from django.conf.urls import patterns, url
urlpatterns = patterns(
'',
url(r'^setlang/', 'lang_pref.views.set_language', name='lang_pref_set_language')
)

View File

@@ -0,0 +1,23 @@
"""
Views for accessing language preferences
"""
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseBadRequest
from user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
@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')

View File

@@ -45,6 +45,7 @@ from student.firebase_token_generator import create_token
from verify_student.models import SoftwareSecurePhotoVerification, MidcourseReverificationWindow
from certificates.models import CertificateStatuses, certificate_status_for_student
from dark_lang.models import DarkLangConfig
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError
@@ -61,6 +62,8 @@ import external_auth.views
from bulk_email.models import Optout, CourseAuthorization
import shoppingcart
from user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
import track.views
@@ -468,23 +471,42 @@ def dashboard(request):
# we'll display the banner
denied_banner = any(item.display for item in reverifications["denied"])
context = {'course_enrollment_pairs': course_enrollment_pairs,
'course_optouts': course_optouts,
'message': message,
'external_auth_map': external_auth_map,
'staff_access': staff_access,
'errored_courses': errored_courses,
'show_courseware_links_for': show_courseware_links_for,
'all_course_modes': course_modes,
'cert_statuses': cert_statuses,
'show_email_settings_for': show_email_settings_for,
'reverifications': reverifications,
'verification_status': verification_status,
'verification_msg': verification_msg,
'show_refund_option_for': show_refund_option_for,
'denied_banner': denied_banner,
'billing_email': settings.PAYMENT_SUPPORT_EMAIL,
}
language_options = DarkLangConfig.current().released_languages_list
# add in the default language if it's not in the list of released languages
if settings.LANGUAGE_CODE not in language_options:
language_options.append(settings.LANGUAGE_CODE)
# try to get the prefered language for the user
cur_lang_code = UserPreference.get_preference(request.user, LANGUAGE_KEY)
if cur_lang_code:
# if the user has a preference, get the name from the code
current_language = settings.LANGUAGE_DICT[cur_lang_code]
else:
# if the user doesn't have a preference, use the default language
current_language = settings.LANGUAGE_DICT[settings.LANGUAGE_CODE]
context = {
'course_enrollment_pairs': course_enrollment_pairs,
'course_optouts': course_optouts,
'message': message,
'external_auth_map': external_auth_map,
'staff_access': staff_access,
'errored_courses': errored_courses,
'show_courseware_links_for': show_courseware_links_for,
'all_course_modes': course_modes,
'cert_statuses': cert_statuses,
'show_email_settings_for': show_email_settings_for,
'reverifications': reverifications,
'verification_status': verification_status,
'verification_msg': verification_msg,
'show_refund_option_for': show_refund_option_for,
'denied_banner': denied_banner,
'billing_email': settings.PAYMENT_SUPPORT_EMAIL,
'language_options': language_options,
'current_language': current_language,
'current_language_code': cur_lang_code,
}
return render_to_response('dashboard.html', context)

View File

View File

@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'UserPreference'
db.create_table('user_api_userpreference', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['auth.User'])),
('key', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
('value', self.gf('django.db.models.fields.TextField')()),
))
db.send_create_signal('user_api', ['UserPreference'])
# Adding unique constraint on 'UserPreference', fields ['user', 'key']
db.create_unique('user_api_userpreference', ['user_id', 'key'])
def backwards(self, orm):
# Removing unique constraint on 'UserPreference', fields ['user', 'key']
db.delete_unique('user_api_userpreference', ['user_id', 'key'])
# Deleting model 'UserPreference'
db.delete_table('user_api_userpreference')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'user_api.userpreference': {
'Meta': {'unique_together': "(('user', 'key'),)", 'object_name': 'UserPreference'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['auth.User']"}),
'value': ('django.db.models.fields.TextField', [], {})
}
}
complete_apps = ['user_api']

View File

@@ -0,0 +1,35 @@
from django.contrib.auth.models import User
from django.db import models
class UserPreference(models.Model):
"""A user's preference, stored as generic text to be processed by client"""
user = models.ForeignKey(User, db_index=True, related_name="+")
key = models.CharField(max_length=255, db_index=True)
value = models.TextField()
class Meta:
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, default=None):
"""
Gets the user preference value for a given key
Returns the given default 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 default

View File

@@ -0,0 +1,26 @@
from django.contrib.auth.models import User
from rest_framework import serializers
from student.models import UserProfile
from user_api.models import UserPreference
class UserSerializer(serializers.HyperlinkedModelSerializer):
name = serializers.SerializerMethodField("get_name")
def get_name(self, user):
profile = UserProfile.objects.get(user=user)
return profile.name
class Meta:
model = User
# This list is the minimal set required by the notification service
fields = ("id", "email", "name", "username")
read_only_fields = ("id", "email", "username")
class UserPreferenceSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = UserPreference
depth = 1

View File

@@ -0,0 +1,10 @@
from factory.django import DjangoModelFactory
from user_api.models import UserPreference
class UserPreferenceFactory(DjangoModelFactory):
FACTORY_FOR = UserPreference
user = None
key = None
value = "default test value"

View File

@@ -0,0 +1,47 @@
from django.db import IntegrityError
from django.test import TestCase
from student.tests.factories import UserFactory
from user_api.tests.factories import UserPreferenceFactory
from user_api.models import UserPreference
class UserPreferenceModelTest(TestCase):
def test_duplicate_user_key(self):
user = UserFactory.create()
UserPreferenceFactory.create(user=user, key="testkey", value="first")
self.assertRaises(
IntegrityError,
UserPreferenceFactory.create,
user=user,
key="testkey",
value="second"
)
def test_arbitrary_values(self):
user = UserFactory.create()
UserPreferenceFactory.create(user=user, key="testkey0", value="")
UserPreferenceFactory.create(user=user, key="testkey1", value="This is some English text!")
UserPreferenceFactory.create(user=user, key="testkey2", value="{'some': 'json'}")
UserPreferenceFactory.create(
user=user,
key="testkey3",
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):
# Checks that you can set a preference and get that preference later
# Also, tests that no preference is returned for keys that are not set
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)

View File

@@ -0,0 +1,361 @@
import base64
from django.test import TestCase
from django.test.utils import override_settings
import json
import re
from student.tests.factories import UserFactory
from unittest import SkipTest
from user_api.models import UserPreference
from user_api.tests.factories import UserPreferenceFactory
TEST_API_KEY = "test_api_key"
USER_LIST_URI = "/user_api/v1/users/"
USER_PREFERENCE_LIST_URI = "/user_api/v1/user_prefs/"
@override_settings(EDX_API_KEY=TEST_API_KEY)
class ApiTestCase(TestCase):
LIST_URI = USER_LIST_URI
def basic_auth(self, username, password):
return {'HTTP_AUTHORIZATION': 'Basic ' + base64.b64encode('%s:%s' % (username, password))}
def request_with_auth(self, method, *args, **kwargs):
"""Issue a get request to the given URI with the API key header"""
return getattr(self.client, method)(*args, HTTP_X_EDX_API_KEY=TEST_API_KEY, **kwargs)
def get_json(self, *args, **kwargs):
"""Make a request with the given args and return the parsed JSON repsonse"""
resp = self.request_with_auth("get", *args, **kwargs)
self.assertHttpOK(resp)
self.assertTrue(resp["Content-Type"].startswith("application/json"))
return json.loads(resp.content)
def get_uri_for_user(self, target_user):
"""Given a user object, get the URI for the corresponding resource"""
users = self.get_json(USER_LIST_URI)["results"]
for user in users:
if user["id"] == target_user.id:
return user["url"]
self.fail()
def get_uri_for_pref(self, target_pref):
"""Given a user preference object, get the URI for the corresponding resource"""
prefs = self.get_json(USER_PREFERENCE_LIST_URI)["results"]
for pref in prefs:
if (pref["user"]["id"] == target_pref.user.id and pref["key"] == target_pref.key):
return pref["url"]
self.fail()
def assertAllowedMethods(self, uri, expected_methods):
"""Assert that the allowed methods for the given URI match the expected list"""
resp = self.request_with_auth("options", uri)
self.assertHttpOK(resp)
allow_header = resp.get("Allow")
self.assertIsNotNone(allow_header)
allowed_methods = re.split('[^A-Z]+', allow_header)
self.assertItemsEqual(allowed_methods, expected_methods)
def assertSelfReferential(self, obj):
"""Assert that accessing the "url" entry in the given object returns the same object"""
copy = self.get_json(obj["url"])
self.assertEqual(obj, copy)
def assertUserIsValid(self, user):
"""Assert that the given user result is valid"""
self.assertItemsEqual(user.keys(), ["email", "id", "name", "username", "url"])
self.assertSelfReferential(user)
def assertPrefIsValid(self, pref):
self.assertItemsEqual(pref.keys(), ["user", "key", "value", "url"])
self.assertSelfReferential(pref)
self.assertUserIsValid(pref["user"])
def assertHttpOK(self, response):
"""Assert that the given response has the status code 200"""
self.assertEqual(response.status_code, 200)
def assertHttpForbidden(self, response):
"""Assert that the given response has the status code 403"""
self.assertEqual(response.status_code, 403)
def assertHttpBadRequest(self, response):
"""Assert that the given response has the status code 400"""
self.assertEqual(response.status_code, 400)
def assertHttpMethodNotAllowed(self, response):
"""Assert that the given response has the status code 405"""
self.assertEqual(response.status_code, 405)
class EmptyUserTestCase(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):
LIST_URI = USER_LIST_URI
def setUp(self):
super(UserViewSetTest, self).setUp()
self.detail_uri = self.get_uri_for_user(self.users[0])
# List view tests
def test_options_list(self):
self.assertAllowedMethods(self.LIST_URI, ["OPTIONS", "GET", "HEAD"])
def test_post_list_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("post", self.LIST_URI))
def test_put_list_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("put", self.LIST_URI))
def test_patch_list_not_allowed(self):
raise SkipTest("Django 1.4's test client does not support patch")
def test_delete_list_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("delete", self.LIST_URI))
def test_list_unauthorized(self):
self.assertHttpForbidden(self.client.get(self.LIST_URI))
@override_settings(DEBUG=True)
@override_settings(EDX_API_KEY=None)
def test_debug_auth(self):
self.assertHttpOK(self.client.get(self.LIST_URI))
@override_settings(DEBUG=False)
@override_settings(EDX_API_KEY=TEST_API_KEY)
def test_basic_auth(self):
# ensure that having basic auth headers in the mix does not break anything
self.assertHttpOK(
self.request_with_auth("get", self.LIST_URI,
**self.basic_auth('someuser', 'somepass')))
self.assertHttpForbidden(
self.client.get(self.LIST_URI, **self.basic_auth('someuser', 'somepass')))
def test_get_list_nonempty(self):
result = self.get_json(self.LIST_URI)
self.assertEqual(result["count"], 5)
self.assertIsNone(result["next"])
self.assertIsNone(result["previous"])
users = result["results"]
self.assertEqual(len(users), 5)
for user in users:
self.assertUserIsValid(user)
def test_get_list_pagination(self):
first_page = self.get_json(self.LIST_URI, data={"page_size": 3})
self.assertEqual(first_page["count"], 5)
first_page_next_uri = first_page["next"]
self.assertIsNone(first_page["previous"])
first_page_users = first_page["results"]
self.assertEqual(len(first_page_users), 3)
second_page = self.get_json(first_page_next_uri)
self.assertEqual(second_page["count"], 5)
self.assertIsNone(second_page["next"])
second_page_prev_uri = second_page["previous"]
second_page_users = second_page["results"]
self.assertEqual(len(second_page_users), 2)
self.assertEqual(self.get_json(second_page_prev_uri), first_page)
for user in first_page_users + second_page_users:
self.assertUserIsValid(user)
all_user_uris = [user["url"] for user in first_page_users + second_page_users]
self.assertEqual(len(set(all_user_uris)), 5)
# Detail view tests
def test_options_detail(self):
self.assertAllowedMethods(self.detail_uri, ["OPTIONS", "GET", "HEAD"])
def test_post_detail_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("post", self.detail_uri))
def test_put_detail_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("put", self.detail_uri))
def test_patch_detail_not_allowed(self):
raise SkipTest("Django 1.4's test client does not support patch")
def test_delete_detail_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("delete", self.detail_uri))
def test_get_detail_unauthorized(self):
self.assertHttpForbidden(self.client.get(self.detail_uri))
def test_get_detail(self):
user = self.users[1]
uri = self.get_uri_for_user(user)
self.assertEqual(
self.get_json(uri),
{
"email": user.email,
"id": user.id,
"name": user.profile.name,
"username": user.username,
"url": uri
}
)
class UserPreferenceViewSetTest(UserApiTestCase):
LIST_URI = USER_PREFERENCE_LIST_URI
def setUp(self):
super(UserPreferenceViewSetTest, self).setUp()
self.detail_uri = self.get_uri_for_pref(self.prefs[0])
# List view tests
def test_options_list(self):
self.assertAllowedMethods(self.LIST_URI, ["OPTIONS", "GET", "HEAD"])
def test_put_list_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("put", self.LIST_URI))
def test_patch_list_not_allowed(self):
raise SkipTest("Django 1.4's test client does not support patch")
def test_delete_list_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("delete", self.LIST_URI))
def test_list_unauthorized(self):
self.assertHttpForbidden(self.client.get(self.LIST_URI))
@override_settings(DEBUG=True)
@override_settings(EDX_API_KEY=None)
def test_debug_auth(self):
self.assertHttpOK(self.client.get(self.LIST_URI))
def test_get_list_nonempty(self):
result = self.get_json(self.LIST_URI)
self.assertEqual(result["count"], 3)
self.assertIsNone(result["next"])
self.assertIsNone(result["previous"])
prefs = result["results"]
self.assertEqual(len(prefs), 3)
for pref in prefs:
self.assertPrefIsValid(pref)
def test_get_list_filter_key_empty(self):
result = self.get_json(self.LIST_URI, data={"key": "non-existent"})
self.assertEqual(result["count"], 0)
self.assertEqual(result["results"], [])
def test_get_list_filter_key_nonempty(self):
result = self.get_json(self.LIST_URI, data={"key": "key0"})
self.assertEqual(result["count"], 2)
prefs = result["results"]
self.assertEqual(len(prefs), 2)
for pref in prefs:
self.assertPrefIsValid(pref)
self.assertEqual(pref["key"], "key0")
def test_get_list_filter_user_empty(self):
def test_id(user_id):
result = self.get_json(self.LIST_URI, data={"user": user_id})
self.assertEqual(result["count"], 0)
self.assertEqual(result["results"], [])
test_id(self.users[2].id)
# TODO: If the given id does not match a user, then the filter is a no-op
# test_id(42)
# test_id("asdf")
def test_get_list_filter_user_nonempty(self):
user_id = self.users[0].id
result = self.get_json(self.LIST_URI, data={"user": user_id})
self.assertEqual(result["count"], 2)
prefs = result["results"]
self.assertEqual(len(prefs), 2)
for pref in prefs:
self.assertPrefIsValid(pref)
self.assertEqual(pref["user"]["id"], user_id)
def test_get_list_pagination(self):
first_page = self.get_json(self.LIST_URI, data={"page_size": 2})
self.assertEqual(first_page["count"], 3)
first_page_next_uri = first_page["next"]
self.assertIsNone(first_page["previous"])
first_page_prefs = first_page["results"]
self.assertEqual(len(first_page_prefs), 2)
second_page = self.get_json(first_page_next_uri)
self.assertEqual(second_page["count"], 3)
self.assertIsNone(second_page["next"])
second_page_prev_uri = second_page["previous"]
second_page_prefs = second_page["results"]
self.assertEqual(len(second_page_prefs), 1)
self.assertEqual(self.get_json(second_page_prev_uri), first_page)
for pref in first_page_prefs + second_page_prefs:
self.assertPrefIsValid(pref)
all_pref_uris = [pref["url"] for pref in first_page_prefs + second_page_prefs]
self.assertEqual(len(set(all_pref_uris)), 3)
# Detail view tests
def test_options_detail(self):
self.assertAllowedMethods(self.detail_uri, ["OPTIONS", "GET", "HEAD"])
def test_post_detail_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("post", self.detail_uri))
def test_put_detail_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("put", self.detail_uri))
def test_patch_detail_not_allowed(self):
raise SkipTest("Django 1.4's test client does not support patch")
def test_delete_detail_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("delete", self.detail_uri))
def test_detail_unauthorized(self):
self.assertHttpForbidden(self.client.get(self.detail_uri))
def test_get_detail(self):
pref = self.prefs[1]
uri = self.get_uri_for_pref(pref)
self.assertEqual(
self.get_json(uri),
{
"user": {
"email": pref.user.email,
"id": pref.user.id,
"name": pref.user.profile.name,
"username": pref.user.username,
"url": self.get_uri_for_user(pref.user),
},
"key": pref.key,
"value": pref.value,
"url": uri,
}
)

View File

@@ -0,0 +1,12 @@
from django.conf.urls import include, patterns, url
from rest_framework import routers
from user_api import views as user_api_views
user_api_router = routers.DefaultRouter()
user_api_router.register(r'users', user_api_views.UserViewSet)
user_api_router.register(r'user_prefs', user_api_views.UserPreferenceViewSet)
urlpatterns = patterns(
'',
url(r'^v1/', include(user_api_router.urls)),
)

View File

@@ -0,0 +1,45 @@
from django.conf import settings
from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import filters
from rest_framework import permissions
from rest_framework import viewsets
from user_api.serializers import UserSerializer, UserPreferenceSerializer
from user_api.models import UserPreference
class ApiKeyHeaderPermission(permissions.BasePermission):
def has_permission(self, request, view):
"""
Check for permissions by matching the configured API key and header
If settings.DEBUG is True and settings.EDX_API_KEY is not set or None,
then allow the request. Otherwise, allow the request if and only if
settings.EDX_API_KEY is set and the X-Edx-Api-Key HTTP header is
present in the request and matches the setting.
"""
api_key = getattr(settings, "EDX_API_KEY", None)
return (
(settings.DEBUG and api_key is None) or
(api_key is not None and request.META.get("HTTP_X_EDX_API_KEY") == api_key)
)
class UserViewSet(viewsets.ReadOnlyModelViewSet):
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (ApiKeyHeaderPermission,)
queryset = User.objects.all()
serializer_class = UserSerializer
paginate_by = 10
paginate_by_param = "page_size"
class UserPreferenceViewSet(viewsets.ReadOnlyModelViewSet):
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (ApiKeyHeaderPermission,)
queryset = UserPreference.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ("key", "user")
serializer_class = UserPreferenceSerializer
paginate_by = 10
paginate_by_param = "page_size"