Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
ecommerce
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
ecommerce
Commits
eec2bfbc
Commit
eec2bfbc
authored
Aug 03, 2015
by
Clinton Blackburn
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #243 from edx/clintonb/course-api-nested-products
Added optional nesting of course products
parents
0c375a95
3a37f80f
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
59 additions
and
32 deletions
+59
-32
ecommerce/extensions/api/serializers.py
+10
-2
ecommerce/extensions/api/v2/tests/views/__init__.py
+27
-0
ecommerce/extensions/api/v2/tests/views/test_courses.py
+14
-4
ecommerce/extensions/api/v2/tests/views/test_products.py
+3
-26
ecommerce/extensions/api/v2/views.py
+5
-0
No files found.
ecommerce/extensions/api/serializers.py
View file @
eec2bfbc
...
@@ -123,9 +123,17 @@ class RefundSerializer(serializers.ModelSerializer):
...
@@ -123,9 +123,17 @@ class RefundSerializer(serializers.ModelSerializer):
class
CourseSerializer
(
serializers
.
HyperlinkedModelSerializer
):
class
CourseSerializer
(
serializers
.
HyperlinkedModelSerializer
):
id
=
serializers
.
RegexField
(
COURSE_ID_REGEX
,
max_length
=
255
)
id
=
serializers
.
RegexField
(
COURSE_ID_REGEX
,
max_length
=
255
)
products
=
ProductSerializer
(
many
=
True
)
products_url
=
serializers
.
SerializerMethodField
()
products_url
=
serializers
.
SerializerMethodField
()
last_edited
=
serializers
.
SerializerMethodField
()
last_edited
=
serializers
.
SerializerMethodField
()
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
CourseSerializer
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
include_products
=
kwargs
[
'context'
]
.
pop
(
'include_products'
,
False
)
if
not
include_products
:
self
.
fields
.
pop
(
'products'
,
None
)
def
get_last_edited
(
self
,
obj
):
def
get_last_edited
(
self
,
obj
):
return
obj
.
history
.
latest
()
.
history_date
.
strftime
(
ISO_8601_FORMAT
)
return
obj
.
history
.
latest
()
.
history_date
.
strftime
(
ISO_8601_FORMAT
)
...
@@ -135,8 +143,8 @@ class CourseSerializer(serializers.HyperlinkedModelSerializer):
...
@@ -135,8 +143,8 @@ class CourseSerializer(serializers.HyperlinkedModelSerializer):
class
Meta
(
object
):
class
Meta
(
object
):
model
=
Course
model
=
Course
fields
=
(
'id'
,
'url'
,
'name'
,
'verification_deadline'
,
'type'
,
'products_url'
,
'last_edited'
)
fields
=
(
'id'
,
'url'
,
'name'
,
'verification_deadline'
,
'type'
,
'products_url'
,
'last_edited'
,
'products'
)
read_only_fields
=
(
'type'
,)
read_only_fields
=
(
'type'
,
'products'
)
extra_kwargs
=
{
extra_kwargs
=
{
'url'
:
{
'view_name'
:
COURSE_DETAIL_VIEW
}
'url'
:
{
'view_name'
:
COURSE_DETAIL_VIEW
}
}
}
...
...
ecommerce/extensions/api/v2/tests/views/__init__.py
View file @
eec2bfbc
from
django.core.urlresolvers
import
reverse
from
django.test
import
RequestFactory
from
django.test
import
RequestFactory
from
oscar.core.loading
import
get_class
from
oscar.test
import
factories
from
oscar.test
import
factories
from
oscar.test.newfactories
import
ProductAttributeValueFactory
from
oscar.test.newfactories
import
ProductAttributeValueFactory
from
ecommerce.core.constants
import
ISO_8601_FORMAT
from
ecommerce.extensions.api.serializers
import
OrderSerializer
from
ecommerce.extensions.api.serializers
import
OrderSerializer
from
ecommerce.tests.mixins
import
UserMixin
,
ThrottlingMixin
from
ecommerce.tests.mixins
import
UserMixin
,
ThrottlingMixin
JSON_CONTENT_TYPE
=
'application/json'
JSON_CONTENT_TYPE
=
'application/json'
Selector
=
get_class
(
'partner.strategy'
,
'Selector'
)
class
OrderDetailViewTestMixin
(
ThrottlingMixin
,
UserMixin
):
class
OrderDetailViewTestMixin
(
ThrottlingMixin
,
UserMixin
):
...
@@ -42,3 +46,26 @@ class TestServerUrlMixin(object):
...
@@ -42,3 +46,26 @@ class TestServerUrlMixin(object):
def
get_full_url
(
self
,
path
):
def
get_full_url
(
self
,
path
):
""" Returns a complete URL with the given path. """
""" Returns a complete URL with the given path. """
return
'http://testserver'
+
path
return
'http://testserver'
+
path
class
ProductSerializerMixin
(
TestServerUrlMixin
):
def
serialize_product
(
self
,
product
):
""" Serializes a Product to a Python dict. """
attribute_values
=
[{
'name'
:
av
.
attribute
.
name
,
'value'
:
av
.
value
}
for
av
in
product
.
attribute_values
.
all
()]
data
=
{
'id'
:
product
.
id
,
'url'
:
self
.
get_full_url
(
reverse
(
'api:v2:product-detail'
,
kwargs
=
{
'pk'
:
product
.
id
})),
'structure'
:
product
.
structure
,
'product_class'
:
unicode
(
product
.
get_product_class
()),
'title'
:
product
.
title
,
'expires'
:
product
.
expires
.
strftime
(
ISO_8601_FORMAT
)
if
product
.
expires
else
None
,
'attribute_values'
:
attribute_values
}
info
=
Selector
()
.
strategy
()
.
fetch_for_product
(
product
)
data
.
update
({
'is_available_to_buy'
:
info
.
availability
.
is_available_to_buy
,
'price'
:
"{0:.2f}"
.
format
(
info
.
price
.
excl_tax
)
if
info
.
availability
.
is_available_to_buy
else
None
})
return
data
ecommerce/extensions/api/v2/tests/views/test_courses.py
View file @
eec2bfbc
...
@@ -10,7 +10,7 @@ from waffle import Switch
...
@@ -10,7 +10,7 @@ from waffle import Switch
from
ecommerce.core.constants
import
ISO_8601_FORMAT
from
ecommerce.core.constants
import
ISO_8601_FORMAT
from
ecommerce.courses.models
import
Course
from
ecommerce.courses.models
import
Course
from
ecommerce.courses.publishers
import
LMSPublisher
from
ecommerce.courses.publishers
import
LMSPublisher
from
ecommerce.extensions.api.v2.tests.views
import
JSON_CONTENT_TYPE
,
TestServerUrl
Mixin
from
ecommerce.extensions.api.v2.tests.views
import
JSON_CONTENT_TYPE
,
ProductSerializer
Mixin
from
ecommerce.extensions.catalogue.tests.mixins
import
CourseCatalogTestMixin
from
ecommerce.extensions.catalogue.tests.mixins
import
CourseCatalogTestMixin
from
ecommerce.tests.mixins
import
UserMixin
from
ecommerce.tests.mixins
import
UserMixin
...
@@ -19,7 +19,7 @@ ProductClass = get_model('catalogue', 'ProductClass')
...
@@ -19,7 +19,7 @@ ProductClass = get_model('catalogue', 'ProductClass')
Selector
=
get_class
(
'partner.strategy'
,
'Selector'
)
Selector
=
get_class
(
'partner.strategy'
,
'Selector'
)
class
CourseViewSetTests
(
TestServerUrl
Mixin
,
CourseCatalogTestMixin
,
UserMixin
,
TestCase
):
class
CourseViewSetTests
(
ProductSerializer
Mixin
,
CourseCatalogTestMixin
,
UserMixin
,
TestCase
):
maxDiff
=
None
maxDiff
=
None
list_path
=
reverse
(
'api:v2:course-list'
)
list_path
=
reverse
(
'api:v2:course-list'
)
...
@@ -32,14 +32,14 @@ class CourseViewSetTests(TestServerUrlMixin, CourseCatalogTestMixin, UserMixin,
...
@@ -32,14 +32,14 @@ class CourseViewSetTests(TestServerUrlMixin, CourseCatalogTestMixin, UserMixin,
def
create_course
(
self
):
def
create_course
(
self
):
return
Course
.
objects
.
create
(
id
=
'edX/DemoX/Demo_Course'
,
name
=
'Test Course'
)
return
Course
.
objects
.
create
(
id
=
'edX/DemoX/Demo_Course'
,
name
=
'Test Course'
)
def
serialize_course
(
self
,
course
):
def
serialize_course
(
self
,
course
,
include_products
=
False
):
""" Serializes a course to a Python dict. """
""" Serializes a course to a Python dict. """
products_url
=
self
.
get_full_url
(
reverse
(
'api:v2:course-product-list'
,
products_url
=
self
.
get_full_url
(
reverse
(
'api:v2:course-product-list'
,
kwargs
=
{
'parent_lookup_course_id'
:
course
.
id
}))
kwargs
=
{
'parent_lookup_course_id'
:
course
.
id
}))
last_edited
=
course
.
history
.
latest
()
.
history_date
.
strftime
(
ISO_8601_FORMAT
)
last_edited
=
course
.
history
.
latest
()
.
history_date
.
strftime
(
ISO_8601_FORMAT
)
return
{
data
=
{
'id'
:
course
.
id
,
'id'
:
course
.
id
,
'name'
:
course
.
name
,
'name'
:
course
.
name
,
'verification_deadline'
:
course
.
verification_deadline
,
'verification_deadline'
:
course
.
verification_deadline
,
...
@@ -49,6 +49,11 @@ class CourseViewSetTests(TestServerUrlMixin, CourseCatalogTestMixin, UserMixin,
...
@@ -49,6 +49,11 @@ class CourseViewSetTests(TestServerUrlMixin, CourseCatalogTestMixin, UserMixin,
'last_edited'
:
last_edited
'last_edited'
:
last_edited
}
}
if
include_products
:
data
[
'products'
]
=
[
self
.
serialize_product
(
product
)
for
product
in
course
.
products
.
all
()]
return
data
def
test_list
(
self
):
def
test_list
(
self
):
""" Verify the view returns a list of Courses. """
""" Verify the view returns a list of Courses. """
response
=
self
.
client
.
get
(
self
.
list_path
)
response
=
self
.
client
.
get
(
self
.
list_path
)
...
@@ -100,6 +105,11 @@ class CourseViewSetTests(TestServerUrlMixin, CourseCatalogTestMixin, UserMixin,
...
@@ -100,6 +105,11 @@ class CourseViewSetTests(TestServerUrlMixin, CourseCatalogTestMixin, UserMixin,
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertDictEqual
(
json
.
loads
(
response
.
content
),
self
.
serialize_course
(
self
.
course
))
self
.
assertDictEqual
(
json
.
loads
(
response
.
content
),
self
.
serialize_course
(
self
.
course
))
# Verify nested products can be included
response
=
self
.
client
.
get
(
path
+
'?include_products=true'
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertDictEqual
(
json
.
loads
(
response
.
content
),
self
.
serialize_course
(
self
.
course
,
include_products
=
True
))
def
test_update
(
self
):
def
test_update
(
self
):
""" Verify the view updates the information of existing courses. """
""" Verify the view updates the information of existing courses. """
course_id
=
self
.
course
.
id
course_id
=
self
.
course
.
id
...
...
ecommerce/extensions/api/v2/tests/views/test_products.py
View file @
eec2bfbc
...
@@ -4,21 +4,19 @@ import json
...
@@ -4,21 +4,19 @@ import json
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
django.test
import
TestCase
from
django.test
import
TestCase
from
oscar.core.loading
import
get_model
,
get_class
from
oscar.core.loading
import
get_model
import
pytz
import
pytz
from
ecommerce.core.constants
import
ISO_8601_FORMAT
from
ecommerce.courses.models
import
Course
from
ecommerce.courses.models
import
Course
from
ecommerce.extensions.api.v2.tests.views
import
JSON_CONTENT_TYPE
,
TestServerUrl
Mixin
from
ecommerce.extensions.api.v2.tests.views
import
JSON_CONTENT_TYPE
,
ProductSerializer
Mixin
from
ecommerce.extensions.catalogue.tests.mixins
import
CourseCatalogTestMixin
from
ecommerce.extensions.catalogue.tests.mixins
import
CourseCatalogTestMixin
from
ecommerce.tests.mixins
import
UserMixin
from
ecommerce.tests.mixins
import
UserMixin
Product
=
get_model
(
'catalogue'
,
'Product'
)
Product
=
get_model
(
'catalogue'
,
'Product'
)
ProductClass
=
get_model
(
'catalogue'
,
'ProductClass'
)
ProductClass
=
get_model
(
'catalogue'
,
'ProductClass'
)
Selector
=
get_class
(
'partner.strategy'
,
'Selector'
)
class
ProductViewSetTests
(
TestServerUrl
Mixin
,
CourseCatalogTestMixin
,
UserMixin
,
TestCase
):
class
ProductViewSetTests
(
ProductSerializer
Mixin
,
CourseCatalogTestMixin
,
UserMixin
,
TestCase
):
maxDiff
=
None
maxDiff
=
None
def
setUp
(
self
):
def
setUp
(
self
):
...
@@ -31,27 +29,6 @@ class ProductViewSetTests(TestServerUrlMixin, CourseCatalogTestMixin, UserMixin,
...
@@ -31,27 +29,6 @@ class ProductViewSetTests(TestServerUrlMixin, CourseCatalogTestMixin, UserMixin,
expires
=
datetime
.
datetime
(
2100
,
1
,
1
,
tzinfo
=
pytz
.
UTC
)
expires
=
datetime
.
datetime
(
2100
,
1
,
1
,
tzinfo
=
pytz
.
UTC
)
self
.
seat
=
self
.
course
.
create_or_update_seat
(
'honor'
,
False
,
0
,
expires
=
expires
)
self
.
seat
=
self
.
course
.
create_or_update_seat
(
'honor'
,
False
,
0
,
expires
=
expires
)
def
serialize_product
(
self
,
product
):
""" Serializes a Product to a Python dict. """
attribute_values
=
[{
'name'
:
av
.
attribute
.
name
,
'value'
:
av
.
value
}
for
av
in
product
.
attribute_values
.
all
()]
data
=
{
'id'
:
product
.
id
,
'url'
:
self
.
get_full_url
(
reverse
(
'api:v2:product-detail'
,
kwargs
=
{
'pk'
:
product
.
id
})),
'structure'
:
product
.
structure
,
'product_class'
:
unicode
(
product
.
get_product_class
()),
'title'
:
product
.
title
,
'expires'
:
product
.
expires
.
strftime
(
ISO_8601_FORMAT
)
if
product
.
expires
else
None
,
'attribute_values'
:
attribute_values
}
info
=
Selector
()
.
strategy
()
.
fetch_for_product
(
product
)
data
.
update
({
'is_available_to_buy'
:
info
.
availability
.
is_available_to_buy
,
'price'
:
"{0:.2f}"
.
format
(
info
.
price
.
excl_tax
)
if
info
.
availability
.
is_available_to_buy
else
None
})
return
data
def
test_list
(
self
):
def
test_list
(
self
):
""" Verify a list of products is returned. """
""" Verify a list of products is returned. """
path
=
reverse
(
'api:v2:product-list'
)
path
=
reverse
(
'api:v2:product-list'
)
...
...
ecommerce/extensions/api/v2/views.py
View file @
eec2bfbc
...
@@ -500,6 +500,11 @@ class CourseViewSet(NonDestroyableModelViewSet):
...
@@ -500,6 +500,11 @@ class CourseViewSet(NonDestroyableModelViewSet):
serializer_class
=
serializers
.
CourseSerializer
serializer_class
=
serializers
.
CourseSerializer
permission_classes
=
(
IsAuthenticated
,
IsAdminUser
,)
permission_classes
=
(
IsAuthenticated
,
IsAdminUser
,)
def
get_serializer_context
(
self
):
context
=
super
(
CourseViewSet
,
self
)
.
get_serializer_context
()
context
[
'include_products'
]
=
bool
(
self
.
request
.
GET
.
get
(
'include_products'
,
False
))
return
context
@detail_route
(
methods
=
[
'post'
])
@detail_route
(
methods
=
[
'post'
])
def
publish
(
self
,
request
,
pk
=
None
):
# pylint: disable=unused-argument
def
publish
(
self
,
request
,
pk
=
None
):
# pylint: disable=unused-argument
""" Publish the course to LMS. """
""" Publish the course to LMS. """
...
...
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