Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions common/djangoapps/user_api/models.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
6 changes: 5 additions & 1 deletion common/djangoapps/user_api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@

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")


Expand Down
72 changes: 71 additions & 1 deletion common/djangoapps/user_api/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ def assertSelfReferential(self, obj):

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):
Expand Down Expand Up @@ -221,6 +225,11 @@ def test_get_detail(self):
"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
}
)
Expand Down Expand Up @@ -352,10 +361,71 @@ def test_get_detail(self):
"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,
"value": pref.value,
"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)
5 changes: 5 additions & 0 deletions common/djangoapps/user_api/urls.py
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -9,4 +10,8 @@
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()
),
)
14 changes: 13 additions & 1 deletion common/djangoapps/user_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
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
Expand All @@ -28,7 +29,7 @@ def has_permission(self, request, view):
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"
Expand All @@ -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")