Commit a8150a51 by Renzo Lucioni

Supplement program data with course and enrollment data

The supplemented data is passed through to the program details page. Part of ECOM-4415.
parent 4f71e263
""" """
Stub implementation of programs service for acceptance tests Stub implementation of programs service for acceptance tests
""" """
import re import re
import urlparse import urlparse
from .http import StubHttpRequestHandler, StubHttpService from .http import StubHttpRequestHandler, StubHttpService
...@@ -11,10 +11,13 @@ class StubProgramsServiceHandler(StubHttpRequestHandler): # pylint: disable=mis ...@@ -11,10 +11,13 @@ class StubProgramsServiceHandler(StubHttpRequestHandler): # pylint: disable=mis
def do_GET(self): # pylint: disable=invalid-name, missing-docstring def do_GET(self): # pylint: disable=invalid-name, missing-docstring
pattern_handlers = { pattern_handlers = {
"/api/v1/programs/$": self.get_programs_list, r'/api/v1/programs/$': self.get_programs_list,
r'/api/v1/programs/(\d+)/$': self.get_program_details,
} }
if self.match_pattern(pattern_handlers): if self.match_pattern(pattern_handlers):
return return
self.send_response(404, content="404 Not Found") self.send_response(404, content="404 Not Found")
def match_pattern(self, pattern_handlers): def match_pattern(self, pattern_handlers):
...@@ -25,7 +28,7 @@ class StubProgramsServiceHandler(StubHttpRequestHandler): # pylint: disable=mis ...@@ -25,7 +28,7 @@ class StubProgramsServiceHandler(StubHttpRequestHandler): # pylint: disable=mis
for pattern in pattern_handlers: for pattern in pattern_handlers:
match = re.match(pattern, path) match = re.match(pattern, path)
if match: if match:
pattern_handlers[pattern](**match.groupdict()) pattern_handlers[pattern](*match.groups())
return True return True
return None return None
...@@ -36,6 +39,13 @@ class StubProgramsServiceHandler(StubHttpRequestHandler): # pylint: disable=mis ...@@ -36,6 +39,13 @@ class StubProgramsServiceHandler(StubHttpRequestHandler): # pylint: disable=mis
programs = self.server.config.get('programs', []) programs = self.server.config.get('programs', [])
self.send_json_response(programs) self.send_json_response(programs)
def get_program_details(self, program_id):
"""
Stubs a program details endpoint.
"""
program = self.server.config.get('programs.{}'.format(program_id), [])
self.send_json_response(program)
class StubProgramsService(StubHttpService): # pylint: disable=missing-docstring class StubProgramsService(StubHttpService): # pylint: disable=missing-docstring
HANDLER_CLASS = StubProgramsServiceHandler HANDLER_CLASS = StubProgramsServiceHandler
""" """
Tools to create programs-related data for use in bok choy tests. Tools to create programs-related data for use in bok choy tests.
""" """
from collections import namedtuple
import json import json
import requests import requests
from . import PROGRAMS_STUB_URL from . import PROGRAMS_STUB_URL
from .config import ConfigModelFixture from .config import ConfigModelFixture
from openedx.core.djangoapps.programs.tests import factories
FakeProgram = namedtuple('FakeProgram', ['name', 'status', 'org_key', 'course_id'])
class ProgramsFixture(object): class ProgramsFixture(object):
""" """
Interface to set up mock responses from the Programs stub server. Interface to set up mock responses from the Programs stub server.
""" """
def install_programs(self, programs, is_list=True):
def install_programs(self, fake_programs): """Sets the response data for Programs API endpoints."""
""" if is_list:
Sets the response data for the programs list endpoint. key = 'programs'
api_result = {'results': programs}
At present, `fake_programs` must be a iterable of FakeProgram named tuples. else:
""" program = programs[0]
programs = [] key = 'programs.{}'.format(program['id'])
for program in fake_programs: api_result = program
run_mode = factories.RunMode(course_key=program.course_id)
course_code = factories.CourseCode(run_modes=[run_mode])
org = factories.Organization(key=program.org_key)
program = factories.Program(
name=program.name,
status=program.status,
organizations=[org],
course_codes=[course_code]
)
programs.append(program)
api_result = {'results': programs}
requests.put( requests.put(
'{}/set_config'.format(PROGRAMS_STUB_URL), '{}/set_config'.format(PROGRAMS_STUB_URL),
data={'programs': json.dumps(api_result)}, data={key: json.dumps(api_result)},
) )
......
...@@ -24,7 +24,8 @@ class ProgramListingPage(PageObject): ...@@ -24,7 +24,8 @@ class ProgramListingPage(PageObject):
class ProgramDetailsPage(PageObject): class ProgramDetailsPage(PageObject):
"""Program details page.""" """Program details page."""
url = BASE_URL + '/dashboard/programs/123/program-name/' program_id = 123
url = BASE_URL + '/dashboard/programs/{}/program-name/'.format(program_id)
def is_browser_on_page(self): def is_browser_on_page(self):
return self.q(css='.js-program-details-wrapper').present return self.q(css='.js-program-details-wrapper').present
"""Acceptance tests for LMS-hosted Programs pages""" """Acceptance tests for LMS-hosted Programs pages"""
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from ...fixtures.programs import FakeProgram, ProgramsFixture, ProgramsConfigMixin from ...fixtures.programs import ProgramsFixture, ProgramsConfigMixin
from ...fixtures.course import CourseFixture from ...fixtures.course import CourseFixture
from ..helpers import UniqueCourseTest from ..helpers import UniqueCourseTest
from ...pages.lms.auto_auth import AutoAuthPage from ...pages.lms.auto_auth import AutoAuthPage
from ...pages.lms.programs import ProgramListingPage, ProgramDetailsPage from ...pages.lms.programs import ProgramListingPage, ProgramDetailsPage
from openedx.core.djangoapps.programs.tests import factories
class ProgramPageBase(ProgramsConfigMixin, UniqueCourseTest): class ProgramPageBase(ProgramsConfigMixin, UniqueCourseTest):
...@@ -15,16 +16,33 @@ class ProgramPageBase(ProgramsConfigMixin, UniqueCourseTest): ...@@ -15,16 +16,33 @@ class ProgramPageBase(ProgramsConfigMixin, UniqueCourseTest):
self.set_programs_api_configuration(is_enabled=True) self.set_programs_api_configuration(is_enabled=True)
def stub_api(self, course_id=None): def create_program(self, program_id=None, course_id=None):
"""Stub out the programs API with fake data.""" """DRY helper for creating test program data."""
name = 'Fake Program'
status = 'active'
org_key = self.course_info['org']
course_id = course_id if course_id else self.course_id course_id = course_id if course_id else self.course_id
ProgramsFixture().install_programs([ run_mode = factories.RunMode(course_key=course_id)
FakeProgram(name=name, status=status, org_key=org_key, course_id=course_id), course_code = factories.CourseCode(run_modes=[run_mode])
]) org = factories.Organization(key=self.course_info['org'])
if program_id:
program = factories.Program(
id=program_id,
status='active',
organizations=[org],
course_codes=[course_code]
)
else:
program = factories.Program(
status='active',
organizations=[org],
course_codes=[course_code]
)
return program
def stub_api(self, programs, is_list=True):
"""Stub out the programs API with fake data."""
ProgramsFixture().install_programs(programs, is_list=is_list)
def auth(self, enroll=True): def auth(self, enroll=True):
"""Authenticate, enrolling the user in the configured course if requested.""" """Authenticate, enrolling the user in the configured course if requested."""
...@@ -43,8 +61,10 @@ class ProgramListingPageTest(ProgramPageBase): ...@@ -43,8 +61,10 @@ class ProgramListingPageTest(ProgramPageBase):
def test_no_enrollments(self): def test_no_enrollments(self):
"""Verify that no cards appear when the user has no enrollments.""" """Verify that no cards appear when the user has no enrollments."""
self.stub_api() program = self.create_program()
self.stub_api([program])
self.auth(enroll=False) self.auth(enroll=False)
self.listing_page.visit() self.listing_page.visit()
self.assertTrue(self.listing_page.is_sidebar_present) self.assertTrue(self.listing_page.is_sidebar_present)
...@@ -59,8 +79,11 @@ class ProgramListingPageTest(ProgramPageBase): ...@@ -59,8 +79,11 @@ class ProgramListingPageTest(ProgramPageBase):
self.course_info['run'], self.course_info['run'],
'other_run' 'other_run'
) )
self.stub_api(course_id=course_id)
program = self.create_program(course_id=course_id)
self.stub_api([program])
self.auth() self.auth()
self.listing_page.visit() self.listing_page.visit()
self.assertTrue(self.listing_page.is_sidebar_present) self.assertTrue(self.listing_page.is_sidebar_present)
...@@ -71,8 +94,10 @@ class ProgramListingPageTest(ProgramPageBase): ...@@ -71,8 +94,10 @@ class ProgramListingPageTest(ProgramPageBase):
Verify that cards appear when the user has enrollments Verify that cards appear when the user has enrollments
which are included in at least one active program. which are included in at least one active program.
""" """
self.stub_api() program = self.create_program()
self.stub_api([program])
self.auth() self.auth()
self.listing_page.visit() self.listing_page.visit()
self.assertTrue(self.listing_page.is_sidebar_present) self.assertTrue(self.listing_page.is_sidebar_present)
...@@ -87,9 +112,11 @@ class ProgramListingPageA11yTest(ProgramPageBase): ...@@ -87,9 +112,11 @@ class ProgramListingPageA11yTest(ProgramPageBase):
self.listing_page = ProgramListingPage(self.browser) self.listing_page = ProgramListingPage(self.browser)
program = self.create_program()
self.stub_api([program])
def test_empty_a11y(self): def test_empty_a11y(self):
"""Test a11y of the page's empty state.""" """Test a11y of the page's empty state."""
self.stub_api()
self.auth(enroll=False) self.auth(enroll=False)
self.listing_page.visit() self.listing_page.visit()
...@@ -100,7 +127,6 @@ class ProgramListingPageA11yTest(ProgramPageBase): ...@@ -100,7 +127,6 @@ class ProgramListingPageA11yTest(ProgramPageBase):
def test_cards_a11y(self): def test_cards_a11y(self):
"""Test a11y when program cards are present.""" """Test a11y when program cards are present."""
self.stub_api()
self.auth() self.auth()
self.listing_page.visit() self.listing_page.visit()
...@@ -118,9 +144,12 @@ class ProgramDetailsPageA11yTest(ProgramPageBase): ...@@ -118,9 +144,12 @@ class ProgramDetailsPageA11yTest(ProgramPageBase):
self.details_page = ProgramDetailsPage(self.browser) self.details_page = ProgramDetailsPage(self.browser)
program = self.create_program(program_id=self.details_page.program_id)
self.stub_api([program], is_list=False)
def test_a11y(self): def test_a11y(self):
"""Test a11y of the page's state.""" """Test the page's a11y compliance."""
self.auth(enroll=False) self.auth()
self.details_page.visit() self.details_page.visit()
self.details_page.a11y_audit.check_for_accessibility_errors() self.details_page.a11y_audit.check_for_accessibility_errors()
...@@ -8,7 +8,7 @@ from uuid import uuid4 ...@@ -8,7 +8,7 @@ from uuid import uuid4
from ...fixtures import PROGRAMS_STUB_URL from ...fixtures import PROGRAMS_STUB_URL
from ...fixtures.config import ConfigModelFixture from ...fixtures.config import ConfigModelFixture
from ...fixtures.programs import FakeProgram, ProgramsFixture, ProgramsConfigMixin from ...fixtures.programs import ProgramsFixture, ProgramsConfigMixin
from ...pages.studio.auto_auth import AutoAuthPage from ...pages.studio.auto_auth import AutoAuthPage
from ...pages.studio.library import LibraryEditPage from ...pages.studio.library import LibraryEditPage
from ...pages.studio.index import DashboardPage, DashboardPageWithPrograms from ...pages.studio.index import DashboardPage, DashboardPageWithPrograms
...@@ -17,6 +17,7 @@ from ..helpers import ( ...@@ -17,6 +17,7 @@ from ..helpers import (
select_option_by_text, select_option_by_text,
get_selected_option_text get_selected_option_text
) )
from openedx.core.djangoapps.programs.tests import factories
class CreateLibraryTest(WebAppTest): class CreateLibraryTest(WebAppTest):
...@@ -111,11 +112,24 @@ class DashboardProgramsTabTest(ProgramsConfigMixin, WebAppTest): ...@@ -111,11 +112,24 @@ class DashboardProgramsTabTest(ProgramsConfigMixin, WebAppTest):
via config, and the results of the program list should display when via config, and the results of the program list should display when
the list is nonempty. the list is nonempty.
""" """
test_program_values = [ test_program_values = [('first program', 'org1'), ('second program', 'org2')]
FakeProgram(name='first program', status='unpublished', org_key='org1', course_id='foo/bar/baz'),
FakeProgram(name='second program', status='unpublished', org_key='org2', course_id='qux/quux/corge'), programs = [
factories.Program(
name=name,
organizations=[
factories.Organization(key=org),
],
course_codes=[
factories.CourseCode(run_modes=[
factories.RunMode(),
]),
]
)
for name, org in test_program_values
] ]
ProgramsFixture().install_programs(test_program_values)
ProgramsFixture().install_programs(programs)
self.set_programs_api_configuration(True) self.set_programs_api_configuration(True)
...@@ -126,8 +140,7 @@ class DashboardProgramsTabTest(ProgramsConfigMixin, WebAppTest): ...@@ -126,8 +140,7 @@ class DashboardProgramsTabTest(ProgramsConfigMixin, WebAppTest):
self.assertFalse(self.dashboard_page.is_empty_list_create_button_present()) self.assertFalse(self.dashboard_page.is_empty_list_create_button_present())
results = self.dashboard_page.get_program_list() results = self.dashboard_page.get_program_list()
expected = [(p.name, p.org_key) for p in test_program_values] self.assertEqual(results, test_program_values)
self.assertEqual(results, expected)
def test_tab_requires_staff(self): def test_tab_requires_staff(self):
""" """
......
...@@ -24,7 +24,7 @@ from openedx.core.djangoapps.programs.tests.mixins import ( ...@@ -24,7 +24,7 @@ from openedx.core.djangoapps.programs.tests.mixins import (
ProgramsDataMixin) ProgramsDataMixin)
from student.models import CourseEnrollment from student.models import CourseEnrollment
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
...@@ -231,13 +231,18 @@ class TestProgramListing( ...@@ -231,13 +231,18 @@ class TestProgramListing(
@httpretty.activate @httpretty.activate
@override_settings(MKTG_URLS={'ROOT': 'http://edx.org'}) @override_settings(MKTG_URLS={'ROOT': 'http://edx.org'})
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class TestProgramDetails(ProgramsApiConfigMixin, TestCase): class TestProgramDetails(ProgramsApiConfigMixin, SharedModuleStoreTestCase):
""" """
Unit tests for the program details page Unit tests for the program details page
""" """
program_id = 123 program_id = 123
password = 'test' password = 'test'
@classmethod
def setUpClass(cls):
super(TestProgramDetails, cls).setUpClass()
cls.course = CourseFactory()
def setUp(self): def setUp(self):
super(TestProgramDetails, self).setUp() super(TestProgramDetails, self).setUp()
...@@ -248,11 +253,12 @@ class TestProgramDetails(ProgramsApiConfigMixin, TestCase): ...@@ -248,11 +253,12 @@ class TestProgramDetails(ProgramsApiConfigMixin, TestCase):
ClientFactory(name=ProgramsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL) ClientFactory(name=ProgramsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL)
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.data = factories.Program( self.data = factories.Program(
organizations=[factories.Organization()], organizations=[self.organization],
course_codes=[ course_codes=[self.course_code]
factories.CourseCode(run_modes=[factories.RunMode()]),
]
) )
def _mock_programs_api(self): def _mock_programs_api(self):
......
...@@ -3,13 +3,13 @@ from urlparse import urljoin ...@@ -3,13 +3,13 @@ from urlparse import urljoin
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_GET
from django.http import Http404 from django.http import Http404
from django.views.decorators.http import require_GET
from edxmako.shortcuts import render_to_response from edxmako.shortcuts import render_to_response
from openedx.core.djangoapps.credentials.utils import get_programs_credentials from openedx.core.djangoapps.credentials.utils import get_programs_credentials
from openedx.core.djangoapps.programs.models import ProgramsApiConfig from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.programs.utils import ProgramProgressMeter, get_programs, get_display_category from openedx.core.djangoapps.programs import utils
from student.views import get_course_enrollments from student.views import get_course_enrollments
...@@ -22,13 +22,13 @@ def view_programs(request): ...@@ -22,13 +22,13 @@ def view_programs(request):
raise Http404 raise Http404
enrollments = list(get_course_enrollments(request.user, None, [])) enrollments = list(get_course_enrollments(request.user, None, []))
meter = ProgramProgressMeter(request.user, enrollments) meter = utils.ProgramProgressMeter(request.user, enrollments)
programs = meter.engaged_programs programs = meter.engaged_programs
# TODO: Pull 'xseries' string from configuration model. # TODO: Pull 'xseries' string from configuration model.
marketing_root = urljoin(settings.MKTG_URLS.get('ROOT'), 'xseries').strip('/') marketing_root = urljoin(settings.MKTG_URLS.get('ROOT'), 'xseries').strip('/')
for program in programs: for program in programs:
program['display_category'] = get_display_category(program) program['display_category'] = utils.get_display_category(program)
program['marketing_url'] = '{root}/{slug}'.format( program['marketing_url'] = '{root}/{slug}'.format(
root=marketing_root, root=marketing_root,
slug=program['marketing_slug'] slug=program['marketing_slug']
...@@ -56,7 +56,8 @@ def program_details(request, program_id): ...@@ -56,7 +56,8 @@ def program_details(request, program_id):
if not show_program_details: if not show_program_details:
raise Http404 raise Http404
program_data = get_programs(request.user, program_id=program_id) program_data = utils.get_programs(request.user, program_id=program_id)
program_data = utils.supplement_program_data(program_data, request.user)
context = { context = {
'program_data': program_data, 'program_data': program_data,
......
"""Tests covering Programs utilities.""" """Tests covering Programs utilities."""
import copy
import datetime
import json import json
from unittest import skipUnless from unittest import skipUnless
import ddt
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.test import TestCase from django.test import TestCase
from django.utils import timezone
import httpretty import httpretty
import mock import mock
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
...@@ -12,6 +17,7 @@ from edx_oauth2_provider.tests.factories import ClientFactory ...@@ -12,6 +17,7 @@ from edx_oauth2_provider.tests.factories import ClientFactory
from provider.constants import CONFIDENTIAL from provider.constants import CONFIDENTIAL
from lms.djangoapps.certificates.api import MODES 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 import factories as credentials_factories
from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin, CredentialsDataMixin from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin, CredentialsDataMixin
from openedx.core.djangoapps.programs import utils from openedx.core.djangoapps.programs import utils
...@@ -20,6 +26,8 @@ from openedx.core.djangoapps.programs.tests import factories ...@@ -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.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from student.tests.factories import UserFactory, CourseEnrollmentFactory 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' UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
...@@ -597,3 +605,78 @@ class TestProgramProgressMeter(ProgramsApiConfigMixin, TestCase): ...@@ -597,3 +605,78 @@ class TestProgramProgressMeter(ProgramsApiConfigMixin, TestCase):
meter, meter,
factories.Progress(id=program['id'], completed=self._extract_names(program, 0)) 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)
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Helper functions for working with Programs.""" """Helper functions for working with Programs."""
import datetime
import logging 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 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.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.lib.edx_api_utils import get_edx_api_data 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__) log = logging.getLogger(__name__)
...@@ -275,3 +284,37 @@ class ProgramProgressMeter(object): ...@@ -275,3 +284,37 @@ class ProgramProgressMeter(object):
} }
return parsed 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
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