From dc0b73bf1980510d63f88220d5b5b54cd38e8f67 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sun, 27 Nov 2016 17:13:26 +0100 Subject: [PATCH] Fix issues introduced in rebase --- src/pretix/base/services/cart.py | 22 +++++++++---------- src/pretix/base/services/orders.py | 34 +++++++++++++++++++++--------- src/tests/presale/test_cart.py | 11 +++++----- src/tests/presale/test_checkout.py | 25 ++++++++++++++++++++-- src/tests/settings.py | 6 +++++- 5 files changed, 68 insertions(+), 30 deletions(-) diff --git a/src/pretix/base/services/cart.py b/src/pretix/base/services/cart.py index 1d493b837..620e355b4 100644 --- a/src/pretix/base/services/cart.py +++ b/src/pretix/base/services/cart.py @@ -37,8 +37,6 @@ error_messages = { 'voucher_invalid': _('This voucher code is not known in our database.'), 'voucher_redeemed': _('This voucher code has already been used the maximum number of times allowed.'), 'voucher_redeemed_partial': _('This voucher code can only be redeemed %d more times.'), - 'voucher_double': _('You already used this voucher code. Remove the associated line from your ' - 'cart if you want to use it for a different product.'), 'voucher_expired': _('This voucher is expired.'), 'voucher_invalid_item': _('This voucher is not valid for this product.'), 'voucher_required': _('You need a valid voucher code to order this product.'), @@ -111,6 +109,7 @@ def _parse_items_and_check_constraints(event: Event, items: List[dict], cart_id: variations_cache = {v.id: v for v in variations_query} quotadiff = Counter() + vouchers = Counter() for i in items: # Check whether the specified items are part of what we just fetched from the database @@ -129,24 +128,25 @@ def _parse_items_and_check_constraints(event: Event, items: List[dict], cart_id: try: voucher = Voucher.objects.get(code=i.get('voucher').strip(), event=event) if voucher.redeemed >= voucher.max_usages: - return error_messages['voucher_redeemed'] + raise CartError(error_messages['voucher_redeemed']) if voucher.valid_until is not None and voucher.valid_until < now_dt: raise CartError(error_messages['voucher_expired']) if not voucher.applies_to(item, variation): - return error_messages['voucher_invalid_item'] + raise CartError(error_messages['voucher_invalid_item']) redeemed_in_carts = CartPosition.objects.filter( - Q(voucher=voucher) & Q(event=event) & - (Q(expires__gte=now_dt) | Q(cart_id=cart_id)) + Q(voucher=voucher) & Q(event=event) & Q(expires__gte=now_dt) ) if 'cp' in i: - redeemed_in_carts = redeemed_in_carts.exclude(pk=i['cp'].pk) + redeemed_in_carts = redeemed_in_carts.exclude(pk=i['_cp'].pk) v_avail = voucher.max_usages - voucher.redeemed - redeemed_in_carts.count() if v_avail < 1: - return error_messages['voucher_redeemed'] - if i['count'] > v_avail: - return error_messages['voucher_redeemed_partial'] % v_avail + raise CartError(error_messages['voucher_redeemed']) + if i['count'] > v_avail - vouchers[voucher]: + raise CartError(error_messages['voucher_redeemed_partial'] % v_avail) + + vouchers[voucher] += i['count'] except Voucher.DoesNotExist: raise CartError(error_messages['voucher_invalid']) @@ -200,7 +200,7 @@ def _parse_items_and_check_constraints(event: Event, items: List[dict], cart_id: def _check_quota_and_create_positions(event: Event, items: List[dict], cart_id: str, now_dt: datetime, - expiry: datetime, quotadiff: Counter,): + expiry: datetime, quotadiff: Counter): """ This method takes the modified items and the quotadiff from _parse_items_and_check_constraints and then diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index dcb5f063c..a82b12b75 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -51,6 +51,8 @@ error_messages = { 'voucher_invalid': _('The voucher code used for one of the items in your cart is not known in our database.'), 'voucher_redeemed': _('The voucher code used for one of the items in your cart has already been used the maximum ' 'number of times allowed. We removed this item from your cart.'), + 'voucher_redeemed_partial': _('The voucher code used for one of the items in your cart can only be redeemed %d ' + 'more times. We removed this item from your cart.'), 'voucher_expired': _('The voucher code used for one of the items in your cart is expired. We removed this item ' 'from your cart.'), 'voucher_invalid_item': _('The voucher code used for one of the items in your cart is not valid for this item. We ' @@ -207,16 +209,6 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio continue cp._quotas = list(cp.item.quotas.all()) if cp.variation is None else list(cp.variation.quotas.all()) - if cp.voucher: - redeemed_in_carts = CartPosition.objects.filter( - Q(voucher=cp.voucher) & Q(event=event) & Q(expires__gte=now_dt) - ).exclude(pk=cp.pk) - v_avail = cp.voucher.max_usages - cp.voucher.redeemed - redeemed_in_carts.count() - if v_avail < 1: - err = err or error_messages['voucher_redeemed'] - cp.delete() # Sorry! - continue - if cp.item.require_voucher and cp.voucher is None: cp.delete() err = error_messages['voucher_required'] @@ -301,7 +293,11 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d def _check_quota_on_expired_positions(event: Event, positions: List[CartPosition], now_dt: datetime): err = None quotadiff = Counter() + vouchers = Counter() for cp in positions: + if not cp.id: + continue + ignore_all_quotas = cp.expires >= now_dt or ( cp.voucher and (cp.voucher.allow_ignore_quota or (cp.voucher.block_quota and cp.voucher.quota is None))) @@ -328,6 +324,24 @@ def _check_quota_on_expired_positions(event: Event, positions: List[CartPosition quotas_ok[quota] = count for cp in positions: + if not cp.id: + continue + + if cp.voucher: + redeemed_in_carts = CartPosition.objects.filter( + Q(voucher=cp.voucher) & Q(event=event) & Q(expires__gte=now_dt) + ).exclude(pk__in=[cp2.pk for cp2 in positions]) + v_avail = cp.voucher.max_usages - cp.voucher.redeemed - redeemed_in_carts.count() + if v_avail < 1: + err = err or error_messages['voucher_redeemed'] + cp.delete() # Sorry! + continue + if v_avail - vouchers[cp.voucher] < 1: + err = err or (error_messages['voucher_redeemed_partial'] % v_avail) + cp.delete() # Sorry! + continue + vouchers[cp.voucher] += 1 + if cp._quotas: if min(quotas_ok[q] for q in cp._quotas) > 0: cp.expires = now_dt + timedelta(minutes=event.settings.get('reservation_time', as_type=int)) diff --git a/src/tests/presale/test_cart.py b/src/tests/presale/test_cart.py index eb23c7b38..027ddf4d6 100644 --- a/src/tests/presale/test_cart.py +++ b/src/tests/presale/test_cart.py @@ -553,10 +553,10 @@ class CartTest(CartTestMixin, TestCase): ) self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'variation_%d_%d' % (self.shirt.id, self.shirt_red.id): '1', - '_voucher_code': v.code, }, follow=True) obj = CartPosition.objects.get(id=cp1.id) self.assertGreater(obj.expires, now()) + self.assertEqual(obj.voucher, v) def test_voucher_variation(self): v = Voucher.objects.create(item=self.shirt, variation=self.shirt_red, event=self.event) @@ -815,10 +815,9 @@ class CartTest(CartTestMixin, TestCase): '_voucher_code': v.code, }, follow=True) doc = BeautifulSoup(response.rendered_content, "lxml") - self.assertIn('already been used', doc.select('.alert-danger')[0].text) + self.assertIn('only be redeemed 1 more time', doc.select('.alert-danger')[0].text) positions = CartPosition.objects.filter(cart_id=self.session_key, event=self.event) - assert positions.count() == 1 - assert all(cp.voucher == v for cp in positions) + assert positions.count() == 0 def test_voucher_multiuse_redeemed(self): v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, @@ -857,9 +856,9 @@ class CartTest(CartTestMixin, TestCase): '_voucher_code': v.code, }, follow=True) doc = BeautifulSoup(response.rendered_content, "lxml") - self.assertIn('already been used', doc.select('.alert-danger')[0].text) + self.assertIn('only be redeemed 1 more time', doc.select('.alert-danger')[0].text) positions = CartPosition.objects.filter(cart_id=self.session_key, event=self.event) - assert positions.count() == 1 + assert positions.count() == 0 def test_voucher_multiuse_redeemed_in_other_cart(self): v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, diff --git a/src/tests/presale/test_checkout.py b/src/tests/presale/test_checkout.py index 220171497..7080479ab 100644 --- a/src/tests/presale/test_checkout.py +++ b/src/tests/presale/test_checkout.py @@ -405,10 +405,31 @@ class CheckoutTestCase(TestCase): self._set_session('payment', 'banktransfer') response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True) doc = BeautifulSoup(response.rendered_content, "lxml") - self.assertIn("has already been", doc.select(".alert-danger")[0].text) + self.assertIn("only be redeemed 1 more time", doc.select(".alert-danger")[0].text) assert CartPosition.objects.filter(cart_id=self.session_key).count() == 1 def test_voucher_multiuse_ok(self): + v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + valid_until=now() + timedelta(days=2), max_usages=3, redeemed=1) + CartPosition.objects.create( + event=self.event, cart_id=self.session_key, item=self.ticket, + price=12, expires=now() + timedelta(minutes=10), voucher=v + ) + CartPosition.objects.create( + event=self.event, cart_id=self.session_key, item=self.ticket, + price=12, expires=now() + timedelta(minutes=10), voucher=v + ) + self._set_session('payment', 'banktransfer') + response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True) + doc = BeautifulSoup(response.rendered_content, "lxml") + self.assertEqual(len(doc.select(".thank-you")), 1) + self.assertFalse(CartPosition.objects.filter(cart_id=self.session_key).exists()) + self.assertEqual(Order.objects.count(), 1) + self.assertEqual(OrderPosition.objects.count(), 2) + v.refresh_from_db() + assert v.redeemed == 3 + + def test_voucher_multiuse_ok_expired(self): v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, valid_until=now() + timedelta(days=2), max_usages=3, redeemed=1) CartPosition.objects.create( @@ -472,7 +493,7 @@ class CheckoutTestCase(TestCase): self._set_session('payment', 'banktransfer') response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True) doc = BeautifulSoup(response.rendered_content, "lxml") - self.assertIn("has already been", doc.select(".alert-danger")[0].text) + self.assertIn("only be redeemed 1 more time", doc.select(".alert-danger")[0].text) assert CartPosition.objects.filter(cart_id=self.session_key).count() == 1 def test_voucher_ignore_quota(self): diff --git a/src/tests/settings.py b/src/tests/settings.py index 4a83b94c6..6b5e7160b 100644 --- a/src/tests/settings.py +++ b/src/tests/settings.py @@ -9,4 +9,8 @@ TEMPLATES[0]['DIRS'].append(os.path.join(TEST_DIR, 'templates')) # NOQA INSTALLED_APPS.append('tests.testdummy') # NOQA for a in PLUGINS: - INSTALLED_APPS.remove(a) \ No newline at end of file + INSTALLED_APPS.remove(a) + +DATABASES['default'] = { + 'ENGINE': 'django.db.backends.sqlite3', +}