Commit acefe9fc by Clinton Blackburn

Updated enrollment resources to support historical data

Change-Id: I88bf4ebefd9742f4647b0e9383fdbac9f97ac805
parent dbde3921
......@@ -88,5 +88,95 @@
"count": 9940,
"date": "2014-07-01"
}
},
{
"model": "v0.CourseEnrollmentByEducation",
"pk": 14,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 6,
"count": 12295,
"date": "2014-07-02"
}
},
{
"model": "v0.CourseEnrollmentByEducation",
"pk": 6,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 7,
"count": 70885,
"date": "2014-07-01"
}
},
{
"model": "v0.CourseEnrollmentByEducation",
"pk": 7,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 3,
"count": 981,
"date": "2014-07-01"
}
},
{
"model": "v0.CourseEnrollmentByEducation",
"pk": 8,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 5,
"count": 51591,
"date": "2014-07-01"
}
},
{
"model": "v0.CourseEnrollmentByEducation",
"pk": 9,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 4,
"count": 6051,
"date": "2014-07-01"
}
},
{
"model": "v0.CourseEnrollmentByEducation",
"pk": 10,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 8,
"count": 53216,
"date": "2014-07-01"
}
},
{
"model": "v0.CourseEnrollmentByEducation",
"pk": 11,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 1,
"count": 667,
"date": "2014-07-01"
}
},
{
"model": "v0.CourseEnrollmentByEducation",
"pk": 12,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 2,
"count": 5722,
"date": "2014-07-01"
}
},
{
"model": "v0.CourseEnrollmentByEducation",
"pk": 13,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 9,
"count": 9940,
"date": "2014-07-01"
}
}
]
......@@ -28,5 +28,35 @@
"count": 423,
"date": "2014-07-01"
}
},
{
"model": "v0.CourseEnrollmentByGender",
"pk": 4,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"gender": "m",
"count": 1332,
"date": "2014-07-02"
}
},
{
"model": "v0.CourseEnrollmentByGender",
"pk": 5,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"gender": "f",
"count": 77445,
"date": "2014-07-02"
}
},
{
"model": "v0.CourseEnrollmentByGender",
"pk": 6,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"gender": "o",
"count": 34,
"date": "2014-07-02"
}
}
]
from django.conf import settings
from rest_framework import serializers
from analytics_data_api.v0.models import CourseActivityByWeek, ProblemResponseAnswerDistribution, \
CourseEnrollmentDaily, CourseEnrollmentByCountry, Country
from analytics_data_api.v0 import models
class CourseIdMixin(object):
......@@ -24,7 +23,7 @@ class CourseActivityByWeekSerializer(serializers.ModelSerializer, CourseIdMixin)
course_id = RequiredSerializerMethodField('get_course_id')
class Meta(object):
model = CourseActivityByWeek
model = models.CourseActivityByWeek
fields = ('interval_start', 'interval_end', 'activity_type', 'count', 'course_id')
......@@ -37,7 +36,7 @@ class ProblemResponseAnswerDistributionSerializer(serializers.ModelSerializer):
"""
class Meta(object):
model = ProblemResponseAnswerDistribution
model = models.ProblemResponseAnswerDistribution
fields = (
'course_id',
'module_id',
......@@ -52,30 +51,56 @@ class ProblemResponseAnswerDistributionSerializer(serializers.ModelSerializer):
)
class CourseEnrollmentDailySerializer(serializers.ModelSerializer, CourseIdMixin):
"""
Representation of course enrollment for a single day and course.
"""
class BaseCourseEnrollmentModelSerializer(serializers.ModelSerializer, CourseIdMixin):
course_id = RequiredSerializerMethodField('get_course_id')
date = serializers.DateField(format=settings.DATE_FORMAT)
class CourseEnrollmentDailySerializer(BaseCourseEnrollmentModelSerializer):
""" Representation of course enrollment for a single day and course. """
class Meta(object):
model = CourseEnrollmentDaily
model = models.CourseEnrollmentDaily
fields = ('course_id', 'date', 'count')
# pylint: disable=no-value-for-parameter
class CountrySerializer(serializers.ModelSerializer):
class Meta(object):
model = Country
model = models.Country
fields = ('code', 'name')
class CourseEnrollmentByCountrySerializer(serializers.ModelSerializer, CourseIdMixin):
course_id = RequiredSerializerMethodField('get_course_id')
# pylint: disable=no-value-for-parameter
class EducationLevelSerializer(serializers.ModelSerializer):
class Meta(object):
model = models.EducationLevel
fields = ('name', 'short_name')
class CourseEnrollmentByCountrySerializer(BaseCourseEnrollmentModelSerializer):
country = CountrySerializer()
date = serializers.DateField(format=settings.DATE_FORMAT)
class Meta(object):
model = CourseEnrollmentByCountry
model = models.CourseEnrollmentByCountry
fields = ('date', 'course_id', 'country', 'count')
class CourseEnrollmentByGenderSerializer(BaseCourseEnrollmentModelSerializer):
class Meta(object):
model = models.CourseEnrollmentByGender
fields = ('course_id', 'date', 'gender', 'count')
class CourseEnrollmentByEducationSerializer(BaseCourseEnrollmentModelSerializer):
education_level = EducationLevelSerializer()
class Meta(object):
model = models.CourseEnrollmentByEducation
fields = ('course_id', 'date', 'education_level', 'count')
class CourseEnrollmentByBirthYearSerializer(BaseCourseEnrollmentModelSerializer):
class Meta(object):
model = models.CourseEnrollmentByBirthYear
fields = ('course_id', 'date', 'birth_year', 'count')
......@@ -81,6 +81,7 @@ class CourseActivityLastWeekTest(TestCaseWithAuthentication):
self.assertEquals(response.status_code, 404)
# pylint: disable=no-member
class CourseEnrollmentViewTestCase(object):
model = None
path = None
......@@ -93,15 +94,49 @@ class CourseEnrollmentViewTestCase(object):
return self._get_non_existent_course_id()
def get_expected_response(self, *args):
raise NotImplementedError
def test_get_not_found(self):
""" Requests made against non-existent courses should return a 404 """
course_id = self._get_non_existent_course_id()
self.assertFalse(self.model.objects.filter(course__course_id=course_id).exists()) # pylint: disable=no-member
response = self.authenticated_get('/api/v0/courses/%s%s' % (course_id, self.path)) # pylint: disable=no-member
self.assertEquals(response.status_code, 404) # pylint: disable=no-member
self.assertFalse(self.model.objects.filter(course__course_id=course_id).exists())
response = self.authenticated_get('/api/v0/courses/%s%s' % (course_id, self.path))
self.assertEquals(response.status_code, 404)
def test_get(self):
raise NotImplementedError
response = self.authenticated_get('/api/v0/courses/%s%s' % (self.course.course_id, self.path,))
self.assertEquals(response.status_code, 200)
expected = self.get_expected_response(*self.model.objects.filter(date=self.date))
self.assertEquals(response.data, expected)
def test_get_with_intervals(self):
expected = self.get_expected_response(*self.model.objects.filter(date=self.date))
self.assertIntervalFilteringWorks(expected, self.date, self.date + datetime.timedelta(days=1))
def assertIntervalFilteringWorks(self, expected_response, start_date, end_date):
course = self.course
# If start date is after date of existing data, no data should be returned
date = (start_date + datetime.timedelta(days=30)).strftime(settings.DATE_FORMAT)
response = self.authenticated_get('/api/v0/courses/%s%s?start_date=%s' % (course.course_id, self.path, date))
self.assertEquals(response.status_code, 200)
self.assertListEqual([], response.data)
# If end date is before date of existing data, no data should be returned
date = (start_date - datetime.timedelta(days=30)).strftime(settings.DATE_FORMAT)
response = self.authenticated_get('/api/v0/courses/%s%s?end_date=%s' % (course.course_id, self.path, date))
self.assertEquals(response.status_code, 200)
self.assertListEqual([], response.data)
# If data falls in date range, data should be returned
start_date = start_date.strftime(settings.DATE_FORMAT)
end_date = end_date.strftime(settings.DATE_FORMAT)
response = self.authenticated_get(
'/api/v0/courses/%s%s?start_date=%s&end_date=%s' % (course.course_id, self.path, start_date, end_date))
self.assertEquals(response.status_code, 200)
self.assertListEqual(response.data, expected_response)
class CourseEnrollmentByBirthYearViewTests(TestCaseWithAuthentication, CourseEnrollmentViewTestCase):
......@@ -111,23 +146,30 @@ class CourseEnrollmentByBirthYearViewTests(TestCaseWithAuthentication, CourseEnr
@classmethod
def setUpClass(cls):
cls.course = G(Course)
cls.ce1 = G(CourseEnrollmentByBirthYear, course=cls.course, birth_year=1956)
cls.ce2 = G(CourseEnrollmentByBirthYear, course=cls.course, birth_year=1986)
cls.date = datetime.date(2014, 1, 1)
G(cls.model, course=cls.course, date=cls.date, birth_year=1956)
G(cls.model, course=cls.course, date=cls.date, birth_year=1986)
G(cls.model, course=cls.course, date=cls.date - datetime.timedelta(days=10), birth_year=1956)
G(cls.model, course=cls.course, date=cls.date - datetime.timedelta(days=10), birth_year=1986)
def get_expected_response(self, *args):
return [{'course_id': ce.course.course_id, 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT),
'birth_year': ce.birth_year} for ce in args]
def test_get(self):
response = self.authenticated_get('/api/v0/courses/%s%s' % (self.course.course_id, self.path,))
self.assertEquals(response.status_code, 200)
expected = {
self.ce1.birth_year: self.ce1.count,
self.ce2.birth_year: self.ce2.count,
}
actual = response.data['birth_years']
self.assertEquals(actual, expected)
expected = self.get_expected_response(*self.model.objects.filter(date=self.date))
self.assertEquals(response.data, expected)
def test_get_with_intervals(self):
expected = self.get_expected_response(*self.model.objects.filter(date=self.date))
self.assertIntervalFilteringWorks(expected, self.date, self.date + datetime.timedelta(days=1))
class CourseEnrollmentByEducationViewTests(TestCaseWithAuthentication, CourseEnrollmentViewTestCase):
path = '/enrollment/education'
path = '/enrollment/education/'
model = CourseEnrollmentByEducation
@classmethod
......@@ -135,41 +177,33 @@ class CourseEnrollmentByEducationViewTests(TestCaseWithAuthentication, CourseEnr
cls.el1 = G(EducationLevel, name='Doctorate', short_name='doctorate')
cls.el2 = G(EducationLevel, name='Top Secret', short_name='top_secret')
cls.course = G(Course)
cls.ce1 = G(CourseEnrollmentByEducation, course=cls.course, education_level=cls.el1)
cls.ce2 = G(CourseEnrollmentByEducation, course=cls.course, education_level=cls.el2)
def test_get(self):
response = self.authenticated_get('/api/v0/courses/%s%s' % (self.course.course_id, self.path,))
self.assertEquals(response.status_code, 200)
cls.date = datetime.date(2014, 1, 1)
G(cls.model, course=cls.course, date=cls.date, education_level=cls.el1)
G(cls.model, course=cls.course, date=cls.date, education_level=cls.el2)
G(cls.model, course=cls.course, date=cls.date - datetime.timedelta(days=2),
education_level=cls.el2)
expected = {
self.ce1.education_level.short_name: self.ce1.count,
self.ce2.education_level.short_name: self.ce2.count,
}
actual = response.data['education_levels']
self.assertEquals(actual, expected)
def get_expected_response(self, *args):
return [{'course_id': ce.course.course_id, 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT),
'education_level': {'name': ce.education_level.name, 'short_name': ce.education_level.short_name}} for
ce in args]
class CourseEnrollmentByGenderViewTests(TestCaseWithAuthentication, CourseEnrollmentViewTestCase):
path = '/enrollment/gender'
path = '/enrollment/gender/'
model = CourseEnrollmentByGender
@classmethod
def setUpClass(cls):
cls.course = G(Course)
cls.ce1 = G(CourseEnrollmentByGender, course=cls.course, gender='m')
cls.ce2 = G(CourseEnrollmentByGender, course=cls.course, gender='f')
def test_get(self):
response = self.authenticated_get('/api/v0/courses/%s%s' % (self.course.course_id, self.path,))
self.assertEquals(response.status_code, 200)
cls.date = datetime.date(2014, 1, 1)
G(cls.model, course=cls.course, gender='m', date=cls.date, count=34)
G(cls.model, course=cls.course, gender='f', date=cls.date, count=45)
G(cls.model, course=cls.course, gender='f', date=cls.date - datetime.timedelta(days=2), count=45)
expected = {
self.ce1.gender: self.ce1.count,
self.ce2.gender: self.ce2.count,
}
actual = response.data['genders']
self.assertEquals(actual, expected)
def get_expected_response(self, *args):
return [{'course_id': ce.course.course_id, 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT),
'gender': ce.gender} for ce in args]
# pylint: disable=no-member,no-value-for-parameter
......@@ -203,20 +237,20 @@ class AnswerDistributionTests(TestCaseWithAuthentication):
self.assertEquals(response.status_code, 404)
class CourseEnrollmentLatestViewTests(TestCaseWithAuthentication, CourseEnrollmentViewTestCase):
class CourseEnrollmentViewTests(TestCaseWithAuthentication, CourseEnrollmentViewTestCase):
model = CourseEnrollmentDaily
path = '/enrollment'
@classmethod
def setUpClass(cls):
cls.course = G(Course)
cls.ce = G(CourseEnrollmentDaily, course=cls.course, date=datetime.date(2014, 1, 1), count=203)
cls.date = datetime.date(2014, 1, 1)
G(cls.model, course=cls.course, date=cls.date, count=203)
G(cls.model, course=cls.course, date=cls.date - datetime.timedelta(days=5), count=203)
def test_get(self):
response = self.authenticated_get('/api/v0/courses/%s%s' % (self.course.course_id, self.path,))
self.assertEquals(response.status_code, 200)
expected = {'course_id': self.ce.course.course_id, 'count': self.ce.count, 'date': self.ce.date}
self.assertDictEqual(response.data, expected)
def get_expected_response(self, *args):
return [{'course_id': ce.course.course_id, 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT)}
for ce in args]
class CourseEnrollmentByLocationViewTests(TestCaseWithAuthentication, CourseEnrollmentViewTestCase):
......@@ -227,43 +261,10 @@ class CourseEnrollmentByLocationViewTests(TestCaseWithAuthentication, CourseEnro
return [{'course_id': ce.course.course_id, 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT),
'country': {'code': ce.country.code, 'name': ce.country.name}} for ce in args]
def test_get(self):
course = G(Course)
date1 = datetime.date(2014, 1, 1)
date2 = datetime.date(2013, 1, 1)
ce1 = G(CourseEnrollmentByCountry, course=course, country=G(Country), count=455, date=date1)
ce2 = G(CourseEnrollmentByCountry, course=course, country=G(Country), count=356, date=date1)
# This should not be returned as the view should return only the latest data when no interval is supplied.
G(CourseEnrollmentByCountry, course=course, country=G(Country), count=12, date=date2)
response = self.authenticated_get('/api/v0/courses/%s%s' % (course.course_id, self.path,))
self.assertEquals(response.status_code, 200)
expected = self.get_expected_response(ce1, ce2)
self.assertListEqual(response.data, expected)
def test_get_with_intervals(self):
course = G(Course)
country1 = G(Country)
country2 = G(Country)
date = datetime.date(2014, 1, 1)
ce1 = G(CourseEnrollmentByCountry, course=course, country=country1, date=date)
ce2 = G(CourseEnrollmentByCountry, course=course, country=country2, date=date)
# If start date is after date of existing data, no data should be returned
response = self.authenticated_get('/api/v0/courses/%s%s?start_date=2014-02-01' % (course.course_id, self.path,))
self.assertEquals(response.status_code, 200)
self.assertListEqual([], response.data)
# If end date is before date of existing data, no data should be returned
response = self.authenticated_get('/api/v0/courses/%s%s?end_date=2013-02-01' % (course.course_id, self.path,))
self.assertEquals(response.status_code, 200)
self.assertListEqual([], response.data)
# If data falls in date range, data should be returned
response = self.authenticated_get(
'/api/v0/courses/%s%s?start_date=2013-02-01&end_date=2014-02-01' % (course.course_id, self.path,))
self.assertEquals(response.status_code, 200)
expected = self.get_expected_response(ce1, ce2)
self.assertListEqual(response.data, expected)
@classmethod
def setUpClass(cls):
cls.course = G(Course)
cls.date = datetime.date(2014, 1, 1)
G(cls.model, course=cls.course, country=G(Country), count=455, date=cls.date)
G(cls.model, course=cls.course, country=G(Country), count=356, date=cls.date)
G(cls.model, course=cls.course, country=G(Country), count=12, date=cls.date - datetime.timedelta(days=29))
......@@ -7,7 +7,7 @@ from analytics_data_api.v0.views import courses as views
COURSE_URLS = [
('recent_activity', views.CourseActivityMostRecentWeekView, 'recent_activity'),
('enrollment', views.CourseEnrollmentLatestView, 'enrollment_latest'),
('enrollment', views.CourseEnrollmentView, 'enrollment_latest'),
('enrollment/birth_year', views.CourseEnrollmentByBirthYearView, 'enrollment_by_birth_year'),
('enrollment/education', views.CourseEnrollmentByEducationView, 'enrollment_by_education'),
('enrollment/gender', views.CourseEnrollmentByGenderView, 'enrollment_by_gender'),
......
import datetime
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Max
from django.http import Http404
from rest_framework import generics
from rest_framework.generics import RetrieveAPIView, get_object_or_404
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.generics import get_object_or_404
from analytics_data_api.v0.models import CourseActivityByWeek, CourseEnrollmentByBirthYear, \
CourseEnrollmentByEducation, CourseEnrollmentByGender, CourseEnrollmentByCountry, CourseEnrollmentDaily, Course
from analytics_data_api.v0.serializers import CourseActivityByWeekSerializer, CourseEnrollmentByCountrySerializer, \
CourseEnrollmentDailySerializer
from analytics_data_api.v0 import models, serializers
class CourseActivityMostRecentWeekView(generics.RetrieveAPIView):
......@@ -42,7 +38,7 @@ class CourseActivityMostRecentWeekView(generics.RetrieveAPIView):
"""
serializer_class = CourseActivityByWeekSerializer
serializer_class = serializers.CourseActivityByWeekSerializer
def get_object(self, queryset=None):
"""Select the activity report for the given course and activity type."""
......@@ -51,63 +47,78 @@ class CourseActivityMostRecentWeekView(generics.RetrieveAPIView):
activity_type = activity_type.lower()
try:
return CourseActivityByWeek.get_most_recent(course_id, activity_type)
return models.CourseActivityByWeek.get_most_recent(course_id, activity_type)
except ObjectDoesNotExist:
raise Http404
class AbstractCourseEnrollmentView(APIView):
model = None
def render_data(self, data):
"""
Render view data
"""
raise NotImplementedError('Subclasses must define a render_data method!')
def get(self, request, *args, **kwargs): # pylint: disable=unused-argument
if not self.model:
raise NotImplementedError('Subclasses must specify a model!')
class BaseCourseEnrollmentView(generics.ListAPIView):
def get_course_or_404(self):
return get_object_or_404(models.Course, course_id=self.kwargs.get('course_id'))
course_id = self.kwargs['course_id']
data = self.model.objects.filter(course__course_id=course_id)
def apply_date_filtering(self, queryset):
if 'start_date' in self.request.QUERY_PARAMS or 'end_date' in self.request.QUERY_PARAMS:
# Filter by start/end date
start_date = self.request.QUERY_PARAMS.get('start_date')
if start_date:
start_date = datetime.datetime.strptime(start_date, settings.DATE_FORMAT)
queryset = queryset.filter(date__gte=start_date)
if not data:
raise Http404
end_date = self.request.QUERY_PARAMS.get('end_date')
if end_date:
end_date = datetime.datetime.strptime(end_date, settings.DATE_FORMAT)
queryset = queryset.filter(date__lt=end_date)
else:
# No date filter supplied, so only return data for the latest date
latest_date = queryset.aggregate(Max('date'))
if latest_date:
latest_date = latest_date['date__max']
queryset = queryset.filter(date=latest_date)
return queryset
return Response(self.render_data(data))
def get_queryset(self):
course = self.get_course_or_404()
queryset = self.model.objects.filter(course=course)
queryset = self.apply_date_filtering(queryset)
return queryset
class CourseEnrollmentByBirthYearView(AbstractCourseEnrollmentView):
class CourseEnrollmentByBirthYearView(BaseCourseEnrollmentView):
"""
Course enrollment broken down by user birth year
Returns the enrollment of a course with users binned by their birth years.
"""
model = CourseEnrollmentByBirthYear
If no start or end dates are passed, the data for the latest date is returned. All dates should are in the UTC zone.
def render_data(self, data):
return {
'birth_years': dict(data.values_list('birth_year', 'count'))
}
Date format: YYYY-mm-dd (e.g. 2014-01-31)
start_date -- Date after which all data should be returned (inclusive)
end_date -- Date before which all data should be returned (exclusive)
"""
serializer_class = serializers.CourseEnrollmentByBirthYearSerializer
model = models.CourseEnrollmentByBirthYear
class CourseEnrollmentByEducationView(AbstractCourseEnrollmentView):
class CourseEnrollmentByEducationView(BaseCourseEnrollmentView):
"""
Course enrollment broken down by user level of education
Returns the enrollment of a course with users binned by their education levels.
"""
model = CourseEnrollmentByEducation
def render_data(self, data):
return {
'education_levels': dict(data.values_list('education_level__short_name', 'count'))
}
If no start or end dates are passed, the data for the latest date is returned. All dates should are in the UTC zone.
Date format: YYYY-mm-dd (e.g. 2014-01-31)
class CourseEnrollmentByGenderView(AbstractCourseEnrollmentView):
start_date -- Date after which all data should be returned (inclusive)
end_date -- Date before which all data should be returned (exclusive)
"""
serializer_class = serializers.CourseEnrollmentByEducationSerializer
model = models.CourseEnrollmentByEducation
class CourseEnrollmentByGenderView(BaseCourseEnrollmentView):
"""
Course enrollment broken down by user gender
......@@ -117,66 +128,56 @@ class CourseEnrollmentByGenderView(AbstractCourseEnrollmentView):
m - male
f - female
o - other
If no start or end dates are passed, the data for the latest date is returned. All dates should are in the UTC zone.
Date format: YYYY-mm-dd (e.g. 2014-01-31)
start_date -- Date after which all data should be returned (inclusive)
end_date -- Date before which all data should be returned (exclusive)
"""
model = CourseEnrollmentByGender
serializer_class = serializers.CourseEnrollmentByGenderSerializer
model = models.CourseEnrollmentByGender
def render_data(self, data):
return {
'genders': dict(data.values_list('gender', 'count'))
}
class CourseEnrollmentView(BaseCourseEnrollmentView):
"""
Returns the enrollment count for the specified course.
class CourseEnrollmentLatestView(RetrieveAPIView):
""" Returns the latest enrollment count for the specified course. """
model = CourseEnrollmentDaily
serializer_class = CourseEnrollmentDailySerializer
If no start or end dates are passed, the data for the latest date is returned. All dates should are in the UTC zone.
def get_object(self, queryset=None):
try:
course_id = self.kwargs['course_id']
return CourseEnrollmentDaily.objects.filter(course__course_id=course_id).order_by('-date')[0]
except IndexError:
raise Http404
Date format: YYYY-mm-dd (e.g. 2014-01-31)
start_date -- Date after which all data should be returned (inclusive)
end_date -- Date before which all data should be returned (exclusive)
"""
serializer_class = serializers.CourseEnrollmentDailySerializer
model = models.CourseEnrollmentDaily
# pylint: disable=line-too-long
class CourseEnrollmentByLocationView(generics.ListAPIView):
class CourseEnrollmentByLocationView(BaseCourseEnrollmentView):
"""
Course enrollment broken down by user location
Returns the enrollment of a course with users binned by their location. Location is calculated based on the user's
IP address. If no start or end dates are passed, the data for the latest date is returned.
IP address.
Countries are denoted by their <a href="http://www.iso.org/iso/country_codes/country_codes" target="_blank">ISO 3166 country code</a>.
If no start or end dates are passed, the data for the latest date is returned. All dates should are in the UTC zone.
Date format: YYYY-mm-dd (e.g. 2014-01-31)
start_date -- Date after which all data should be returned (inclusive)
end_date -- Date before which all data should be returned (exclusive)
"""
serializer_class = CourseEnrollmentByCountrySerializer
serializer_class = serializers.CourseEnrollmentByCountrySerializer
def get_queryset(self):
course = get_object_or_404(Course, course_id=self.kwargs.get('course_id'))
queryset = CourseEnrollmentByCountry.objects.filter(course=course)
if 'start_date' in self.request.QUERY_PARAMS or 'end_date' in self.request.QUERY_PARAMS:
# Filter by start/end date
start_date = self.request.QUERY_PARAMS.get('start_date')
if start_date:
start_date = datetime.datetime.strptime(start_date, settings.DATE_FORMAT)
queryset = queryset.filter(date__gte=start_date)
end_date = self.request.QUERY_PARAMS.get('end_date')
if end_date:
end_date = datetime.datetime.strptime(end_date, settings.DATE_FORMAT)
queryset = queryset.filter(date__lt=end_date)
else:
# No date filter supplied, so only return data for the latest date
latest_date = queryset.aggregate(Max('date'))
if latest_date:
latest_date = latest_date['date__max']
queryset = queryset.filter(date=latest_date)
course = self.get_course_or_404()
queryset = models.CourseEnrollmentByCountry.objects.filter(course=course)
queryset = self.apply_date_filtering(queryset)
return queryset
......@@ -83,3 +83,7 @@ ENABLE_ADMIN_SITE = True
########## END ANALYTICS DATA API CONFIGURATION
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
SWAGGER_SETTINGS = {
'api_key': 'analytics'
}
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