Skip to content

Commit 147bdd3

Browse files
committed
Merge pull request openedx-unsupported#181 from edx/dsjen/edu-demographics
Updated education levels to new API format.
2 parents b644920 + 8e84455 commit 147bdd3

File tree

7 files changed

+134
-96
lines changed

7 files changed

+134
-96
lines changed

acceptance_tests/test_course_enrollment_demographics.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from unittest import skipUnless
33
from bok_choy.web_app_test import WebAppTest
44

5+
import analyticsclient.constants.education_level as EDUCATION_LEVEL
56
from analyticsclient.constants import demographic
67
from acceptance_tests import ENABLE_DEMOGRAPHICS_TESTS
78
from acceptance_tests.mixins import CourseDemographicsPageTestsMixin
@@ -139,6 +140,20 @@ def _test_table_row(self, datum, column, sum_count):
139140

140141
@skipUnless(ENABLE_DEMOGRAPHICS_TESTS, 'Demographics tests are not enabled.')
141142
class CourseEnrollmentDemographicsEducationTests(CourseDemographicsPageTestsMixin, WebAppTest):
143+
144+
EDUCATION_NAMES = {
145+
EDUCATION_LEVEL.NONE: 'None',
146+
EDUCATION_LEVEL.OTHER: 'Other',
147+
EDUCATION_LEVEL.PRIMARY: 'Primary',
148+
EDUCATION_LEVEL.JUNIOR_SECONDARY: 'Middle',
149+
EDUCATION_LEVEL.SECONDARY: 'Secondary',
150+
EDUCATION_LEVEL.ASSOCIATES: 'Associate',
151+
EDUCATION_LEVEL.BACHELORS: "Bachelor's",
152+
EDUCATION_LEVEL.MASTERS: "Master's",
153+
EDUCATION_LEVEL.DOCTORATE: 'Doctorate',
154+
None: 'Unknown'
155+
}
156+
142157
help_path = 'enrollment/Demographics_Education.html'
143158

144159
demographic_type = demographic.EDUCATION
@@ -175,13 +190,13 @@ def _test_metrics(self):
175190
for group in education_groups:
176191
selector = 'data-stat-type={}'.format(group['stat_type'])
177192
filtered_group = ([education for education in self.demographic_data
178-
if education['education_level']['short_name'] in group['levels']])
193+
if education['education_level'] in group['levels']])
179194
group_total = float(sum([datum['count'] for datum in filtered_group]))
180195
expected_percent_display = self.build_display_percentage(group_total, total)
181196
self.assertSummaryPointValueEquals(selector, expected_percent_display)
182197

183198
def _test_table_row(self, datum, column, sum_count):
184-
expected = [datum['education_level']['name'],
199+
expected = [self.EDUCATION_NAMES[datum['education_level']],
185200
unicode(datum['count'])]
186201
actual = [column[0].text, column[1].text]
187202
self.assertListEqual(actual, expected)

analytics_dashboard/courses/presenters.py

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import datetime
33
import logging
44

5-
from django.utils.translation import ugettext as _
5+
from django.utils.translation import ugettext_lazy as _
66
from django.conf import settings
77
from django_countries import countries
88
from waffle import switch_is_active
@@ -34,19 +34,35 @@
3434
GENDER.OTHER: 2
3535
}
3636

37+
38+
KNOWN_EDUCATION_LEVELS = [EDUCATION_LEVEL.NONE, EDUCATION_LEVEL.OTHER, EDUCATION_LEVEL.PRIMARY,
39+
EDUCATION_LEVEL.JUNIOR_SECONDARY, EDUCATION_LEVEL.SECONDARY, EDUCATION_LEVEL.ASSOCIATES,
40+
EDUCATION_LEVEL.BACHELORS, EDUCATION_LEVEL.MASTERS, EDUCATION_LEVEL.DOCTORATE]
3741
# for display
38-
EDUCATION_SHORT_NAMES = {
39-
EDUCATION_LEVEL.NONE: 'None',
40-
EDUCATION_LEVEL.OTHER: 'Other',
41-
EDUCATION_LEVEL.PRIMARY: 'Elementary',
42-
EDUCATION_LEVEL.JUNIOR_SECONDARY: 'Middle',
43-
EDUCATION_LEVEL.SECONDARY: 'High',
44-
EDUCATION_LEVEL.ASSOCIATES: 'Associates',
45-
EDUCATION_LEVEL.BACHELORS: 'Bachelors',
46-
EDUCATION_LEVEL.MASTERS: 'Masters',
47-
EDUCATION_LEVEL.DOCTORATE: 'Doctorate'
42+
EDUCATION_NAMES = {
43+
# Translators: This describes the learner's education level.
44+
EDUCATION_LEVEL.NONE: _('None'),
45+
# Translators: This describes the learner's education level.
46+
EDUCATION_LEVEL.OTHER: _('Other'),
47+
# Translators: This describes the learner's education level (e.g. Elementary School Degree).
48+
EDUCATION_LEVEL.PRIMARY: _('Primary'),
49+
# Translators: This describes the learner's education level (e.g. Middle School Degree).
50+
EDUCATION_LEVEL.JUNIOR_SECONDARY: _('Middle'),
51+
# Translators: This describes the learner's education level.
52+
EDUCATION_LEVEL.SECONDARY: _('Secondary'),
53+
# Translators: This describes the learner's education level (e.g. Associate's Degree).
54+
EDUCATION_LEVEL.ASSOCIATES: _("Associate"),
55+
# Translators: This describes the learner's education level (e.g. Bachelor's Degree).
56+
EDUCATION_LEVEL.BACHELORS: _("Bachelor's"),
57+
# Translators: This describes the learner's education level (e.g. Master's Degree).
58+
EDUCATION_LEVEL.MASTERS: _("Master's"),
59+
# Translators: This describes the learner's education level (e.g. Doctorate Degree).
60+
EDUCATION_LEVEL.DOCTORATE: _('Doctorate')
4861
}
4962

63+
# Translators: This describes the learner's education level.
64+
UNKNOWN_EDUCATION_LEVEL_NAME = _('Unknown')
65+
5066
# order for displaying in the chart
5167
EDUCATION_ORDER = {
5268
EDUCATION_LEVEL.NONE: 0,
@@ -564,7 +580,7 @@ def _calculate_known_total_percent(self, api_response, enrollment_key):
564580
def _calculate_education_percent(self, api_response, levels):
565581
""" Aggregates levels of education and returns the percent of the total. """
566582
filtered_levels = ([education for education in api_response
567-
if education['education_level']['short_name'] in levels])
583+
if education['education_level'] in levels])
568584
subset_enrollment = self._calculate_total_enrollment(filtered_levels)
569585
return self._calculate_percent(subset_enrollment, self._calculate_total_enrollment(api_response))
570586

@@ -585,11 +601,10 @@ def _build_education_summary(self, api_response):
585601
def _build_education_levels(self, api_response):
586602
known_education = [i for i in api_response if i['education_level']]
587603
known_enrollment_total = self._calculate_total_enrollment(known_education)
588-
levels = [{'educationLevelShort': EDUCATION_SHORT_NAMES[datum['education_level']['short_name']],
589-
'educationLevelLong': datum['education_level']['name'],
604+
levels = [{'educationLevel': EDUCATION_NAMES[datum['education_level']],
590605
'count': datum['count'],
591606
'percent': self._calculate_percent(datum['count'], known_enrollment_total),
592-
'order': EDUCATION_ORDER[datum['education_level']['short_name']]}
607+
'order': EDUCATION_ORDER[datum['education_level']]}
593608
for datum in known_education]
594609

595610
levels = sorted(levels, key=lambda i: i['order'], reverse=False)
@@ -599,13 +614,25 @@ def _build_education_levels(self, api_response):
599614
if unknown:
600615
unknown_count = unknown[0]['count']
601616
levels.append({
602-
'educationLevelShort': 'Unknown',
603-
'educationLevelLong': 'Unknown',
617+
'educationLevel': UNKNOWN_EDUCATION_LEVEL_NAME,
604618
'count': unknown_count
605619
})
606620

607621
return levels
608622

623+
def _fill_empty_education_levels(self, api_response):
624+
found_levels = [level['education_level'] for level in api_response]
625+
# get the symmetric difference
626+
missed_levels = list(set(found_levels) ^ set(KNOWN_EDUCATION_LEVELS))
627+
628+
for level in missed_levels:
629+
api_response.append({
630+
'education_level': level,
631+
'count': 0
632+
})
633+
634+
return api_response
635+
609636
def get_education(self):
610637
api_response = self.course.enrollment(demographic.EDUCATION)
611638
education_levels = None
@@ -615,6 +642,7 @@ def get_education(self):
615642

616643
if api_response:
617644
last_updated = self.parse_api_datetime(api_response[0]['created'])
645+
api_response = self._fill_empty_education_levels(api_response)
618646
education_levels = self._build_education_levels(api_response)
619647
education_summary = self._build_education_summary(api_response)
620648
known_enrollment_percent = self._calculate_known_total_percent(api_response, 'education_level')
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from django.core.serializers.json import DjangoJSONEncoder
2+
from django.utils.encoding import force_text
3+
from django.utils.functional import Promise
4+
5+
6+
class LazyEncoder(DjangoJSONEncoder):
7+
"""
8+
Force the conversion of lazy translations so that they can be serialized to JSON.
9+
via https://docs.djangoproject.com/en/dev/topics/serialization/
10+
"""
11+
12+
# pylint: disable=method-hidden
13+
def default(self, obj):
14+
if isinstance(obj, Promise):
15+
return force_text(obj)
16+
return super(LazyEncoder, self).default(obj)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import json
2+
3+
from django.test import TestCase
4+
from django.utils.translation import ugettext_lazy as _
5+
6+
from courses.serializers import LazyEncoder
7+
8+
9+
class CourseEngagementPresenterTests(TestCase):
10+
11+
def test_lazy_encode(self):
12+
primary = _('primary')
13+
expected = '{{"education_level": "{0}"}}'.format(unicode(primary))
14+
actual = json.dumps({'education_level': primary}, cls=LazyEncoder)
15+
self.assertEqual(actual, expected)

analytics_dashboard/courses/tests/utils.py

Lines changed: 19 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -275,90 +275,63 @@ def get_mock_api_enrollment_education_data(course_id):
275275
{
276276
'course_id': course_id,
277277
'date': '2014-09-22',
278-
'education_level': {
279-
'name': 'None',
280-
'short_name': EDUCATION_LEVEL.NONE
281-
},
278+
'education_level': EDUCATION_LEVEL.NONE,
282279
'count': 100,
283280
'created': CREATED_DATETIME_STRING
284281
},
285282
{
286283
'course_id': course_id,
287284
'date': '2014-09-22',
288-
'education_level': {
289-
'name': 'Other',
290-
'short_name': EDUCATION_LEVEL.OTHER
291-
},
285+
'education_level': EDUCATION_LEVEL.OTHER,
292286
'count': 200,
293287
'created': CREATED_DATETIME_STRING
294288
},
295289
{
296290
'course_id': course_id,
297291
'date': '2014-09-22',
298-
'education_level': {
299-
'name': 'Elementary/Primary School',
300-
'short_name': EDUCATION_LEVEL.PRIMARY
301-
},
292+
'education_level': EDUCATION_LEVEL.PRIMARY,
302293
'count': 100,
303294
'created': CREATED_DATETIME_STRING
304295
},
305296
{
306297
'course_id': course_id,
307298
'date': '2014-09-22',
308-
'education_level': {
309-
'name': 'Junior Secondary/Junior High/Middle School',
310-
'short_name': EDUCATION_LEVEL.JUNIOR_SECONDARY
311-
},
299+
'education_level': EDUCATION_LEVEL.JUNIOR_SECONDARY,
312300
'count': 100,
313301
'created': CREATED_DATETIME_STRING
314302
},
315303
{
316304
'course_id': course_id,
317305
'date': '2014-09-22',
318-
'education_level': {
319-
'name': 'Secondary/High School',
320-
'short_name': EDUCATION_LEVEL.SECONDARY
321-
},
306+
'education_level': EDUCATION_LEVEL.SECONDARY,
322307
'count': 100,
323308
'created': CREATED_DATETIME_STRING
324309
},
325310
{
326311
'course_id': course_id,
327312
'date': '2014-09-22',
328-
'education_level': {
329-
'name': "Associate's Degree",
330-
'short_name': EDUCATION_LEVEL.ASSOCIATES
331-
},
313+
'education_level': EDUCATION_LEVEL.ASSOCIATES,
332314
'count': 100,
333315
'created': CREATED_DATETIME_STRING
334316
},
335317
{
336318
'course_id': course_id,
337319
'date': '2014-09-22',
338-
'education_level': {
339-
'name': "Bachelor's Degree",
340-
'short_name': EDUCATION_LEVEL.BACHELORS
341-
},
320+
'education_level': EDUCATION_LEVEL.BACHELORS,
342321
'count': 100,
343322
'created': CREATED_DATETIME_STRING
344323
},
345324
{
346325
'course_id': course_id,
347326
'date': '2014-09-22',
348-
'education_level': {
349-
'name': "Master's or Professional Degree",
350-
'short_name': EDUCATION_LEVEL.MASTERS
351-
},
327+
'education_level': EDUCATION_LEVEL.MASTERS,
352328
'count': 100,
353329
'created': CREATED_DATETIME_STRING
354330
},
355331
{
356332
'course_id': course_id,
357333
'date': '2014-09-22',
358-
'education_level': {
359-
'name': 'Doctorate',
360-
'short_name': EDUCATION_LEVEL.DOCTORATE
361-
},
334+
'education_level': EDUCATION_LEVEL.DOCTORATE,
362335
'count': 100,
363336
'created': CREATED_DATETIME_STRING
364337
},
@@ -377,71 +350,61 @@ def get_mock_api_enrollment_education_data(course_id):
377350
def get_mock_presenter_enrollment_education_data():
378351
data = [
379352
{
380-
'educationLevelShort': 'None',
381-
'educationLevelLong': 'None',
353+
'educationLevel': 'None',
382354
'count': 100,
383355
'percent': 0.1,
384356
'order': 0
385357
},
386358
{
387-
'educationLevelShort': 'Elementary',
388-
'educationLevelLong': 'Elementary/Primary School',
359+
'educationLevel': 'Primary',
389360
'count': 100,
390361
'percent': 0.1,
391362
'order': 1
392363
},
393364
{
394-
'educationLevelShort': 'Middle',
395-
'educationLevelLong': 'Junior Secondary/Junior High/Middle School',
365+
'educationLevel': 'Middle',
396366
'count': 100,
397367
'percent': 0.1,
398368
'order': 2
399369
},
400370
{
401-
'educationLevelShort': 'High',
402-
'educationLevelLong': 'Secondary/High School',
371+
'educationLevel': 'Secondary',
403372
'count': 100,
404373
'percent': 0.1,
405374
'order': 3
406375
},
407376
{
408-
'educationLevelShort': 'Associates',
409-
'educationLevelLong': "Associate's Degree",
377+
'educationLevel': "Associate",
410378
'count': 100,
411379
'percent': 0.1,
412380
'order': 4
413381
},
414382
{
415-
'educationLevelShort': 'Bachelors',
416-
'educationLevelLong': "Bachelor's Degree",
383+
'educationLevel': "Bachelor's",
417384
'count': 100,
418385
'percent': 0.1,
419386
'order': 5
420387
},
421388
{
422-
'educationLevelShort': 'Masters',
423-
'educationLevelLong': "Master's or Professional Degree",
389+
'educationLevel': "Master's",
424390
'count': 100,
425391
'percent': 0.1,
426392
'order': 6
427393
},
428394
{
429-
'educationLevelShort': 'Doctorate',
430-
'educationLevelLong': 'Doctorate',
395+
'educationLevel': 'Doctorate',
431396
'count': 100,
432397
'percent': 0.1,
433398
'order': 7
434399
},
435400
{
436-
'educationLevelShort': 'Other',
437-
'educationLevelLong': 'Other',
401+
'educationLevel': 'Other',
438402
'count': 200,
439403
'percent': 0.2,
440404
'order': 8
441405
},
442406
{
443-
'educationLevelShort': 'Unknown',
444-
'educationLevelLong': 'Unknown',
407+
'educationLevel': 'Unknown',
445408
'count': 1000
446409
}
447410
]

0 commit comments

Comments
 (0)