Commit ea0c0d8a by Ivan Ivic Committed by GitHub

Merge pull request #1052 from edx/iivic/SOL-2168

[SOL-2168] Add Backend Validation for Course Seat Types
parents f409cf0d 78b63e2b
...@@ -731,6 +731,21 @@ class CouponViewSetFunctionalTest(CouponMixin, CourseCatalogTestMixin, CourseCat ...@@ -731,6 +731,21 @@ class CouponViewSetFunctionalTest(CouponMixin, CourseCatalogTestMixin, CourseCat
}) })
self.assert_post_response_status(self.data) self.assert_post_response_status(self.data)
@ddt.data(8, 'string', {'dict': 'type'})
def test_creating_coupon_with_wrong_course_seat_types(self, course_seat_types):
""" Verify creating coupon with course seat types not a list returns bad request. """
self.data.update({'course_seat_types': course_seat_types})
self.assert_post_response_status(self.data)
def test_creating_coupon_with_course_seat_types(self):
""" Verify creating coupon with course seat types list creates coupon. """
self.data.update({
'catalog_query': 'test',
'course_seat_types': ['verified'],
'stock_record_ids': None
})
self.assert_post_response_status(self.data, status.HTTP_200_OK)
class CouponCategoriesListViewTests(TestCase): class CouponCategoriesListViewTests(TestCase):
""" Tests for the coupon category list view. """ """ Tests for the coupon category list view. """
......
...@@ -8,6 +8,7 @@ from django.core.exceptions import ValidationError ...@@ -8,6 +8,7 @@ from django.core.exceptions import ValidationError
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.http import Http404 from django.http import Http404
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _
from oscar.core.loading import get_model from oscar.core.loading import get_model
from rest_framework import filters, generics, serializers, status, viewsets from rest_framework import filters, generics, serializers, status, viewsets
from rest_framework.permissions import IsAdminUser, IsAuthenticated from rest_framework.permissions import IsAdminUser, IsAuthenticated
...@@ -44,7 +45,7 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet): ...@@ -44,7 +45,7 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet):
""" Coupon resource. """ """ Coupon resource. """
queryset = Product.objects.filter(product_class__name='Coupon') queryset = Product.objects.filter(product_class__name='Coupon')
permission_classes = (IsAuthenticated, IsAdminUser) permission_classes = (IsAuthenticated, IsAdminUser)
filter_backends = (filters.DjangoFilterBackend, ) filter_backends = (filters.DjangoFilterBackend,)
filter_class = ProductFilter filter_class = ProductFilter
def get_serializer_class(self): def get_serializer_class(self):
...@@ -94,7 +95,14 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet): ...@@ -94,7 +95,14 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet):
pass pass
if course_seat_types: if course_seat_types:
course_seat_types = prepare_course_seat_types(course_seat_types) try:
course_seat_types = prepare_course_seat_types(course_seat_types)
except (AttributeError, TypeError) as e:
logger.exception('Failed to create coupon. Invalid course seat types data: %s', e.message)
return Response(
_('Invalid course seat types data: {}').format(e.message),
status=status.HTTP_400_BAD_REQUEST
)
try: try:
category = Category.objects.get(name=category_data['name']) category = Category.objects.get(name=category_data['name'])
......
...@@ -94,16 +94,33 @@ class ConditionalOffer(AbstractConditionalOffer): ...@@ -94,16 +94,33 @@ class ConditionalOffer(AbstractConditionalOffer):
return super(ConditionalOffer, self).is_condition_satisfied(basket) # pylint: disable=bad-super-call return super(ConditionalOffer, self).is_condition_satisfied(basket) # pylint: disable=bad-super-call
def validate_credit_seat_type(value): def validate_credit_seat_type(course_seat_types, allowed_seat_types):
if len(value.split(',')) > 1 and 'credit' in value: if not isinstance(course_seat_types, basestring):
logger.exception('Failed to create Range. Credit seat types must be type str or unicode.')
raise ValidationError(_('Credit seat types must be type str or unicode.'))
course_seat_types_list = course_seat_types.split(',')
if len(course_seat_types_list) > 1 and 'credit' in course_seat_types_list:
logger.exception('Failed to create Range. Credit seat type cannot be paired with other seat types.')
raise ValidationError('Credit seat types cannot be paired with other seat types.') raise ValidationError('Credit seat types cannot be paired with other seat types.')
if not set(course_seat_types_list).issubset(set(allowed_seat_types)):
logger.exception(
'Failed to create Range. Not allowed course seat types %s. Allowed values for course seat types are %s',
course_seat_types_list, allowed_seat_types
)
raise ValidationError(_(
'Not allowed course seat types {}. Allowed values for course seat types are {}'
).format(course_seat_types_list, allowed_seat_types))
class Range(AbstractRange): class Range(AbstractRange):
UPDATABLE_RANGE_FIELDS = [ UPDATABLE_RANGE_FIELDS = [
'catalog_query', 'catalog_query',
'course_seat_types', 'course_seat_types',
] ]
ALLOWED_SEAT_TYPES = ['credit', 'professional', 'verified']
catalog = models.ForeignKey('catalogue.Catalog', blank=True, null=True, related_name='ranges') catalog = models.ForeignKey('catalogue.Catalog', blank=True, null=True, related_name='ranges')
catalog_query = models.TextField(blank=True, null=True) catalog_query = models.TextField(blank=True, null=True)
course_seat_types = models.CharField( course_seat_types = models.CharField(
...@@ -135,6 +152,9 @@ class Range(AbstractRange): ...@@ -135,6 +152,9 @@ class Range(AbstractRange):
logger.exception(exception_msg) logger.exception(exception_msg)
raise ValidationError(validation_error_msg) raise ValidationError(validation_error_msg)
if self.course_seat_types:
validate_credit_seat_type(self.course_seat_types, self.ALLOWED_SEAT_TYPES)
def run_catalog_query(self, product): def run_catalog_query(self, product):
""" """
Retrieve the results from running the query contained in catalog_query field. Retrieve the results from running the query contained in catalog_query field.
......
...@@ -12,7 +12,6 @@ from oscar.test import factories ...@@ -12,7 +12,6 @@ from oscar.test import factories
from ecommerce.core.tests.decorators import mock_course_catalog_api_client from ecommerce.core.tests.decorators import mock_course_catalog_api_client
from ecommerce.coupons.tests.mixins import CourseCatalogMockMixin, CouponMixin from ecommerce.coupons.tests.mixins import CourseCatalogMockMixin, CouponMixin
from ecommerce.extensions.catalogue.tests.mixins import CourseCatalogTestMixin from ecommerce.extensions.catalogue.tests.mixins import CourseCatalogTestMixin
from ecommerce.extensions.offer.models import validate_credit_seat_type
from ecommerce.tests.testcases import TestCase from ecommerce.tests.testcases import TestCase
Catalog = get_model('catalogue', 'Catalog') Catalog = get_model('catalogue', 'Catalog')
...@@ -75,7 +74,7 @@ class RangeTests(CouponMixin, CourseCatalogTestMixin, CourseCatalogMockMixin, Te ...@@ -75,7 +74,7 @@ class RangeTests(CouponMixin, CourseCatalogTestMixin, CourseCatalogMockMixin, Te
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
""" """
self.range.catalog_query = large_query self.range.catalog_query = large_query
self.range.course_seat_types = ['verified', ] self.range.course_seat_types = 'verified'
self.range.save() self.range.save()
self.assertEqual(self.range.catalog_query, large_query) self.assertEqual(self.range.catalog_query, large_query)
...@@ -142,16 +141,6 @@ class RangeTests(CouponMixin, CourseCatalogTestMixin, CourseCatalogMockMixin, Te ...@@ -142,16 +141,6 @@ class RangeTests(CouponMixin, CourseCatalogTestMixin, CourseCatalogMockMixin, Te
self.range.course_seat_types = 'verified' self.range.course_seat_types = 'verified'
self.assertEqual(len(self.range.all_products()), 0) self.assertEqual(len(self.range.all_products()), 0)
def test_credit_seat_type_validator(self):
"""
Verify the validator raises error for combination of credit and another seat type.
"""
self.assertIsNone(validate_credit_seat_type('verified,professional'))
self.assertIsNone(validate_credit_seat_type('credit'))
with self.assertRaises(ValidationError):
validate_credit_seat_type('credit,verified')
@ddt.data( @ddt.data(
{'catalog_query': '*:*'}, {'catalog_query': '*:*'},
{'catalog_query': '', 'course_seat_types': ['verified']}, {'catalog_query': '', 'course_seat_types': ['verified']},
...@@ -177,13 +166,33 @@ class RangeTests(CouponMixin, CourseCatalogTestMixin, CourseCatalogMockMixin, Te ...@@ -177,13 +166,33 @@ class RangeTests(CouponMixin, CourseCatalogTestMixin, CourseCatalogMockMixin, Te
"""Verify creating range with catalog_query or catalog_seat_types creates range with those values.""" """Verify creating range with catalog_query or catalog_seat_types creates range with those values."""
data = { data = {
'catalog_query': 'id:testquery', 'catalog_query': 'id:testquery',
'course_seat_types': ['verified', 'professional'] 'course_seat_types': 'verified,professional'
} }
new_range = Range.objects.create(**data) new_range = Range.objects.create(**data)
self.assertEqual(new_range.catalog_query, data['catalog_query']) self.assertEqual(new_range.catalog_query, data['catalog_query'])
self.assertEqual(new_range.course_seat_types, data['course_seat_types']) self.assertEqual(new_range.course_seat_types, data['course_seat_types'])
self.assertEqual(new_range.catalog, None) self.assertEqual(new_range.catalog, None)
@ddt.data(5, 'credit,verified', 'verified,not_allowed_value')
def test_creating_range_with_wrong_course_seat_types(self, course_seat_types):
""" Verify creating range with incorrect course seat types will raise exception. """
data = {
'catalog_query': '*:*',
'course_seat_types': course_seat_types
}
with self.assertRaises(ValidationError):
Range.objects.create(**data)
@ddt.data('credit', 'professional', 'verified', 'professional,verified')
def test_creating_range_with_course_seat_types(self, course_seat_types):
""" Verify creating range with allowed course seat types values creates range. """
data = {
'catalog_query': '*:*',
'course_seat_types': course_seat_types
}
_range = Range.objects.create(**data)
self.assertEqual(_range.course_seat_types, course_seat_types)
class ConditionalOfferTests(TestCase): class ConditionalOfferTests(TestCase):
"""Tests for custom ConditionalOffer model.""" """Tests for custom ConditionalOffer model."""
......
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