Commit f86ecabb by Clinton Blackburn Committed by Clinton Blackburn

Merge pull request #26 from edx/parts-unknown

Grouping and Returning Unknown Locations
parents a817d2c8 47c74d7f
...@@ -48,12 +48,13 @@ class Command(BaseCommand): ...@@ -48,12 +48,13 @@ class Command(BaseCommand):
'doctorate': 0.0470 'doctorate': 0.0470
} }
country_ratios = { country_ratios = {
'US': 0.34, 'US': 0.33,
'GH': 0.12, 'GH': 0.12,
'IN': 0.10, 'IN': 0.10,
'CA': 0.14, 'CA': 0.14,
'CN': 0.22, 'CN': 0.22,
'DE': 0.08 'DE': 0.08,
'UNKNOWN': 0.01
} }
# Generate birth year ratios # Generate birth year ratios
......
from django.db import models from django.db import models
from iso3166 import countries from iso3166 import countries, Country
class CourseActivityWeekly(models.Model): class CourseActivityWeekly(models.Model):
...@@ -99,6 +99,7 @@ class ProblemResponseAnswerDistribution(models.Model): ...@@ -99,6 +99,7 @@ class ProblemResponseAnswerDistribution(models.Model):
class CourseEnrollmentByCountry(BaseCourseEnrollment): class CourseEnrollmentByCountry(BaseCourseEnrollment):
UNKNOWN_COUNTRY_CODE = 'UNKNOWN'
country_code = models.CharField(max_length=255, null=False, db_column='country_code') country_code = models.CharField(max_length=255, null=False, db_column='country_code')
@property @property
...@@ -107,6 +108,9 @@ class CourseEnrollmentByCountry(BaseCourseEnrollment): ...@@ -107,6 +108,9 @@ class CourseEnrollmentByCountry(BaseCourseEnrollment):
Returns a Country object representing the country in this model's country_code. Returns a Country object representing the country in this model's country_code.
""" """
try: try:
if self.country_code == self.UNKNOWN_COUNTRY_CODE:
return Country(self.UNKNOWN_COUNTRY_CODE, None, None, None)
return countries.get(self.country_code) return countries.get(self.country_code)
except (KeyError, ValueError): except (KeyError, ValueError):
# Country code is not valid ISO-3166 # Country code is not valid ISO-3166
......
...@@ -323,13 +323,27 @@ class CourseEnrollmentByLocationViewTests(CourseEnrollmentViewTestCaseMixin, Tes ...@@ -323,13 +323,27 @@ class CourseEnrollmentByLocationViewTests(CourseEnrollmentViewTestCaseMixin, Tes
model = models.CourseEnrollmentByCountry model = models.CourseEnrollmentByCountry
def format_as_response(self, *args): def format_as_response(self, *args):
unknown = {'course_id': None, 'count': 0, 'date': None,
'country': {'alpha2': None, 'alpha3': None, 'name': u'UNKNOWN'}}
for arg in args:
if not arg.country:
unknown['course_id'] = arg.course_id
unknown['date'] = arg.date.strftime(settings.DATE_FORMAT)
unknown['count'] += arg.count
args = [arg for arg in args if arg.country_code not in ['', 'A1', 'A2', 'AP', 'EU', 'O1', 'UNKNOWN']] args = [arg for arg in args if arg.country_code not in ['', 'A1', 'A2', 'AP', 'EU', 'O1', 'UNKNOWN']]
args = sorted(args, key=lambda item: (item.date, item.course_id, item.country.alpha3)) args = sorted(args, key=lambda item: (item.date, item.course_id, item.country.alpha3))
return [ response = [
{'course_id': str(ce.course_id), 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT), {'course_id': str(ce.course_id), 'count': ce.count, 'date': ce.date.strftime(settings.DATE_FORMAT),
'country': {'alpha2': ce.country.alpha2, 'alpha3': ce.country.alpha3, 'name': ce.country.name}} for ce in 'country': {'alpha2': ce.country.alpha2, 'alpha3': ce.country.alpha3, 'name': ce.country.name}} for ce in
args] args]
# Unknown comes last
response.append(unknown)
return response
def setUp(self): def setUp(self):
super(CourseEnrollmentByLocationViewTests, self).setUp() super(CourseEnrollmentByLocationViewTests, self).setUp()
self.country = countries.get('US') self.country = countries.get('US')
......
...@@ -313,7 +313,8 @@ class CourseEnrollmentByLocationView(BaseCourseEnrollmentView): ...@@ -313,7 +313,8 @@ class CourseEnrollmentByLocationView(BaseCourseEnrollmentView):
Course enrollment broken down by user location 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 Returns the enrollment of a course with users binned by their location. Location is calculated based on the user's
IP address. IP address. Enrollment counts for users whose location cannot be determined will be included in an entry with
country.name set to UNKNOWN.
Countries are denoted by their <a href="http://www.iso.org/iso/country_codes/country_codes" target="_blank">ISO 3166 country code</a>. Countries are denoted by their <a href="http://www.iso.org/iso/country_codes/country_codes" target="_blank">ISO 3166 country code</a>.
...@@ -333,10 +334,36 @@ class CourseEnrollmentByLocationView(BaseCourseEnrollmentView): ...@@ -333,10 +334,36 @@ class CourseEnrollmentByLocationView(BaseCourseEnrollmentView):
def get_queryset(self): def get_queryset(self):
queryset = super(CourseEnrollmentByLocationView, self).get_queryset() queryset = super(CourseEnrollmentByLocationView, self).get_queryset()
# Remove all items where country is None # Get all of the data from the database
items = [item for item in queryset.all() if item.country is not None] items = queryset.all()
# Split into known and unknown
knowns = []
unknowns = []
for item in items:
if item.country:
knowns.append(item)
else:
unknowns.append(item)
# Group the unknowns by date and combine the counts
for key, group in groupby(unknowns, lambda x: (x.date, x.course_id)):
date = key[0]
course_id = key[1]
count = 0
for item in group:
count += item.count
# pylint: disable=unexpected-keyword-arg,no-value-for-parameter
knowns.append(models.CourseEnrollmentByCountry(
course_id=course_id,
date=date,
country_code=models.CourseEnrollmentByCountry.UNKNOWN_COUNTRY_CODE,
count=count
))
# Note: We are returning a list, instead of a queryset. This is # Note: We are returning a list, instead of a queryset. This is
# acceptable since the consuming code simply expects the returned # acceptable since the consuming code simply expects the returned
# value to be iterable, not necessarily a queryset. # value to be iterable, not necessarily a queryset.
return items return knowns
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