Commit 91bdf0e6 by Awais

ECOM-3739

Creating end-point for Affiliate datafeed window.
parent ef7d6985
from rest_framework_xml.renderers import XMLRenderer
class AffiliateWindowXMLRenderer(XMLRenderer):
""" XML renderer for Affiliate Window product feed.
Note:
See http://wiki.affiliatewindow.com/index.php/Product_Feed_Building for the complete spec.
"""
item_tag_name = 'product'
root_tag_name = 'merchant'
......@@ -154,3 +154,31 @@ class ContainedCoursesSerializer(serializers.Serializer): # pylint: disable=abs
child=serializers.BooleanField(),
help_text=_('Dictionary mapping course IDs to boolean values')
)
class AffiliateWindowSerializer(serializers.ModelSerializer):
pid = serializers.SerializerMethodField()
name = serializers.CharField(source='course_run.course.title')
desc = serializers.CharField(source='course_run.course.short_description')
purl = serializers.CharField(source='course_run.course.marketing_url')
imgurl = serializers.CharField(source='course_run.image')
category = serializers.SerializerMethodField()
price = serializers.SerializerMethodField()
class Meta(object):
model = Seat
fields = (
'name', 'pid', 'desc', 'category', 'purl', 'imgurl', 'price', 'currency'
)
def get_pid(self, obj):
return '{}-{}'.format(obj.course_run.key, obj.type)
def get_price(self, obj):
return {
'actualp': obj.price
}
def get_category(self, obj): # pylint: disable=unused-argument
# Using hardcoded value for category. This value comes from an Affiliate Window taxonomy.
return 'Other Experiences'
......@@ -6,7 +6,7 @@ from django.test import TestCase
from course_discovery.apps.api.serializers import(
CatalogSerializer, CourseSerializer, CourseRunSerializer, ContainedCoursesSerializer, ImageSerializer,
SubjectSerializer, PrerequisiteSerializer, VideoSerializer, OrganizationSerializer, SeatSerializer,
PersonSerializer,
PersonSerializer, AffiliateWindowSerializer
)
from course_discovery.apps.catalogs.tests.factories import CatalogFactory
from course_discovery.apps.core.tests.factories import UserFactory
......@@ -215,3 +215,26 @@ class PersonSerializerTests(TestCase):
}
self.assertDictEqual(serializer.data, expected)
class AffiliateWindowSerializerTests(TestCase):
def test_data(self):
user = UserFactory()
CatalogFactory(query='*:*', viewers=[user])
course_run = CourseRunFactory()
seat = SeatFactory(course_run=course_run)
serializer = AffiliateWindowSerializer(seat)
expected = {
'pid': '{}-{}'.format(course_run.key, seat.type),
'name': course_run.course.title,
'desc': course_run.course.short_description,
'purl': course_run.course.marketing_url,
'price': {
'actualp': seat.price
},
'currency': seat.currency.code,
'imgurl': course_run.image.src,
'category': 'Other Experiences'
}
self.assertDictEqual(serializer.data, expected)
<!--
* All rights reserved. No part of this code may be reproduced, modified,
* amended or retransmitted in any form or by any means for any purpose without
* prior written consent of Digital Window.
* You must ensure that this copyright notice remains intact at all times
*
* @category DigitalWindow
* @package Merchant
* @copyright Copyright (c) Digital Window Limited 2000-2015. All rights reserved.
* @version $Id$
-->
<!ELEMENT merchant (product*)>
<!--
basic requirements for a product are:pid, name, purl, category and price(actualp).
The more data you provide the better an affiliate can promote your products!
-->
<!ELEMENT product ( name, pid, desc, category, purl, imgurl?, price, ean?, upc?, isbn?, mpn?, parentpid?, brand?, modelno?, basket_link?, colour?, cg?, condition?, dimensions?, keywords?, lang?, ptype?, weight?, promotext?, spec?, warranty?, currency?, delcost?, delivery_restrictions?, deltime?, instock?, forsale?, preorder?, stockquant?, saving?, validfrom?, validto?, weboffer?, alternate_image?, large_image?, thumburl?, average_rating?, reviews?, rating?, custom1?, custom2?, custom3?, custom4?, custom5?, fashion?, tickets?, travel?, groupbuy?, telco?, giftoptions?, lastupdated?)>
<!ATTLIST product
weboffer (no|yes|0|1) "no"
preorder (no|yes|0|1) "no"
instock (no|yes|0|1) "yes"
forsale (no|yes|0|1) "yes"
>
<!-- Product name. -->
<!ELEMENT name (#PCDATA)>
<!-- Unique product ID in Merchants Database. -->
<!ELEMENT pid (#PCDATA)>
<!-- Description. -->
<!ELEMENT desc (#PCDATA)>
<!-- Default category the product falls in. -->
<!ELEMENT category (#PCDATA)>
<!-- Deep link into site to the product. -->
<!ELEMENT purl (#PCDATA)>
<!-- Standard product image. -->
<!ELEMENT imgurl (#PCDATA)>
<!-- Prices for the product -->
<!ELEMENT price (actualp, rrpp?, storep?)>
<!-- Textual price for the product (text). -->
<!ELEMENT actualp (#PCDATA)>
<!-- prices for the product (float). -->
<!ELEMENT rrpp (#PCDATA)>
<!-- In-store price (float). -->
<!ELEMENT storep (#PCDATA)>
<!-- Universal Article Number - similar to upc - eruopean standard. -->
<!ELEMENT ean (#PCDATA)>
<!-- Universal Product Code - a number which uniquely identifys the product. -->
<!ELEMENT upc (#PCDATA)>
<!-- A number which uniquely identifies any book. -->
<!ELEMENT isbn (#PCDATA)>
<!-- Manufacturer Part Number - similar to upc - assigned by manufacturer. -->
<!ELEMENT mpn (#PCDATA)>
<!-- Parent product id. -->
<!ELEMENT parentpid (#PCDATA)>
<!-- Manufacture name or brand -->
<!ELEMENT brand (#PCDATA)>
<!-- Model Number can be assigned to a product. -->
<!ELEMENT modelno (#PCDATA)>
<!-- The link to the basket -->
<!ELEMENT basket_link (#PCDATA)>
<!-- The colour of the product -->
<!ELEMENT colour (#PCDATA)>
<!-- Commission group. -->
<!ELEMENT cg (#PCDATA)>
<!-- Condition - new, very good, good, used, poor, rare, refurbished. -->
<!ELEMENT condition (#PCDATA)>
<!-- The dimensions of the product e.g HxWxL -->
<!ELEMENT dimensions (#PCDATA)>
<!-- The keywords associated with this product -->
<!ELEMENT keywords (#PCDATA)>
<!-- Language the product details are in. -->
<!ELEMENT lang (#PCDATA)>
<!-- Product type - main, bundle, accessory, consumer, business, consumer existing customer, consumer upgrade. -->
<!ELEMENT ptype (#PCDATA)>
<!-- The delivery weight of the product -->
<!ELEMENT weight (#PCDATA)>
<!-- Promotional text, should not include html except <p><br><li><ul><ol>. -->
<!ELEMENT promotext (#PCDATA)>
<!-- Specifications. -->
<!ELEMENT spec (#PCDATA)>
<!-- Warranty text associated with the product. -->
<!ELEMENT warranty (#PCDATA)>
<!-- currency product is sold in -->
<!ELEMENT currency (#PCDATA)>
<!-- Delivery cost (float). -->
<!ELEMENT delcost (#PCDATA)>
<!-- The delivery restrictions on the shipment of the product -->
<!ELEMENT delivery_restrictions (#PCDATA)>
<!-- Delivery period. -->
<!ELEMENT deltime (#PCDATA)>
<!-- Number of items in stock. -->
<!ELEMENT stockquant (#PCDATA)>
<!-- The current price saving on this product -->
<!ELEMENT saving (#PCDATA)>
<!-- Start of any period for which this product is valid. -->
<!ELEMENT validfrom (#PCDATA)>
<!-- End of any period for which this product is valid. -->
<!ELEMENT validto (#PCDATA)>
<!-- An extra image URL to associate with the product -->
<!ELEMENT alternate_image (#PCDATA)>
<!-- An extra image URL to associate with the product -->
<!ELEMENT large_image (#PCDATA)>
<!-- Smaller thumbnail image. -->
<!ELEMENT thumburl (#PCDATA)>
<!-- The average consumer rating of the product -->
<!ELEMENT average_rating (#PCDATA)>
<!-- Reviews from customers associated with the product -->
<!ELEMENT reviews (#PCDATA)>
<!-- The rating for this product -->
<!ELEMENT rating (#PCDATA)>
<!-- Custom field 1 -->
<!ELEMENT custom1 (#PCDATA)>
<!-- Custom field 2 -->
<!ELEMENT custom2 (#PCDATA)>
<!-- Custom field 3 -->
<!ELEMENT custom3 (#PCDATA)>
<!-- Custom field 4 -->
<!ELEMENT custom4 (#PCDATA)>
<!-- Custom field 5 -->
<!ELEMENT custom5 (#PCDATA)>
<!-- Fashion specific fields -->
<!ELEMENT fashion (swatch?, material?, pattern?, size?, suitable_for?)>
<!-- An image url for the swatches associated with this product -->
<!ELEMENT swatch (#PCDATA)>
<!-- The material this item is made from -->
<!ELEMENT material (#PCDATA)>
<!-- The pattern on this material e.g. polka dots -->
<!ELEMENT pattern (#PCDATA)>
<!-- The size of this item -->
<!ELEMENT size (#PCDATA)>
<!-- Who this item suitable for e.g. children, males etc -->
<!ELEMENT suitable_for (#PCDATA)>
<!-- Ticket specific fields -->
<!ELEMENT ticket (available_from?, event_date?, event_name?, genre?, max_price?, min_price?, primary_artist?, second_artist?, venue_address?, venue_name?, latitude?, longitude?)>
<!-- The date that tickets become available from -->
<!ELEMENT available_from (#PCDATA)>
<!-- The date of the event -->
<!ELEMENT event_date (#PCDATA)>
<!-- The name of the event -->
<!ELEMENT event_name (#PCDATA)>
<!-- The category of this event e.g. theatre -->
<!ELEMENT genre (#PCDATA)>
<!-- The maximum price of a ticket -->
<!ELEMENT max_price (#PCDATA)>
<!-- The minimum price of a ticket -->
<!ELEMENT min_price (#PCDATA)>
<!-- The name of the primary artist of this event -->
<!ELEMENT primary_artist (#PCDATA)>
<!-- The name of the supporting artist of this event -->
<!ELEMENT second_artist (#PCDATA)>
<!-- The address of the venue -->
<!ELEMENT venue_address (#PCDATA)>
<!-- The name of the venue the event is being held in -->
<!ELEMENT venue_name (#PCDATA)>
<!-- Travel specific fields -->
<!ELEMENT travel (arrival_code?, availability?, board_basis?, cancellation_policy?, check_in_date?, check_in_time?, check_out_date?, check_out_time?, child_policy?, class?, departure_code?, departure_date?, duration?, guests_per_room?, hotel_address?, hotel_features?, hotel_name?, hotel_stars?, inbound_duration?, inbound_stops?, latitude?, location?, longitude?, outbound_duration?, outbound_stops?, return_date?, room_amenities?, room_description?, room_type?, starting_from_price?, ticket_type?)>
<!-- The arrival airport associated with this airport -->
<!ELEMENT arrival_code (#PCDATA)>
<!-- The availability of the item -->
<!ELEMENT availability (#PCDATA)>
<!-- The board basis of the accommodation -->
<!ELEMENT board_basis (#PCDATA)>
<!-- The cancellation policy of the hotel -->
<!ELEMENT cancellation_policy (#PCDATA)>
<!-- The check in date -->
<!ELEMENT check_in_date (#PCDATA)>
<!-- The check in from time -->
<!ELEMENT check_in_time (#PCDATA)>
<!-- The check out date -->
<!ELEMENT check_out_date (#PCDATA)>
<!-- The check out time -->
<!ELEMENT check_out_time (#PCDATA)>
<!-- The children policy of the hotel -->
<!ELEMENT child_policy (#PCDATA)>
<!-- The class associated with this item -->
<!ELEMENT class (#PCDATA)>
<!-- The departure airport associated with this item -->
<!ELEMENT departure_code (#PCDATA)>
<!-- The departure date associated with this item -->
<!ELEMENT departure_date (#PCDATA)>
<!-- The duration of the stay -->
<!ELEMENT duration (#PCDATA)>
<!-- The number of guests -->
<!ELEMENT guests_per_room (#PCDATA)>
<!-- The address of the hotel -->
<!ELEMENT hotel_address (#PCDATA)>
<!-- The features of the hotel -->
<!ELEMENT hotel_features (#PCDATA)>
<!-- The name of the hotel -->
<!ELEMENT hotel_name (#PCDATA)>
<!-- The star rating of the hotel -->
<!ELEMENT hotel_stars (#PCDATA)>
<!-- The duration of the inbound leg of the journey -->
<!ELEMENT inbound_duration (#PCDATA)>
<!-- The number of stop on the inbound leg of the journey -->
<!ELEMENT inbound_stops (#PCDATA)>
<!-- The latitude of the hotel -->
<!ELEMENT latitude (#PCDATA)>
<!-- The location of the holiday -->
<!ELEMENT location (#PCDATA)>
<!-- The longitude of the hotel -->
<!ELEMENT longitude (#PCDATA)>
<!-- The duration of the outbound leg of the journey -->
<!ELEMENT outbound_duration (#PCDATA)>
<!-- The number of stops on the outbound leg of the journey -->
<!ELEMENT outbound_stops (#PCDATA)>
<!-- The return date associated with this item -->
<!ELEMENT return_date (#PCDATA)>
<!-- The features of the room -->
<!ELEMENT room_amenities (#PCDATA)>
<!-- The description of the room associated with this item -->
<!ELEMENT room_description (#PCDATA)>
<!-- The room type associated with this item -->
<!ELEMENT room_type (#PCDATA)>
<!-- The price that tickets start from for this item -->
<!ELEMENT starting_from_price (#PCDATA)>
<!-- The type of ticket -->
<!ELEMENT ticket_type (#PCDATA)>
<!-- Group buying specific fields -->
<!ELEMENT groupbuy (event_address?, event_city?, deal_start?, deal_end?, group_latitude?, group_longitude?, min_required?, number_sessions?, number_sold?, supplier?, terms?)>
<!-- The address associated with this item -->
<!ELEMENT event_address (#PCDATA)>
<!-- The city associated with this item -->
<!ELEMENT event_city (#PCDATA)>
<!-- The start date of this deal -->
<!ELEMENT deal_start (#PCDATA)>
<!-- The end date of this deal -->
<!ELEMENT deal_end (#PCDATA)>
<!-- The group_latitude associated with this item -->
<!ELEMENT group_latitude (#PCDATA)>
<!-- The group_longitude associated with this item -->
<!ELEMENT group_longitude (#PCDATA)>
<!-- The minimum number to be sold required to make this sale active -->
<!ELEMENT min_required (#PCDATA)>
<!-- The number of sessions associated with this item -->
<!ELEMENT number_sessions (#PCDATA)>
<!-- The quantity already sold -->
<!ELEMENT number_sold (#PCDATA)>
<!-- The supplier of this item -->
<!ELEMENT supplier (#PCDATA)>
<!-- The terms and conditions of this offer -->
<!ELEMENT terms (#PCDATA)>
<!-- Telco specific fields -->
<!ELEMENT telco (connectivity?, contract_type?, gift?, inc_data?, inc_minutes?, inc_texts?, initial_cost?, network?, month_cost?, operating_system?, special_offer?, storage_size?, tariff?, term?)>
<!-- The connectivity options associated with this item -->
<!ELEMENT connectivity (#PCDATA)>
<!-- The type of contract associated with this item -->
<!ELEMENT contract_type (#PCDATA)>
<!-- The free gift associated with this item -->
<!ELEMENT gift (#PCDATA)>
<!-- The amount of monthly inclusive data included in this item -->
<!ELEMENT inc_data (#PCDATA)>
<!-- The number of monthly inclusive minutes included in this item -->
<!ELEMENT inc_minutes (#PCDATA)>
<!-- The number of inclusive texts with this item -->
<!ELEMENT inc_texts (#PCDATA)>
<!-- The initial cost associated with this item -->
<!ELEMENT initial_cost (#PCDATA)>
<!-- The contract provider of this item -->
<!ELEMENT network (#PCDATA)>
<!-- The monthly cost associated with this item -->
<!ELEMENT month_cost (#PCDATA)>
<!-- The operating system for the item -->
<!ELEMENT operating_system (#PCDATA)>
<!-- The details of any special offer associated with this item -->
<!ELEMENT special_offer (#PCDATA)>
<!-- the size of the storage of this item -->
<!ELEMENT storage_size (#PCDATA)>
<!-- The tariff associated with this item -->
<!ELEMENT tariff (#PCDATA)>
<!-- The term of the contract associated with this item -->
<!ELEMENT term (#PCDATA)>
<!-- The Gift Finder options for the product. -->
<!ELEMENT giftoptions (recipientid*, personalityid*, occasionid*)>
<!-- The recipient associated with the product. -->
<!ELEMENT recipientid (#PCDATA)>
<!-- The personality associated with the product. -->
<!ELEMENT personalityid (#PCDATA)>
<!-- The occasion associated with the product. -->
<!ELEMENT occasionid (#PCDATA)>
<!-- Last Updated timestamp, reference: http://www.gnu.org/software/tar/manual/html_node/Date-input-formats.html -->
<!ELEMENT lastupdated (#PCDATA)>
<!ENTITY pound "&#163;">
<!ENTITY reg "&#174;">
<!ENTITY trade "&#153;">
<!ENTITY egrave "&#232;">
<!ENTITY lsquo "&#8216;">
<!ENTITY rsquo "&#8217;">
<!ENTITY ldquo "&#8220;">
<!ENTITY rdquo "&#8221;">
<!ENTITY Acirc "&#194;">
<!ENTITY acirc "&#226;">
<!ENTITY Atilde "&#195;">
<!ENTITY atilde "&#227;">
<!ENTITY ocirc "&#244;">
<!ENTITY ccedil "&#231;">
<!ENTITY eacute "&#233;">
<!ENTITY icirc "&#238;">
<!ENTITY iacute "&#237;">
<!ENTITY times "&#215;">
<!ENTITY bdquo "&#8222;">
<!ENTITY cent "&#162;">
<!ENTITY euro "&#8364;">
<!ENTITY aring "&#229;">
<!ENTITY nbsp "&#160;">
<!ENTITY ecirc "&#234;">
<!ENTITY ouml "&#246;">
<!ENTITY uuml "&#252;">
<!ENTITY aacute "&#225;">
<!ENTITY scaron "&#353;">
<!ENTITY Scaron "&#352;">
<!ENTITY uacute "&#250;">
<!ENTITY euml "&#235;">
<!ENTITY Eacute "&#201;">
<!ENTITY oslash "&#248;">
<!ENTITY hellip "&#8211;">
<!ENTITY deg "&#176;">
<!ENTITY ndash "&#8211;">
<!ENTITY Aring "&#197;">
<!ENTITY agrave "&#224;">
<!ENTITY ntilde "&#241;">
<!ENTITY auml "&#228;">
# pylint: disable=redefined-builtin,no-member
import datetime
from os.path import abspath, join, dirname
import xml.etree.ElementTree as ET
import ddt
import pytz
from lxml import etree
from rest_framework.reverse import reverse
from rest_framework.test import APITestCase
from course_discovery.apps.api.v1.tests.test_views.mixins import SerializationMixin
from course_discovery.apps.catalogs.tests.factories import CatalogFactory
from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.core.tests.mixins import ElasticsearchTestMixin
from course_discovery.apps.course_metadata.models import Seat
from course_discovery.apps.course_metadata.tests.factories import CourseRunFactory, SeatFactory
@ddt.ddt
class AffiliateWindowViewSetTests(ElasticsearchTestMixin, SerializationMixin, APITestCase):
""" Tests for the AffiliateWindowViewSet. """
def setUp(self):
super(AffiliateWindowViewSetTests, self).setUp()
self.user = UserFactory()
self.client.force_authenticate(self.user)
self.catalog = CatalogFactory(query='*:*', viewers=[self.user])
self.enrollment_end = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30)
self.course_run = CourseRunFactory(enrollment_end=self.enrollment_end)
self.seat_verified = SeatFactory(course_run=self.course_run, type=Seat.VERIFIED)
self.course = self.course_run.course
self.affiliate_url = reverse('api:v1:partners:affiliate_window-detail', kwargs={'pk': self.catalog.id})
self.affiliate_window_category = 'Other Experiences'
self.refresh_index()
def test_without_authentication(self):
""" Verify authentication is required when accessing the endpoint. """
self.client.logout()
response = self.client.get(self.affiliate_url)
self.assertEqual(response.status_code, 403)
def test_affiliate_with_supported_seats(self):
""" Verify that endpoint returns course runs for verified and professional seats only. """
response = self.client.get(self.affiliate_url)
self.assertEqual(response.status_code, 200)
root = ET.fromstring(response.content)
self.assertEqual(1, len(root.findall('product')))
self.assert_product_xml(
root.findall('product/[pid="{}-{}"]'.format(self.course_run.key, self.seat_verified.type))[0],
self.seat_verified
)
# Add professional seat.
seat_professional = SeatFactory(course_run=self.course_run, type=Seat.PROFESSIONAL)
response = self.client.get(self.affiliate_url)
root = ET.fromstring(response.content)
self.assertEqual(2, len(root.findall('product')))
self.assert_product_xml(
root.findall('product/[pid="{}-{}"]'.format(self.course_run.key, self.seat_verified.type))[0],
self.seat_verified
)
self.assert_product_xml(
root.findall('product/[pid="{}-{}"]'.format(self.course_run.key, seat_professional.type))[0],
seat_professional
)
@ddt.data(Seat.CREDIT, Seat.HONOR, Seat.AUDIT)
def test_with_non_supported_seats(self, non_supporting_seat):
""" Verify that endpoint returns no data for honor, credit and audit seats. """
self.seat_verified.type = non_supporting_seat
self.seat_verified.save()
response = self.client.get(self.affiliate_url)
self.assertEqual(response.status_code, 200)
root = ET.fromstring(response.content)
self.assertEqual(0, len(root.findall('product')))
def test_with_closed_enrollment(self):
""" Verify that endpoint returns no data if enrollment is close. """
self.course_run.enrollment_end = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=-100)
self.course_run.save()
response = self.client.get(self.affiliate_url)
self.assertEqual(response.status_code, 200)
root = ET.fromstring(response.content)
self.assertEqual(0, len(root.findall('product')))
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.course.title)
self.assertEqual(content.find('desc').text, self.course_run.course.short_description)
self.assertEqual(content.find('purl').text, self.course_run.course.marketing_url)
self.assertEqual(content.find('imgurl').text, self.course_run.image.src)
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, self.affiliate_window_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)
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))
""" API v1 URLs. """
from rest_framework import routers
from django.conf.urls import include, url
from course_discovery.apps.api.v1 import views
urlpatterns = []
partners_router = routers.SimpleRouter()
partners_router.register(r'affiliate_window/catalogs', views.AffiliateWindowViewSet, base_name='affiliate_window')
partners_urls = partners_router.urls
urlpatterns = [
url(r'^partners/', include(partners_urls, namespace='partners')),
]
router = routers.SimpleRouter()
router.register(r'catalogs', views.CatalogViewSet)
......
......@@ -4,6 +4,7 @@ from io import StringIO
from django.core.management import call_command
from django.db.models.functions import Lower
from django.shortcuts import get_object_or_404
from dry_rest_permissions.generics import DRYPermissions
from edx_rest_framework_extensions.permissions import IsSuperuser
from rest_framework import viewsets
......@@ -14,11 +15,12 @@ from rest_framework.response import Response
from course_discovery.apps.api.filters import PermissionsFilter
from course_discovery.apps.api.serializers import (
CatalogSerializer, CourseSerializer, CourseRunSerializer, ContainedCoursesSerializer,
CourseSerializerExcludingClosedRuns,
CourseSerializerExcludingClosedRuns, AffiliateWindowSerializer
)
from course_discovery.apps.api.renderers import AffiliateWindowXMLRenderer
from course_discovery.apps.catalogs.models import Catalog
from course_discovery.apps.course_metadata.constants import COURSE_ID_REGEX, COURSE_RUN_ID_REGEX
from course_discovery.apps.course_metadata.models import Course, CourseRun
from course_discovery.apps.course_metadata.models import Course, CourseRun, Seat
logger = logging.getLogger(__name__)
......@@ -202,3 +204,27 @@ class ManagementViewSet(viewsets.ViewSet):
log=log.getvalue())
return Response(output, content_type='text/plain')
class AffiliateWindowViewSet(viewsets.ViewSet):
""" AffiliateWindow Resource. """
permission_classes = (IsAuthenticated,)
renderer_classes = (AffiliateWindowXMLRenderer,)
serializer_class = AffiliateWindowSerializer
def retrieve(self, request, pk=None): # pylint: disable=redefined-builtin,unused-argument
"""
Return verified and professional seats of courses against provided catalog id.
---
produces:
- application/xml
"""
catalog = get_object_or_404(Catalog, pk=pk)
queryset = catalog.courses().active()
seats = Seat.objects.filter(
course_run__course__in=queryset, type__in=[Seat.VERIFIED, Seat.PROFESSIONAL]
)
serializer = AffiliateWindowSerializer(seats, many=True)
return Response(serializer.data)
......@@ -261,6 +261,7 @@ REST_FRAMEWORK = {
'rest_framework.authentication.SessionAuthentication',
'edx_rest_framework_extensions.authentication.BearerAuthentication',
'edx_rest_framework_extensions.authentication.JwtAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'DEFAULT_PERMISSION_CLASSES': (
......
......@@ -8,6 +8,7 @@ django-sortedm2m==1.1.1
django-waffle==0.11
djangorestframework==3.3.3
djangorestframework-jwt==1.7.2
djangorestframework-xml==1.3.0
django-rest-swagger[reST]==0.3.5
dry-rest-permissions==0.1.6
edx-auth-backends==0.2.3
......
......@@ -6,6 +6,7 @@ ddt==1.0.1
django-nose==1.4.2
edx-lint==0.5.0
factory-boy==2.6.0
lxml==3.4.2
mock==1.3.0
nose-ignore-docstring==0.2
pep8==1.6.2
......
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