Merge pull request #2890 from edx/gprice/user-api-with-prefs
Augment user API to allow easier access to user preferences
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.validators import RegexValidator
|
||||
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)
|
||||
KEY_REGEX = r"[-_a-zA-Z0-9]+"
|
||||
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()
|
||||
|
||||
class Meta: # pylint: disable=missing-docstring
|
||||
|
||||
@@ -6,15 +6,19 @@ from user_api.models import UserPreference
|
||||
|
||||
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
||||
name = serializers.SerializerMethodField("get_name")
|
||||
preferences = serializers.SerializerMethodField("get_preferences")
|
||||
|
||||
def get_name(self, user):
|
||||
profile = UserProfile.objects.get(user=user)
|
||||
return profile.name
|
||||
|
||||
def get_preferences(self, user):
|
||||
return dict([(pref.key, pref.value) for pref in user.preferences.all()])
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
# 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")
|
||||
|
||||
|
||||
|
||||
@@ -66,7 +66,11 @@ class ApiTestCase(TestCase):
|
||||
|
||||
def assertUserIsValid(self, user):
|
||||
"""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)
|
||||
|
||||
def assertPrefIsValid(self, pref):
|
||||
@@ -221,6 +225,11 @@ class UserViewSetTest(UserApiTestCase):
|
||||
"id": user.id,
|
||||
"name": user.profile.name,
|
||||
"username": user.username,
|
||||
"preferences": dict([
|
||||
(user_pref.key, user_pref.value)
|
||||
for user_pref in self.prefs
|
||||
if user_pref.user == user
|
||||
]),
|
||||
"url": uri
|
||||
}
|
||||
)
|
||||
@@ -352,6 +361,11 @@ class UserPreferenceViewSetTest(UserApiTestCase):
|
||||
"id": pref.user.id,
|
||||
"name": pref.user.profile.name,
|
||||
"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),
|
||||
},
|
||||
"key": pref.key,
|
||||
@@ -359,3 +373,59 @@ class UserPreferenceViewSetTest(UserApiTestCase):
|
||||
"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)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from django.conf.urls import include, patterns, url
|
||||
from rest_framework import routers
|
||||
from user_api import views as user_api_views
|
||||
from user_api.models import UserPreference
|
||||
|
||||
|
||||
user_api_router = routers.DefaultRouter()
|
||||
@@ -9,4 +10,8 @@ user_api_router.register(r'user_prefs', user_api_views.UserPreferenceViewSet)
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
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
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework import authentication
|
||||
from rest_framework import filters
|
||||
from rest_framework import generics
|
||||
from rest_framework import permissions
|
||||
from rest_framework import viewsets
|
||||
from user_api.serializers import UserSerializer, UserPreferenceSerializer
|
||||
@@ -28,7 +29,7 @@ class ApiKeyHeaderPermission(permissions.BasePermission):
|
||||
class UserViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
authentication_classes = (authentication.SessionAuthentication,)
|
||||
permission_classes = (ApiKeyHeaderPermission,)
|
||||
queryset = User.objects.all()
|
||||
queryset = User.objects.all().prefetch_related("preferences")
|
||||
serializer_class = UserSerializer
|
||||
paginate_by = 10
|
||||
paginate_by_param = "page_size"
|
||||
@@ -43,3 +44,14 @@ class UserPreferenceViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
serializer_class = UserPreferenceSerializer
|
||||
paginate_by = 10
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user