Commit d3081bb2 by Adam Stankiewicz

add enrollment code SKU in Seat model, modify ecommerce data loader to get enrollment codes

parent 1efd2cbd
...@@ -387,6 +387,7 @@ class SeatSerializer(serializers.ModelSerializer): ...@@ -387,6 +387,7 @@ class SeatSerializer(serializers.ModelSerializer):
credit_provider = serializers.CharField() credit_provider = serializers.CharField()
credit_hours = serializers.IntegerField() credit_hours = serializers.IntegerField()
sku = serializers.CharField() sku = serializers.CharField()
bulk_sku = serializers.CharField()
@classmethod @classmethod
def prefetch_queryset(cls): def prefetch_queryset(cls):
...@@ -394,7 +395,7 @@ class SeatSerializer(serializers.ModelSerializer): ...@@ -394,7 +395,7 @@ class SeatSerializer(serializers.ModelSerializer):
class Meta(object): class Meta(object):
model = Seat model = Seat
fields = ('type', 'price', 'currency', 'upgrade_deadline', 'credit_provider', 'credit_hours', 'sku',) fields = ('type', 'price', 'currency', 'upgrade_deadline', 'credit_provider', 'credit_hours', 'sku', 'bulk_sku')
class CourseEntitlementSerializer(serializers.ModelSerializer): class CourseEntitlementSerializer(serializers.ModelSerializer):
......
...@@ -1117,7 +1117,8 @@ class SeatSerializerTests(TestCase): ...@@ -1117,7 +1117,8 @@ class SeatSerializerTests(TestCase):
'upgrade_deadline': json_date_format(seat.upgrade_deadline), 'upgrade_deadline': json_date_format(seat.upgrade_deadline),
'credit_provider': seat.credit_provider, # pylint: disable=no-member 'credit_provider': seat.credit_provider, # pylint: disable=no-member
'credit_hours': seat.credit_hours, # pylint: disable=no-member 'credit_hours': seat.credit_hours, # pylint: disable=no-member
'sku': seat.sku 'sku': seat.sku,
'bulk_sku': seat.bulk_sku
} }
self.assertDictEqual(serializer.data, expected) self.assertDictEqual(serializer.data, expected)
......
...@@ -257,6 +257,7 @@ class EcommerceApiDataLoader(AbstractDataLoader): ...@@ -257,6 +257,7 @@ class EcommerceApiDataLoader(AbstractDataLoader):
super(EcommerceApiDataLoader, self).__init__( super(EcommerceApiDataLoader, self).__init__(
partner, api_url, access_token, token_type, max_workers, is_threadsafe, **kwargs partner, api_url, access_token, token_type, max_workers, is_threadsafe, **kwargs
) )
self.enrollment_skus = []
self.entitlement_skus = [] self.entitlement_skus = []
def ingest(self): def ingest(self):
...@@ -265,11 +266,14 @@ class EcommerceApiDataLoader(AbstractDataLoader): ...@@ -265,11 +266,14 @@ class EcommerceApiDataLoader(AbstractDataLoader):
initial_page = 1 initial_page = 1
course_runs = self._request_course_runs(initial_page) course_runs = self._request_course_runs(initial_page)
entitlements = self._request_entitlments(initial_page) entitlements = self._request_entitlments(initial_page)
enrollment_codes = self._request_enrollment_codes(initial_page)
count = course_runs['count'] + entitlements['count'] count = course_runs['count'] + entitlements['count']
pages = math.ceil(count / self.PAGE_SIZE) pages = math.ceil(count / self.PAGE_SIZE)
self.entitlement_skus = [] self.entitlement_skus = []
self.enrollment_skus = []
self._process_course_runs(course_runs) self._process_course_runs(course_runs)
self._process_entitlements(entitlements) self._process_entitlements(entitlements)
self._process_enrollment_codes(enrollment_codes)
pagerange = range(initial_page + 1, pages + 1) pagerange = range(initial_page + 1, pages + 1)
...@@ -284,9 +288,13 @@ class EcommerceApiDataLoader(AbstractDataLoader): ...@@ -284,9 +288,13 @@ class EcommerceApiDataLoader(AbstractDataLoader):
for future in [executor.submit(self._request_entitlments, page) for page in pagerange]: for future in [executor.submit(self._request_entitlments, page) for page in pagerange]:
response = future.result() response = future.result()
self._process_entitlements(response) self._process_entitlements(response)
for future in [executor.submit(self._request_enrollment_codes, page) for page in pagerange]:
response = future.result()
self._process_enrollment_codes(response)
logger.info('Retrieved %d course seats and %d course entitlements from %s.', course_runs['count'], logger.info('Retrieved %d course seats, %d course entitlements, and %d course enrollment codes from %s.',
entitlements['count'], self.partner.ecommerce_api_url) course_runs['count'], entitlements['count'],
enrollment_codes['count'], self.partner.ecommerce_api_url)
self.delete_orphans() self.delete_orphans()
self._delete_entitlements() self._delete_entitlements()
...@@ -297,6 +305,8 @@ class EcommerceApiDataLoader(AbstractDataLoader): ...@@ -297,6 +305,8 @@ class EcommerceApiDataLoader(AbstractDataLoader):
self._process_course_runs(course_runs) self._process_course_runs(course_runs)
entitlements = self._request_entitlments(page) entitlements = self._request_entitlments(page)
self._process_entitlements(entitlements) self._process_entitlements(entitlements)
enrollment_codes = self._request_enrollment_codes(page)
self._process_enrollment_codes(enrollment_codes)
def _request_course_runs(self, page): def _request_course_runs(self, page):
return self.api_client.courses().get(page=page, page_size=self.PAGE_SIZE, include_products=True) return self.api_client.courses().get(page=page, page_size=self.PAGE_SIZE, include_products=True)
...@@ -304,6 +314,9 @@ class EcommerceApiDataLoader(AbstractDataLoader): ...@@ -304,6 +314,9 @@ class EcommerceApiDataLoader(AbstractDataLoader):
def _request_entitlments(self, page): def _request_entitlments(self, page):
return self.api_client.products().get(page=page, page_size=self.PAGE_SIZE, product_class='Course Entitlement') return self.api_client.products().get(page=page, page_size=self.PAGE_SIZE, product_class='Course Entitlement')
def _request_enrollment_codes(self, page):
return self.api_client.products().get(page=page, page_size=self.PAGE_SIZE, product_class='Enrollment Code')
def _process_course_runs(self, response): def _process_course_runs(self, response):
results = response['results'] results = response['results']
logger.info('Retrieved %d course seats...', len(results)) logger.info('Retrieved %d course seats...', len(results))
...@@ -320,6 +333,14 @@ class EcommerceApiDataLoader(AbstractDataLoader): ...@@ -320,6 +333,14 @@ class EcommerceApiDataLoader(AbstractDataLoader):
body = self.clean_strings(body) body = self.clean_strings(body)
self.entitlement_skus.append(self.update_entitlement(body)) self.entitlement_skus.append(self.update_entitlement(body))
def _process_enrollment_codes(self, response):
results = response['results']
logger.info('Retrieved %d course enrollment codes...', len(results))
for body in results:
body = self.clean_strings(body)
self.enrollment_skus.append(self.update_enrollment_code(body))
def _delete_entitlements(self): def _delete_entitlements(self):
entitlements_to_delete = CourseEntitlement.objects.filter( entitlements_to_delete = CourseEntitlement.objects.filter(
partner=self.partner partner=self.partner
...@@ -453,6 +474,73 @@ class EcommerceApiDataLoader(AbstractDataLoader): ...@@ -453,6 +474,73 @@ class EcommerceApiDataLoader(AbstractDataLoader):
course.entitlements.update_or_create(mode=mode, defaults=defaults) course.entitlements.update_or_create(mode=mode, defaults=defaults)
return sku return sku
def update_enrollment_code(self, body):
"""
Argument:
body (dict): enrollment code product data from ecommerce
Returns:
enrollment code product sku if no exceptions, else None
"""
attributes = {attribute['code']: attribute['value'] for attribute in body['attribute_values']}
course_key = attributes.get('course_key')
title = body['title']
if body['stockrecords']:
stock_record = body['stockrecords'][0]
else:
msg = 'Enrollment code product {title} has no stockrecords'.format(title=title)
logger.warning(msg)
return None
try:
currency_code = stock_record['price_currency']
bulk_sku = stock_record['partner_sku']
except (KeyError, ValueError):
msg = 'A necessary stockrecord field is missing or incorrectly set for enrollment code {title}'.format(
title=title
)
logger.warning(msg)
return None
try:
course_run = CourseRun.objects.get(key=course_key)
except CourseRun.DoesNotExist:
msg = 'Could not find course run {key} while loading enrollment code {title} with sku {sku}'.format(
key=course_key, title=title, sku=bulk_sku
)
logger.warning(msg)
return None
try:
Currency.objects.get(code=currency_code)
except Currency.DoesNotExist:
msg = 'Could not find currency {code} while loading enrollment code {title} with sku {sku}'.format(
code=currency_code, title=title, sku=bulk_sku
)
logger.warning(msg)
return None
seat_type = attributes.get('seat_type')
try:
Seat.objects.get(course_run=course_run, type=seat_type)
except SeatType.DoesNotExist:
msg = 'Could not find seat type {type} while loading enrollment code {title} with sku {sku}'.format(
type=seat_type, title=title, sku=bulk_sku
)
logger.warning(msg)
return None
defaults = {
'bulk_sku': bulk_sku
}
msg = 'Creating enrollment code {title} with sku {sku} for partner {partner}'.format(
title=title, sku=bulk_sku, partner=self.partner
)
logger.info(msg)
course_run.seats.update_or_create(type=seat_type, defaults=defaults)
return bulk_sku
def get_certificate_type(self, product): def get_certificate_type(self, product):
return next( return next(
(att['value'] for att in product['attribute_values'] if att['name'] == 'certificate_type'), (att['value'] for att in product['attribute_values'] if att['name'] == 'certificate_type'),
......
...@@ -231,6 +231,23 @@ ECOMMERCE_API_BODIES = [ ...@@ -231,6 +231,23 @@ ECOMMERCE_API_BODIES = [
"partner_sku": "sku003", "partner_sku": "sku003",
} }
] ]
},
{
"structure": "standalone",
"expires": "2017-01-01T12:00:00Z",
"attribute_values": [
{
"code": "seat_type",
"value": "verified"
}
],
"stockrecords": [
{
"price_currency": "EUR",
"price_excl_tax": "25.00",
"partner_sku": "sku004"
}
]
} }
] ]
}, },
...@@ -255,7 +272,7 @@ ECOMMERCE_API_BODIES = [ ...@@ -255,7 +272,7 @@ ECOMMERCE_API_BODIES = [
{ {
"price_currency": "USD", "price_currency": "USD",
"price_excl_tax": "0.00", "price_excl_tax": "0.00",
"partner_sku": "sku004", "partner_sku": "sku005",
} }
] ]
}, },
...@@ -272,7 +289,7 @@ ECOMMERCE_API_BODIES = [ ...@@ -272,7 +289,7 @@ ECOMMERCE_API_BODIES = [
{ {
"price_currency": "USD", "price_currency": "USD",
"price_excl_tax": "25.00", "price_excl_tax": "25.00",
"partner_sku": "sku005", "partner_sku": "sku006",
} }
] ]
}, },
...@@ -301,7 +318,7 @@ ECOMMERCE_API_BODIES = [ ...@@ -301,7 +318,7 @@ ECOMMERCE_API_BODIES = [
{ {
"price_currency": "USD", "price_currency": "USD",
"price_excl_tax": "250.00", "price_excl_tax": "250.00",
"partner_sku": "sku006", "partner_sku": "sku007",
} }
] ]
}, },
...@@ -330,7 +347,7 @@ ECOMMERCE_API_BODIES = [ ...@@ -330,7 +347,7 @@ ECOMMERCE_API_BODIES = [
{ {
"price_currency": "USD", "price_currency": "USD",
"price_excl_tax": "250.00", "price_excl_tax": "250.00",
"partner_sku": "sku007", "partner_sku": "sku008",
} }
] ]
} }
...@@ -355,7 +372,7 @@ ECOMMERCE_API_BODIES = [ ...@@ -355,7 +372,7 @@ ECOMMERCE_API_BODIES = [
{ {
"price_currency": "123", "price_currency": "123",
"price_excl_tax": "0.00", "price_excl_tax": "0.00",
"partner_sku": "sku008", "partner_sku": "sku009",
} }
] ]
} }
...@@ -380,7 +397,7 @@ ECOMMERCE_API_BODIES = [ ...@@ -380,7 +397,7 @@ ECOMMERCE_API_BODIES = [
{ {
"price_currency": "USD", "price_currency": "USD",
"price_excl_tax": "0.00", "price_excl_tax": "0.00",
"partner_sku": "sku009", "partner_sku": "sku010",
} }
] ]
} }
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2018-03-09 19:38
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course_metadata', '0079_enable_program_default_true'),
]
operations = [
migrations.AddField(
model_name='seat',
name='bulk_sku',
field=models.CharField(blank=True, max_length=128, null=True),
),
]
...@@ -832,6 +832,7 @@ class Seat(TimeStampedModel): ...@@ -832,6 +832,7 @@ class Seat(TimeStampedModel):
credit_provider = models.CharField(max_length=255, null=True, blank=True) credit_provider = models.CharField(max_length=255, null=True, blank=True)
credit_hours = models.IntegerField(null=True, blank=True) credit_hours = models.IntegerField(null=True, blank=True)
sku = models.CharField(max_length=128, null=True, blank=True) sku = models.CharField(max_length=128, null=True, blank=True)
bulk_sku = models.CharField(max_length=128, null=True, blank=True)
class Meta(object): class Meta(object):
unique_together = ( unique_together = (
......
...@@ -155,6 +155,7 @@ class SeatFactory(factory.DjangoModelFactory): ...@@ -155,6 +155,7 @@ class SeatFactory(factory.DjangoModelFactory):
currency = factory.Iterator(Currency.objects.all()) currency = factory.Iterator(Currency.objects.all())
upgrade_deadline = FuzzyDateTime(datetime.datetime(2014, 1, 1, tzinfo=UTC)) upgrade_deadline = FuzzyDateTime(datetime.datetime(2014, 1, 1, tzinfo=UTC))
sku = FuzzyText(length=8) sku = FuzzyText(length=8)
bulk_sku = FuzzyText(length=8)
course_run = factory.SubFactory(CourseRunFactory) course_run = factory.SubFactory(CourseRunFactory)
class Meta: class Meta:
......
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