Commit 5f21b965 by Jason Bau

Merge pull request #35 from dylanrhodes/dylanrhodes/aggregate_data_apis

Added REST API for Grade Distribution and Sequential Open Distribution
parents 646ad4a1 e533cdf1
...@@ -119,3 +119,29 @@ class CourseEnrollmentByCountry(BaseCourseEnrollment): ...@@ -119,3 +119,29 @@ class CourseEnrollmentByCountry(BaseCourseEnrollment):
db_table = 'course_enrollment_location_current' db_table = 'course_enrollment_location_current'
ordering = ('date', 'course_id', 'country_code') ordering = ('date', 'course_id', 'country_code')
unique_together = [('course_id', 'date', 'country_code')] unique_together = [('course_id', 'date', 'country_code')]
class GradeDistribution(models.Model):
""" Each row stores the count of a particular grade on a module for a given course. """
class Meta(object):
db_table = 'grade_distribution'
module_id = models.CharField(db_index=True, max_length=255)
course_id = models.CharField(db_index=True, max_length=255)
grade = models.IntegerField()
max_grade = models.IntegerField()
count = models.IntegerField()
created = models.DateTimeField(auto_now_add=True)
class SequentialOpenDistribution(models.Model):
""" Each row stores the count of views a particular module has had in a given course. """
class Meta(object):
db_table = 'sequential_open_distribution'
module_id = models.CharField(db_index=True, max_length=255)
course_id = models.CharField(db_index=True, max_length=255)
count = models.IntegerField()
created = models.DateTimeField(auto_now_add=True)
...@@ -54,6 +54,38 @@ class ProblemResponseAnswerDistributionSerializer(serializers.ModelSerializer): ...@@ -54,6 +54,38 @@ class ProblemResponseAnswerDistributionSerializer(serializers.ModelSerializer):
) )
class GradeDistributionSerializer(serializers.ModelSerializer):
"""
Representation of the grade_distribution table without id
"""
class Meta(object):
model = models.GradeDistribution
fields = (
'module_id',
'course_id',
'grade',
'max_grade',
'count',
'created'
)
class SequentialOpenDistributionSerializer(serializers.ModelSerializer):
"""
Representation of the sequential_open_distribution table without id
"""
class Meta(object):
model = models.SequentialOpenDistribution
fields = (
'module_id',
'course_id',
'count',
'created'
)
class BaseCourseEnrollmentModelSerializer(serializers.ModelSerializer): class BaseCourseEnrollmentModelSerializer(serializers.ModelSerializer):
date = serializers.DateField(format=settings.DATE_FORMAT) date = serializers.DateField(format=settings.DATE_FORMAT)
created = serializers.DateTimeField(format=settings.DATETIME_FORMAT) created = serializers.DateTimeField(format=settings.DATETIME_FORMAT)
......
...@@ -18,6 +18,8 @@ from analytics_data_api.v0 import models ...@@ -18,6 +18,8 @@ from analytics_data_api.v0 import models
from analytics_data_api.v0.constants import UNKNOWN_COUNTRY, UNKNOWN_COUNTRY_CODE from analytics_data_api.v0.constants import UNKNOWN_COUNTRY, UNKNOWN_COUNTRY_CODE
from analytics_data_api.v0.models import CourseActivityWeekly from analytics_data_api.v0.models import CourseActivityWeekly
from analytics_data_api.v0.serializers import ProblemResponseAnswerDistributionSerializer from analytics_data_api.v0.serializers import ProblemResponseAnswerDistributionSerializer
from analytics_data_api.v0.serializers import GradeDistributionSerializer
from analytics_data_api.v0.serializers import SequentialOpenDistributionSerializer
from analytics_data_api.v0.tests.utils import flatten from analytics_data_api.v0.tests.utils import flatten
from analyticsdataserver.tests import TestCaseWithAuthentication from analyticsdataserver.tests import TestCaseWithAuthentication
...@@ -498,3 +500,61 @@ class CourseActivityWeeklyViewTests(CourseViewTestCaseMixin, TestCaseWithAuthent ...@@ -498,3 +500,61 @@ class CourseActivityWeeklyViewTests(CourseViewTestCaseMixin, TestCaseWithAuthent
expected = self.format_as_response(*self.model.objects.all()) expected = self.format_as_response(*self.model.objects.all())
self.assertEqual(len(expected), 2) self.assertEqual(len(expected), 2)
self.assertIntervalFilteringWorks(expected, self.interval_start, interval_end + datetime.timedelta(days=1)) self.assertIntervalFilteringWorks(expected, self.interval_start, interval_end + datetime.timedelta(days=1))
# pylint: disable=no-member,no-value-for-parameter
class GradeDistributionTests(TestCaseWithAuthentication):
path = '/grade_distribution'
maxDiff = None
@classmethod
def setUpClass(cls):
cls.course_id = "org/class/test"
cls.module_id = "i4x://org/class/test/problem/RANDOM_NUMBER"
cls.ad1 = G(
models.GradeDistribution,
course_id=cls.course_id,
module_id=cls.module_id,
)
def test_get(self):
response = self.authenticated_get('/api/v0/problems/%s%s' % (self.module_id, self.path))
self.assertEquals(response.status_code, 200)
expected_dict = GradeDistributionSerializer(self.ad1).data
actual_list = response.data
self.assertEquals(len(actual_list), 1)
self.assertDictEqual(actual_list[0], expected_dict)
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)
# pylint: disable=no-member,no-value-for-parameter
class SequentialOpenDistributionTests(TestCaseWithAuthentication):
path = '/sequential_open_distribution'
maxDiff = None
@classmethod
def setUpClass(cls):
cls.course_id = "org/class/test"
cls.module_id = "i4x://org/class/test/problem/RANDOM_NUMBER"
cls.ad1 = G(
models.SequentialOpenDistribution,
course_id=cls.course_id,
module_id=cls.module_id,
)
def test_get(self):
response = self.authenticated_get('/api/v0/problems/%s%s' % (self.module_id, self.path))
self.assertEquals(response.status_code, 200)
expected_dict = SequentialOpenDistributionSerializer(self.ad1).data
actual_list = response.data
self.assertEquals(len(actual_list), 1)
self.assertDictEqual(actual_list[0], expected_dict)
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)
...@@ -3,13 +3,18 @@ import re ...@@ -3,13 +3,18 @@ import re
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
from analytics_data_api.v0.views.problems import ProblemResponseAnswerDistributionView from analytics_data_api.v0.views.problems import ProblemResponseAnswerDistributionView
from analytics_data_api.v0.views.problems import GradeDistributionView
from analytics_data_api.v0.views.problems import SequentialOpenDistributionView
PROBLEM_URLS = [ PROBLEM_URLS = [
('answer_distribution', ProblemResponseAnswerDistributionView, 'answer_distribution'), ('answer_distribution', ProblemResponseAnswerDistributionView, 'answer_distribution'),
('grade_distribution', GradeDistributionView, 'grade_distribution'),
] ]
urlpatterns = patterns( urlpatterns = patterns(
'', '',
url(r'^(?P<module_id>.+)/sequential_open_distribution$',
SequentialOpenDistributionView.as_view(), name='sequential_open_distribution'),
) )
for path, view, name in PROBLEM_URLS: for path, view, name in PROBLEM_URLS:
......
...@@ -3,6 +3,12 @@ from rest_framework import generics ...@@ -3,6 +3,12 @@ from rest_framework import generics
from analytics_data_api.v0.models import ProblemResponseAnswerDistribution from analytics_data_api.v0.models import ProblemResponseAnswerDistribution
from analytics_data_api.v0.serializers import ProblemResponseAnswerDistributionSerializer from analytics_data_api.v0.serializers import ProblemResponseAnswerDistributionSerializer
from analytics_data_api.v0.models import GradeDistribution
from analytics_data_api.v0.serializers import GradeDistributionSerializer
from analytics_data_api.v0.models import SequentialOpenDistribution
from analytics_data_api.v0.serializers import SequentialOpenDistributionSerializer
class ProblemResponseAnswerDistributionView(generics.ListAPIView): class ProblemResponseAnswerDistributionView(generics.ListAPIView):
""" """
...@@ -18,3 +24,31 @@ class ProblemResponseAnswerDistributionView(generics.ListAPIView): ...@@ -18,3 +24,31 @@ class ProblemResponseAnswerDistributionView(generics.ListAPIView):
"""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')
return ProblemResponseAnswerDistribution.objects.filter(module_id=problem_id) return ProblemResponseAnswerDistribution.objects.filter(module_id=problem_id)
class GradeDistributionView(generics.ListAPIView):
"""
Distribution of grades for a particular module in a given course
"""
serializer_class = GradeDistributionSerializer
allow_empty = False
def get_queryset(self):
"""Select all grade distributions for a particular module"""
problem_id = self.kwargs.get('problem_id')
return GradeDistribution.objects.filter(module_id=problem_id)
class SequentialOpenDistributionView(generics.ListAPIView):
"""
Distribution of view counts for a particular module in a given course
"""
serializer_class = SequentialOpenDistributionSerializer
allow_empty = False
def get_queryset(self):
"""Select the view count for a specific module"""
module_id = self.kwargs.get('module_id')
return SequentialOpenDistribution.objects.filter(module_id=module_id)
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