Commit be9144d7 by Clinton Blackburn

Added resource for latest enrollment count

Renamed num_enrolled_students field to count

Change-Id: I1aa8108be02f5947ce61afb36c2a94db479d25dc
parent 5399bf96
......@@ -35,7 +35,7 @@ Loading Data
The fixtures directory contains demo data. This data can be loaded with the following commands:
$ ./manage.py syncdb --migrate --noinput --database=analytics
$ ./manage.py loaddata courses education_levels single_course_activity course_enrollment_birth_year course_enrollment_education course_enrollment_gender problem_response_answer_distribution --database=analytics
$ ./manage.py loaddata courses education_levels single_course_activity course_enrollment_birth_year course_enrollment_education course_enrollment_gender course_enrollment_daily problem_response_answer_distribution --database=analytics
Running Tests
-------------
......
[
{
"model": "v0.CourseEnrollmentDaily",
"pk": 1,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"date": "2014-06-01",
"count": 100
}
},
{
"model": "v0.CourseEnrollmentDaily",
"pk": 2,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"date": "2014-06-02",
"count": 150
}
},
{
"model": "v0.CourseEnrollmentDaily",
"pk": 3,
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"date": "2014-06-03",
"count": 300
}
}
]
......@@ -5,9 +5,8 @@
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 6,
"num_enrolled_students": 12255,
"interval_start": "2014-07-01T00:00:00Z",
"interval_end": "2014-07-02T23:59:59Z"
"count": 12255,
"date": "2014-07-01"
}
},
{
......@@ -16,9 +15,8 @@
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 7,
"num_enrolled_students": 70885,
"interval_start": "2014-07-01T00:00:00Z",
"interval_end": "2014-07-02T23:59:59Z"
"count": 70885,
"date": "2014-07-01"
}
},
{
......@@ -27,9 +25,8 @@
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 3,
"num_enrolled_students": 981,
"interval_start": "2014-07-01T00:00:00Z",
"interval_end": "2014-07-02T23:59:59Z"
"count": 981,
"date": "2014-07-01"
}
},
{
......@@ -38,9 +35,8 @@
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 5,
"num_enrolled_students": 51591,
"interval_start": "2014-07-01T00:00:00Z",
"interval_end": "2014-07-02T23:59:59Z"
"count": 51591,
"date": "2014-07-01"
}
},
{
......@@ -49,9 +45,8 @@
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 4,
"num_enrolled_students": 6051,
"interval_start": "2014-07-01T00:00:00Z",
"interval_end": "2014-07-02T23:59:59Z"
"count": 6051,
"date": "2014-07-01"
}
},
{
......@@ -60,9 +55,8 @@
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 8,
"num_enrolled_students": 53216,
"interval_start": "2014-07-01T00:00:00Z",
"interval_end": "2014-07-02T23:59:59Z"
"count": 53216,
"date": "2014-07-01"
}
},
{
......@@ -71,9 +65,8 @@
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 1,
"num_enrolled_students": 667,
"interval_start": "2014-07-01T00:00:00Z",
"interval_end": "2014-07-02T23:59:59Z"
"count": 667,
"date": "2014-07-01"
}
},
{
......@@ -82,9 +75,8 @@
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 2,
"num_enrolled_students": 5722,
"interval_start": "2014-07-01T00:00:00Z",
"interval_end": "2014-07-02T23:59:59Z"
"count": 5722,
"date": "2014-07-01"
}
},
{
......@@ -93,9 +85,8 @@
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"education_level": 9,
"num_enrolled_students": 9940,
"interval_start": "2014-07-01T00:00:00Z",
"interval_end": "2014-07-02T23:59:59Z"
"count": 9940,
"date": "2014-07-01"
}
}
]
......@@ -5,9 +5,8 @@
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"gender": "m",
"num_enrolled_students": 133240,
"interval_start": "2014-07-01T00:00:00Z",
"interval_end": "2014-07-02T23:59:59Z"
"count": 133240,
"date": "2014-07-01"
}
},
{
......@@ -16,9 +15,8 @@
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"gender": "f",
"num_enrolled_students": 77495,
"interval_start": "2014-07-01T00:00:00Z",
"interval_end": "2014-07-02T23:59:59Z"
"count": 77495,
"date": "2014-07-01"
}
},
{
......@@ -27,9 +25,8 @@
"fields": {
"course": ["edX/DemoX/Demo_Course"],
"gender": "o",
"num_enrolled_students": 423,
"interval_start": "2014-07-01T00:00:00Z",
"interval_end": "2014-07-02T23:59:59Z"
"count": 423,
"date": "2014-07-01"
}
}
]
......@@ -5,7 +5,7 @@ from analytics_data_api.v0.managers import CourseManager
class Course(models.Model):
course_id = models.CharField(unique=True, max_length=255)
objects = CourseManager() # pylint: disable=no-value-for-parameter
objects = CourseManager() # pylint: disable=no-value-for-parameter
class Meta(object):
db_table = 'courses'
......@@ -31,12 +31,20 @@ class CourseActivityByWeek(models.Model):
class BaseCourseEnrollment(models.Model):
course = models.ForeignKey(Course, null=False)
interval_start = models.DateTimeField(null=False)
interval_end = models.DateTimeField(null=False)
num_enrolled_students = models.IntegerField(null=False)
date = models.DateField(null=False)
count = models.IntegerField(null=False)
class Meta(object):
abstract = True
unique_together = [('course', 'date',)]
class CourseEnrollmentDaily(BaseCourseEnrollment):
class Meta(object):
db_table = 'course_enrollment_daily'
ordering = ('course', '-date')
unique_together = [('course', 'date',)]
get_latest_by = 'date'
class CourseEnrollmentByBirthYear(BaseCourseEnrollment):
......@@ -45,6 +53,7 @@ class CourseEnrollmentByBirthYear(BaseCourseEnrollment):
class Meta(object):
db_table = 'course_enrollment_birth_year'
ordering = ('course', 'birth_year')
unique_together = [('course', 'date', 'birth_year')]
class EducationLevel(models.Model):
......@@ -61,6 +70,7 @@ class CourseEnrollmentByEducation(BaseCourseEnrollment):
class Meta(object):
db_table = 'course_enrollment_education_level'
ordering = ('course', 'education_level')
unique_together = [('course', 'date', 'education_level')]
class CourseEnrollmentByGender(BaseCourseEnrollment):
......@@ -69,10 +79,10 @@ class CourseEnrollmentByGender(BaseCourseEnrollment):
class Meta(object):
db_table = 'course_enrollment_gender'
ordering = ('course', 'gender')
unique_together = [('course', 'date', 'gender')]
class ProblemResponseAnswerDistribution(models.Model):
""" Each row stores the count of a particular answer to a response in a problem in a course (usage). """
class Meta(object):
......
from rest_framework import serializers
from analytics_data_api.v0.models import CourseActivityByWeek, ProblemResponseAnswerDistribution
from analytics_data_api.v0.models import CourseActivityByWeek, ProblemResponseAnswerDistribution, CourseEnrollmentDaily
class CourseActivityByWeekSerializer(serializers.ModelSerializer):
class CourseIdMixin(object):
def get_course_id(self, obj):
return obj.course.course_id
class CourseActivityByWeekSerializer(serializers.ModelSerializer, CourseIdMixin):
"""
Representation of CourseActivityByWeek that excludes the id field.
......@@ -11,9 +16,6 @@ class CourseActivityByWeekSerializer(serializers.ModelSerializer):
"""
course_id = serializers.SerializerMethodField('get_course_id')
def get_course_id(self, obj):
return obj.course.course_id
class Meta(object):
model = CourseActivityByWeek
fields = ('interval_start', 'interval_end', 'activity_type', 'count', 'course_id')
......@@ -41,3 +43,15 @@ class ProblemResponseAnswerDistributionSerializer(serializers.ModelSerializer):
'variant',
'created'
)
class CourseEnrollmentDailySerializer(serializers.ModelSerializer, CourseIdMixin):
"""
Representation of course enrollment for a single day and course.
"""
course_id = serializers.SerializerMethodField('get_course_id')
class Meta(object):
model = CourseEnrollmentDaily
fields = ('course_id', 'date', 'count')
......@@ -2,7 +2,7 @@
# change for versions greater than 1.0.0. Tests target a specific version of the API, additional tests should be added
# for subsequent versions if there are breaking changes introduced in those versions.
from datetime import datetime
from datetime import datetime, date
import random
from django.core.exceptions import ObjectDoesNotExist
......@@ -11,7 +11,7 @@ from django_dynamic_fixture import G
import pytz
from analytics_data_api.v0.models import CourseEnrollmentByBirthYear, CourseEnrollmentByEducation, EducationLevel, \
CourseEnrollmentByGender, CourseActivityByWeek, Course, ProblemResponseAnswerDistribution
CourseEnrollmentByGender, CourseActivityByWeek, Course, ProblemResponseAnswerDistribution, CourseEnrollmentDaily
from analytics_data_api.v0.serializers import ProblemResponseAnswerDistributionSerializer
from analyticsdataserver.tests import TestCaseWithAuthentication
......@@ -93,9 +93,7 @@ class CourseEnrollmentViewTestCase(object):
return self._get_non_existent_course_id()
def test_get_not_found(self):
'''
Requests made against non-existent courses should return a 404
'''
""" 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
......@@ -120,8 +118,8 @@ class CourseEnrollmentByBirthYearViewTests(TestCaseWithAuthentication, CourseEnr
self.assertEquals(response.status_code, 200)
expected = {
self.ce1.birth_year: self.ce1.num_enrolled_students,
self.ce2.birth_year: self.ce2.num_enrolled_students,
self.ce1.birth_year: self.ce1.count,
self.ce2.birth_year: self.ce2.count,
}
actual = response.data['birth_years']
self.assertEquals(actual, expected)
......@@ -144,8 +142,8 @@ class CourseEnrollmentByEducationViewTests(TestCaseWithAuthentication, CourseEnr
self.assertEquals(response.status_code, 200)
expected = {
self.ce1.education_level.short_name: self.ce1.num_enrolled_students,
self.ce2.education_level.short_name: self.ce2.num_enrolled_students,
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)
......@@ -166,8 +164,8 @@ class CourseEnrollmentByGenderViewTests(TestCaseWithAuthentication, CourseEnroll
self.assertEquals(response.status_code, 200)
expected = {
self.ce1.gender: self.ce1.num_enrolled_students,
self.ce2.gender: self.ce2.num_enrolled_students,
self.ce1.gender: self.ce1.count,
self.ce2.gender: self.ce2.count,
}
actual = response.data['genders']
self.assertEquals(actual, expected)
......@@ -211,3 +209,19 @@ class AnswerDistributionTests(TestCaseWithAuthentication):
def test_get_404(self):
response = self.authenticated_get('/api/v0/problems/%s%s' % ("DOES-NOT-EXIST", self.path))
self.assertEquals(response.status_code, 404)
class CourseEnrollmentLatestViewTests(TestCaseWithAuthentication, CourseEnrollmentViewTestCase):
model = CourseEnrollmentDaily
path = '/enrollment'
@classmethod
def setUpClass(cls):
cls.course = G(Course)
cls.ce = G(CourseEnrollmentDaily, course=cls.course, date=date(2014, 1, 1), 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)
......@@ -3,11 +3,12 @@ import re
from django.conf.urls import patterns, url
from analytics_data_api.v0.views.courses import CourseActivityMostRecentWeekView, CourseEnrollmentByEducationView, \
CourseEnrollmentByBirthYearView, CourseEnrollmentByGenderView
CourseEnrollmentByBirthYearView, CourseEnrollmentByGenderView, CourseEnrollmentLatestView
COURSE_URLS = [
('recent_activity', CourseActivityMostRecentWeekView, 'recent_activity'),
('enrollment', CourseEnrollmentLatestView, 'enrollment_latest'),
('enrollment/birth_year', CourseEnrollmentByBirthYearView, 'enrollment_by_birth_year'),
('enrollment/education', CourseEnrollmentByEducationView, 'enrollment_by_education'),
('enrollment/gender', CourseEnrollmentByGenderView, 'enrollment_by_gender'),
......
from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404
from rest_framework import generics
from rest_framework.generics import RetrieveAPIView
from rest_framework.response import Response
from rest_framework.views import APIView
from analytics_data_api.v0.models import CourseActivityByWeek, CourseEnrollmentByBirthYear, \
CourseEnrollmentByEducation, CourseEnrollmentByGender
from analytics_data_api.v0.serializers import CourseActivityByWeekSerializer
CourseEnrollmentByEducation, CourseEnrollmentByGender, CourseEnrollmentDaily
from analytics_data_api.v0.serializers import CourseActivityByWeekSerializer, CourseEnrollmentDailySerializer
class CourseActivityMostRecentWeekView(generics.RetrieveAPIView):
......@@ -46,7 +47,6 @@ class CourseActivityMostRecentWeekView(generics.RetrieveAPIView):
activity_type = activity_type.lower()
try:
print CourseActivityByWeek.objects.all()
return CourseActivityByWeek.get_most_recent(course_id, activity_type)
except ObjectDoesNotExist:
raise Http404
......@@ -85,7 +85,7 @@ class CourseEnrollmentByBirthYearView(AbstractCourseEnrollmentView):
def render_data(self, data):
return {
'birth_years': dict(data.values_list('birth_year', 'num_enrolled_students'))
'birth_years': dict(data.values_list('birth_year', 'count'))
}
......@@ -99,7 +99,7 @@ class CourseEnrollmentByEducationView(AbstractCourseEnrollmentView):
def render_data(self, data):
return {
'education_levels': dict(data.values_list('education_level__short_name', 'num_enrolled_students'))
'education_levels': dict(data.values_list('education_level__short_name', 'count'))
}
......@@ -118,5 +118,18 @@ class CourseEnrollmentByGenderView(AbstractCourseEnrollmentView):
def render_data(self, data):
return {
'genders': dict(data.values_list('gender', 'num_enrolled_students'))
'genders': dict(data.values_list('gender', 'count'))
}
class CourseEnrollmentLatestView(RetrieveAPIView):
""" Returns the latest enrollment count for the specified course. """
model = CourseEnrollmentDaily
serializer_class = CourseEnrollmentDailySerializer
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
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