Commit a3eff5ac by Alan Boudreault Committed by Jonathan Piacenti

api: writing custom query set

Fixed failing test

more test and improvements to code quality
parent 39e18c41
...@@ -3,16 +3,14 @@ import uuid ...@@ -3,16 +3,14 @@ import uuid
import json import json
from collections import OrderedDict from collections import OrderedDict
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone from django.utils import timezone
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
from api_manager.models import GroupRelationship, CourseGroupRelationship, GroupProfile, APIUser as User
from api_manager.models import GroupRelationship, CourseGroupRelationship, GroupProfile
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from api_manager.permissions import SecureAPIView from api_manager.permissions import SecureAPIView
from xmodule.modulestore import Location, InvalidLocationError from xmodule.modulestore import Location, InvalidLocationError
......
...@@ -6,6 +6,7 @@ from django.contrib.auth.models import Group, User ...@@ -6,6 +6,7 @@ from django.contrib.auth.models import Group, User
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from .utils import is_int
from projects.models import Workgroup from projects.models import Workgroup
...@@ -154,3 +155,30 @@ class CourseModuleCompletion(TimeStampedModel): ...@@ -154,3 +155,30 @@ class CourseModuleCompletion(TimeStampedModel):
user = models.ForeignKey(User, db_index=True, related_name="course_completions") user = models.ForeignKey(User, db_index=True, related_name="course_completions")
course_id = models.CharField(max_length=255, db_index=True) course_id = models.CharField(max_length=255, db_index=True)
content_id = models.CharField(max_length=255, db_index=True) content_id = models.CharField(max_length=255, db_index=True)
class APIUserQuerySet(models.query.QuerySet): # pylint: disable=R0924
""" Custom QuerySet to modify id based lookup """
def filter(self, *args, **kwargs):
if 'id' in kwargs and not is_int(kwargs['id']):
kwargs['anonymoususerid__anonymous_user_id'] = kwargs['id']
del kwargs['id']
return super(APIUserQuerySet, self).filter(*args, **kwargs)
class APIUserManager(models.Manager):
""" Custom Manager """
def get_query_set(self):
return APIUserQuerySet(self.model)
class APIUser(User):
"""
A proxy model for django's auth.User to add AnonymousUserId fallback
support in User lookups
"""
objects = APIUserManager()
class Meta:
""" Meta attribute to make this a proxy model"""
proxy = True
""" Django REST Framework Serializers """ """ Django REST Framework Serializers """
from django.contrib.auth.models import User from api_manager.models import APIUser
from rest_framework import serializers from rest_framework import serializers
...@@ -9,6 +8,6 @@ class UserSerializer(serializers.ModelSerializer): ...@@ -9,6 +8,6 @@ class UserSerializer(serializers.ModelSerializer):
""" Serializer for User model interactions """ """ Serializer for User model interactions """
class Meta: class Meta:
""" Serializer/field specification """ """ Serializer/field specification """
model = User model = APIUser
fields = ("id", "email", "username", "first_name", "last_name") fields = ("id", "email", "username", "first_name", "last_name")
read_only_fields = ("id", "email", "username") read_only_fields = ("id", "email", "username")
...@@ -13,6 +13,8 @@ from django.utils.translation import ugettext as _ ...@@ -13,6 +13,8 @@ from django.utils.translation import ugettext as _
from django.core.cache import cache from django.core.cache import cache
from django.test import TestCase, Client from django.test import TestCase, Client
from django.test.utils import override_settings from django.test.utils import override_settings
from student.tests.factories import UserFactory
from student.models import anonymous_id_for_user
from projects.models import Project from projects.models import Project
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
...@@ -68,6 +70,7 @@ class UsersApiTests(TestCase): ...@@ -68,6 +70,7 @@ class UsersApiTests(TestCase):
content_id=self.course_content.id + 'b2' content_id=self.course_content.id + 'b2'
) )
self.user = UserFactory()
self.client = SecureClient() self.client = SecureClient()
cache.clear() cache.clear()
...@@ -950,7 +953,8 @@ class UsersApiTests(TestCase): ...@@ -950,7 +953,8 @@ class UsersApiTests(TestCase):
str(response.data['year_of_birth']), data["year_of_birth"]) str(response.data['year_of_birth']), data["year_of_birth"])
def test_user_organizations_list(self): def test_user_organizations_list(self):
user_id = self._create_test_user() user_id = self.user.id
anonymous_id = anonymous_id_for_user(self.user, self.course.id)
for i in xrange(1, 7): for i in xrange(1, 7):
data = { data = {
'name': 'Org ' + str(i), 'name': 'Org ' + str(i),
...@@ -966,13 +970,20 @@ class UsersApiTests(TestCase): ...@@ -966,13 +970,20 @@ class UsersApiTests(TestCase):
self.assertEqual(len(response.data['results']), 6) self.assertEqual(len(response.data['results']), 6)
self.assertEqual(response.data['num_pages'], 1) self.assertEqual(response.data['num_pages'], 1)
# test with anonymous user id
test_uri = '/api/users/{}/organizations/'.format(anonymous_id)
response = self.do_get(test_uri)
self.assertEqual(response.data['count'], 6)
# test with invalid user # test with invalid user
response = self.do_get('/api/users/4356340/organizations/') response = self.do_get('/api/users/4356340/organizations/')
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
def test_user_workgroups_list(self): def test_user_workgroups_list(self):
test_workgroups_uri = '/api/workgroups/' test_workgroups_uri = '/api/workgroups/'
user_id = self._create_test_user() user_id = self.user.id
# create anonymous user
anonymous_id = anonymous_id_for_user(self.user, self.course.id)
for i in xrange(1, 12): for i in xrange(1, 12):
project_id = self.test_project.id project_id = self.test_project.id
if i > 7: # set to other project if i > 7: # set to other project
...@@ -989,13 +1000,14 @@ class UsersApiTests(TestCase): ...@@ -989,13 +1000,14 @@ class UsersApiTests(TestCase):
response = self.do_post(users_uri, data) response = self.do_post(users_uri, data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
test_uri = '/api/users/{}/workgroups/?page_size=10'.format(user_id) # test with anonymous user id
test_uri = '/api/users/{}/workgroups/?page_size=10'.format(anonymous_id)
response = self.do_get(test_uri) response = self.do_get(test_uri)
self.assertEqual(response.data['count'], 11) self.assertEqual(response.data['count'], 11)
self.assertEqual(len(response.data['results']), 10) self.assertEqual(len(response.data['results']), 10)
self.assertEqual(response.data['num_pages'], 2) self.assertEqual(response.data['num_pages'], 2)
# test with course_id filter # test with course_id filter and integer user id
response = self.do_get('/api/users/{}/workgroups/?course_id={}'.format(user_id, self.course.id)) response = self.do_get('/api/users/{}/workgroups/?course_id={}'.format(user_id, self.course.id))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['count'], 7) self.assertEqual(response.data['count'], 7)
......
...@@ -8,16 +8,16 @@ from api_manager.users import views as users_views ...@@ -8,16 +8,16 @@ from api_manager.users import views as users_views
urlpatterns = patterns( urlpatterns = patterns(
'', '',
url(r'/*$^', users_views.UsersList.as_view(), name='apimgr-users-list'), url(r'/*$^', users_views.UsersList.as_view(), name='apimgr-users-list'),
url(r'^(?P<user_id>[0-9]+)$', users_views.UsersDetail.as_view(), name='apimgr-users-detail'), url(r'^(?P<user_id>[a-zA-Z0-9]+)$', users_views.UsersDetail.as_view(), name='apimgr-users-detail'),
url(r'^(?P<user_id>[0-9]+)/courses/*$', users_views.UsersCoursesList.as_view(), name='users-courses-list'), url(r'^(?P<user_id>[a-zA-Z0-9]+)/courses/*$', users_views.UsersCoursesList.as_view(), name='users-courses-list'),
url(r'^(?P<user_id>[0-9]+)/courses/(?P<course_id>[^/]+/[^/]+/[^/]+)$', users_views.UsersCoursesDetail.as_view(), name='users-courses-detail'), url(r'^(?P<user_id>[a-zA-Z0-9]+)/courses/(?P<course_id>[^/]+/[^/]+/[^/]+)$', users_views.UsersCoursesDetail.as_view(), name='users-courses-detail'),
url(r'^(?P<user_id>[0-9]+)/courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/grades$', users_views.UsersCoursesGradesDetail.as_view(), name='users-courses-grades-detail'), url(r'^(?P<user_id>[a-zA-Z0-9]+)/courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/grades$', users_views.UsersCoursesGradesDetail.as_view(), name='users-courses-grades-detail'),
url(r'^(?P<user_id>[0-9]+)/courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/metrics/social/$', users_views.UsersSocialMetrics.as_view(), name='users-social-metrics'), url(r'^(?P<user_id>[a-zA-Z0-9]+)/courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/metrics/social/$', users_views.UsersSocialMetrics.as_view(), name='users-social-metrics'),
url(r'^(?P<user_id>[0-9]+)/groups/*$', users_views.UsersGroupsList.as_view(), name='users-groups-list'), url(r'^(?P<user_id>[a-zA-Z0-9]+)/groups/*$', users_views.UsersGroupsList.as_view(), name='users-groups-list'),
url(r'^(?P<user_id>[0-9]+)/groups/(?P<group_id>[0-9]+)$', users_views.UsersGroupsDetail.as_view(), name='users-groups-detail'), url(r'^(?P<user_id>[a-zA-Z0-9]+)/groups/(?P<group_id>[0-9]+)$', users_views.UsersGroupsDetail.as_view(), name='users-groups-detail'),
url(r'^(?P<user_id>[0-9]+)/preferences$', users_views.UsersPreferences.as_view(), name='users-preferences-list'), url(r'^(?P<user_id>[a-zA-Z0-9]+)/preferences$', users_views.UsersPreferences.as_view(), name='users-preferences-list'),
url(r'^(?P<user_id>[0-9]+)/organizations/$', users_views.UsersOrganizationsList.as_view(), name='users-organizations-list'), url(r'^(?P<user_id>[a-zA-Z0-9]+)/organizations/$', users_views.UsersOrganizationsList.as_view(), name='users-organizations-list'),
url(r'^(?P<user_id>[0-9]+)/workgroups/$', users_views.UsersWorkgroupsList.as_view(), name='users-workgroups-list'), url(r'^(?P<user_id>[a-zA-Z0-9]+)/workgroups/$', users_views.UsersWorkgroupsList.as_view(), name='users-workgroups-list'),
) )
urlpatterns = format_suffix_patterns(urlpatterns) urlpatterns = format_suffix_patterns(urlpatterns)
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import logging import logging
from django.contrib.auth.models import User, Group from django.contrib.auth.models import Group
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import IntegrityError from django.db import IntegrityError
from django.core.validators import validate_email, validate_slug, ValidationError from django.core.validators import validate_email, validate_slug, ValidationError
...@@ -15,7 +15,7 @@ from rest_framework.response import Response ...@@ -15,7 +15,7 @@ from rest_framework.response import Response
from django.db.models import Q from django.db.models import Q
from api_manager.permissions import SecureAPIView, SecureListAPIView from api_manager.permissions import SecureAPIView, SecureListAPIView
from api_manager.models import GroupProfile from api_manager.models import GroupProfile, APIUser as User
from api_manager.organizations.serializers import OrganizationSerializer from api_manager.organizations.serializers import OrganizationSerializer
from api_manager.utils import generate_base_uri from api_manager.utils import generate_base_uri
from projects.serializers import BasicWorkgroupSerializer from projects.serializers import BasicWorkgroupSerializer
...@@ -525,7 +525,7 @@ class UsersGroupsList(SecureAPIView): ...@@ -525,7 +525,7 @@ class UsersGroupsList(SecureAPIView):
return Response(response_data, status=status.HTTP_409_CONFLICT) return Response(response_data, status=status.HTTP_409_CONFLICT)
except ObjectDoesNotExist: except ObjectDoesNotExist:
existing_user.groups.add(existing_group.id) existing_user.groups.add(existing_group.id)
response_data['uri'] = '{}/{}'.format(base_uri, existing_user.id) response_data['uri'] = '{}/{}'.format(base_uri, existing_group.id)
response_data['group_id'] = str(existing_group.id) response_data['group_id'] = str(existing_group.id)
response_data['user_id'] = str(existing_user.id) response_data['user_id'] = str(existing_user.id)
return Response(response_data, status=status.HTTP_201_CREATED) return Response(response_data, status=status.HTTP_201_CREATED)
...@@ -813,7 +813,7 @@ class UsersPreferences(SecureAPIView): ...@@ -813,7 +813,7 @@ class UsersPreferences(SecureAPIView):
* POSTing a duplicate preference will cause the existing preference to be overwritten (effectively a PUT operation) * POSTing a duplicate preference will cause the existing preference to be overwritten (effectively a PUT operation)
""" """
def get(self, request, user_id): # pylint: disable=W0613 def get(self, request, user_id): # pylint: disable=W0613
""" """
GET returns the preferences for the specified user GET returns the preferences for the specified user
""" """
......
...@@ -44,3 +44,14 @@ def generate_base_uri(request, strip_qs=False): ...@@ -44,3 +44,14 @@ def generate_base_uri(request, strip_qs=False):
return request.build_absolute_uri(request.path) # Don't need querystring that why giving location parameter return request.build_absolute_uri(request.path) # Don't need querystring that why giving location parameter
else: else:
return request.build_absolute_uri() return request.build_absolute_uri()
def is_int(value):
"""
checks if a string value can be interpreted as integer
"""
try:
int(value)
return True
except ValueError:
return False
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