Commit 3809ba5b by Dennis Jen

Update DRF to 3.4.6

parent fee33f4c
...@@ -234,7 +234,7 @@ class Video(BaseVideo): ...@@ -234,7 +234,7 @@ class Video(BaseVideo):
class RosterUpdate(DocType): class RosterUpdate(DocType):
date = Date() date = Date(format=settings.DATE_FORMAT)
# pylint: disable=old-style-class # pylint: disable=old-style-class
class Meta: class Meta:
...@@ -265,8 +265,8 @@ class RosterEntry(DocType): ...@@ -265,8 +265,8 @@ class RosterEntry(DocType):
attempt_ratio_order = Integer() attempt_ratio_order = Integer()
discussion_contributions = Integer() discussion_contributions = Integer()
videos_watched = Integer() videos_watched = Integer()
enrollment_date = Date() enrollment_date = Date(format=settings.DATE_FORMAT)
last_updated = Date() last_updated = Date(format=settings.DATE_FORMAT)
# pylint: disable=old-style-class # pylint: disable=old-style-class
class Meta: class Meta:
...@@ -460,7 +460,7 @@ class ModuleEngagement(models.Model): ...@@ -460,7 +460,7 @@ class ModuleEngagement(models.Model):
course_id = models.CharField(db_index=True, max_length=255) course_id = models.CharField(db_index=True, max_length=255)
username = models.CharField(max_length=255) username = models.CharField(max_length=255)
date = models.DateTimeField() date = models.DateField()
# This will be one of "problem", "video" or "discussion" # This will be one of "problem", "video" or "discussion"
entity_type = models.CharField(max_length=255) entity_type = models.CharField(max_length=255)
# For problems this will be the usage key, for videos it will be the html encoded module ID, # For problems this will be the usage key, for videos it will be the html encoded module ID,
......
...@@ -210,10 +210,11 @@ class CourseActivityLastWeekTest(DemoCourseMixin, TestCaseWithAuthentication): ...@@ -210,10 +210,11 @@ class CourseActivityLastWeekTest(DemoCourseMixin, TestCaseWithAuthentication):
@staticmethod @staticmethod
def get_activity_record(**kwargs): def get_activity_record(**kwargs):
datetime_format = "%Y-%m-%dT%H:%M:%SZ"
default = { default = {
'course_id': DEMO_COURSE_ID, 'course_id': DEMO_COURSE_ID,
'interval_start': datetime.datetime(2014, 1, 1, 0, 0, tzinfo=pytz.utc), 'interval_start': datetime.datetime(2014, 1, 1, 0, 0, tzinfo=pytz.utc).strftime(datetime_format),
'interval_end': datetime.datetime(2014, 1, 8, 0, 0, tzinfo=pytz.utc), 'interval_end': datetime.datetime(2014, 1, 8, 0, 0, tzinfo=pytz.utc).strftime(datetime_format),
'activity_type': 'any', 'activity_type': 'any',
'count': 300, 'count': 300,
} }
...@@ -339,6 +340,9 @@ class CourseEnrollmentByGenderViewTests(CourseEnrollmentViewTestCaseMixin, Defau ...@@ -339,6 +340,9 @@ class CourseEnrollmentByGenderViewTests(CourseEnrollmentViewTestCaseMixin, Defau
super(CourseEnrollmentByGenderViewTests, self).setUp() super(CourseEnrollmentByGenderViewTests, self).setUp()
self.generate_data() self.generate_data()
def tearDown(self):
self.destroy_data()
def serialize_enrollment(self, enrollment): def serialize_enrollment(self, enrollment):
return { return {
'created': enrollment.created.strftime(settings.DATETIME_FORMAT), 'created': enrollment.created.strftime(settings.DATETIME_FORMAT),
...@@ -606,8 +610,8 @@ class CourseProblemsListViewTests(DemoCourseMixin, TestCaseWithAuthentication): ...@@ -606,8 +610,8 @@ class CourseProblemsListViewTests(DemoCourseMixin, TestCaseWithAuthentication):
# Create multiple objects here to test the grouping. Add a model with a different module_id to break up the # Create multiple objects here to test the grouping. Add a model with a different module_id to break up the
# natural order and ensure the view properly sorts the objects before grouping. # natural order and ensure the view properly sorts the objects before grouping.
module_id = 'i4x://test/problem/1' module_id = u'i4x://test/problem/1'
alt_module_id = 'i4x://test/problem/2' alt_module_id = u'i4x://test/problem/2'
created = datetime.datetime.utcnow() created = datetime.datetime.utcnow()
alt_created = created + datetime.timedelta(seconds=2) alt_created = created + datetime.timedelta(seconds=2)
date_time_format = '%Y-%m-%d %H:%M:%S' date_time_format = '%Y-%m-%d %H:%M:%S'
...@@ -624,21 +628,21 @@ class CourseProblemsListViewTests(DemoCourseMixin, TestCaseWithAuthentication): ...@@ -624,21 +628,21 @@ class CourseProblemsListViewTests(DemoCourseMixin, TestCaseWithAuthentication):
'module_id': module_id, 'module_id': module_id,
'total_submissions': 150, 'total_submissions': 150,
'correct_submissions': 50, 'correct_submissions': 50,
'part_ids': [o1.part_id, o3.part_id], 'part_ids': unicode([o1.part_id, o3.part_id]),
'created': alt_created.strftime(settings.DATETIME_FORMAT) 'created': alt_created.strftime(settings.DATETIME_FORMAT)
}, },
{ {
'module_id': alt_module_id, 'module_id': alt_module_id,
'total_submissions': 100, 'total_submissions': 100,
'correct_submissions': 100, 'correct_submissions': 100,
'part_ids': [o2.part_id], 'part_ids': unicode([o2.part_id]),
'created': created.strftime(settings.DATETIME_FORMAT) 'created': unicode(created.strftime(settings.DATETIME_FORMAT))
} }
] ]
response = self._get_data(self.course_id) response = self._get_data(self.course_id)
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
self.assertListEqual(response.data, expected) self.assertListEqual([dict(d) for d in response.data], expected)
def test_get_404(self): def test_get_404(self):
""" """
...@@ -669,8 +673,8 @@ class CourseProblemsAndTagsListViewTests(DemoCourseMixin, TestCaseWithAuthentica ...@@ -669,8 +673,8 @@ class CourseProblemsAndTagsListViewTests(DemoCourseMixin, TestCaseWithAuthentica
# Create multiple objects here to test the grouping. Add a model with a different module_id to break up the # Create multiple objects here to test the grouping. Add a model with a different module_id to break up the
# natural order and ensure the view properly sorts the objects before grouping. # natural order and ensure the view properly sorts the objects before grouping.
module_id = 'i4x://test/problem/1' module_id = u'i4x://test/problem/1'
alt_module_id = 'i4x://test/problem/2' alt_module_id = u'i4x://test/problem/2'
tags = { tags = {
'difficulty': ['Easy', 'Medium', 'Hard'], 'difficulty': ['Easy', 'Medium', 'Hard'],
...@@ -695,26 +699,26 @@ class CourseProblemsAndTagsListViewTests(DemoCourseMixin, TestCaseWithAuthentica ...@@ -695,26 +699,26 @@ class CourseProblemsAndTagsListViewTests(DemoCourseMixin, TestCaseWithAuthentica
'module_id': module_id, 'module_id': module_id,
'total_submissions': 11, 'total_submissions': 11,
'correct_submissions': 4, 'correct_submissions': 4,
'tags': { 'tags': unicode({
'difficulty': 'Easy', u'difficulty': u'Easy',
'learning_outcome': 'Learned a few things', u'learning_outcome': u'Learned a few things',
}, }),
'created': alt_created.strftime(settings.DATETIME_FORMAT) 'created': alt_created.strftime(settings.DATETIME_FORMAT)
}, },
{ {
'module_id': alt_module_id, 'module_id': alt_module_id,
'total_submissions': 4, 'total_submissions': 4,
'correct_submissions': 0, 'correct_submissions': 0,
'tags': { 'tags': unicode({
'learning_outcome': 'Learned everything', u'learning_outcome': u'Learned everything',
}, }),
'created': created.strftime(settings.DATETIME_FORMAT) 'created': created.strftime(settings.DATETIME_FORMAT)
} }
] ]
response = self._get_data(self.course_id) response = self._get_data(self.course_id)
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
self.assertListEqual(sorted(response.data), sorted(expected)) self.assertListEqual(sorted([dict(d) for d in response.data]), sorted(expected))
def test_get_404(self): def test_get_404(self):
""" """
......
...@@ -205,8 +205,7 @@ class LearnerListTests(LearnerAPITestMixin, VerifyCourseIdMixin, TestCaseWithAut ...@@ -205,8 +205,7 @@ class LearnerListTests(LearnerAPITestMixin, VerifyCourseIdMixin, TestCaseWithAut
returned. returned.
""" """
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
payload = json.loads(response.content) returned_learners = json.loads(response.content)['results']
returned_learners = payload['results']
if expected_learners is None: if expected_learners is None:
self.assertEqual(returned_learners, list()) self.assertEqual(returned_learners, list())
else: else:
......
...@@ -12,7 +12,7 @@ class CourseViewMixin(object): ...@@ -12,7 +12,7 @@ class CourseViewMixin(object):
course_id = None course_id = None
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.course_id = self.kwargs.get('course_id', request.QUERY_PARAMS.get('course_id', None)) self.course_id = self.kwargs.get('course_id', request.query_params.get('course_id', None))
if not self.course_id: if not self.course_id:
raise CourseNotSpecifiedError() raise CourseNotSpecifiedError()
......
...@@ -15,6 +15,8 @@ from analytics_data_api.constants import enrollment_modes ...@@ -15,6 +15,8 @@ from analytics_data_api.constants import enrollment_modes
from analytics_data_api.utils import dictfetchall from analytics_data_api.utils import dictfetchall
from analytics_data_api.v0 import models, serializers from analytics_data_api.v0 import models, serializers
from utils import raise_404_if_none
class BaseCourseView(generics.ListAPIView): class BaseCourseView(generics.ListAPIView):
start_date = None start_date = None
...@@ -25,8 +27,8 @@ class BaseCourseView(generics.ListAPIView): ...@@ -25,8 +27,8 @@ class BaseCourseView(generics.ListAPIView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.course_id = self.kwargs.get('course_id') self.course_id = self.kwargs.get('course_id')
start_date = request.QUERY_PARAMS.get('start_date') start_date = request.query_params.get('start_date')
end_date = request.QUERY_PARAMS.get('end_date') end_date = request.query_params.get('end_date')
timezone = utc timezone = utc
self.start_date = self.parse_date(start_date, timezone) self.start_date = self.parse_date(start_date, timezone)
...@@ -46,6 +48,7 @@ class BaseCourseView(generics.ListAPIView): ...@@ -46,6 +48,7 @@ class BaseCourseView(generics.ListAPIView):
def apply_date_filtering(self, queryset): def apply_date_filtering(self, queryset):
raise NotImplementedError raise NotImplementedError
@raise_404_if_none
def get_queryset(self): def get_queryset(self):
queryset = self.model.objects.filter(course_id=self.course_id) queryset = self.model.objects.filter(course_id=self.course_id)
queryset = self.apply_date_filtering(queryset) queryset = self.apply_date_filtering(queryset)
...@@ -232,9 +235,9 @@ class CourseActivityMostRecentWeekView(generics.RetrieveAPIView): ...@@ -232,9 +235,9 @@ class CourseActivityMostRecentWeekView(generics.RetrieveAPIView):
""" Retrieve the activity type from the query string. """ """ Retrieve the activity type from the query string. """
# Support the old label param # Support the old label param
activity_type = self.request.QUERY_PARAMS.get('label', None) activity_type = self.request.query_params.get('label', None)
activity_type = activity_type or self.request.QUERY_PARAMS.get('activity_type', self.DEFAULT_ACTIVITY_TYPE) activity_type = activity_type or self.request.query_params.get('activity_type', self.DEFAULT_ACTIVITY_TYPE)
activity_type = self._format_activity_type(activity_type) activity_type = self._format_activity_type(activity_type)
return activity_type return activity_type
...@@ -633,6 +636,7 @@ class ProblemsListView(BaseCourseView): ...@@ -633,6 +636,7 @@ class ProblemsListView(BaseCourseView):
serializer_class = serializers.ProblemSerializer serializer_class = serializers.ProblemSerializer
allow_empty = False allow_empty = False
@raise_404_if_none
def get_queryset(self): def get_queryset(self):
# last_response_count is the number of submissions for the problem part and must # last_response_count is the number of submissions for the problem part and must
# be divided by the number of problem parts to get the problem submission rather # be divided by the number of problem parts to get the problem submission rather
...@@ -709,6 +713,7 @@ class ProblemsAndTagsListView(BaseCourseView): ...@@ -709,6 +713,7 @@ class ProblemsAndTagsListView(BaseCourseView):
allow_empty = False allow_empty = False
model = models.ProblemsAndTags model = models.ProblemsAndTags
@raise_404_if_none
def get_queryset(self): def get_queryset(self):
queryset = self.model.objects.filter(course_id=self.course_id) queryset = self.model.objects.filter(course_id=self.course_id)
items = queryset.all() items = queryset.all()
......
...@@ -5,9 +5,6 @@ import logging ...@@ -5,9 +5,6 @@ import logging
from rest_framework import generics, status from rest_framework import generics, status
from analytics_data_api.constants import (
learner
)
from analytics_data_api.v0.exceptions import ( from analytics_data_api.v0.exceptions import (
LearnerEngagementTimelineNotFoundError, LearnerEngagementTimelineNotFoundError,
LearnerNotFoundError, LearnerNotFoundError,
...@@ -21,7 +18,7 @@ from analytics_data_api.v0.models import ( ...@@ -21,7 +18,7 @@ from analytics_data_api.v0.models import (
) )
from analytics_data_api.v0.serializers import ( from analytics_data_api.v0.serializers import (
CourseLearnerMetadataSerializer, CourseLearnerMetadataSerializer,
ElasticsearchDSLSearchSerializer, EdxPaginationSerializer,
EngagementDaySerializer, EngagementDaySerializer,
LastUpdatedSerializer, LastUpdatedSerializer,
LearnerSerializer, LearnerSerializer,
...@@ -187,14 +184,12 @@ class LearnerListView(LastUpdateMixin, CourseViewMixin, generics.ListAPIView): ...@@ -187,14 +184,12 @@ class LearnerListView(LastUpdateMixin, CourseViewMixin, generics.ListAPIView):
""" """
serializer_class = LearnerSerializer serializer_class = LearnerSerializer
pagination_serializer_class = ElasticsearchDSLSearchSerializer pagination_class = EdxPaginationSerializer
paginate_by_param = 'page_size'
paginate_by = learner.LEARNER_API_DEFAULT_LIST_PAGE_SIZE
max_paginate_by = 100 # TODO -- tweak during load testing max_paginate_by = 100 # TODO -- tweak during load testing
def _validate_query_params(self): def _validate_query_params(self):
"""Validates various querystring parameters.""" """Validates various querystring parameters."""
query_params = self.request.QUERY_PARAMS query_params = self.request.query_params
page = query_params.get('page') page = query_params.get('page')
if page: if page:
try: try:
...@@ -222,8 +217,9 @@ class LearnerListView(LastUpdateMixin, CourseViewMixin, generics.ListAPIView): ...@@ -222,8 +217,9 @@ class LearnerListView(LastUpdateMixin, CourseViewMixin, generics.ListAPIView):
""" """
response = super(LearnerListView, self).list(request, args, kwargs) response = super(LearnerListView, self).list(request, args, kwargs)
last_updated = self.get_last_updated() last_updated = self.get_last_updated()
for result in response.data['results']: if response.data['results'] is not None:
result.update(last_updated) for result in response.data['results']:
result.update(last_updated)
return response return response
def get_queryset(self): def get_queryset(self):
...@@ -232,7 +228,7 @@ class LearnerListView(LastUpdateMixin, CourseViewMixin, generics.ListAPIView): ...@@ -232,7 +228,7 @@ class LearnerListView(LastUpdateMixin, CourseViewMixin, generics.ListAPIView):
as a an array of dicts with fields "learner" and "last_updated". as a an array of dicts with fields "learner" and "last_updated".
""" """
self._validate_query_params() self._validate_query_params()
query_params = self.request.QUERY_PARAMS query_params = self.request.query_params
order_by = query_params.get('order_by') order_by = query_params.get('order_by')
sort_order = query_params.get('sort_order') sort_order = query_params.get('sort_order')
......
...@@ -22,6 +22,8 @@ from analytics_data_api.v0.serializers import ( ...@@ -22,6 +22,8 @@ from analytics_data_api.v0.serializers import (
) )
from analytics_data_api.utils import matching_tuple from analytics_data_api.utils import matching_tuple
from utils import raise_404_if_none
class ProblemResponseAnswerDistributionView(generics.ListAPIView): class ProblemResponseAnswerDistributionView(generics.ListAPIView):
""" """
...@@ -98,6 +100,7 @@ class ProblemResponseAnswerDistributionView(generics.ListAPIView): ...@@ -98,6 +100,7 @@ class ProblemResponseAnswerDistributionView(generics.ListAPIView):
return consolidated_answers return consolidated_answers
@raise_404_if_none
def get_queryset(self): def get_queryset(self):
"""Select all the answer distribution response having to do with this usage of the problem.""" """Select all the answer distribution response having to do with this usage of the problem."""
problem_id = self.kwargs.get('problem_id') problem_id = self.kwargs.get('problem_id')
...@@ -142,6 +145,7 @@ class GradeDistributionView(generics.ListAPIView): ...@@ -142,6 +145,7 @@ class GradeDistributionView(generics.ListAPIView):
serializer_class = GradeDistributionSerializer serializer_class = GradeDistributionSerializer
allow_empty = False allow_empty = False
@raise_404_if_none
def get_queryset(self): def get_queryset(self):
"""Select all grade distributions for a particular module""" """Select all grade distributions for a particular module"""
problem_id = self.kwargs.get('problem_id') problem_id = self.kwargs.get('problem_id')
...@@ -170,6 +174,7 @@ class SequentialOpenDistributionView(generics.ListAPIView): ...@@ -170,6 +174,7 @@ class SequentialOpenDistributionView(generics.ListAPIView):
serializer_class = SequentialOpenDistributionSerializer serializer_class = SequentialOpenDistributionSerializer
allow_empty = False allow_empty = False
@raise_404_if_none
def get_queryset(self): def get_queryset(self):
"""Select the view count for a specific module""" """Select the view count for a specific module"""
module_id = self.kwargs.get('module_id') module_id = self.kwargs.get('module_id')
......
"""Utilities for view-level API logic.""" """Utilities for view-level API logic."""
from django.http import Http404
def split_query_argument(argument): def split_query_argument(argument):
...@@ -10,3 +11,15 @@ def split_query_argument(argument): ...@@ -10,3 +11,15 @@ def split_query_argument(argument):
return argument.split(',') return argument.split(',')
else: else:
return None return None
def raise_404_if_none(func):
"""
Decorator for raiseing Http404 if function evaulation is falsey (e.g. empty queryset).
"""
def func_wrapper(self):
queryset = func(self)
if queryset:
return queryset
else:
raise Http404
return func_wrapper
\ No newline at end of file
...@@ -7,6 +7,7 @@ from rest_framework import generics ...@@ -7,6 +7,7 @@ from rest_framework import generics
from analytics_data_api.v0.models import VideoTimeline from analytics_data_api.v0.models import VideoTimeline
from analytics_data_api.v0.serializers import VideoTimelineSerializer from analytics_data_api.v0.serializers import VideoTimelineSerializer
from utils import raise_404_if_none
class VideoTimelineView(generics.ListAPIView): class VideoTimelineView(generics.ListAPIView):
""" """
...@@ -30,6 +31,7 @@ class VideoTimelineView(generics.ListAPIView): ...@@ -30,6 +31,7 @@ class VideoTimelineView(generics.ListAPIView):
serializer_class = VideoTimelineSerializer serializer_class = VideoTimelineSerializer
allow_empty = False allow_empty = False
@raise_404_if_none
def get_queryset(self): def get_queryset(self):
"""Select the view count for a specific module""" """Select the view count for a specific module"""
video_id = self.kwargs.get('video_id') video_id = self.kwargs.get('video_id')
......
boto==2.22.1 # MIT boto==2.22.1 # MIT
Django==1.8.14 # BSD License Django==1.8.14 # BSD License
django-model-utils==2.2 # BSD django-model-utils==2.2 # BSD
djangorestframework==2.4.4 # BSD djangorestframework==3.4.6 # BSD
django-rest-swagger==0.2.8 # BSD django-rest-swagger==0.2.8 # BSD
djangorestframework-csv==1.3.3 # BSD djangorestframework-csv==1.3.3 # BSD
django-countries==3.2 # MIT django-countries==3.2 # MIT
......
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