Commit 8bf70642 by Renzo Lucioni

Serialize additional fields for Affiliate Window

These fields aren't required, but give additional data to affiliates using the API.

LEARNER-1700
parent c72e8479
......@@ -888,18 +888,50 @@ class AffiliateWindowSerializer(serializers.ModelSerializer):
# We use a hardcoded value since it is determined by Affiliate Window's taxonomy.
CATEGORY = 'Other Experiences'
# These field names are dictated by Affiliate Window (AWIN). These fields are
# required. They're documented at http://wiki.awin.com/index.php/Product_Feed_File_Structure.
pid = serializers.SerializerMethodField()
name = serializers.CharField(source='course_run.title')
desc = serializers.CharField(source='course_run.short_description')
desc = serializers.CharField(source='course_run.full_description')
purl = serializers.CharField(source='course_run.marketing_url')
imgurl = serializers.CharField(source='course_run.card_image_url')
category = serializers.SerializerMethodField()
price = serializers.SerializerMethodField()
# These fields are optional. They're documented at
# http://wiki.awin.com/index.php/Product_Feed_Advanced_File_Structure.
lang = serializers.SerializerMethodField()
validfrom = serializers.DateTimeField(source='course_run.start', format='%Y-%m-%d')
validto = serializers.DateTimeField(source='course_run.end', format='%Y-%m-%d')
# These field names are required by AWIN for data that doesn't fit into one
# of their default fields.
custom1 = serializers.CharField(source='course_run.pacing_type')
custom2 = serializers.SlugRelatedField(source='course_run.level_type', read_only=True, slug_field='name')
custom3 = serializers.SerializerMethodField()
custom4 = serializers.SerializerMethodField()
custom5 = serializers.CharField(source='course_run.short_description')
class Meta:
model = Seat
# The order of these fields must match the order in which they appear in
# the DTD file! Validation will fail otherwise.
fields = (
'name', 'pid', 'desc', 'category', 'purl', 'imgurl', 'price', 'currency'
'name',
'pid',
'desc',
'category',
'purl',
'imgurl',
'price',
'lang',
'currency',
'validfrom',
'validto',
'custom1',
'custom2',
'custom3',
'custom4',
'custom5',
)
def get_pid(self, obj):
......@@ -913,6 +945,17 @@ class AffiliateWindowSerializer(serializers.ModelSerializer):
def get_category(self, obj): # pylint: disable=unused-argument
return self.CATEGORY
def get_lang(self, obj):
language = obj.course_run.language
return language.code.split('-')[0].upper() if language else 'EN'
def get_custom3(self, obj):
return ','.join(subject.name for subject in obj.course_run.subjects.all())
def get_custom4(self, obj):
return ','.join(org.name for org in obj.course_run.authoring_organizations.all())
class FlattenedCourseRunWithCourseSerializer(CourseRunSerializer):
seats = serializers.SerializerMethodField()
......
......@@ -1114,21 +1114,30 @@ class AffiliateWindowSerializerTests(TestCase):
# Verify none of the course run attributes are empty; otherwise, Affiliate Window will report errors.
# pylint: disable=no-member
self.assertTrue(all((course_run.title, course_run.short_description, course_run.marketing_url)))
assert all((course_run.title, course_run.short_description, course_run.marketing_url))
expected = {
'pid': '{}-{}'.format(course_run.key, seat.type),
'name': course_run.title,
'desc': course_run.short_description,
'desc': course_run.full_description,
'purl': course_run.marketing_url,
'price': {
'actualp': seat.price
},
'currency': seat.currency.code,
'imgurl': course_run.card_image_url,
'category': 'Other Experiences'
'category': 'Other Experiences',
'validfrom': course_run.start.strftime('%Y-%m-%d'),
'validto': course_run.end.strftime('%Y-%m-%d'),
'lang': course_run.language.code.split('-')[0].upper(),
'custom1': course_run.pacing_type,
'custom2': course_run.level_type.name,
'custom3': ','.join(subject.name for subject in course_run.subjects.all()),
'custom4': ','.join(org.name for org in course_run.authoring_organizations.all()),
'custom5': course_run.short_description,
}
self.assertDictEqual(serializer.data, expected)
assert serializer.data == expected
class CourseSearchSerializerTests(TestCase):
......
......@@ -100,24 +100,24 @@ class AffiliateWindowViewSetTests(ElasticsearchTestMixin, SerializationMixin, AP
def assert_product_xml(self, content, seat):
""" Helper method to verify product data in xml format. """
self.assertEqual(content.find('pid').text, '{}-{}'.format(self.course_run.key, seat.type))
self.assertEqual(content.find('name').text, self.course_run.title)
self.assertEqual(content.find('desc').text, self.course_run.short_description)
self.assertEqual(content.find('purl').text, self.course_run.marketing_url)
self.assertEqual(content.find('imgurl').text, self.course_run.card_image_url)
self.assertEqual(content.find('price/actualp').text, str(seat.price))
self.assertEqual(content.find('currency').text, seat.currency.code)
self.assertEqual(content.find('category').text, AffiliateWindowSerializer.CATEGORY)
assert content.find('pid').text == '{}-{}'.format(self.course_run.key, seat.type)
assert content.find('name').text == self.course_run.title
assert content.find('desc').text == self.course_run.full_description
assert content.find('purl').text == self.course_run.marketing_url
assert content.find('imgurl').text == self.course_run.card_image_url
assert content.find('price/actualp').text == str(seat.price)
assert content.find('currency').text == seat.currency.code
assert content.find('category').text == AffiliateWindowSerializer.CATEGORY
def test_dtd_with_valid_data(self):
""" Verify the XML data produced by the endpoint conforms to the DTD file. """
response = self.client.get(self.affiliate_url)
self.assertEqual(response.status_code, 200)
assert response.status_code == 200
filename = abspath(join(dirname(dirname(__file__)), 'affiliate_window_product_feed.1.4.dtd'))
dtd = etree.DTD(open(filename))
root = etree.XML(response.content)
self.assertTrue(dtd.validate(root))
assert dtd.validate(root)
def test_permissions(self):
""" Verify only users with the appropriate permissions can access the endpoint. """
......
......@@ -37,7 +37,16 @@ class AffiliateWindowViewSet(viewsets.ViewSet):
courses = catalog.courses()
course_runs = CourseRun.objects.filter(course__in=courses).active().marketable()
seats = Seat.objects.filter(type__in=[Seat.VERIFIED, Seat.PROFESSIONAL]).filter(course_run__in=course_runs)
seats = seats.select_related('course_run').prefetch_related('course_run__course', 'course_run__course__partner')
seats = seats.select_related(
'course_run',
'course_run__language',
'course_run__course',
'course_run__course__level_type',
'course_run__course__partner',
).prefetch_related(
'course_run__course__authoring_organizations',
'course_run__course__subjects',
)
serializer = serializers.AffiliateWindowSerializer(seats, many=True)
return Response(serializer.data)
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