Commit 23802316 by Ivan Ivic Committed by GitHub

Merge pull request #1017 from edx/iivic/SOL-2137

[SOL-2137] Add Backend Validation for Coupon Creation - Benefit Value
parents 69cfbf69 071a15fd
...@@ -262,21 +262,29 @@ class CouponViewSetFunctionalTest(CouponMixin, CourseCatalogTestMixin, CourseCat ...@@ -262,21 +262,29 @@ class CouponViewSetFunctionalTest(CouponMixin, CourseCatalogTestMixin, CourseCat
self.assertEqual(details_response['coupon_type'], 'Enrollment code') self.assertEqual(details_response['coupon_type'], 'Enrollment code')
self.assertEqual(details_response['code_status'], 'ACTIVE') self.assertEqual(details_response['code_status'], 'ACTIVE')
@ddt.data( def test_create_coupon_product_invalid_category_data(self):
({'category': {'a': 'a', 'b': 'b'}}, status.HTTP_400_BAD_REQUEST),
({'category': {'id': 10000, 'name': 'Category Not Found'}}, status.HTTP_404_NOT_FOUND)
)
@ddt.unpack
def test_create_coupon_product_invalid_category_data(self, category_data, expected_status_code):
"""Test creating coupon when provided category data is invalid.""" """Test creating coupon when provided category data is invalid."""
self.data.update(category_data) self.data.update({'category': {'id': 10000, 'name': 'Category Not Found'}})
response_data = self.client.post(COUPONS_LINK, json.dumps(self.data), 'application/json') response_data = self.client.post(COUPONS_LINK, json.dumps(self.data), 'application/json')
self.assertEqual(response_data.status_code, expected_status_code) self.assertEqual(response_data.status_code, status.HTTP_404_NOT_FOUND)
@ddt.data('', 'Incorrect benefit type') @ddt.data(
def test_create_coupon_product_invalid_benefit_type(self, benefit_type): ('benefit_type', ['', 'Incorrect benefit type']),
"""Test creating coupon when provided benefit type is invalid.""" ('benefit_value', ['', 'Incorrect benefit value', -1, 101]),
self.data.update({'benefit_type': benefit_type}) ('category', [{'a': 'a', 'b': 'b'}]),
)
@ddt.unpack
def test_create_coupon_product_invalid_data(self, key, values):
"""Test creating coupon when provided data is invalid."""
for value in values:
self.data.update({key: value})
response_data = self.client.post(COUPONS_LINK, json.dumps(self.data), 'application/json')
self.assertEqual(response_data.status_code, status.HTTP_400_BAD_REQUEST)
@ddt.data('benefit_type', 'benefit_value')
def test_create_coupon_product_no_data_provided(self, key):
"""Test creating coupon when data is not provided in json."""
del self.data[key]
response_data = self.client.post(COUPONS_LINK, json.dumps(self.data), 'application/json') response_data = self.client.post(COUPONS_LINK, json.dumps(self.data), 'application/json')
self.assertEqual(response_data.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response_data.status_code, status.HTTP_400_BAD_REQUEST)
......
...@@ -21,7 +21,6 @@ from ecommerce.extensions.api.serializers import CategorySerializer, CouponSeria ...@@ -21,7 +21,6 @@ from ecommerce.extensions.api.serializers import CategorySerializer, CouponSeria
from ecommerce.extensions.basket.utils import prepare_basket from ecommerce.extensions.basket.utils import prepare_basket
from ecommerce.extensions.catalogue.utils import create_coupon_product, get_or_create_catalog from ecommerce.extensions.catalogue.utils import create_coupon_product, get_or_create_catalog
from ecommerce.extensions.checkout.mixins import EdxOrderPlacementMixin from ecommerce.extensions.checkout.mixins import EdxOrderPlacementMixin
from ecommerce.extensions.offer.models import VALID_BENEFIT_TYPES
from ecommerce.extensions.payment.processors.invoice import InvoicePayment from ecommerce.extensions.payment.processors.invoice import InvoicePayment
from ecommerce.extensions.voucher.models import CouponVouchers from ecommerce.extensions.voucher.models import CouponVouchers
from ecommerce.extensions.voucher.utils import update_voucher_offer from ecommerce.extensions.voucher.utils import update_voucher_offer
...@@ -35,7 +34,7 @@ Order = get_model('order', 'Order') ...@@ -35,7 +34,7 @@ Order = get_model('order', 'Order')
Product = get_model('catalogue', 'Product') Product = get_model('catalogue', 'Product')
ProductCategory = get_model('catalogue', 'ProductCategory') ProductCategory = get_model('catalogue', 'ProductCategory')
ProductClass = get_model('catalogue', 'ProductClass') ProductClass = get_model('catalogue', 'ProductClass')
Range = Range = get_model('offer', 'Range') Range = get_model('offer', 'Range')
StockRecord = get_model('partner', 'StockRecord') StockRecord = get_model('partner', 'StockRecord')
Voucher = get_model('voucher', 'Voucher') Voucher = get_model('voucher', 'Voucher')
...@@ -167,9 +166,6 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet): ...@@ -167,9 +166,6 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet):
return Response(response_data, status=status.HTTP_200_OK) return Response(response_data, status=status.HTTP_200_OK)
except ValidationError as e: except ValidationError as e:
logger.exception(
'Failed to create Benefit. Benefit type must be one of the following %s.', VALID_BENEFIT_TYPES
)
raise serializers.ValidationError(e.message) raise serializers.ValidationError(e.message)
def create_order_for_invoice(self, basket, coupon_id, client, invoice_data=None): def create_order_for_invoice(self, basket, coupon_id, client, invoice_data=None):
......
from __future__ import unicode_literals from __future__ import unicode_literals
from decimal import Decimal
from hashlib import md5 from hashlib import md5
import logging import logging
...@@ -78,7 +77,7 @@ def create_coupon_product( ...@@ -78,7 +77,7 @@ def create_coupon_product(
try: try:
create_vouchers( create_vouchers(
benefit_type=benefit_type, benefit_type=benefit_type,
benefit_value=Decimal(benefit_value), benefit_value=benefit_value,
catalog=catalog, catalog=catalog,
catalog_query=catalog_query, catalog_query=catalog_query,
code=code or None, code=code or None,
......
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
import hashlib import hashlib
import logging
import re import re
from django.conf import settings from django.conf import settings
...@@ -10,7 +11,7 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -10,7 +11,7 @@ from django.utils.translation import ugettext_lazy as _
from oscar.apps.offer.abstract_models import AbstractBenefit, AbstractConditionalOffer, AbstractRange from oscar.apps.offer.abstract_models import AbstractBenefit, AbstractConditionalOffer, AbstractRange
from threadlocals.threadlocals import get_current_request from threadlocals.threadlocals import get_current_request
logger = logging.getLogger(__name__)
VALID_BENEFIT_TYPES = [AbstractBenefit.PERCENTAGE, AbstractBenefit.FIXED] VALID_BENEFIT_TYPES = [AbstractBenefit.PERCENTAGE, AbstractBenefit.FIXED]
...@@ -21,12 +22,21 @@ class Benefit(AbstractBenefit): ...@@ -21,12 +22,21 @@ class Benefit(AbstractBenefit):
def clean(self): def clean(self):
self.clean_type() self.clean_type()
self.clean_value()
super(Benefit, self).clean() # pylint: disable=bad-super-call super(Benefit, self).clean() # pylint: disable=bad-super-call
def clean_type(self): def clean_type(self):
if self.type not in VALID_BENEFIT_TYPES: if self.type not in VALID_BENEFIT_TYPES:
logger.exception(
'Failed to create Benefit. Benefit type must be one of the following %s.', VALID_BENEFIT_TYPES
)
raise ValidationError(_('Unrecognised benefit type {type}'.format(type=self.type))) raise ValidationError(_('Unrecognised benefit type {type}'.format(type=self.type)))
def clean_value(self):
if self.value < 0:
logger.exception('Failed to create Benefit. Benefit value may not be a negative number.')
raise ValidationError(_('Benefit value must be a positive number or 0.'))
class ConditionalOffer(AbstractConditionalOffer): class ConditionalOffer(AbstractConditionalOffer):
email_domains = models.CharField(max_length=255, blank=True, null=True) email_domains = models.CharField(max_length=255, blank=True, null=True)
......
"""Voucher Utility Methods. """ """Voucher Utility Methods. """
from decimal import Decimal, DecimalException
import base64 import base64
import datetime import datetime
import hashlib import hashlib
...@@ -7,6 +8,7 @@ import uuid ...@@ -7,6 +8,7 @@ import uuid
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
...@@ -290,12 +292,16 @@ def _get_or_create_offer( ...@@ -290,12 +292,16 @@ def _get_or_create_offer(
type=Condition.COUNT, type=Condition.COUNT,
value=1, value=1,
) )
offer_benefit, __ = Benefit.objects.get_or_create( try:
range=product_range, offer_benefit, __ = Benefit.objects.get_or_create(
type=benefit_type, range=product_range,
value=benefit_value, type=benefit_type,
max_affected_items=1, value=Decimal(benefit_value),
) max_affected_items=1,
)
except (TypeError, DecimalException): # If the benefit_value parameter is not sent TypeError will be raised
logger.exception('Failed to create Benefit. Benefit value may not be empty or a string.')
raise ValidationError(_('Benefit value must be a positive number or 0.'))
offer_name = "Coupon [{}]-{}-{}".format(coupon_id, offer_benefit.type, offer_benefit.value) offer_name = "Coupon [{}]-{}-{}".format(coupon_id, offer_benefit.type, offer_benefit.value)
if offer_number: if offer_number:
......
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