Add program marketing view

This change adds the URL configuration and Django view required
to implement a program marketing page. It is left to theme builders
to implement a template that fulfills their own UX requirements.

WL-766
This commit is contained in:
Hasnain
2017-02-08 15:07:49 +05:00
committed by Douglas Hall
parent b23854244d
commit d6d2b2d50f
7 changed files with 247 additions and 47 deletions

View File

@@ -10,10 +10,12 @@ from django.core.urlresolvers import reverse
from django.utils.functional import cached_property
from opaque_keys.edx.keys import CourseKey
from pytz import utc
from itertools import chain
from course_modes.models import CourseMode
from lms.djangoapps.certificates import api as certificate_api
from lms.djangoapps.commerce.utils import EcommerceService
from lms.djangoapps.courseware.access import has_access
from openedx.core.djangoapps.catalog.utils import get_programs
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from student.models import CourseEnrollment
@@ -248,12 +250,9 @@ class ProgramDataExtender(object):
self.course_overview = None
self.enrollment_start = None
def extend(self, include_instructors=False):
def extend(self):
"""Execute extension handlers, returning the extended data."""
if include_instructors:
self._execute('_extend')
else:
self._execute('_extend_course_runs')
self._execute('_extend')
return self.data
def _execute(self, prefix, *args):
@@ -265,9 +264,6 @@ class ProgramDataExtender(object):
"""Returns a generator yielding method names beginning with the given prefix."""
return (name for name in cls.__dict__ if name.startswith(prefix))
def _extend_with_instructors(self):
self._execute('_attach_instructors')
def _extend_course_runs(self):
"""Execute course run data handlers."""
for course in self.data['courses']:
@@ -334,31 +330,99 @@ class ProgramDataExtender(object):
else:
run_mode['upgrade_url'] = None
def _attach_instructors(self):
# pylint: disable=missing-docstring
class ProgramMarketingDataExtender(ProgramDataExtender):
"""
Utility for extending program data meant for the program marketing page which lives in the
edx-platform git repository with user-specific (e.g., CourseEnrollment) data, pricing data,
and program instructor data.
Arguments:
program_data (dict): Representation of a program.
user (User): The user whose enrollments to inspect.
"""
def __init__(self, program_data, user):
super(ProgramMarketingDataExtender, self).__init__(program_data, user)
# Aggregate dict of instructors for the program keyed by name
self.instructors = {}
# Values for programs' price calculation.
self.data['avg_price_per_course'] = 0
self.data['number_of_courses'] = 0
self.data['full_program_price'] = 0
def _extend_program(self):
"""Aggregates data from the program data structure."""
cache_key = 'program.instructors.{uuid}'.format(
uuid=self.data['uuid']
)
program_instructors = cache.get(cache_key)
for course in self.data['courses']:
self._execute('_collect_course', course)
if not program_instructors:
for course_run in course['course_runs']:
self._execute('_collect_instructors', course_run)
if not program_instructors:
# We cache the program instructors list to avoid repeated modulestore queries
program_instructors = self.instructors.values()
cache.set(cache_key, program_instructors, 3600)
self.data['instructors'] = program_instructors
@classmethod
def _handlers(cls, prefix):
"""Returns a generator yielding method names beginning with the given prefix."""
# We use a set comprehension here to deduplicate the list of
# function names given the fact that the subclass overrides
# some functions on the parent class.
return {name for name in chain(cls.__dict__, ProgramDataExtender.__dict__) if name.startswith(prefix)}
def _attach_course_run_can_enroll(self, run_mode):
run_mode['can_enroll'] = bool(has_access(self.user, 'enroll', self.course_overview))
def _attach_course_run_certificate_url(self, run_mode):
"""
We override this function here and stub it out because
the superclass (ProgramDataExtender) requires a non-anonymous
User which we may or may not have when rendering marketing
pages. The certificate URL is not needed when rendering
the program marketing page.
"""
pass
def _attach_course_run_upgrade_url(self, run_mode):
if not self.user.is_anonymous():
super(ProgramMarketingDataExtender, self)._attach_course_run_upgrade_url(run_mode)
else:
run_mode['upgrade_url'] = None
def _collect_course_pricing(self, course):
self.data['number_of_courses'] += 1
course_runs = course['course_runs']
if course_runs:
seats = course_runs[0]['seats']
if seats:
self.data['full_program_price'] += float(seats[0]['price'])
self.data['avg_price_per_course'] = self.data['full_program_price'] / self.data['number_of_courses']
def _collect_instructors(self, course_run):
"""
Extend the program data with instructor data. The instructor data added here is persisted
on each course in modulestore and can be edited in Studio. Once the course metadata publisher tool
supports the authoring of course instructor data, we will be able to migrate course
instructor data into the catalog, retrieve it via the catalog API, and remove this code.
"""
cache_key = 'program.instructors.{uuid}'.format(
uuid=self.data['uuid']
)
program_instructors = cache.get(cache_key)
if not program_instructors:
instructors_by_name = {}
module_store = modulestore()
for course in self.data['courses']:
for course_run in course['course_runs']:
course_run_key = CourseKey.from_string(course_run['key'])
course_descriptor = module_store.get_course(course_run_key)
if course_descriptor:
course_instructors = getattr(course_descriptor, 'instructor_info', {})
# Deduplicate program instructors using instructor name
instructors_by_name.update({instructor.get('name'): instructor for instructor
in course_instructors.get('instructors', [])})
module_store = modulestore()
course_run_key = CourseKey.from_string(course_run['key'])
course_descriptor = module_store.get_course(course_run_key)
if course_descriptor:
course_instructors = getattr(course_descriptor, 'instructor_info', {})
program_instructors = instructors_by_name.values()
cache.set(cache_key, program_instructors, 3600)
self.data['instructors'] = program_instructors
# Deduplicate program instructors using instructor name
self.instructors.update(
{instructor.get('name'): instructor for instructor in course_instructors.get('instructors', [])}
)