Commit 654239dc by Greg Price

Merge pull request #2890 from edx/gprice/user-api-with-prefs

Augment user API to allow easier access to user preferences
parents 779d0aab bbd6f788
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.validators import RegexValidator
from django.db import models from django.db import models
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"""
user = models.ForeignKey(User, db_index=True, related_name="+") KEY_REGEX = r"[-_a-zA-Z0-9]+"
key = models.CharField(max_length=255, db_index=True) user = models.ForeignKey(User, db_index=True, related_name="preferences")
key = models.CharField(max_length=255, db_index=True, validators=[RegexValidator(KEY_REGEX)])
value = models.TextField() value = models.TextField()
class Meta: # pylint: disable=missing-docstring class Meta: # pylint: disable=missing-docstring
......
...@@ -6,15 +6,19 @@ from user_api.models import UserPreference ...@@ -6,15 +6,19 @@ from user_api.models import UserPreference
class UserSerializer(serializers.HyperlinkedModelSerializer): class UserSerializer(serializers.HyperlinkedModelSerializer):
name = serializers.SerializerMethodField("get_name") name = serializers.SerializerMethodField("get_name")
preferences = serializers.SerializerMethodField("get_preferences")
def get_name(self, user): def get_name(self, user):
profile = UserProfile.objects.get(user=user) profile = UserProfile.objects.get(user=user)
return profile.name return profile.name
def get_preferences(self, user):
return dict([(pref.key, pref.value) for pref in user.preferences.all()])
class Meta: class Meta:
model = User model = User
# This list is the minimal set required by the notification service # This list is the minimal set required by the notification service
fields = ("id", "email", "name", "username") fields = ("id", "email", "name", "username", "preferences")
read_only_fields = ("id", "email", "username") read_only_fields = ("id", "email", "username")
......
...@@ -66,7 +66,11 @@ class ApiTestCase(TestCase): ...@@ -66,7 +66,11 @@ class ApiTestCase(TestCase):
def assertUserIsValid(self, user): def assertUserIsValid(self, user):
"""Assert that the given user result is valid""" """Assert that the given user result is valid"""
self.assertItemsEqual(user.keys(), ["email", "id", "name", "username", "url"]) self.assertItemsEqual(user.keys(), ["email", "id", "name", "username", "preferences", "url"])
self.assertItemsEqual(
user["preferences"].items(),
[(pref.key, pref.value) for pref in self.prefs if pref.user.id == user["id"]]
)
self.assertSelfReferential(user) self.assertSelfReferential(user)
def assertPrefIsValid(self, pref): def assertPrefIsValid(self, pref):
...@@ -221,6 +225,11 @@ class UserViewSetTest(UserApiTestCase): ...@@ -221,6 +225,11 @@ class UserViewSetTest(UserApiTestCase):
"id": user.id, "id": user.id,
"name": user.profile.name, "name": user.profile.name,
"username": user.username, "username": user.username,
"preferences": dict([
(user_pref.key, user_pref.value)
for user_pref in self.prefs
if user_pref.user == user
]),
"url": uri "url": uri
} }
) )
...@@ -352,6 +361,11 @@ class UserPreferenceViewSetTest(UserApiTestCase): ...@@ -352,6 +361,11 @@ class UserPreferenceViewSetTest(UserApiTestCase):
"id": pref.user.id, "id": pref.user.id,
"name": pref.user.profile.name, "name": pref.user.profile.name,
"username": pref.user.username, "username": pref.user.username,
"preferences": dict([
(user_pref.key, user_pref.value)
for user_pref in self.prefs
if user_pref.user == pref.user
]),
"url": self.get_uri_for_user(pref.user), "url": self.get_uri_for_user(pref.user),
}, },
"key": pref.key, "key": pref.key,
...@@ -359,3 +373,59 @@ class UserPreferenceViewSetTest(UserApiTestCase): ...@@ -359,3 +373,59 @@ class UserPreferenceViewSetTest(UserApiTestCase):
"url": uri, "url": uri,
} }
) )
class PreferenceUsersListViewTest(UserApiTestCase):
LIST_URI = "/user_api/v1/preferences/key0/users/"
def test_options(self):
self.assertAllowedMethods(self.LIST_URI, ["OPTIONS", "GET", "HEAD"])
def test_put_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("put", self.LIST_URI))
def test_patch_not_allowed(self):
raise SkipTest("Django 1.4's test client does not support patch")
def test_delete_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("delete", self.LIST_URI))
def test_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_basic(self):
result = self.get_json(self.LIST_URI)
self.assertEqual(result["count"], 2)
self.assertIsNone(result["next"])
self.assertIsNone(result["previous"])
users = result["results"]
self.assertEqual(len(users), 2)
for user in users:
self.assertUserIsValid(user)
def test_get_pagination(self):
first_page = self.get_json(self.LIST_URI, data={"page_size": 1})
self.assertEqual(first_page["count"], 2)
first_page_next_uri = first_page["next"]
self.assertIsNone(first_page["previous"])
first_page_users = first_page["results"]
self.assertEqual(len(first_page_users), 1)
second_page = self.get_json(first_page_next_uri)
self.assertEqual(second_page["count"], 2)
self.assertIsNone(second_page["next"])
second_page_prev_uri = second_page["previous"]
second_page_users = second_page["results"]
self.assertEqual(len(second_page_users), 1)
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)), 2)
from django.conf.urls import include, patterns, url from django.conf.urls import include, patterns, url
from rest_framework import routers from rest_framework import routers
from user_api import views as user_api_views from user_api import views as user_api_views
from user_api.models import UserPreference
user_api_router = routers.DefaultRouter() user_api_router = routers.DefaultRouter()
...@@ -9,4 +10,8 @@ user_api_router.register(r'user_prefs', user_api_views.UserPreferenceViewSet) ...@@ -9,4 +10,8 @@ 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'^v1/preferences/(?P<pref_key>{})/users/$'.format(UserPreference.KEY_REGEX),
user_api_views.PreferenceUsersListView.as_view()
),
) )
...@@ -2,6 +2,7 @@ from django.conf import settings ...@@ -2,6 +2,7 @@ from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from rest_framework import authentication from rest_framework import authentication
from rest_framework import filters from rest_framework import filters
from rest_framework import generics
from rest_framework import permissions from rest_framework import permissions
from rest_framework import viewsets from rest_framework import viewsets
from user_api.serializers import UserSerializer, UserPreferenceSerializer from user_api.serializers import UserSerializer, UserPreferenceSerializer
...@@ -28,7 +29,7 @@ class ApiKeyHeaderPermission(permissions.BasePermission): ...@@ -28,7 +29,7 @@ class ApiKeyHeaderPermission(permissions.BasePermission):
class UserViewSet(viewsets.ReadOnlyModelViewSet): class UserViewSet(viewsets.ReadOnlyModelViewSet):
authentication_classes = (authentication.SessionAuthentication,) authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
queryset = User.objects.all() queryset = User.objects.all().prefetch_related("preferences")
serializer_class = UserSerializer serializer_class = UserSerializer
paginate_by = 10 paginate_by = 10
paginate_by_param = "page_size" paginate_by_param = "page_size"
...@@ -43,3 +44,14 @@ class UserPreferenceViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -43,3 +44,14 @@ 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"
class PreferenceUsersListView(generics.ListAPIView):
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (ApiKeyHeaderPermission,)
serializer_class = UserSerializer
paginate_by = 10
paginate_by_param = "page_size"
def get_queryset(self):
return User.objects.filter(preferences__key=self.kwargs["pref_key"]).prefetch_related("preferences")
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