Commit eb995328 by Tasawer

add endpoints for stock records + tests for endpoints

ECOM-2231
parent 5e1e6e08
...@@ -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):
......
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 }
]
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)
...@@ -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
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)
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