Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
course-discovery
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
course-discovery
Commits
91bdf0e6
Commit
91bdf0e6
authored
Apr 25, 2016
by
Awais
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ECOM-3739
Creating end-point for Affiliate datafeed window.
parent
ef7d6985
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
547 additions
and
4 deletions
+547
-4
course_discovery/apps/api/renderers.py
+11
-0
course_discovery/apps/api/serializers.py
+28
-0
course_discovery/apps/api/tests/test_serializers.py
+24
-1
course_discovery/apps/api/v1/tests/affiliate_window_product_feed.1.4.dtd
+333
-0
course_discovery/apps/api/v1/tests/test_views/test_affiliate_window.py
+112
-0
course_discovery/apps/api/v1/urls.py
+8
-1
course_discovery/apps/api/v1/views.py
+28
-2
course_discovery/settings/base.py
+1
-0
requirements/base.txt
+1
-0
requirements/test.txt
+1
-0
No files found.
course_discovery/apps/api/renderers.py
0 → 100644
View file @
91bdf0e6
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'
course_discovery/apps/api/serializers.py
View file @
91bdf0e6
...
...
@@ -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'
course_discovery/apps/api/tests/test_serializers.py
View file @
91bdf0e6
...
...
@@ -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
)
course_discovery/apps/api/v1/tests/affiliate_window_product_feed.1.4.dtd
0 → 100644
View file @
91bdf0e6
<!--
* 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 "£">
<!ENTITY reg "®">
<!ENTITY trade "™">
<!ENTITY egrave "è">
<!ENTITY lsquo "‘">
<!ENTITY rsquo "’">
<!ENTITY ldquo "“">
<!ENTITY rdquo "”">
<!ENTITY Acirc "Â">
<!ENTITY acirc "â">
<!ENTITY Atilde "Ã">
<!ENTITY atilde "ã">
<!ENTITY ocirc "ô">
<!ENTITY ccedil "ç">
<!ENTITY eacute "é">
<!ENTITY icirc "î">
<!ENTITY iacute "í">
<!ENTITY times "×">
<!ENTITY bdquo "„">
<!ENTITY cent "¢">
<!ENTITY euro "€">
<!ENTITY aring "å">
<!ENTITY nbsp " ">
<!ENTITY ecirc "ê">
<!ENTITY ouml "ö">
<!ENTITY uuml "ü">
<!ENTITY aacute "á">
<!ENTITY scaron "š">
<!ENTITY Scaron "Š">
<!ENTITY uacute "ú">
<!ENTITY euml "ë">
<!ENTITY Eacute "É">
<!ENTITY oslash "ø">
<!ENTITY hellip "–">
<!ENTITY deg "°">
<!ENTITY ndash "–">
<!ENTITY Aring "Å">
<!ENTITY agrave "à">
<!ENTITY ntilde "ñ">
<!ENTITY auml "ä">
course_discovery/apps/api/v1/tests/test_views/test_affiliate_window.py
0 → 100644
View file @
91bdf0e6
# 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
))
course_discovery/apps/api/v1/urls.py
View file @
91bdf0e6
""" 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
)
...
...
course_discovery/apps/api/v1/views.py
View file @
91bdf0e6
...
...
@@ -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
)
course_discovery/settings/base.py
View file @
91bdf0e6
...
...
@@ -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'
:
(
...
...
requirements/base.txt
View file @
91bdf0e6
...
...
@@ -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
...
...
requirements/test.txt
View file @
91bdf0e6
...
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment