Commit 8ac02398 by Clinton Blackburn

Added Enrollment Mode

parent 5001a93a
...@@ -2,8 +2,3 @@ from iso3166 import Country ...@@ -2,8 +2,3 @@ from iso3166 import Country
UNKNOWN_COUNTRY_CODE = u'UNKNOWN' UNKNOWN_COUNTRY_CODE = u'UNKNOWN'
UNKNOWN_COUNTRY = Country(UNKNOWN_COUNTRY_CODE, None, None, None) UNKNOWN_COUNTRY = Country(UNKNOWN_COUNTRY_CODE, None, None, None)
FEMALE_GENDER = u'female'
MALE_GENDER = u'male'
OTHER_GENDER = u'other'
UNKNOWN_GENDER = u'unknown'
AUDIT = u'audit'
HONOR = u'honor'
PROFESSIONAL = u'professional'
VERIFIED = u'verified'
ALL = [AUDIT, HONOR, PROFESSIONAL, VERIFIED]
FEMALE = u'female'
MALE = u'male'
OTHER = u'other'
UNKNOWN = u'unknown'
...@@ -31,7 +31,8 @@ def get_count(start): ...@@ -31,7 +31,8 @@ def get_count(start):
class Command(BaseCommand): class Command(BaseCommand):
help = 'Generate fake data' help = 'Generate fake data'
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('-n', '--num-weeks', action='store', type="int", dest='num_weeks', help='"Number of weeks worth of data to generate.'), make_option('-n', '--num-weeks', action='store', type="int", dest='num_weeks',
help='"Number of weeks worth of data to generate.'),
) )
def generate_daily_data(self, course_id, start_date, end_date): def generate_daily_data(self, course_id, start_date, end_date):
...@@ -63,6 +64,13 @@ class Command(BaseCommand): ...@@ -63,6 +64,13 @@ class Command(BaseCommand):
'UNKNOWN': 0.01 'UNKNOWN': 0.01
} }
enrollment_mode_ratios = {
'audit': 0.15,
'honor': 0.35,
'professional': 0.10,
'verified': 0.40
}
# Generate birth year ratios # Generate birth year ratios
birth_years = range(1960, 2005) birth_years = range(1960, 2005)
ratios = [n / 1000.0 for n in constrained_sum_sample_pos(len(birth_years), 1000)] ratios = [n / 1000.0 for n in constrained_sum_sample_pos(len(birth_years), 1000)]
...@@ -87,6 +95,11 @@ class Command(BaseCommand): ...@@ -87,6 +95,11 @@ class Command(BaseCommand):
daily_total = get_count(daily_total) daily_total = get_count(daily_total)
models.CourseEnrollmentDaily.objects.create(course_id=course_id, date=date, count=daily_total) models.CourseEnrollmentDaily.objects.create(course_id=course_id, date=date, count=daily_total)
for mode, ratio in enrollment_mode_ratios.iteritems():
count = int(ratio * daily_total)
models.CourseEnrollmentModeDaily.objects.create(course_id=course_id, date=date, count=count,
mode=mode)
for gender, ratio in gender_ratios.iteritems(): for gender, ratio in gender_ratios.iteritems():
count = int(ratio * daily_total) count = int(ratio * daily_total)
models.CourseEnrollmentByGender.objects.create(course_id=course_id, date=date, count=count, models.CourseEnrollmentByGender.objects.create(course_id=course_id, date=date, count=count,
......
from django.db import models from django.db import models
from iso3166 import countries from iso3166 import countries
from analytics_data_api.v0.constants import UNKNOWN_COUNTRY, FEMALE_GENDER, MALE_GENDER, OTHER_GENDER, UNKNOWN_GENDER from analytics_data_api.constants import UNKNOWN_COUNTRY, genders
class CourseActivityWeekly(models.Model): class CourseActivityWeekly(models.Model):
...@@ -45,6 +45,15 @@ class CourseEnrollmentDaily(BaseCourseEnrollment): ...@@ -45,6 +45,15 @@ class CourseEnrollmentDaily(BaseCourseEnrollment):
unique_together = [('course_id', 'date',)] unique_together = [('course_id', 'date',)]
class CourseEnrollmentModeDaily(BaseCourseEnrollment):
mode = models.CharField(max_length=255)
class Meta(BaseCourseEnrollment.Meta):
db_table = 'course_enrollment_mode_daily'
ordering = ('date', 'course_id', 'mode')
unique_together = [('course_id', 'date', 'mode')]
class CourseEnrollmentByBirthYear(BaseCourseEnrollment): class CourseEnrollmentByBirthYear(BaseCourseEnrollment):
birth_year = models.IntegerField(null=False) birth_year = models.IntegerField(null=False)
...@@ -76,9 +85,9 @@ class CourseEnrollmentByEducation(BaseCourseEnrollment): ...@@ -76,9 +85,9 @@ class CourseEnrollmentByEducation(BaseCourseEnrollment):
class CourseEnrollmentByGender(BaseCourseEnrollment): class CourseEnrollmentByGender(BaseCourseEnrollment):
CLEANED_GENDERS = { CLEANED_GENDERS = {
'f': FEMALE_GENDER, u'f': genders.FEMALE,
'm': MALE_GENDER, u'm': genders.MALE,
'o': OTHER_GENDER u'o': genders.OTHER
} }
gender = models.CharField(max_length=255, null=True, db_column='gender') gender = models.CharField(max_length=255, null=True, db_column='gender')
...@@ -88,7 +97,7 @@ class CourseEnrollmentByGender(BaseCourseEnrollment): ...@@ -88,7 +97,7 @@ class CourseEnrollmentByGender(BaseCourseEnrollment):
""" """
Returns the gender with full names and 'unknown' replacing null/None. Returns the gender with full names and 'unknown' replacing null/None.
""" """
return self.CLEANED_GENDERS.get(self.gender, UNKNOWN_GENDER) return self.CLEANED_GENDERS.get(self.gender, genders.UNKNOWN)
class Meta(BaseCourseEnrollment.Meta): class Meta(BaseCourseEnrollment.Meta):
db_table = 'course_enrollment_gender_daily' db_table = 'course_enrollment_gender_daily'
......
from django.conf import settings from django.conf import settings
from rest_framework import serializers from rest_framework import serializers
from analytics_data_api.constants import enrollment_modes
from analytics_data_api.v0 import models from analytics_data_api.v0 import models
...@@ -98,6 +99,26 @@ class CourseEnrollmentDailySerializer(BaseCourseEnrollmentModelSerializer): ...@@ -98,6 +99,26 @@ class CourseEnrollmentDailySerializer(BaseCourseEnrollmentModelSerializer):
fields = ('course_id', 'date', 'count', 'created') fields = ('course_id', 'date', 'count', 'created')
class CourseEnrollmentModeDailySerializer(BaseCourseEnrollmentModelSerializer):
""" Representation of course enrollment, broken down by mode, for a single day and course. """
def get_default_fields(self):
# pylint: disable=super-on-old-class
fields = super(CourseEnrollmentModeDailySerializer, self).get_default_fields()
# Create a field for each enrollment mode
for mode in enrollment_modes.ALL:
fields[mode] = serializers.IntegerField(required=True)
return fields
class Meta(object):
model = models.CourseEnrollmentDaily
# Declare the dynamically-created fields here as well so that they will be picked up by Swagger.
fields = ['course_id', 'date', 'count', 'created'] + enrollment_modes.ALL
class CountrySerializer(serializers.Serializer): class CountrySerializer(serializers.Serializer):
""" """
Serialize country to an object with fields for the complete country name Serialize country to an object with fields for the complete country name
......
...@@ -3,7 +3,7 @@ from django_dynamic_fixture import G ...@@ -3,7 +3,7 @@ from django_dynamic_fixture import G
from iso3166 import countries from iso3166 import countries
from analytics_data_api.v0 import models from analytics_data_api.v0 import models
from analytics_data_api.v0.constants import UNKNOWN_COUNTRY from analytics_data_api.constants import UNKNOWN_COUNTRY
class EducationLevelTests(TestCase): class EducationLevelTests(TestCase):
......
...@@ -15,7 +15,7 @@ import pytz ...@@ -15,7 +15,7 @@ import pytz
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from analytics_data_api.v0 import models from analytics_data_api.v0 import models
from analytics_data_api.v0.constants import UNKNOWN_COUNTRY, UNKNOWN_COUNTRY_CODE from analytics_data_api.constants import UNKNOWN_COUNTRY, UNKNOWN_COUNTRY_CODE, enrollment_modes
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 GradeDistributionSerializer
...@@ -404,6 +404,39 @@ class CourseEnrollmentViewTests(CourseEnrollmentViewTestCaseMixin, TestCaseWithA ...@@ -404,6 +404,39 @@ class CourseEnrollmentViewTests(CourseEnrollmentViewTestCaseMixin, TestCaseWithA
for ce in args] for ce in args]
class CourseEnrollmentModeViewTests(CourseEnrollmentViewTestCaseMixin, TestCaseWithAuthentication):
model = models.CourseEnrollmentModeDaily
path = '/enrollment/mode'
csv_filename_slug = u'enrollment_mode'
def setUp(self):
super(CourseEnrollmentModeViewTests, self).setUp()
self.generate_data()
def generate_data(self, course_id=None):
course_id = course_id or self.course_id
for mode in enrollment_modes.ALL:
G(self.model, course_id=course_id, date=self.date, mode=mode)
def format_as_response(self, *args):
arg = args[0]
response = {
u'course_id': arg.course_id,
u'date': arg.date.strftime(settings.DATE_FORMAT),
u'created': arg.created.strftime(settings.DATETIME_FORMAT)
}
total = 0
for ce in args:
total += ce.count
response[ce.mode] = ce.count
response[u'count'] = total
return [response]
class CourseEnrollmentByLocationViewTests(CourseEnrollmentViewTestCaseMixin, TestCaseWithAuthentication): class CourseEnrollmentByLocationViewTests(CourseEnrollmentViewTestCaseMixin, TestCaseWithAuthentication):
path = '/enrollment/location/' path = '/enrollment/location/'
model = models.CourseEnrollmentByCountry model = models.CourseEnrollmentByCountry
......
...@@ -7,6 +7,7 @@ COURSE_URLS = [ ...@@ -7,6 +7,7 @@ COURSE_URLS = [
('activity', views.CourseActivityWeeklyView, 'activity'), ('activity', views.CourseActivityWeeklyView, 'activity'),
('recent_activity', views.CourseActivityMostRecentWeekView, 'recent_activity'), ('recent_activity', views.CourseActivityMostRecentWeekView, 'recent_activity'),
('enrollment', views.CourseEnrollmentView, 'enrollment_latest'), ('enrollment', views.CourseEnrollmentView, 'enrollment_latest'),
('enrollment/mode', views.CourseEnrollmentModeView, 'enrollment_by_mode'),
('enrollment/birth_year', views.CourseEnrollmentByBirthYearView, 'enrollment_by_birth_year'), ('enrollment/birth_year', views.CourseEnrollmentByBirthYearView, 'enrollment_by_birth_year'),
('enrollment/education', views.CourseEnrollmentByEducationView, 'enrollment_by_education'), ('enrollment/education', views.CourseEnrollmentByEducationView, 'enrollment_by_education'),
('enrollment/gender', views.CourseEnrollmentByGenderView, 'enrollment_by_gender'), ('enrollment/gender', views.CourseEnrollmentByGenderView, 'enrollment_by_gender'),
......
...@@ -289,12 +289,6 @@ class CourseEnrollmentByGenderView(BaseCourseEnrollmentView): ...@@ -289,12 +289,6 @@ class CourseEnrollmentByGenderView(BaseCourseEnrollmentView):
Returns the enrollment of a course where each row/item contains user genders for the day. Returns the enrollment of a course where each row/item contains user genders for the day.
Genders:
male
female
other
unknown
If no start or end dates are passed, the data for the latest date is returned. All dates are in the UTC zone. If no start or end dates are passed, the data for the latest date is returned. All dates are in the UTC zone.
Data is sorted chronologically (earliest to latest). Data is sorted chronologically (earliest to latest).
...@@ -350,6 +344,50 @@ class CourseEnrollmentView(BaseCourseEnrollmentView): ...@@ -350,6 +344,50 @@ class CourseEnrollmentView(BaseCourseEnrollmentView):
model = models.CourseEnrollmentDaily model = models.CourseEnrollmentDaily
class CourseEnrollmentModeView(BaseCourseEnrollmentView):
"""
Course enrollment broken down by enrollment mode.
If no start or end dates are passed, the data for the latest date is returned. All dates are in the UTC zone.
Data is sorted chronologically (earliest to latest).
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)
"""
slug = u'enrollment_mode'
serializer_class = serializers.CourseEnrollmentModeDailySerializer
model = models.CourseEnrollmentModeDaily
def get_queryset(self):
queryset = super(CourseEnrollmentModeView, self).get_queryset()
formatted_data = []
for key, group in groupby(queryset, lambda x: (x.course_id, x.date)):
item = {
u'course_id': key[0],
u'date': key[1],
u'created': None
}
total = 0
for enrollment in group:
mode = enrollment.mode
item[mode] = enrollment.count
item[u'created'] = max(enrollment.created, item[u'created']) if item[u'created'] else enrollment.created
total += enrollment.count
item[u'count'] = total
formatted_data.append(item)
return formatted_data
# pylint: disable=line-too-long # pylint: disable=line-too-long
class CourseEnrollmentByLocationView(BaseCourseEnrollmentView): class CourseEnrollmentByLocationView(BaseCourseEnrollmentView):
""" """
......
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