-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix backfill_enrollment_data_for_course task
* Problem was that `update_enrollment_data_for_course` returns a list of tuples. Each tuple is `(object, created)`, where `object` is the EnrollmentData record and `created` tells if the record was created * Added logging to the backfill task * Updated tests * Added `get_from_enrollment` method to `EnrollmentDataManager`
- Loading branch information
1 parent
21838ef
commit ca245db
Showing
7 changed files
with
243 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
"""This module handles enrollments | ||
Enrollment specific functionality should go here unless the functionality would | ||
only be run in the pipeline. | ||
At some point we might make an `Enrollment` class. But for now, this module is | ||
here to build functionality to shed more light into what such a class would | ||
look like. | ||
""" | ||
from figures.compat import StudentModule | ||
from figures.models import EnrollmentData | ||
|
||
|
||
def student_modules_for_enrollment(enrollment): | ||
"""Return an enrollment's StudentModule records, if they exist | ||
This function exists because edx-platform does not have a model relationship between | ||
`CourseEnrollment` and `StudentModule`. | ||
""" | ||
return StudentModule.objects.filter(student_id=enrollment.user_id, | ||
course_id=enrollment.course_id) | ||
|
||
|
||
def student_modules_for_enrollment_after_date(enrollment, date_for): | ||
"""Return StudentModule records modified for the enrollment after a date | ||
TODO: Test if we need to do `modified__gte=next_day(date_for)` or if | ||
`modified__gt=date_for` will trigger for times after midnight but before | ||
the next day | ||
We can ignore `StudentModule.created` because a new record also sets the | ||
`modified` field. | ||
""" | ||
return student_modules_for_enrollment(enrollment).filter(modified__gte=date_for) | ||
|
||
|
||
def is_enrollment_data_out_of_date(enrollment): | ||
""" | ||
Assumes being called with an enrollment with student modules | ||
""" | ||
# has EnrollmentData records? if not, then out of date | ||
edrec = EnrollmentData.objects.get_for_enrollment(enrollment) | ||
if edrec: | ||
sm = student_modules_for_enrollment_after_date(enrollment, edrec.date_for) | ||
# All we care about is if there are student modules modified | ||
# after the EnrollmentData record was `date_for` | ||
# out_of_date.append(enrollment) | ||
out_of_date = True if sm.exists() else False | ||
else: | ||
# Just check if there are student modules for the enrollment, ever | ||
# If there are student modules (and we've already check there is no | ||
# enrollment), then we need to update | ||
# If there are no student modules, then the enrollment had no activity | ||
# then return whether there are StudentModule records or not | ||
out_of_date = student_modules_for_enrollment(enrollment).exists() | ||
|
||
return out_of_date |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,55 @@ | ||
"""Test Figures backfill Celery tasks | ||
""" | ||
from __future__ import absolute_import | ||
import logging | ||
import pytest | ||
|
||
from figures.tasks import backfill_enrollment_data_for_course | ||
|
||
from tests.factories import EnrollmentDataFactory | ||
|
||
|
||
def test_backfill_enrollment_data_for_course(transactional_db, monkeypatch): | ||
""" | ||
The Celery task is a simple wrapper around the pipeline function | ||
""" | ||
course_id = 'course-v1:SomeOrg+SomeNum+SomeRun' | ||
ed_recs = [EnrollmentDataFactory() for _ in range(2)] | ||
@pytest.mark.django_db | ||
class TestBackfillEnrollmentDataForCourse(object): | ||
|
||
func_path = 'figures.tasks.update_enrollment_data_for_course' | ||
monkeypatch.setattr(func_path, lambda course_id: ed_recs) | ||
ed_ids = backfill_enrollment_data_for_course(course_id) | ||
assert set(ed_ids) == set([obj.id for obj in ed_recs]) | ||
@pytest.fixture(autouse=True) | ||
def setup(self, db): | ||
self.expected_message_template = ( | ||
'figures.tasks.backfill_enrollment_data_for_course "{course_id}".' | ||
' Updated {edrec_count} enrollment data records.') | ||
|
||
def test_backfill_enrollment_data_for_course_no_update(self, transactional_db, | ||
monkeypatch, caplog): | ||
""" | ||
The Celery task is a simple wrapper around the pipeline function | ||
""" | ||
course_id = 'course-v1:SomeOrg+SomeNum+SomeRun' | ||
|
||
# The function returns a list of tuples with (object, created) | ||
# ed_recs = [(EnrollmentDataFactory(), False) for _ in range(2)] | ||
caplog.set_level(logging.INFO) | ||
func_path = 'figures.tasks.update_enrollment_data_for_course' | ||
monkeypatch.setattr(func_path, lambda course_id: []) | ||
backfill_enrollment_data_for_course(course_id) | ||
assert len(caplog.records) == 1 | ||
assert caplog.records[0].message == self.expected_message_template.format( | ||
course_id=course_id, | ||
edrec_count=0) | ||
|
||
def test_backfill_enrollment_data_for_course_with_updates(self, transactional_db, | ||
monkeypatch, caplog): | ||
""" | ||
The Celery task is a simple wrapper around the pipeline function | ||
""" | ||
course_id = 'course-v1:SomeOrg+SomeNum+SomeRun' | ||
|
||
# The function returns a list of tuples with (object, created) | ||
ed_recs = [(EnrollmentDataFactory(), False) for _ in range(2)] | ||
caplog.set_level(logging.INFO) | ||
func_path = 'figures.tasks.update_enrollment_data_for_course' | ||
monkeypatch.setattr(func_path, lambda course_id: ed_recs) | ||
backfill_enrollment_data_for_course(course_id) | ||
assert len(caplog.records) == 1 | ||
assert caplog.records[0].message == self.expected_message_template.format( | ||
course_id=course_id, | ||
edrec_count=len(ed_recs)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
"""Tests figures.enrollment module | ||
""" | ||
from __future__ import absolute_import | ||
from datetime import timedelta | ||
|
||
import pytest | ||
from faker import Faker | ||
|
||
from figures.enrollment import ( | ||
student_modules_for_enrollment, | ||
student_modules_for_enrollment_after_date, | ||
is_enrollment_data_out_of_date | ||
) | ||
from figures.helpers import as_date, as_datetime | ||
|
||
from tests.factories import ( | ||
CourseEnrollmentFactory, | ||
EnrollmentDataFactory, | ||
StudentModuleFactory, | ||
) | ||
|
||
|
||
fake = Faker() | ||
|
||
|
||
@pytest.mark.django_db | ||
class TestStudentModulesForEnrollmentAfterDate(object): | ||
"""Tests figures.enrollment.student_modules_for_enrollment_after_date | ||
This test also exercises `student_modules_for_enrollment` function. | ||
""" | ||
@pytest.fixture(autouse=True) | ||
def setup(self, db): | ||
self.enrollment = CourseEnrollmentFactory() | ||
|
||
def test_no_student_modules(self): | ||
qs = student_modules_for_enrollment_after_date(self.enrollment, fake.date()) | ||
assert not qs.exists() | ||
|
||
def test_none_after(self): | ||
sm_date = fake.date_this_year() | ||
td_params = dict(days=1) | ||
check_date = sm_date + timedelta(**td_params) | ||
sm = StudentModuleFactory.from_course_enrollment(self.enrollment, | ||
modified=as_datetime(sm_date)) | ||
qs = student_modules_for_enrollment_after_date(self.enrollment, check_date) | ||
assert not qs.exists() | ||
|
||
|
||
|
||
|
||
@pytest.mark.django_db | ||
class TestIsEnrollmentDataOutOfDate(object): | ||
"""Tests figures.enrollment.is_enrollment_data_out_of_date | ||
""" | ||
@pytest.fixture(autouse=True) | ||
def setup(self, db): | ||
self.enrollment = CourseEnrollmentFactory() | ||
|
||
def test_no_edrec_no_sm(self): | ||
assert is_enrollment_data_out_of_date(self.enrollment) == False | ||
|
||
def test_no_edrec_has_sm(self): | ||
StudentModuleFactory.from_course_enrollment(self.enrollment) | ||
assert is_enrollment_data_out_of_date(self.enrollment) == True | ||
|
||
def test_has_edrec_but_out_of_date(self): | ||
edrec = EnrollmentDataFactory.from_course_enrollment( | ||
self.enrollment, date_for=fake.date_this_year()) | ||
|
||
sm_date_for = edrec.date_for + timedelta(days=1) | ||
StudentModuleFactory.from_course_enrollment( | ||
self.enrollment, modified=sm_date_for) | ||
assert is_enrollment_data_out_of_date(self.enrollment) == True | ||
|
||
def test_has_edrec_and_up_to_date(self): | ||
edrec = EnrollmentDataFactory.from_course_enrollment(self.enrollment) | ||
assert is_enrollment_data_out_of_date(self.enrollment) == False |