Commit 484ec256 by Marko Jevtić Committed by GitHub

Merge pull request #15270 from edx/mjevtic/LEARNER-1183

[LEARNER-1183] Prepare program data to be presented on program marketing page
parents 9c4869c1 bf164ae9
...@@ -801,8 +801,13 @@ def program_marketing(request, program_uuid): ...@@ -801,8 +801,13 @@ def program_marketing(request, program_uuid):
if not program_data: if not program_data:
raise Http404 raise Http404
program = ProgramMarketingDataExtender(program_data, request.user).extend()
skus = program.get('skus')
ecommerce_service = EcommerceService()
return render_to_response('courseware/program_marketing.html', { 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 @@ ...@@ -3,6 +3,7 @@
from functools import partial from functools import partial
import factory import factory
import uuid
from faker import Faker from faker import Faker
...@@ -34,6 +35,19 @@ def generate_zulu_datetime(): ...@@ -34,6 +35,19 @@ def generate_zulu_datetime():
return fake.date_time().isoformat() + 'Z' 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): class DictFactoryBase(factory.Factory):
""" """
Subclass this to make factories that can be used to produce fake API response Subclass this to make factories that can be used to produce fake API response
...@@ -77,12 +91,15 @@ class OrganizationFactory(DictFactoryBase): ...@@ -77,12 +91,15 @@ class OrganizationFactory(DictFactoryBase):
key = factory.Faker('word') key = factory.Faker('word')
name = factory.Faker('company') name = factory.Faker('company')
uuid = factory.Faker('uuid4') uuid = factory.Faker('uuid4')
logo_image_url = factory.Faker('image_url')
class SeatFactory(DictFactoryBase): class SeatFactory(DictFactoryBase):
type = factory.Faker('word')
price = factory.Faker('random_int')
currency = 'USD' 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): class CourseRunFactory(DictFactoryBase):
...@@ -91,13 +108,13 @@ class CourseRunFactory(DictFactoryBase): ...@@ -91,13 +108,13 @@ class CourseRunFactory(DictFactoryBase):
enrollment_end = factory.LazyFunction(generate_zulu_datetime) enrollment_end = factory.LazyFunction(generate_zulu_datetime)
enrollment_start = factory.LazyFunction(generate_zulu_datetime) enrollment_start = factory.LazyFunction(generate_zulu_datetime)
image = ImageFactory() image = ImageFactory()
is_enrolled = False
key = factory.LazyFunction(generate_course_run_key) key = factory.LazyFunction(generate_course_run_key)
marketing_url = factory.Faker('url') marketing_url = factory.Faker('url')
pacing_type = 'self_paced' pacing_type = 'self_paced'
seats = factory.LazyFunction(partial(generate_instances, SeatFactory)) seats = factory.LazyFunction(partial(generate_instances, SeatFactory))
short_description = factory.Faker('sentence') short_description = factory.Faker('sentence')
start = factory.LazyFunction(generate_zulu_datetime) start = factory.LazyFunction(generate_zulu_datetime)
status = 'published'
title = factory.Faker('catch_phrase') title = factory.Faker('catch_phrase')
type = 'verified' type = 'verified'
uuid = factory.Faker('uuid4') uuid = factory.Faker('uuid4')
...@@ -112,20 +129,57 @@ class CourseFactory(DictFactoryBase): ...@@ -112,20 +129,57 @@ class CourseFactory(DictFactoryBase):
uuid = factory.Faker('uuid4') 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): class ProgramFactory(DictFactoryBase):
authoring_organizations = factory.LazyFunction(partial(generate_instances, OrganizationFactory, count=1)) authoring_organizations = factory.LazyFunction(partial(generate_instances, OrganizationFactory, count=1))
applicable_seat_types = []
banner_image = factory.LazyFunction(generate_sized_stdimage) banner_image = factory.LazyFunction(generate_sized_stdimage)
card_image_url = factory.Faker('image_url') card_image_url = factory.Faker('image_url')
courses = factory.LazyFunction(partial(generate_instances, CourseFactory)) 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 is_program_eligible_for_one_click_purchase = True
job_outlook_items = factory.LazyFunction(partial(generate_instances, JobOutlookItemFactory))
marketing_slug = factory.Faker('slug') marketing_slug = factory.Faker('slug')
marketing_url = factory.Faker('url') 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' status = 'active'
subtitle = factory.Faker('sentence') subtitle = factory.Faker('sentence')
title = factory.Faker('catch_phrase') title = factory.Faker('catch_phrase')
type = factory.Faker('word') type = factory.Faker('word')
uuid = factory.Faker('uuid4') uuid = factory.Faker('uuid4')
hidden = False weeks_to_complete = fake.random_int(1, 45)
class ProgramTypeFactory(DictFactoryBase): class ProgramTypeFactory(DictFactoryBase):
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Helper functions for working with Programs.""" """Helper functions for working with Programs."""
import datetime import datetime
import logging
from collections import defaultdict from collections import defaultdict
from copy import deepcopy from copy import deepcopy
from itertools import chain from itertools import chain
...@@ -8,17 +9,21 @@ from urlparse import urljoin ...@@ -8,17 +9,21 @@ from urlparse import urljoin
from dateutil.parser import parse from dateutil.parser import parse
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.cache import cache from django.core.cache import cache
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.functional import cached_property from django.utils.functional import cached_property
from edx_rest_api_client.exceptions import SlumberBaseException
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from pytz import utc from pytz import utc
from requests.exceptions import ConnectionError, Timeout
from course_modes.models import CourseMode from course_modes.models import CourseMode
from lms.djangoapps.certificates import api as certificate_api from lms.djangoapps.certificates import api as certificate_api
from lms.djangoapps.commerce.utils import EcommerceService from lms.djangoapps.commerce.utils import EcommerceService
from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.access import has_access
from openedx.core.djangoapps.catalog.utils import get_programs 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.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.credentials.utils import get_credentials from openedx.core.djangoapps.credentials.utils import get_credentials
from student.models import CourseEnrollment from student.models import CourseEnrollment
...@@ -28,6 +33,8 @@ from xmodule.modulestore.django import modulestore ...@@ -28,6 +33,8 @@ from xmodule.modulestore.django import modulestore
# The datetime module's strftime() methods require a year >= 1900. # The datetime module's strftime() methods require a year >= 1900.
DEFAULT_ENROLLMENT_START_DATE = datetime.datetime(1900, 1, 1, tzinfo=utc) DEFAULT_ENROLLMENT_START_DATE = datetime.datetime(1900, 1, 1, tzinfo=utc)
log = logging.getLogger(__name__)
def get_program_marketing_url(programs_config): def get_program_marketing_url(programs_config):
"""Build a URL used to link to programs on the marketing site.""" """Build a URL used to link to programs on the marketing site."""
...@@ -507,27 +514,25 @@ class ProgramMarketingDataExtender(ProgramDataExtender): ...@@ -507,27 +514,25 @@ class ProgramMarketingDataExtender(ProgramDataExtender):
uuid=self.data['uuid'] uuid=self.data['uuid']
) )
program_instructors = cache.get(cache_key) 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']: for course in self.data['courses']:
self._execute('_collect_course', course) self._execute('_collect_course', course)
if not program_instructors: if not program_instructors:
for course_run in course['course_runs']: for course_run in course['course_runs']:
self._execute('_collect_instructors', course_run) 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: if not program_instructors:
# We cache the program instructors list to avoid repeated modulestore queries # We cache the program instructors list to avoid repeated modulestore queries
program_instructors = self.instructors.values() program_instructors = self.instructors.values()
cache.set(cache_key, program_instructors, 3600) cache.set(cache_key, program_instructors, 3600)
self.data.update({ self.data['instructors'] = program_instructors
'instructors': program_instructors,
'is_learner_eligible_for_one_click_purchase': is_learner_eligible_for_one_click_purchase, 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 @classmethod
def _handlers(cls, prefix): def _handlers(cls, prefix):
...@@ -582,3 +587,53 @@ class ProgramMarketingDataExtender(ProgramDataExtender): ...@@ -582,3 +587,53 @@ class ProgramMarketingDataExtender(ProgramDataExtender):
self.instructors.update( self.instructors.update(
{instructor.get('name'): instructor for instructor in course_instructors.get('instructors', [])} {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