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
db6774fb
Commit
db6774fb
authored
Oct 16, 2015
by
tasawernawaz
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #383 from edx/tasawer/story/ecom-2231-add-stokrecord-end-point
Add StockRecord Endpoint
parents
5e1e6e08
eb995328
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
210 additions
and
18 deletions
+210
-18
ecommerce/extensions/api/serializers.py
+14
-2
ecommerce/extensions/api/v2/tests/views/__init__.py
+13
-13
ecommerce/extensions/api/v2/tests/views/test_stockrecords.py
+149
-0
ecommerce/extensions/api/v2/urls.py
+3
-3
ecommerce/extensions/api/v2/views/stockrecords.py
+31
-0
No files found.
ecommerce/extensions/api/serializers.py
View file @
db6774fb
...
@@ -57,10 +57,22 @@ class ProductAttributeValueSerializer(serializers.ModelSerializer):
...
@@ -57,10 +57,22 @@ class ProductAttributeValueSerializer(serializers.ModelSerializer):
class
StockRecordSerializer
(
serializers
.
ModelSerializer
):
class
StockRecordSerializer
(
serializers
.
ModelSerializer
):
""" Serializer for StockRecordsSerializer objects. """
""" Serializer for stock record objects. """
class
Meta
(
object
):
model
=
StockRecord
fields
=
(
'id'
,
'product'
,
'partner'
,
'partner_sku'
,
'price_currency'
,
'price_excl_tax'
,)
class
PartialStockRecordSerializerForUpdate
(
StockRecordSerializer
):
""" Stock record objects serializer for PUT requests.
Allowed fields to update are 'price_currency' and 'price_excl_tax'.
"""
class
Meta
(
object
):
class
Meta
(
object
):
model
=
StockRecord
model
=
StockRecord
fields
=
(
'
id'
,
'partner'
,
'partner_sku'
,
'
price_currency'
,
'price_excl_tax'
,)
fields
=
(
'price_currency'
,
'price_excl_tax'
,)
class
ProductSerializer
(
serializers
.
HyperlinkedModelSerializer
):
class
ProductSerializer
(
serializers
.
HyperlinkedModelSerializer
):
...
...
ecommerce/extensions/api/v2/tests/views/__init__.py
View file @
db6774fb
from
django.core.urlresolvers
import
reverse
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.core.loading
import
get_class
,
get_model
from
oscar.test
import
factories
from
oscar.test
import
factories
from
oscar.test.newfactories
import
ProductAttributeValueFactory
from
oscar.test.newfactories
import
ProductAttributeValueFactory
...
@@ -9,6 +9,7 @@ from ecommerce.extensions.api.serializers import OrderSerializer
...
@@ -9,6 +9,7 @@ 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'
Product
=
get_model
(
'catalogue'
,
'Product'
)
Selector
=
get_class
(
'partner.strategy'
,
'Selector'
)
Selector
=
get_class
(
'partner.strategy'
,
'Selector'
)
...
@@ -60,7 +61,7 @@ class ProductSerializerMixin(TestServerUrlMixin):
...
@@ -60,7 +61,7 @@ class ProductSerializerMixin(TestServerUrlMixin):
'title'
:
product
.
title
,
'title'
:
product
.
title
,
'expires'
:
product
.
expires
.
strftime
(
ISO_8601_FORMAT
)
if
product
.
expires
else
None
,
'expires'
:
product
.
expires
.
strftime
(
ISO_8601_FORMAT
)
if
product
.
expires
else
None
,
'attribute_values'
:
attribute_values
,
'attribute_values'
:
attribute_values
,
'stockrecords'
:
self
.
serialize_stock_records
(
product
.
stockrecords
.
all
())
'stockrecords'
:
[
self
.
serialize_stockrecord
(
record
)
for
record
in
product
.
stockrecords
.
all
()]
}
}
info
=
Selector
()
.
strategy
()
.
fetch_for_product
(
product
)
info
=
Selector
()
.
strategy
()
.
fetch_for_product
(
product
)
...
@@ -71,14 +72,13 @@ class ProductSerializerMixin(TestServerUrlMixin):
...
@@ -71,14 +72,13 @@ class ProductSerializerMixin(TestServerUrlMixin):
return
data
return
data
def
serialize_stock_records
(
self
,
stock_records
):
def
serialize_stockrecord
(
self
,
stockrecord
):
return
[
""" Serialize a stock record to a python dict. """
{
return
{
'id'
:
record
.
id
,
'id'
:
stockrecord
.
id
,
'partner'
:
record
.
partner
.
id
,
'partner'
:
stockrecord
.
partner
.
id
,
'partner_sku'
:
record
.
partner_sku
,
'product'
:
stockrecord
.
product
.
id
,
'price_currency'
:
record
.
price_currency
,
'partner_sku'
:
stockrecord
.
partner_sku
,
'price_excl_tax'
:
str
(
record
.
price_excl_tax
),
'price_currency'
:
stockrecord
.
price_currency
,
'price_excl_tax'
:
unicode
(
stockrecord
.
price_excl_tax
),
}
for
record
in
stock_records
}
]
ecommerce/extensions/api/v2/tests/views/test_stockrecords.py
0 → 100644
View file @
db6774fb
import
json
from
django.contrib.auth.models
import
Permission
from
django.core.urlresolvers
import
reverse
from
django.test
import
TestCase
from
oscar.core.loading
import
get_model
from
ecommerce.courses.models
import
Course
from
ecommerce.extensions.api.v2.tests.views
import
JSON_CONTENT_TYPE
,
ProductSerializerMixin
from
ecommerce.extensions.catalogue.tests.mixins
import
CourseCatalogTestMixin
from
ecommerce.tests.mixins
import
UserMixin
,
ThrottlingMixin
Product
=
get_model
(
'catalogue'
,
'Product'
)
StockRecord
=
get_model
(
'partner'
,
'StockRecord'
)
class
StockRecordViewSetTests
(
ProductSerializerMixin
,
UserMixin
,
CourseCatalogTestMixin
,
ThrottlingMixin
,
TestCase
):
maxDiff
=
None
list_path
=
reverse
(
'api:v2:stockrecords-list'
)
detail_path
=
'api:v2:stockrecords-detail'
def
setUp
(
self
):
super
(
StockRecordViewSetTests
,
self
)
.
setUp
()
self
.
user
=
self
.
create_user
()
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
self
.
password
)
self
.
course
=
Course
.
objects
.
create
(
id
=
'edX/DemoX/Demo_Course'
,
name
=
'Demo Course'
)
self
.
product
=
self
.
course
.
create_or_update_seat
(
'honor'
,
False
,
0
,
self
.
partner
)
self
.
stockrecord
=
self
.
product
.
stockrecords
.
first
()
self
.
change_permission
=
Permission
.
objects
.
get
(
codename
=
'change_stockrecord'
)
def
test_list
(
self
):
""" Verify a list of stock records is returned. """
StockRecord
.
objects
.
create
(
partner
=
self
.
partner
,
product
=
self
.
product
,
partner_sku
=
'dummy-sku'
,
price_currency
=
'USD'
,
price_excl_tax
=
200.00
)
response
=
self
.
client
.
get
(
self
.
list_path
)
self
.
assertEqual
(
response
.
status_code
,
200
)
results
=
[
self
.
serialize_stockrecord
(
stockrecord
)
for
stockrecord
in
self
.
product
.
stockrecords
.
all
()]
expected
=
{
'count'
:
2
,
'next'
:
None
,
'previous'
:
None
,
'results'
:
results
}
self
.
assertDictEqual
(
json
.
loads
(
response
.
content
),
expected
)
def
test_list_with_no_stockrecords
(
self
):
""" Verify the endpoint returns an empty list. """
StockRecord
.
objects
.
all
()
.
delete
()
response
=
self
.
client
.
get
(
self
.
list_path
)
self
.
assertEqual
(
response
.
status_code
,
200
)
expected
=
{
'count'
:
0
,
'next'
:
None
,
'previous'
:
None
,
'results'
:
[]}
self
.
assertDictEqual
(
json
.
loads
(
response
.
content
),
expected
)
def
test_retrieve_with_invalid_id
(
self
):
""" Verify endpoint returns 404 if no stockrecord is available. """
path
=
reverse
(
self
.
detail_path
,
kwargs
=
{
'pk'
:
999
})
response
=
self
.
client
.
get
(
path
)
self
.
assertEqual
(
response
.
status_code
,
404
)
def
test_retrieve
(
self
):
""" Verify a single stockrecord is returned. """
path
=
reverse
(
self
.
detail_path
,
kwargs
=
{
'pk'
:
self
.
stockrecord
.
id
})
response
=
self
.
client
.
get
(
path
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertDictEqual
(
json
.
loads
(
response
.
content
),
self
.
serialize_stockrecord
(
self
.
stockrecord
))
def
test_update
(
self
):
""" Verify update endpoint allows to update 'price_currency' and 'price_excl_tax'. """
self
.
user
.
user_permissions
.
add
(
self
.
change_permission
)
self
.
user
.
save
()
data
=
{
"price_currency"
:
"PKR"
,
"price_excl_tax"
:
"500.00"
}
response
=
self
.
attempt_update
(
data
)
self
.
assertEqual
(
response
.
status_code
,
200
)
stockrecord
=
StockRecord
.
objects
.
get
(
id
=
self
.
stockrecord
.
id
)
self
.
assertEqual
(
unicode
(
stockrecord
.
price_excl_tax
),
data
[
'price_excl_tax'
])
self
.
assertEqual
(
stockrecord
.
price_currency
,
data
[
'price_currency'
])
def
test_update_without_permission
(
self
):
""" Verify only users with the change_stockrecord permission can update stock records. """
self
.
user
.
user_permissions
.
clear
()
self
.
user
.
save
()
data
=
{
"price_currency"
:
"PKR"
,
"price_excl_tax"
:
"500.00"
}
response
=
self
.
attempt_update
(
data
)
self
.
assertEqual
(
response
.
status_code
,
403
)
def
test_allowed_fields_for_update
(
self
):
""" Verify the endpoint only allows the price_excl_tax and price_currency fields to be updated. """
self
.
user
.
user_permissions
.
add
(
self
.
change_permission
)
self
.
user
.
save
()
data
=
{
"partner_sku"
:
"new_sku"
,
}
response
=
self
.
attempt_update
(
data
)
self
.
assertEqual
(
response
.
status_code
,
400
,
response
.
content
)
stockrecord
=
StockRecord
.
objects
.
get
(
id
=
self
.
stockrecord
.
id
)
self
.
assertEqual
(
self
.
serialize_stockrecord
(
self
.
stockrecord
),
self
.
serialize_stockrecord
(
stockrecord
))
self
.
assertDictEqual
(
json
.
loads
(
response
.
content
),
{
'message'
:
'Only the price_currency and price_excl_tax fields are allowed to be modified.'
})
def
attempt_update
(
self
,
data
):
""" Helper method that attempts to update an existing StockRecord object.
Arguments:
data (dict): Data to be converted to JSON and sent to the API.
Returns:
Response: HTTP response from the API.
"""
path
=
reverse
(
self
.
detail_path
,
kwargs
=
{
'pk'
:
self
.
stockrecord
.
id
})
return
self
.
client
.
put
(
path
,
json
.
dumps
(
data
),
JSON_CONTENT_TYPE
)
def
test_create_stockrecord
(
self
):
""" Verify the endpoint supports the creation of new stock records. """
self
.
user
.
user_permissions
.
add
(
Permission
.
objects
.
get
(
codename
=
'add_stockrecord'
))
self
.
user
.
save
()
response
=
self
.
attempt_create
()
self
.
assertEqual
(
response
.
status_code
,
201
)
# verify stock record exists
self
.
assertTrue
(
StockRecord
.
objects
.
filter
(
product
=
self
.
product
.
id
,
partner_sku
=
"new-sku"
)
.
exists
())
def
test_create_without_permission
(
self
):
""" Verify only users with the add_stockrecord permission can add stock records. """
self
.
user
.
user_permissions
.
clear
()
self
.
user
.
save
()
response
=
self
.
attempt_create
()
self
.
assertEqual
(
response
.
status_code
,
403
)
def
attempt_create
(
self
):
""" Helping method that will try to create a new stockrecord. """
data
=
{
"product"
:
self
.
product
.
id
,
"partner"
:
self
.
partner
.
id
,
"partner_sku"
:
"new-sku"
,
"price_currency"
:
"USD"
,
"price_excl_tax"
:
50.00
}
return
self
.
client
.
post
(
self
.
list_path
,
json
.
dumps
(
data
),
JSON_CONTENT_TYPE
)
ecommerce/extensions/api/v2/urls.py
View file @
db6774fb
...
@@ -7,8 +7,8 @@ from ecommerce.extensions.api.v2.views import (baskets as basket_views, payments
...
@@ -7,8 +7,8 @@ from ecommerce.extensions.api.v2.views import (baskets as basket_views, payments
orders
as
order_views
,
refunds
as
refund_views
,
orders
as
order_views
,
refunds
as
refund_views
,
products
as
product_views
,
courses
as
course_views
,
products
as
product_views
,
courses
as
course_views
,
publication
as
publication_views
,
partners
as
partner_views
,
publication
as
publication_views
,
partners
as
partner_views
,
catalog
as
catalog_views
)
catalog
as
catalog_views
,
stockrecords
as
stockrecords_views
)
ORDER_NUMBER_PATTERN
=
r'(?P<number>[-\w]+)'
ORDER_NUMBER_PATTERN
=
r'(?P<number>[-\w]+)'
BASKET_ID_PATTERN
=
r'(?P<basket_id>[\w]+)'
BASKET_ID_PATTERN
=
r'(?P<basket_id>[\w]+)'
...
@@ -74,5 +74,5 @@ router.register(r'partners', partner_views.PartnerViewSet) \
...
@@ -74,5 +74,5 @@ router.register(r'partners', partner_views.PartnerViewSet) \
.
register
(
r'catalogs'
,
catalog_views
.
CatalogViewSet
,
.
register
(
r'catalogs'
,
catalog_views
.
CatalogViewSet
,
base_name
=
'partner-catalogs'
,
parents_query_lookups
=
[
'partner_id'
])
base_name
=
'partner-catalogs'
,
parents_query_lookups
=
[
'partner_id'
])
router
.
register
(
r'products'
,
product_views
.
ProductViewSet
)
router
.
register
(
r'products'
,
product_views
.
ProductViewSet
)
router
.
register
(
r'stockrecords'
,
stockrecords_views
.
StockRecordViewSet
,
base_name
=
'stockrecords'
)
urlpatterns
+=
router
.
urls
urlpatterns
+=
router
.
urls
ecommerce/extensions/api/v2/views/stockrecords.py
0 → 100644
View file @
db6774fb
from
oscar.core.loading
import
get_model
from
rest_framework
import
status
,
viewsets
from
rest_framework.permissions
import
DjangoModelPermissionsOrAnonReadOnly
from
rest_framework.response
import
Response
from
ecommerce.extensions.api
import
serializers
StockRecord
=
get_model
(
'partner'
,
'StockRecord'
)
class
StockRecordViewSet
(
viewsets
.
ModelViewSet
):
""" Endpoint for listing stock records. """
permission_classes
=
(
DjangoModelPermissionsOrAnonReadOnly
,)
serializer_class
=
serializers
.
StockRecordSerializer
queryset
=
StockRecord
.
objects
.
all
()
def
get_serializer_class
(
self
):
serializer_class
=
self
.
serializer_class
if
self
.
request
.
method
==
'PUT'
:
serializer_class
=
serializers
.
PartialStockRecordSerializerForUpdate
return
serializer_class
def
update
(
self
,
request
,
*
args
,
**
kwargs
):
allowed_fields
=
[
'price_currency'
,
'price_excl_tax'
]
if
any
([
key
not
in
allowed_fields
for
key
in
request
.
data
.
keys
()]):
return
Response
({
'message'
:
"Only the price_currency and price_excl_tax fields are allowed to be modified."
},
status
=
status
.
HTTP_400_BAD_REQUEST
)
return
super
(
StockRecordViewSet
,
self
)
.
update
(
request
,
*
args
,
**
kwargs
)
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