Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

Commit e95f217

Browse files
authored
Merge pull request #135 from edx/dsjen/updgrade-drf
Update DRF to 3.4.6
2 parents 1e8aa54 + 87c4a49 commit e95f217

11 files changed

Lines changed: 184 additions & 135 deletions

File tree

analytics_data_api/v0/models.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ class Meta(BaseVideo.Meta):
234234

235235
class RosterUpdate(DocType):
236236

237-
date = Date()
237+
date = Date(format=settings.DATE_FORMAT)
238238

239239
# pylint: disable=old-style-class
240240
class Meta:
@@ -265,8 +265,8 @@ class RosterEntry(DocType):
265265
attempt_ratio_order = Integer()
266266
discussion_contributions = Integer()
267267
videos_watched = Integer()
268-
enrollment_date = Date()
269-
last_updated = Date()
268+
enrollment_date = Date(format=settings.DATE_FORMAT)
269+
last_updated = Date(format=settings.DATE_FORMAT)
270270

271271
# pylint: disable=old-style-class
272272
class Meta:
@@ -460,7 +460,7 @@ class ModuleEngagement(models.Model):
460460

461461
course_id = models.CharField(db_index=True, max_length=255)
462462
username = models.CharField(max_length=255)
463-
date = models.DateTimeField()
463+
date = models.DateField()
464464
# This will be one of "problem", "video" or "discussion"
465465
entity_type = models.CharField(max_length=255)
466466
# For problems this will be the usage key, for videos it will be the html encoded module ID,

analytics_data_api/v0/serializers.py

Lines changed: 109 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
from collections import OrderedDict
12
from urlparse import urljoin
23
from django.conf import settings
34
from rest_framework import pagination, serializers
5+
from rest_framework.response import Response
46

57
from analytics_data_api.constants import (
68
engagement_events,
79
enrollment_modes,
8-
genders,
10+
learner,
911
)
1012
from analytics_data_api.v0 import models
1113

@@ -23,7 +25,7 @@ class CourseActivityByWeekSerializer(serializers.ModelSerializer):
2325
particular record is likely to change unexpectedly so we avoid exposing it.
2426
"""
2527

26-
activity_type = serializers.SerializerMethodField('get_activity_type')
28+
activity_type = serializers.SerializerMethodField()
2729

2830
def get_activity_type(self, obj):
2931
"""
@@ -45,6 +47,7 @@ class ModelSerializerWithCreatedField(serializers.ModelSerializer):
4547
created = serializers.DateTimeField(format=settings.DATETIME_FORMAT)
4648

4749

50+
# pylint: disable=abstract-method
4851
class ProblemSerializer(serializers.Serializer):
4952
"""
5053
Serializer for problems.
@@ -57,6 +60,7 @@ class ProblemSerializer(serializers.Serializer):
5760
created = serializers.DateTimeField(format=settings.DATETIME_FORMAT)
5861

5962

63+
# pylint: disable=abstract-method
6064
class ProblemsAndTagsSerializer(serializers.Serializer):
6165
"""
6266
Serializer for problems and tags.
@@ -187,13 +191,7 @@ class Meta(object):
187191
)
188192

189193

190-
class DefaultIfNoneMixin(object):
191-
192-
def default_if_none(self, value, default=0):
193-
return value if value is not None else default
194-
195-
196-
class BaseCourseEnrollmentModelSerializer(DefaultIfNoneMixin, ModelSerializerWithCreatedField):
194+
class BaseCourseEnrollmentModelSerializer(ModelSerializerWithCreatedField):
197195
date = serializers.DateField(format=settings.DATE_FORMAT)
198196

199197

@@ -207,32 +205,35 @@ class Meta(object):
207205

208206
class CourseEnrollmentModeDailySerializer(BaseCourseEnrollmentModelSerializer):
209207
""" Representation of course enrollment, broken down by mode, for a single day and course. """
208+
audit = serializers.SerializerMethodField()
209+
credit = serializers.SerializerMethodField()
210+
honor = serializers.SerializerMethodField()
211+
professional = serializers.SerializerMethodField()
212+
verified = serializers.SerializerMethodField()
210213

211-
def get_default_fields(self):
212-
# pylint: disable=super-on-old-class
213-
fields = super(CourseEnrollmentModeDailySerializer, self).get_default_fields()
214+
def get_audit(self, obj):
215+
return obj.get('audit', 0)
214216

215-
# Create a field for each enrollment mode
216-
for mode in ENROLLMENT_MODES:
217-
fields[mode] = serializers.IntegerField(required=True, default=0)
217+
def get_honor(self, obj):
218+
return obj.get('honor', 0)
218219

219-
# Create a transform method for each field
220-
setattr(self, 'transform_%s' % mode, self._transform_mode)
220+
def get_credit(self, obj):
221+
return obj.get('credit', 0)
221222

222-
fields['cumulative_count'] = serializers.IntegerField(required=True, default=0)
223+
def get_professional(self, obj):
224+
return obj.get('professional', 0)
223225

224-
return fields
225-
226-
def _transform_mode(self, _obj, value):
227-
return self.default_if_none(value, 0)
226+
def get_verified(self, obj):
227+
return obj.get('verified', 0)
228228

229229
class Meta(object):
230-
model = models.CourseEnrollmentDaily
230+
model = models.CourseEnrollmentModeDaily
231231

232232
# Declare the dynamically-created fields here as well so that they will be picked up by Swagger.
233233
fields = ['course_id', 'date', 'count', 'cumulative_count', 'created'] + ENROLLMENT_MODES
234234

235235

236+
# pylint: disable=abstract-method
236237
class CountrySerializer(serializers.Serializer):
237238
"""
238239
Serialize country to an object with fields for the complete country name
@@ -256,21 +257,23 @@ class Meta(object):
256257

257258

258259
class CourseEnrollmentByGenderSerializer(BaseCourseEnrollmentModelSerializer):
259-
def get_default_fields(self):
260-
# pylint: disable=super-on-old-class
261-
fields = super(CourseEnrollmentByGenderSerializer, self).get_default_fields()
262260

263-
# Create a field for each gender
264-
for gender in genders.ALL:
265-
fields[gender] = serializers.IntegerField(required=True, default=0)
261+
female = serializers.ReadOnlyField()
262+
male = serializers.ReadOnlyField()
263+
other = serializers.ReadOnlyField()
264+
unknown = serializers.ReadOnlyField()
265+
266+
def get_female(self, obj):
267+
return obj.get('female', None)
266268

267-
# Create a transform method for each field
268-
setattr(self, 'transform_%s' % gender, self._transform_gender)
269+
def get_male(self, obj):
270+
return obj.get('male', None)
269271

270-
return fields
272+
def get_other(self, obj):
273+
return obj.get('other', None)
271274

272-
def _transform_gender(self, _obj, value):
273-
return self.default_if_none(value, 0)
275+
def get_unknown(self, obj):
276+
return obj.get('unknown', None)
274277

275278
class Meta(object):
276279
model = models.CourseEnrollmentByGender
@@ -329,35 +332,47 @@ class Meta(object):
329332
)
330333

331334

335+
# pylint: disable=abstract-method
332336
class LastUpdatedSerializer(serializers.Serializer):
333-
last_updated = serializers.DateField(source='date', format=settings.DATE_FORMAT)
337+
last_updated = serializers.DateTimeField(source='date', format=settings.DATE_FORMAT)
334338

335339

336-
class LearnerSerializer(serializers.Serializer, DefaultIfNoneMixin):
337-
username = serializers.CharField(source='username')
338-
enrollment_mode = serializers.CharField(source='enrollment_mode')
339-
name = serializers.CharField(source='name')
340-
account_url = serializers.SerializerMethodField('get_account_url')
341-
email = serializers.CharField(source='email')
342-
segments = serializers.Field(source='segments')
343-
engagements = serializers.SerializerMethodField('get_engagements')
344-
enrollment_date = serializers.DateField(source='enrollment_date', format=settings.DATE_FORMAT)
345-
cohort = serializers.CharField(source='cohort')
346-
347-
def transform_segments(self, _obj, value):
348-
# returns null instead of empty strings
349-
return value or []
340+
# pylint: disable=abstract-method
341+
class LearnerSerializer(serializers.Serializer):
342+
username = serializers.CharField()
343+
enrollment_mode = serializers.CharField()
344+
name = serializers.CharField()
345+
account_url = serializers.SerializerMethodField()
346+
email = serializers.CharField()
347+
segments = serializers.SerializerMethodField()
348+
engagements = serializers.SerializerMethodField()
349+
enrollment_date = serializers.DateTimeField(format=settings.DATE_FORMAT)
350+
cohort = serializers.SerializerMethodField()
351+
352+
def get_segments(self, obj):
353+
# using hasattr() instead because DocType.get() is overloaded and makes a request
354+
if hasattr(obj, 'segments'):
355+
# json parsing will fail unless in unicode
356+
return [unicode(segment) for segment in obj.segments]
357+
else:
358+
return []
350359

351-
def transform_cohort(self, _obj, value):
352-
# returns null instead of empty strings
353-
return value or None
360+
def get_cohort(self, obj):
361+
# using hasattr() instead because DocType.get() is overloaded and makes a request
362+
if hasattr(obj, 'cohort') and len(obj.cohort) > 0:
363+
return obj.cohort
364+
else:
365+
return None
354366

355367
def get_account_url(self, obj):
356368
if settings.LMS_USER_ACCOUNT_BASE_URL:
357369
return urljoin(settings.LMS_USER_ACCOUNT_BASE_URL, obj.username)
358370
else:
359371
return None
360372

373+
def default_if_none(self, value, default=0):
374+
return value if value is not None else default
375+
361376
def get_engagements(self, obj):
362377
"""
363378
Add the engagement totals.
@@ -376,62 +391,65 @@ def get_engagements(self, obj):
376391
return engagements
377392

378393

379-
class EdxPaginationSerializer(pagination.PaginationSerializer):
394+
class EdxPaginationSerializer(pagination.PageNumberPagination):
380395
"""
381396
Adds values to the response according to edX REST API Conventions.
382397
"""
383-
count = serializers.Field(source='paginator.count')
384-
num_pages = serializers.Field(source='paginator.num_pages')
385-
386-
387-
class ElasticsearchDSLSearchSerializer(EdxPaginationSerializer):
388-
def __init__(self, *args, **kwargs):
389-
"""Make sure that the elasticsearch query is executed."""
390-
# Because the elasticsearch-dsl search object has a different
391-
# API from the queryset object that's expected by the django
392-
# Paginator object, we have to manually execute the query.
393-
# Note that the `kwargs['instance']` is the Page object, and
394-
# `kwargs['instance'].object_list` is actually an
395-
# elasticsearch-dsl search object.
396-
kwargs['instance'].object_list = kwargs['instance'].object_list.execute()
397-
super(ElasticsearchDSLSearchSerializer, self).__init__(*args, **kwargs)
398-
399-
400-
class EngagementDaySerializer(DefaultIfNoneMixin, serializers.Serializer):
398+
page_size_query_param = 'page_size'
399+
page_size = learner.LEARNER_API_DEFAULT_LIST_PAGE_SIZE
400+
max_page_size = 100 # TODO -- tweak during load testing
401+
402+
def get_paginated_response(self, data):
403+
# The output is more readable with num_pages included not at the end, but
404+
# inefficient to insert into an OrderedDict, so the response is copied from
405+
# rest_framework.pagination with the addition of "num_pages".
406+
return Response(OrderedDict([
407+
('count', self.page.paginator.count),
408+
('num_pages', self.page.paginator.num_pages),
409+
('next', self.get_next_link()),
410+
('previous', self.get_previous_link()),
411+
('results', data)
412+
]))
413+
414+
415+
# pylint: disable=abstract-method
416+
class EngagementDaySerializer(serializers.Serializer):
401417
date = serializers.DateField(format=settings.DATE_FORMAT)
402-
problems_attempted = serializers.IntegerField(required=True, default=0)
403-
problems_completed = serializers.IntegerField(required=True, default=0)
404-
discussion_contributions = serializers.IntegerField(required=True, default=0)
405-
videos_viewed = serializers.IntegerField(required=True, default=0)
418+
problems_attempted = serializers.SerializerMethodField()
419+
problems_completed = serializers.SerializerMethodField()
420+
discussion_contributions = serializers.SerializerMethodField()
421+
videos_viewed = serializers.SerializerMethodField()
406422

407-
def transform_problems_attempted(self, _obj, value):
408-
return self.default_if_none(value, 0)
423+
def get_problems_attempted(self, obj):
424+
return obj.get('problems_attempted', 0)
409425

410-
def transform_problems_completed(self, _obj, value):
411-
return self.default_if_none(value, 0)
426+
def get_problems_completed(self, obj):
427+
return obj.get('problems_completed', 0)
412428

413-
def transform_discussion_contributions(self, _obj, value):
414-
return self.default_if_none(value, 0)
429+
def get_discussion_contributions(self, obj):
430+
return obj.get('discussion_contributions', 0)
415431

416-
def transform_videos_viewed(self, _obj, value):
417-
return self.default_if_none(value, 0)
432+
def get_videos_viewed(self, obj):
433+
return obj.get('videos_viewed', 0)
418434

419435

436+
# pylint: disable=abstract-method
420437
class DateRangeSerializer(serializers.Serializer):
421438
start = serializers.DateTimeField(source='start_date', format=settings.DATE_FORMAT)
422439
end = serializers.DateTimeField(source='end_date', format=settings.DATE_FORMAT)
423440

424441

442+
# pylint: disable=abstract-method
425443
class EnagementRangeMetricSerializer(serializers.Serializer):
426444
"""
427445
Serializes ModuleEngagementMetricRanges ('bottom', 'average', and 'top') into
428446
the class_rank_bottom, class_rank_average, and class_rank_top ranges
429447
represented as arrays. If any one of the ranges is not defined, it is not
430448
included in the serialized output.
431449
"""
432-
class_rank_bottom = serializers.SerializerMethodField('get_class_rank_bottom')
433-
class_rank_average = serializers.SerializerMethodField('get_class_rank_average')
434-
class_rank_top = serializers.SerializerMethodField('get_class_rank_top')
450+
class_rank_bottom = serializers.SerializerMethodField()
451+
class_rank_average = serializers.SerializerMethodField()
452+
class_rank_top = serializers.SerializerMethodField()
435453

436454
def get_class_rank_average(self, obj):
437455
return self._transform_range(obj['average'])
@@ -446,11 +464,12 @@ def _transform_range(self, metric_range):
446464
return [metric_range.low_value, metric_range.high_value] if metric_range else None
447465

448466

467+
# pylint: disable=abstract-method
449468
class CourseLearnerMetadataSerializer(serializers.Serializer):
450-
enrollment_modes = serializers.Field(source='es_data.enrollment_modes')
451-
segments = serializers.Field(source='es_data.segments')
452-
cohorts = serializers.Field(source='es_data.cohorts')
453-
engagement_ranges = serializers.SerializerMethodField('get_engagement_ranges')
469+
enrollment_modes = serializers.ReadOnlyField(source='es_data.enrollment_modes')
470+
segments = serializers.ReadOnlyField(source='es_data.segments')
471+
cohorts = serializers.ReadOnlyField(source='es_data.cohorts')
472+
engagement_ranges = serializers.SerializerMethodField()
454473

455474
def get_engagement_ranges(self, obj):
456475
query_set = obj['engagement_ranges']

0 commit comments

Comments
 (0)