Merge pull request #12599 from edx/renzo/supplement-program-data
Supplement program data with course and enrollment data
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
"""Tests covering Programs utilities."""
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
from unittest import skipUnless
|
||||
|
||||
import ddt
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
import httpretty
|
||||
import mock
|
||||
from nose.plugins.attrib import attr
|
||||
@@ -12,6 +17,7 @@ from edx_oauth2_provider.tests.factories import ClientFactory
|
||||
from provider.constants import CONFIDENTIAL
|
||||
|
||||
from lms.djangoapps.certificates.api import MODES
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.credentials.tests import factories as credentials_factories
|
||||
from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin, CredentialsDataMixin
|
||||
from openedx.core.djangoapps.programs import utils
|
||||
@@ -20,6 +26,8 @@ from openedx.core.djangoapps.programs.tests import factories
|
||||
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from student.tests.factories import UserFactory, CourseEnrollmentFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
|
||||
UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
|
||||
@@ -597,3 +605,78 @@ class TestProgramProgressMeter(ProgramsApiConfigMixin, TestCase):
|
||||
meter,
|
||||
factories.Progress(id=program['id'], completed=self._extract_names(program, 0))
|
||||
)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
"""Tests of the utility function used to supplement program data."""
|
||||
password = 'test'
|
||||
human_friendly_format = '%x'
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
super(TestSupplementProgramData, self).setUp()
|
||||
|
||||
self.user = UserFactory()
|
||||
self.client.login(username=self.user.username, password=self.password)
|
||||
|
||||
ClientFactory(name=ProgramsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL)
|
||||
|
||||
self.course = CourseFactory()
|
||||
self.course.start = timezone.now() - datetime.timedelta(days=1)
|
||||
self.course.end = timezone.now() + datetime.timedelta(days=1)
|
||||
self.course = self.update_course(self.course, self.user.id) # pylint: disable=no-member
|
||||
|
||||
self.organization = factories.Organization()
|
||||
self.run_mode = factories.RunMode(course_key=unicode(self.course.id)) # pylint: disable=no-member
|
||||
self.course_code = factories.CourseCode(run_modes=[self.run_mode])
|
||||
self.program = factories.Program(
|
||||
organizations=[self.organization],
|
||||
course_codes=[self.course_code]
|
||||
)
|
||||
|
||||
def _assert_supplemented(self, actual, is_enrolled=False, is_enrollment_open=True):
|
||||
"""DRY helper used to verify that program data is extended correctly."""
|
||||
course_overview = CourseOverview.get_from_id(self.course.id) # pylint: disable=no-member
|
||||
|
||||
run_mode = factories.RunMode(
|
||||
course_key=unicode(self.course.id), # pylint: disable=no-member
|
||||
course_url=reverse('course_root', args=[self.course.id]), # pylint: disable=no-member
|
||||
course_image_url=course_overview.course_image_url,
|
||||
start_date=self.course.start.strftime(self.human_friendly_format),
|
||||
end_date=self.course.end.strftime(self.human_friendly_format),
|
||||
is_enrolled=is_enrolled,
|
||||
is_enrollment_open=is_enrollment_open,
|
||||
marketing_url='',
|
||||
)
|
||||
course_code = factories.CourseCode(display_name=self.course_code['display_name'], run_modes=[run_mode])
|
||||
expected = copy.deepcopy(self.program)
|
||||
expected['course_codes'] = [course_code]
|
||||
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_student_enrollment_status(self, is_enrolled):
|
||||
"""Verify that program data is supplemented correctly."""
|
||||
if is_enrolled:
|
||||
CourseEnrollmentFactory(user=self.user, course_id=self.course.id) # pylint: disable=no-member
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
self._assert_supplemented(data, is_enrolled=is_enrolled)
|
||||
|
||||
@ddt.data(
|
||||
[1, 1, False],
|
||||
[1, -1, True],
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_course_enrollment_status(self, start_offset, end_offset, is_enrollment_open):
|
||||
"""Verify that course enrollment status is reflected correctly."""
|
||||
self.course.enrollment_start = timezone.now() - datetime.timedelta(days=start_offset)
|
||||
self.course.enrollment_end = timezone.now() - datetime.timedelta(days=end_offset)
|
||||
self.course = self.update_course(self.course, self.user.id) # pylint: disable=no-member
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
self._assert_supplemented(data, is_enrollment_open=is_enrollment_open)
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Helper functions for working with Programs."""
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils import timezone
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
import pytz
|
||||
|
||||
from lms.djangoapps.certificates.api import get_certificates_for_user, is_passing_status
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
from openedx.core.lib.edx_api_utils import get_edx_api_data
|
||||
from student.models import CourseEnrollment
|
||||
from xmodule.course_metadata_utils import DEFAULT_START_DATE
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -275,3 +284,37 @@ class ProgramProgressMeter(object):
|
||||
}
|
||||
|
||||
return parsed
|
||||
|
||||
|
||||
def supplement_program_data(program_data, user):
|
||||
"""Supplement program course codes with CourseOverview and CourseEnrollment data.
|
||||
|
||||
Arguments:
|
||||
program_data (dict): Representation of a program.
|
||||
user (User): The user whose enrollments to inspect.
|
||||
"""
|
||||
for course_code in program_data['course_codes']:
|
||||
for run_mode in course_code['run_modes']:
|
||||
course_key = CourseKey.from_string(run_mode['course_key'])
|
||||
course_overview = CourseOverview.get_from_id(course_key)
|
||||
|
||||
run_mode['course_url'] = reverse('course_root', args=[course_key])
|
||||
run_mode['course_image_url'] = course_overview.course_image_url
|
||||
|
||||
human_friendly_format = '%x'
|
||||
start_date = course_overview.start or DEFAULT_START_DATE
|
||||
end_date = course_overview.end or datetime.datetime.max.replace(tzinfo=pytz.UTC)
|
||||
run_mode['start_date'] = start_date.strftime(human_friendly_format)
|
||||
run_mode['end_date'] = end_date.strftime(human_friendly_format)
|
||||
|
||||
run_mode['is_enrolled'] = CourseEnrollment.is_enrolled(user, course_key)
|
||||
|
||||
enrollment_start = course_overview.enrollment_start or datetime.datetime.min.replace(tzinfo=pytz.UTC)
|
||||
enrollment_end = course_overview.enrollment_end or datetime.datetime.max.replace(tzinfo=pytz.UTC)
|
||||
is_enrollment_open = enrollment_start <= timezone.now() < enrollment_end
|
||||
run_mode['is_enrollment_open'] = is_enrollment_open
|
||||
|
||||
# TODO: Currently unavailable on LMS.
|
||||
run_mode['marketing_url'] = ''
|
||||
|
||||
return program_data
|
||||
|
||||
Reference in New Issue
Block a user