Commit bf164ae9 by Marko Jevtic

[LEARNER-1183] Prepare program data to be presented on program marketing page

[LEARNER-1393] Filter program course runs by status
parent 9c4869c1
......@@ -801,8 +801,13 @@ def program_marketing(request, program_uuid):
if not program_data:
raise Http404
program = ProgramMarketingDataExtender(program_data, request.user).extend()
skus = program.get('skus')
ecommerce_service = EcommerceService()
return render_to_response('courseware/program_marketing.html', {
'program': ProgramMarketingDataExtender(program_data, request.user).extend()
'buy_button_href': ecommerce_service.get_checkout_page_url(*skus) if skus else '#courses',
'program': program,
})
......
......@@ -3,6 +3,7 @@
from functools import partial
import factory
import uuid
from faker import Faker
......@@ -34,6 +35,19 @@ def generate_zulu_datetime():
return fake.date_time().isoformat() + 'Z'
def generate_price_ranges():
return [{
'currency': 'USD',
'max': 1000,
'min': 100,
'total': 500
}]
def generate_seat_sku():
return uuid.uuid4().hex[:7].upper()
class DictFactoryBase(factory.Factory):
"""
Subclass this to make factories that can be used to produce fake API response
......@@ -77,12 +91,15 @@ class OrganizationFactory(DictFactoryBase):
key = factory.Faker('word')
name = factory.Faker('company')
uuid = factory.Faker('uuid4')
logo_image_url = factory.Faker('image_url')
class SeatFactory(DictFactoryBase):
type = factory.Faker('word')
price = factory.Faker('random_int')
currency = 'USD'
price = factory.Faker('random_int')
sku = factory.LazyFunction(generate_seat_sku)
type = 'verified'
upgrade_deadline = factory.LazyFunction(generate_zulu_datetime)
class CourseRunFactory(DictFactoryBase):
......@@ -91,13 +108,13 @@ class CourseRunFactory(DictFactoryBase):
enrollment_end = factory.LazyFunction(generate_zulu_datetime)
enrollment_start = factory.LazyFunction(generate_zulu_datetime)
image = ImageFactory()
is_enrolled = False
key = factory.LazyFunction(generate_course_run_key)
marketing_url = factory.Faker('url')
pacing_type = 'self_paced'
seats = factory.LazyFunction(partial(generate_instances, SeatFactory))
short_description = factory.Faker('sentence')
start = factory.LazyFunction(generate_zulu_datetime)
status = 'published'
title = factory.Faker('catch_phrase')
type = 'verified'
uuid = factory.Faker('uuid4')
......@@ -112,20 +129,57 @@ class CourseFactory(DictFactoryBase):
uuid = factory.Faker('uuid4')
class JobOutlookItemFactory(DictFactoryBase):
value = factory.Faker('sentence')
class PersonFactory(DictFactoryBase):
bio = factory.Faker('paragraphs')
given_name = factory.Faker('first_name')
family_name = factory.Faker('last_name')
profile_image_url = factory.Faker('image_url')
uuid = factory.Faker('uuid4')
class EndorserFactory(DictFactoryBase):
person = PersonFactory()
quote = factory.Faker('sentence')
class ExpectedLearningItemFactory(DictFactoryBase):
value = factory.Faker('sentence')
class FAQFactory(DictFactoryBase):
answer = factory.Faker('sentence')
question = factory.Faker('sentence')
class ProgramFactory(DictFactoryBase):
authoring_organizations = factory.LazyFunction(partial(generate_instances, OrganizationFactory, count=1))
applicable_seat_types = []
banner_image = factory.LazyFunction(generate_sized_stdimage)
card_image_url = factory.Faker('image_url')
courses = factory.LazyFunction(partial(generate_instances, CourseFactory))
expected_learning_items = factory.LazyFunction(partial(generate_instances, CourseFactory))
faq = factory.LazyFunction(partial(generate_instances, FAQFactory))
hidden = False
individual_endorsements = factory.LazyFunction(partial(generate_instances, EndorserFactory))
is_program_eligible_for_one_click_purchase = True
job_outlook_items = factory.LazyFunction(partial(generate_instances, JobOutlookItemFactory))
marketing_slug = factory.Faker('slug')
marketing_url = factory.Faker('url')
max_hours_effort_per_week = fake.random_int(21, 28)
min_hours_effort_per_week = fake.random_int(7, 14)
overview = factory.Faker('sentence')
price_ranges = factory.LazyFunction(generate_price_ranges)
staff = factory.LazyFunction(partial(generate_instances, PersonFactory))
status = 'active'
subtitle = factory.Faker('sentence')
title = factory.Faker('catch_phrase')
type = factory.Faker('word')
uuid = factory.Faker('uuid4')
hidden = False
weeks_to_complete = fake.random_int(1, 45)
class ProgramTypeFactory(DictFactoryBase):
......
# -*- coding: utf-8 -*-
"""Helper functions for working with Programs."""
import datetime
import logging
from collections import defaultdict
from copy import deepcopy
from itertools import chain
......@@ -8,17 +9,21 @@ from urlparse import urljoin
from dateutil.parser import parse
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.utils.functional import cached_property
from edx_rest_api_client.exceptions import SlumberBaseException
from opaque_keys.edx.keys import CourseKey
from pytz import utc
from requests.exceptions import ConnectionError, Timeout
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.commerce.utils import ecommerce_api_client
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.credentials.utils import get_credentials
from student.models import CourseEnrollment
......@@ -28,6 +33,8 @@ from xmodule.modulestore.django import modulestore
# The datetime module's strftime() methods require a year >= 1900.
DEFAULT_ENROLLMENT_START_DATE = datetime.datetime(1900, 1, 1, tzinfo=utc)
log = logging.getLogger(__name__)
def get_program_marketing_url(programs_config):
"""Build a URL used to link to programs on the marketing site."""
......@@ -507,27 +514,25 @@ class ProgramMarketingDataExtender(ProgramDataExtender):
uuid=self.data['uuid']
)
program_instructors = cache.get(cache_key)
is_learner_eligible_for_one_click_purchase = self.data['is_program_eligible_for_one_click_purchase']
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 is_learner_eligible_for_one_click_purchase:
is_learner_eligible_for_one_click_purchase = not any(
course_run['is_enrolled'] for course_run in course['course_runs']
)
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.update({
'instructors': program_instructors,
'is_learner_eligible_for_one_click_purchase': is_learner_eligible_for_one_click_purchase,
})
self.data['instructors'] = program_instructors
def extend(self):
"""Execute extension handlers, returning the extended data."""
self.data.update(super(ProgramMarketingDataExtender, self).extend())
self._collect_one_click_purchase_eligibility_data()
return self.data
@classmethod
def _handlers(cls, prefix):
......@@ -582,3 +587,53 @@ class ProgramMarketingDataExtender(ProgramDataExtender):
self.instructors.update(
{instructor.get('name'): instructor for instructor in course_instructors.get('instructors', [])}
)
def _collect_one_click_purchase_eligibility_data(self):
"""
Extend the program data with data about learner's eligibility for one click purchase,
discount data of the program and SKUs of seats that should be added to basket.
"""
applicable_seat_types = self.data['applicable_seat_types']
is_learner_eligible_for_one_click_purchase = self.data['is_program_eligible_for_one_click_purchase']
skus = []
if is_learner_eligible_for_one_click_purchase:
for course in self.data['courses']:
is_learner_eligible_for_one_click_purchase = not any(
course_run['is_enrolled'] for course_run in course['course_runs']
)
if is_learner_eligible_for_one_click_purchase:
published_course_runs = filter(lambda run: run['status'] == 'published', course['course_runs'])
if len(published_course_runs) == 1:
for seat in published_course_runs[0]['seats']:
if seat['type'] in applicable_seat_types:
skus.append(seat['sku'])
else:
# If a course in the program has more than 1 published course run
# learner won't be eligible for a one click purchase.
is_learner_eligible_for_one_click_purchase = False
skus = []
break
else:
skus = []
break
if skus:
try:
User = get_user_model()
service_user = User.objects.get(username=settings.ECOMMERCE_SERVICE_WORKER_USERNAME)
api = ecommerce_api_client(service_user)
# Make an API call to calculate the discounted price
discount_data = api.baskets.calculate.get(sku=skus)
self.data.update({
'discount_data': discount_data,
'full_program_price': discount_data['total_incl_tax']
})
except (ConnectionError, SlumberBaseException, Timeout):
log.exception('Failed to get discount price for following product SKUs: %s ', ', '.join(skus))
self.data.update({
'is_learner_eligible_for_one_click_purchase': is_learner_eligible_for_one_click_purchase,
'skus': skus,
})
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