Commit d7be3278 by McKenzie Welter Committed by McKenzie Welter

consider entitlements in program one click purchase eligibility

parent d872147a
......@@ -401,6 +401,7 @@ class CourseEntitlementSerializer(serializers.ModelSerializer):
currency = serializers.SlugRelatedField(read_only=True, slug_field='code')
sku = serializers.CharField()
mode = serializers.SlugRelatedField(slug_field='name', queryset=SeatType.objects.all())
expires = serializers.DateTimeField()
@classmethod
def prefetch_queryset(cls):
......@@ -408,7 +409,7 @@ class CourseEntitlementSerializer(serializers.ModelSerializer):
class Meta(object):
model = CourseEntitlement
fields = ('mode', 'price', 'currency', 'sku',)
fields = ('mode', 'price', 'currency', 'sku', 'expires')
class MinimalOrganizationSerializer(serializers.ModelSerializer):
......
......@@ -394,7 +394,8 @@ class EcommerceApiDataLoader(AbstractDataLoader):
defaults = {
'price': price,
'currency': currency,
'sku': sku
'sku': sku,
'expires': self.parse_date(body['expires'])
}
course.entitlements.update_or_create(mode=mode, defaults=defaults)
return sku
......
......@@ -438,6 +438,7 @@ class EcommerceApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestC
""" Assert a Course Entitlement was loaded into the database for each entry in the specified data body. """
self.assertEqual(CourseEntitlement.objects.count(), len(body))
for datum in body:
expires = datum['expires']
attributes = {attribute['name']: attribute['value'] for attribute in datum['attribute_values']}
course = Course.objects.get(uuid=attributes['UUID'])
stock_record = datum['stockrecords'][0]
......@@ -450,6 +451,7 @@ class EcommerceApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestC
entitlement = course.entitlements.get(mode=mode)
self.assertEqual(entitlement.expires, expires)
self.assertEqual(entitlement.course, course)
self.assertEqual(entitlement.mode, mode)
self.assertEqual(entitlement.price, price)
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-11-16 17:08
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course_metadata', '0068_auto_20171108_1614'),
]
operations = [
migrations.AddField(
model_name='courseentitlement',
name='expires',
field=models.DateTimeField(blank=True, null=True),
),
]
......@@ -779,6 +779,7 @@ class CourseEntitlement(TimeStampedModel):
price = models.DecimalField(**PRICE_FIELD_CONFIG)
currency = models.ForeignKey(Currency)
sku = models.CharField(max_length=128, null=True, blank=True)
expires = models.DateTimeField(null=True, blank=True)
class Meta(object):
unique_together = (
......@@ -934,6 +935,11 @@ class Program(TimeStampedModel):
applicable_seat_types = [seat_type.name.lower() for seat_type in self.type.applicable_seat_types.all()]
for course in self.courses.all():
entitlement_products = set(course.entitlements.filter(mode__name__in=applicable_seat_types).exclude(
expires__lte=datetime.datetime.now(pytz.UTC)))
if len(entitlement_products) == 1:
continue
course_runs = set(course.course_runs.filter(status=CourseRunStatus.Published)) - excluded_course_runs
if len(course_runs) != 1:
......
......@@ -355,6 +355,7 @@ class CourseEntitlementFactory(factory.DjangoModelFactory):
price = FuzzyDecimal(0.0, 650.0)
currency = factory.Iterator(Currency.objects.all())
sku = FuzzyText(length=8)
expires = FuzzyDateTime(datetime.datetime(2014, 1, 1, tzinfo=UTC))
course = factory.SubFactory(CourseFactory)
class Meta:
......
......@@ -386,7 +386,7 @@ class ProgramEligibilityFilterTests(SiteMixin, TestCase):
courses=[course_run.course],
one_click_purchase_enabled=True,
)
with self.assertNumQueries(11):
with self.assertNumQueries(12):
self.assertEqual(
list(program_filter.queryset({}, Program.objects.all())),
[one_click_purchase_eligible_program]
......
......@@ -510,6 +510,30 @@ class ProgramTests(TestCase):
return factories.ProgramFactory(type=program_type, courses=[course_run.course])
def create_program_with_entitlements_and_seats(self):
verified_seat_type, __ = SeatType.objects.get_or_create(name=Seat.VERIFIED)
program_type = factories.ProgramTypeFactory(applicable_seat_types=[verified_seat_type])
courses = []
for __ in range(3):
entitlement = factories.CourseEntitlementFactory(mode=verified_seat_type, expires=None)
for __ in range(3):
factories.SeatFactory(
course_run=factories.CourseRunFactory(
end=None,
enrollment_end=None,
course=entitlement.course
),
type=Seat.VERIFIED, upgrade_deadline=None
)
courses.append(entitlement.course)
program = factories.ProgramFactory(
courses=courses,
one_click_purchase_enabled=True,
type=program_type,
)
return program, courses
def assert_one_click_purchase_ineligible_program(
self, end=None, enrollment_start=None, enrollment_end=None, seat_type=Seat.VERIFIED,
upgrade_deadline=None, one_click_purchase_enabled=True, excluded_course_runs=None, program_type=None
......@@ -570,6 +594,69 @@ class ProgramTests(TestCase):
)
self.assertTrue(program.is_program_eligible_for_one_click_purchase)
def test_one_click_purchase_eligible_with_entitlements(self):
""" Verify that program is one click purchase eligible when its courses have unexpired entitlement products. """
# Program has one_click_purchase_enabled set to True,
# all courses have a verified mode entitlement product and multiple course runs.
program, __ = self.create_program_with_entitlements_and_seats()
self.assertTrue(program.is_program_eligible_for_one_click_purchase)
def test_one_click_purchase_ineligible_expired_entitlement(self):
""" Verify that program is not one click purchase eligible if course entitlement product is expired. """
program, courses = self.create_program_with_entitlements_and_seats()
expired_entitlement = courses[2].entitlements.first()
expired_entitlement.expires = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=7)
expired_entitlement.save()
self.assertFalse(program.is_program_eligible_for_one_click_purchase)
def test_one_click_purchase_eligible_expired_entitlement_one_run(self):
"""
Verify that program is one click purchase eligible if there is only one
published course run for the course whose entitlement product is expired.
"""
program, courses = self.create_program_with_entitlements_and_seats()
expired_entitlement = courses[2].entitlements.first()
expired_entitlement.expires = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=7)
expired_entitlement.save()
CourseRun.objects.filter(course=courses[2]).delete()
factories.SeatFactory(
course_run=factories.CourseRunFactory(
end=None,
enrollment_end=None,
course=courses[2]
),
type=Seat.VERIFIED, upgrade_deadline=None
)
self.assertTrue(program.is_program_eligible_for_one_click_purchase)
def test_one_click_purchase_eligible_future_expires(self):
""" Verify that program is one click purchase eligible if course entitlement product expires in the future. """
program, courses = self.create_program_with_entitlements_and_seats()
future_expiring_entitlement = courses[1].entitlements.first()
future_expiring_entitlement.expires = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=7)
future_expiring_entitlement.save()
self.assertTrue(program.is_program_eligible_for_one_click_purchase)
def test_one_click_purchase_ineligible_wrong_mode(self):
""" Verify that program is not one click purchase eligible if course entitlement product has the wrong mode. """
program, courses = self.create_program_with_entitlements_and_seats()
honor_seat_type, __ = SeatType.objects.get_or_create(name=Seat.HONOR)
honor_mode_entitlement = courses[0].entitlements.first()
honor_mode_entitlement.mode = honor_seat_type
honor_mode_entitlement.save()
self.assertFalse(program.is_program_eligible_for_one_click_purchase)
def test_one_click_purchase_ineligible_multiple_entitlements(self):
"""
Verify that program is not one click purchase eligible if course has
multiple entitlement products with correct modes.
"""
program, courses = self.create_program_with_entitlements_and_seats()
credit_seat_type, __ = SeatType.objects.get_or_create(name=Seat.CREDIT)
program.type.applicable_seat_types.add(credit_seat_type)
factories.CourseEntitlementFactory(mode=credit_seat_type, expires=None, course=courses[0])
self.assertFalse(program.is_program_eligible_for_one_click_purchase)
def test_one_click_purchase_eligible_with_unpublished_runs(self):
""" Verify that program with unpublished course runs is one click purchase eligible. """
......
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