Skip to content

Commit 9eb0331

Browse files
authored
Merge pull request #213 from appsembler/john/fix_progress_percent
Figures bug fixes
2 parents 0293795 + 3f12fa3 commit 9eb0331

8 files changed

+136
-33
lines changed

figures/migrations/0010_site_monthly_metrics.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
# -*- coding: utf-8 -*-
22
# Generated by Django 1.11.15 on 2020-04-08 21:46
3+
# Manually updated to support Django 1.8 as well as 1.11
4+
35
from __future__ import unicode_literals
46

7+
from django import VERSION as DJANGO_VERSION
58
from django.db import migrations, models
69
import django.db.models.deletion
710
import django.utils.timezone
811
import model_utils.fields
912

1013

1114
class Migration(migrations.Migration):
12-
13-
dependencies = [
14-
('sites', '0002_alter_domain_unique'),
15-
('figures', '0009_mau_metrics'),
16-
]
15+
if DJANGO_VERSION[0:2] == (1,8):
16+
dependencies = [
17+
('sites', '0001_initial'),
18+
('figures', '0009_mau_metrics'),
19+
]
20+
else: # Assuming 1.11+
21+
dependencies = [
22+
('sites', '0002_alter_domain_unique'),
23+
('figures', '0009_mau_metrics'),
24+
]
1725

1826
operations = [
1927
migrations.CreateModel(

figures/serializers.py

+10-9
Original file line numberDiff line numberDiff line change
@@ -611,17 +611,21 @@ def get_progress_data(self, course_enrollment):
611611
else:
612612
course_completed = False
613613

614+
# Default values if we can't retrieve progress data
615+
progress_percent = 0.0
616+
course_progress_details = None
617+
614618
try:
615619
obj = LearnerCourseGradeMetrics.objects.most_recent_for_learner_course(
616620
user=course_enrollment.user,
617621
course_id=str(course_enrollment.course_id))
618-
course_progress = dict(
619-
progress_percent=obj.progress_percent,
620-
course_progress_details=obj.progress_details)
622+
if obj:
623+
progress_percent = obj.progress_percent
624+
course_progress_details = obj.progress_details
621625
except Exception as e: # pylint: disable=broad-except
622626
# TODO: Use more specific database-related exception
623627
error_data = dict(
624-
msg='Unable to get learner course metrics',
628+
msg='Exception trying to get learner course metrics',
625629
username=course_enrollment.user.username,
626630
course_id=str(course_enrollment.course_id),
627631
exception=str(e)
@@ -630,18 +634,15 @@ def get_progress_data(self, course_enrollment):
630634
error_data=error_data,
631635
error_type=PipelineError.UNSPECIFIED_DATA,
632636
)
633-
course_progress = dict(
634-
progress_percent=0.0,
635-
course_progress_details=None)
636637

637638
# Empty list initially, then will fill after we implement capturing
638639
# learner specific progress
639640
course_progress_history = []
640641

641642
data = dict(
642643
course_completed=course_completed,
643-
course_progress=course_progress['progress_percent'],
644-
course_progress_details=course_progress['course_progress_details'],
644+
course_progress=progress_percent,
645+
course_progress_details=course_progress_details,
645646
course_progress_history=course_progress_history,
646647
)
647648
return data

tests/models/test_course_daily_metrics_model.py

-7
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,11 @@ def test_with_valid_average_progress(self, average_progress):
130130
course_id='course-v1:SomeOrg+ABC01+2121',
131131
enrollment_count=11,
132132
active_learners_today=1,
133-
# average_progress=Decimal(str(average_progress)),
134133
average_progress=str(average_progress),
135134
average_days_to_complete=5,
136135
num_learners_completed=10
137136
)
138137
metrics = CourseDailyMetrics.objects.create(**rec)
139-
# assert metrics.average_progress == Decimal(str(average_progress))
140138
assert metrics.average_progress == average_progress
141139
metrics.clean_fields()
142140

@@ -154,7 +152,6 @@ def test_with_valid_average_progress_2(self, average_progress):
154152
defaults=dict(
155153
enrollment_count=11,
156154
active_learners_today=1,
157-
# average_progress=Decimal(str(average_progress)),
158155
average_progress=str(average_progress),
159156
average_days_to_complete=5,
160157
num_learners_completed=10,
@@ -163,10 +160,6 @@ def test_with_valid_average_progress_2(self, average_progress):
163160
cdm, created = CourseDailyMetrics.objects.update_or_create(**rec)
164161
cdm.save()
165162

166-
# import pdb; pdb.set_trace()
167-
168-
169-
# assert metrics.average_progress == Decimal(str(average_progress))
170163
assert cdm.average_progress == str(average_progress)
171164
cdm.clean_fields()
172165

tests/models/test_learner_course_grade_metrics_model.py

+47-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,55 @@
66
import pytest
77

88
from django.contrib.sites.models import Site
9+
from django.utils.timezone import utc
910

11+
from figures.helpers import as_date
1012
from figures.models import LearnerCourseGradeMetrics
1113

12-
from tests.factories import CourseEnrollmentFactory
14+
from tests.factories import (
15+
CourseEnrollmentFactory,
16+
CourseOverviewFactory,
17+
LearnerCourseGradeMetricsFactory,
18+
UserFactory
19+
)
20+
21+
22+
@pytest.mark.django_db
23+
def test_most_recent_with_data(db):
24+
"""Make sure the query works with a couple of existing models
25+
26+
We create two LearnerCourseGradeMetrics models and test that the function
27+
retrieves the newer one
28+
"""
29+
user = UserFactory()
30+
first_date = as_date('2020-02-02')
31+
second_date = as_date('2020-04-01')
32+
course_overview = CourseOverviewFactory()
33+
older_lcgm = LearnerCourseGradeMetricsFactory(user=user,
34+
course_id=str(course_overview.id),
35+
date_for=first_date)
36+
newer_lcgm = LearnerCourseGradeMetricsFactory(user=user,
37+
course_id=str(course_overview.id),
38+
date_for=second_date)
39+
40+
obj = LearnerCourseGradeMetrics.objects.most_recent_for_learner_course(
41+
user=user, course_id=course_overview.id)
42+
assert obj == newer_lcgm
43+
44+
45+
@pytest.mark.django_db
46+
def test_most_recent_with_empty_table(db):
47+
"""Make sure the query works when there are no models to find
48+
49+
Tests that the function returns None and does not fail when it cannot find
50+
any LearnerCourseGradeMetrics model instances
51+
"""
52+
assert not LearnerCourseGradeMetrics.objects.count()
53+
user = UserFactory()
54+
course_overview = CourseOverviewFactory()
55+
obj = LearnerCourseGradeMetrics.objects.most_recent_for_learner_course(
56+
user=user, course_id=course_overview.id)
57+
assert not obj
1358

1459

1560
@pytest.mark.django_db
@@ -69,7 +114,7 @@ def test_most_recent_for_learner_course_multiple_dates(self):
69114

70115
def test_progress_percent(self):
71116
expected = (self.grade_data['sections_worked'] /
72-
self.grade_data['sections_possible'])
117+
self.grade_data['sections_possible'])
73118
obj = LearnerCourseGradeMetrics(**self.create_rec)
74119
assert obj.progress_percent == expected
75120

tests/pipeline/test_site_monthly_metrics.py

+12
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from courseware.models import StudentModule
1313

14+
from figures.compat import RELEASE_LINE
1415
from figures.models import SiteMonthlyMetrics
1516
from figures.pipeline.site_monthly_metrics import fill_month, fill_last_month
1617

@@ -70,13 +71,24 @@ def mock_get_student_modules_for_site(site):
7071
assert obj.month_for == smm_test_data['month_before'].date()
7172

7273

74+
@pytest.mark.skipif(RELEASE_LINE=='ginkgo',
75+
reason='Freezegun breaks the StudentModule query')
7376
def test_fill_last_month_wo_overwrite(monkeypatch, smm_test_data):
7477
"""
78+
This test breaks when run in the Ginkgo environment. The problem is that
79+
after `freezer.start()`, StudentModule queries on `modified` fields return
80+
empty results when there are data for the modified fields set for the filter
81+
values. See tests/test_ginkgo.py
7582
"""
7683
assert SiteMonthlyMetrics.objects.count() == 0
7784
mock_today = smm_test_data['mock_today']
7885
freezer = freeze_time(mock_today)
86+
year_check = smm_test_data['last_month_sm'][0].modified.year
87+
assert StudentModule.objects.filter(modified__year=year_check), \
88+
'before freezegun start'
7989
freezer.start()
90+
assert StudentModule.objects.filter(modified__year=year_check), \
91+
'after freezegun start'
8092

8193
site = smm_test_data['site']
8294

tests/test_serializers.py

+50-8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from figures.models import (
1919
CourseDailyMetrics,
2020
CourseMauMetrics,
21+
LearnerCourseGradeMetrics,
2122
SiteDailyMetrics,
2223
SiteMauMetrics,
2324
)
@@ -45,6 +46,7 @@
4546
CourseMauMetricsFactory,
4647
CourseOverviewFactory,
4748
GeneratedCertificateFactory,
49+
LearnerCourseGradeMetricsFactory,
4850
SiteDailyMetricsFactory,
4951
SiteMauMetricsFactory,
5052
UserFactory,
@@ -395,7 +397,7 @@ def test_has_fields(self):
395397
data = self.serializer.data
396398

397399
assert set(data.keys()) == set(self.expected_fields)
398-
400+
399401
# This is to make sure that the serializer retrieves the correct nested
400402
# model (UserProfile) data
401403
assert data['username'] == 'alpha_one'
@@ -413,13 +415,6 @@ class TestLearnerCourseDetailsSerializer(object):
413415
'''
414416
@pytest.fixture(autouse=True)
415417
def setup(self, db):
416-
# self.model = CourseEnrollment
417-
# self.user_attributes = {
418-
# 'username': 'alpha_one',
419-
# 'profile__name': 'Alpha One',
420-
# 'profile__country': 'CA',
421-
# }
422-
#self.user = UserFactory(**self.user_attributes)
423418
self.site = Site.objects.first()
424419
self.certificate_date = datetime.datetime(2018, 4, 1, tzinfo=utc)
425420
self.course_enrollment = CourseEnrollmentFactory(
@@ -442,6 +437,53 @@ def test_has_fields(self):
442437
data = self.serializer.data
443438
assert set(data.keys()) == expected_fields
444439

440+
def test_get_progress_data(self):
441+
"""
442+
Method should return data of the form:
443+
{'course_progress_history': [],
444+
'course_progress_details': {
445+
'sections_worked': 5,
446+
'points_possible': 30.0,
447+
'sections_possible': 10,
448+
'points_earned': 15.0
449+
},
450+
'course_progress': (0.5,),
451+
'course_completed': datetime.datetime(2018, 4, 1, 0, 0, tzinfo=<UTC>)
452+
}
453+
"""
454+
metrics_data = dict(
455+
points_possible=1,
456+
points_earned=2,
457+
sections_worked=3,
458+
sections_possible=4)
459+
lcgm = LearnerCourseGradeMetricsFactory(
460+
user=self.course_enrollment.user,
461+
course_id=str(self.course_enrollment.course_id),
462+
**metrics_data
463+
)
464+
465+
data = self.serializer.get_progress_data(self.course_enrollment)
466+
details = data['course_progress_details']
467+
for key, val in metrics_data.items():
468+
assert details[key] == val
469+
assert data['course_progress'] == lcgm.progress_percent
470+
assert data['course_completed'] == self.generated_certificate.created_date
471+
472+
def test_get_progress_data_with_no_data(self):
473+
"""Tests that the serializer method succeeds when no learner course
474+
grade metrics records
475+
"""
476+
expected_data = {
477+
'course_progress_history': [],
478+
'course_progress_details': None,
479+
'course_progress': 0.0,
480+
'course_completed': False
481+
}
482+
assert not LearnerCourseGradeMetrics.objects.count()
483+
course_enrollment = CourseEnrollmentFactory()
484+
data = self.serializer.get_progress_data(course_enrollment)
485+
assert data == expected_data
486+
445487

446488
@pytest.mark.django_db
447489
class TestLearnerDetailsSerializer(object):

tests/views/test_learner_details_view.py

-1
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,6 @@ def test_serializer(self):
301301
queryset = enrollments.filter(user=self.my_site_users[0])
302302
assert queryset
303303
serializer = LearnerCourseDetailsSerializer(queryset[0])
304-
# import pdb; pdb.set_trace()
305304
# We're asserting that we can get the serializer `data` property without
306305
# error, not checking the contents of the data. That should be done in
307306
# the serializer specific tests (see tests/test_serializers.py).

tests/views/test_site_monthly_metrics_viewset.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ def check_response(self, response, endpoint):
105105
return True
106106

107107
def run_request(self, endpoint, user_reg_test_data):
108-
import pdb; pdb.set_trace()
108+
"""
109+
Stub
110+
"""
111+
pass
109112

110113
@pytest.mark.skip(reason='We need to confirm who is in total registered users')
111114
def test_registered_learners(self, monkeypatch, user_reg_test_data):

0 commit comments

Comments
 (0)