diff --git a/src/pretix/base/migrations/0048_auto_20161129_1330.py b/src/pretix/base/migrations/0048_auto_20161129_1330.py new file mode 100644 index 000000000..c7424471f --- /dev/null +++ b/src/pretix/base/migrations/0048_auto_20161129_1330.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2016-11-29 13:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0047_auto_20161126_1300'), + ] + + operations = [ + migrations.AddField( + model_name='voucher', + name='price_mode', + field=models.CharField(choices=[('none', 'No effect'), ('set', 'Set product price to'), ('subtract', 'Subtract from product price'), ('percent', 'Reduce product price by (%)')], default='set', max_length=100, verbose_name='Price mode'), + ), + migrations.RenameField( + model_name='voucher', + old_name='price', + new_name='value', + ), + migrations.AlterField( + model_name='voucher', + name='value', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Voucher value'), + ), + ] diff --git a/src/pretix/base/models/vouchers.py b/src/pretix/base/models/vouchers.py index ad1605a03..e397493d0 100644 --- a/src/pretix/base/models/vouchers.py +++ b/src/pretix/base/models/vouchers.py @@ -1,3 +1,5 @@ +from decimal import Decimal + from django.conf import settings from django.core.exceptions import ValidationError from django.db import models @@ -5,6 +7,7 @@ from django.utils.crypto import get_random_string from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ +from ..decimal import round_decimal from .base import LoggedModel from .event import Event from .items import Item, ItemVariation, Quota @@ -59,6 +62,13 @@ class Voucher(LoggedModel): * You need to either select a quota or an item * If you select an item that has variations but do not select a variation, you cannot set block_quota """ + PRICE_MODES = ( + ('none', _('No effect')), + ('set', _('Set product price to')), + ('subtract', _('Subtract from product price')), + ('percent', _('Reduce product price by (%)')), + ) + event = models.ForeignKey( Event, on_delete=models.CASCADE, @@ -98,10 +108,15 @@ class Voucher(LoggedModel): "If activated, a holder of this voucher code can buy tickets, even if there are none left." ) ) - price = models.DecimalField( - verbose_name=_("Set product price to"), + price_mode = models.CharField( + verbose_name=_("Price mode"), + max_length=100, + choices=PRICE_MODES, + default='set' + ) + value = models.DecimalField( + verbose_name=_("Voucher value"), decimal_places=2, max_digits=10, null=True, blank=True, - help_text=_('If empty, the product will cost its normal price.') ) item = models.ForeignKey( Item, related_name='vouchers', @@ -208,3 +223,19 @@ class Voucher(LoggedModel): if self.valid_until and self.valid_until < now(): return False return True + + def calculate_price(self, original_price: Decimal) -> Decimal: + """ + Returns how the price given in original_price would be modified if this + voucher is applied, i.e. replaced by a different price or reduced by a + certain percentage. If the voucher does not modify the price, the + original price will be returned. + """ + if self.value: + if self.price_mode == 'set': + return self.value + elif self.price_mode == 'subtract': + return original_price - self.value + elif self.price_mode == 'percent': + return round_decimal(original_price * (Decimal('100.00') - self.value) / Decimal('100.00')) + return original_price diff --git a/src/pretix/base/services/cart.py b/src/pretix/base/services/cart.py index bc54f2d44..4be716255 100644 --- a/src/pretix/base/services/cart.py +++ b/src/pretix/base/services/cart.py @@ -168,11 +168,10 @@ def _add_new_items(event: Event, items: List[dict], err = err or error_messages['in_part'] quota_ok = min(quota_ok, avail[1]) - if voucher and voucher.price is not None: - price = voucher.price - else: - price = item.default_price if variation is None else ( - variation.default_price if variation.default_price is not None else item.default_price) + price = item.default_price if variation is None else ( + variation.default_price if variation.default_price is not None else item.default_price) + if voucher: + price = voucher.calculate_price(price) if item.free_price and 'price' in i and i['price'] is not None and i['price'] != "": custom_price = i['price'] diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index 288bb7415..dc87233af 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -233,8 +233,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio err = err or error_messages['voucher_expired'] cp.delete() continue - if cp.voucher.price is not None: - price = cp.voucher.price + price = cp.voucher.calculate_price(price) if price != cp.price and not (cp.item.free_price and cp.price > price): positions[i] = cp diff --git a/src/pretix/control/forms/vouchers.py b/src/pretix/control/forms/vouchers.py index 5ceadeefc..453773706 100644 --- a/src/pretix/control/forms/vouchers.py +++ b/src/pretix/control/forms/vouchers.py @@ -22,8 +22,8 @@ class VoucherForm(I18nModelForm): model = Voucher localized_fields = '__all__' fields = [ - 'code', 'valid_until', 'block_quota', 'allow_ignore_quota', 'price', 'tag', - 'comment', 'max_usages' + 'code', 'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag', + 'comment', 'max_usages', 'price_mode' ] widgets = { 'valid_until': forms.DateTimeInput(attrs={'class': 'datetimepicker'}), @@ -187,8 +187,8 @@ class VoucherBulkForm(VoucherForm): model = Voucher localized_fields = '__all__' fields = [ - 'valid_until', 'block_quota', 'allow_ignore_quota', 'price', 'tag', 'comment', - 'max_usages' + 'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag', 'comment', + 'max_usages', 'price_mode' ] widgets = { 'valid_until': forms.DateTimeInput(attrs={'class': 'datetimepicker'}), diff --git a/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html b/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html index 91ee27144..522daf4b6 100644 --- a/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html +++ b/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html @@ -33,7 +33,15 @@ {% bootstrap_field form.valid_until layout="horizontal" %} {% bootstrap_field form.block_quota layout="horizontal" %} {% bootstrap_field form.allow_ignore_quota layout="horizontal" %} - {% bootstrap_field form.price layout="horizontal" %} +
+ +
+ {% bootstrap_field form.price_mode show_label=False form_group_class="" %} +
+
+ {% bootstrap_field form.value show_label=False form_group_class="" %} +
+
{% bootstrap_field form.itemvar layout="horizontal" %}
diff --git a/src/pretix/control/templates/pretixcontrol/vouchers/detail.html b/src/pretix/control/templates/pretixcontrol/vouchers/detail.html index 92ae2a552..52e1d510b 100644 --- a/src/pretix/control/templates/pretixcontrol/vouchers/detail.html +++ b/src/pretix/control/templates/pretixcontrol/vouchers/detail.html @@ -27,7 +27,15 @@ {% bootstrap_field form.valid_until layout="horizontal" %} {% bootstrap_field form.block_quota layout="horizontal" %} {% bootstrap_field form.allow_ignore_quota layout="horizontal" %} - {% bootstrap_field form.price layout="horizontal" %} +
+ +
+ {% bootstrap_field form.price_mode show_label=False form_group_class="" %} +
+
+ {% bootstrap_field form.value show_label=False form_group_class="" %} +
+
{% bootstrap_field form.itemvar layout="horizontal" %}
diff --git a/src/pretix/control/views/vouchers.py b/src/pretix/control/views/vouchers.py index 29b7c2d2c..40fb96b0f 100644 --- a/src/pretix/control/views/vouchers.py +++ b/src/pretix/control/views/vouchers.py @@ -59,7 +59,7 @@ class VoucherList(EventPermissionRequiredMixin, ListView): headers = [ _('Voucher code'), _('Valid until'), _('Product'), _('Reserve quota'), _('Bypass quota'), - _('Price'), _('Tag'), _('Redeemed'), _('Maximum usages') + _('Price effect'), _('Value'), _('Tag'), _('Redeemed'), _('Maximum usages') ] writer.writerow(headers) @@ -77,7 +77,8 @@ class VoucherList(EventPermissionRequiredMixin, ListView): prod, _("Yes") if v.block_quota else _("No"), _("Yes") if v.allow_ignore_quota else _("No"), - str(v.price) if v.price else "", + v.get_price_mode_display(), + str(v.value) if v.value else "", v.tag, str(v.redeemed), str(v.max_usages) diff --git a/src/pretix/presale/views/cart.py b/src/pretix/presale/views/cart.py index 12fe1814b..4912a480d 100644 --- a/src/pretix/presale/views/cart.py +++ b/src/pretix/presale/views/cart.py @@ -198,20 +198,16 @@ class RedeemView(EventViewMixin, TemplateView): item.cached_availability = (Quota.AVAILABILITY_OK, 1) else: item.cached_availability = item.check_quotas() - if self.voucher.price is not None: - item.price = self.voucher.price - else: - item.price = item.default_price + item.price = self.voucher.calculate_price(item.default_price) else: for var in item.available_variations: if self.voucher.allow_ignore_quota or self.voucher.block_quota: var.cached_availability = (Quota.AVAILABILITY_OK, 1) else: var.cached_availability = list(var.check_quotas()) - if self.voucher.price is not None: - var.price = self.voucher.price - else: - var.price = var.default_price if var.default_price is not None else item.default_price + var.price = self.voucher.calculate_price( + var.default_price if var.default_price is not None else item.default_price + ) if len(item.available_variations) > 0: item.min_price = min([v.price for v in item.available_variations]) diff --git a/src/tests/base/test_models.py b/src/tests/base/test_models.py index 90fa047f5..38029fb6d 100644 --- a/src/tests/base/test_models.py +++ b/src/tests/base/test_models.py @@ -1,6 +1,7 @@ import datetime import sys from datetime import timedelta +from decimal import Decimal import pytest from django.conf import settings @@ -406,6 +407,29 @@ class QuotaTestCase(BaseQuotaTestCase): v.clean() +class VoucherTestCase(BaseQuotaTestCase): + + def test_calculate_price_none(self): + v = Voucher.objects.create(event=self.event, price_mode='none', value=Decimal('10.00')) + v.calculate_price(Decimal('23.42')) == Decimal('23.42') + + def test_calculate_price_set_empty(self): + v = Voucher.objects.create(event=self.event, price_mode='set') + v.calculate_price(Decimal('23.42')) == Decimal('23.42') + + def test_calculate_price_set(self): + v = Voucher.objects.create(event=self.event, price_mode='set', value=Decimal('10.00')) + v.calculate_price(Decimal('23.42')) == Decimal('10.00') + + def test_calculate_price_subtract(self): + v = Voucher.objects.create(event=self.event, price_mode='subtract', value=Decimal('10.00')) + v.calculate_price(Decimal('23.42')) == Decimal('13.42') + + def test_calculate_price_percent(self): + v = Voucher.objects.create(event=self.event, price_mode='percent', value=Decimal('23.00')) + v.calculate_price(Decimal('100.00')) == Decimal('77.00') + + class OrderTestCase(BaseQuotaTestCase): def setUp(self): super().setUp() diff --git a/src/tests/control/test_vouchers.py b/src/tests/control/test_vouchers.py index 6bd560f6d..49e84fb44 100644 --- a/src/tests/control/test_vouchers.py +++ b/src/tests/control/test_vouchers.py @@ -80,9 +80,9 @@ class VoucherFormTest(SoupTest): def test_csv(self): self.event.vouchers.create(item=self.ticket, code='ABCDEFG') doc = self.client.get('/control/event/%s/%s/vouchers/?download=yes' % (self.orga.slug, self.event.slug)) - assert doc.content.strip() == '"Voucher code","Valid until","Product","Reserve quota","Bypass quota","Price",' \ - '"Tag","Redeemed","Maximum usages"\r\n"ABCDEFG","","Early-bird ticket","No",' \ - '"No","","","0","1"'.encode('utf-8') + assert doc.content.strip() == '"Voucher code","Valid until","Product","Reserve quota","Bypass quota",' \ + '"Price effect","Value","Tag","Redeemed","Maximum usages"\r\n"ABCDEFG","",' \ + '"Early-bird ticket","No","No","Set product price to","","","0","1"'.encode('utf-8') def test_filter_status_valid(self): v = self.event.vouchers.create(item=self.ticket) diff --git a/src/tests/presale/test_cart.py b/src/tests/presale/test_cart.py index 47e3bcf58..112c41b8a 100644 --- a/src/tests/presale/test_cart.py +++ b/src/tests/presale/test_cart.py @@ -488,7 +488,7 @@ class CartTest(CartTestMixin, TestCase): event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_red, price=14, expires=now() + timedelta(minutes=10) ) - v = Voucher.objects.create(item=self.shirt, variation=self.shirt_red, price=Decimal('10.00'), event=self.event) + v = Voucher.objects.create(item=self.shirt, variation=self.shirt_red, value=Decimal('10.00'), event=self.event) self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'variation_%d_%d_voucher' % (self.shirt.id, self.shirt_red.id): v.code, }, follow=True) @@ -594,7 +594,7 @@ class CartTest(CartTestMixin, TestCase): self.assertEqual(len(objs), 0) def test_voucher_price(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event) + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event) self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '1', '_voucher_code': v.code, @@ -605,8 +605,76 @@ class CartTest(CartTestMixin, TestCase): self.assertIsNone(objs[0].variation) self.assertEqual(objs[0].price, Decimal('12.00')) + def test_voucher_price_percent(self): + v = Voucher.objects.create(item=self.ticket, value=Decimal('10.00'), price_mode='percent', event=self.event) + self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { + 'item_%d' % self.ticket.id: '1', + '_voucher_code': v.code, + }, follow=True) + objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event)) + self.assertEqual(len(objs), 1) + self.assertEqual(objs[0].item, self.ticket) + self.assertIsNone(objs[0].variation) + self.assertEqual(objs[0].price, Decimal('20.70')) + + def test_voucher_price_subtract(self): + v = Voucher.objects.create(item=self.ticket, value=Decimal('10.00'), price_mode='subtract', event=self.event) + self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { + 'item_%d' % self.ticket.id: '1', + '_voucher_code': v.code, + }, follow=True) + objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event)) + self.assertEqual(len(objs), 1) + self.assertEqual(objs[0].item, self.ticket) + self.assertIsNone(objs[0].variation) + self.assertEqual(objs[0].price, Decimal('13.00')) + + def test_voucher_free_price(self): + v = Voucher.objects.create(item=self.ticket, value=Decimal('10.00'), price_mode='percent', event=self.event) + self.ticket.free_price = True + self.ticket.save() + response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { + 'item_%d' % self.ticket.id: '1', + 'price_%d' % self.ticket.id: '21.00', + '_voucher_code': v.code, + }, follow=True) + self.assertRedirects(response, '/%s/%s/' % (self.orga.slug, self.event.slug), + target_status_code=200) + doc = BeautifulSoup(response.rendered_content, "lxml") + self.assertIn('Early-bird', doc.select('.cart .cart-row')[0].select('strong')[0].text) + self.assertIn('1', doc.select('.cart .cart-row')[0].select('.count')[0].text) + self.assertIn('21', doc.select('.cart .cart-row')[0].select('.price')[0].text) + self.assertIn('21', doc.select('.cart .cart-row')[0].select('.price')[1].text) + objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event)) + self.assertEqual(len(objs), 1) + self.assertEqual(objs[0].item, self.ticket) + self.assertIsNone(objs[0].variation) + self.assertEqual(objs[0].price, Decimal('21.00')) + + def test_voucher_free_price_lower_bound(self): + v = Voucher.objects.create(item=self.ticket, value=Decimal('10.00'), price_mode='percent', event=self.event) + self.ticket.free_price = False + self.ticket.save() + response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { + 'item_%d' % self.ticket.id: '1', + 'price_%d' % self.ticket.id: '20.00', + '_voucher_code': v.code, + }, follow=True) + self.assertRedirects(response, '/%s/%s/' % (self.orga.slug, self.event.slug), + target_status_code=200) + doc = BeautifulSoup(response.rendered_content, "lxml") + self.assertIn('Early-bird', doc.select('.cart .cart-row')[0].select('strong')[0].text) + self.assertIn('1', doc.select('.cart .cart-row')[0].select('.count')[0].text) + self.assertIn('20.70', doc.select('.cart .cart-row')[0].select('.price')[0].text) + self.assertIn('20.70', doc.select('.cart .cart-row')[0].select('.price')[1].text) + objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event)) + self.assertEqual(len(objs), 1) + self.assertEqual(objs[0].item, self.ticket) + self.assertIsNone(objs[0].variation) + self.assertEqual(objs[0].price, Decimal('20.70')) + def test_voucher_redemed(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, redeemed=1) + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, redeemed=1) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '1', '_voucher_code': v.code, @@ -616,7 +684,7 @@ class CartTest(CartTestMixin, TestCase): self.assertFalse(CartPosition.objects.filter(cart_id=self.session_key, event=self.event).exists()) def test_voucher_expired(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, valid_until=now() - timedelta(days=2)) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '1', @@ -638,7 +706,7 @@ class CartTest(CartTestMixin, TestCase): def test_voucher_quota_empty(self): self.quota_tickets.size = 0 self.quota_tickets.save() - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event) + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '1', '_voucher_code': v.code, @@ -650,7 +718,7 @@ class CartTest(CartTestMixin, TestCase): def test_voucher_quota_ignore(self): self.quota_tickets.size = 0 self.quota_tickets.save() - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, allow_ignore_quota=True) self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '1', @@ -665,7 +733,7 @@ class CartTest(CartTestMixin, TestCase): def test_voucher_quota_block(self): self.quota_tickets.size = 1 self.quota_tickets.save() - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, block_quota=True) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '1', @@ -684,7 +752,7 @@ class CartTest(CartTestMixin, TestCase): self.assertEqual(objs[0].price, Decimal('12.00')) def test_voucher_doubled(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event) + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '1', '_voucher_code': v.code, @@ -758,7 +826,7 @@ class CartTest(CartTestMixin, TestCase): self.assertEqual(len(objs), 0) def test_voucher_multiuse_ok(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, max_usages=2, redeemed=0) self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '2', @@ -769,7 +837,7 @@ class CartTest(CartTestMixin, TestCase): assert all(cp.voucher == v for cp in positions) def test_voucher_multiuse_multiprod_ok(self): - v = Voucher.objects.create(quota=self.quota_all, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(quota=self.quota_all, value=Decimal('12.00'), event=self.event, max_usages=2, redeemed=0) self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '1', @@ -781,7 +849,7 @@ class CartTest(CartTestMixin, TestCase): assert all(cp.voucher == v for cp in positions) def test_voucher_multiuse_partially(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, max_usages=2, redeemed=1) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '2', @@ -793,7 +861,7 @@ class CartTest(CartTestMixin, TestCase): assert not positions.exists() def test_voucher_multiuse_multiprod_partially(self): - v = Voucher.objects.create(quota=self.quota_all, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(quota=self.quota_all, value=Decimal('12.00'), event=self.event, max_usages=2, redeemed=1) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '1', @@ -807,7 +875,7 @@ class CartTest(CartTestMixin, TestCase): assert all(cp.voucher == v for cp in positions) def test_voucher_multiuse_redeemed(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, max_usages=2, redeemed=2) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '2', @@ -819,7 +887,7 @@ class CartTest(CartTestMixin, TestCase): assert not positions.exists() def test_voucher_multiuse_multiprod_redeemed(self): - v = Voucher.objects.create(quota=self.quota_all, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(quota=self.quota_all, value=Decimal('12.00'), event=self.event, max_usages=2, redeemed=2) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '1', @@ -832,7 +900,7 @@ class CartTest(CartTestMixin, TestCase): assert not positions.exists() def test_voucher_multiuse_redeemed_in_my_cart(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, max_usages=2, redeemed=1) CartPosition.objects.create( expires=now() - timedelta(minutes=10), item=self.ticket, voucher=v, price=Decimal('12.00'), @@ -848,7 +916,7 @@ class CartTest(CartTestMixin, TestCase): assert positions.count() == 1 def test_voucher_multiuse_redeemed_in_other_cart(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, max_usages=2, redeemed=1) CartPosition.objects.create( expires=now() + timedelta(minutes=10), item=self.ticket, voucher=v, price=Decimal('12.00'), @@ -864,7 +932,7 @@ class CartTest(CartTestMixin, TestCase): assert not positions.exists() def test_voucher_multiuse_redeemed_in_other_expired_cart(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, max_usages=2, redeemed=1) CartPosition.objects.create( expires=now() - timedelta(minutes=10), item=self.ticket, voucher=v, price=Decimal('12.00'), diff --git a/src/tests/presale/test_checkout.py b/src/tests/presale/test_checkout.py index 220171497..8cd0b9e5a 100644 --- a/src/tests/presale/test_checkout.py +++ b/src/tests/presale/test_checkout.py @@ -294,7 +294,7 @@ class CheckoutTestCase(TestCase): self.assertEqual(cr1.price, 24) def test_voucher(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, valid_until=now() + timedelta(days=2)) cr1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, @@ -312,7 +312,7 @@ class CheckoutTestCase(TestCase): self.assertEqual(Voucher.objects.get(pk=v.pk).redeemed, 1) def test_voucher_required(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, valid_until=now() + timedelta(days=2)) self.ticket.require_voucher = True self.ticket.save() @@ -341,7 +341,7 @@ class CheckoutTestCase(TestCase): assert doc.select(".alert-danger") def test_voucher_price_changed(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, valid_until=now() + timedelta(days=2)) cr1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, @@ -355,20 +355,8 @@ class CheckoutTestCase(TestCase): cr1 = CartPosition.objects.get(id=cr1.id) self.assertEqual(cr1.price, Decimal('12.00')) - def test_voucher_expired(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, - valid_until=now() - timedelta(days=2)) - 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.assertIn("expired", doc.select(".alert-danger")[0].text) - def test_voucher_redeemed(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, valid_until=now() + timedelta(days=2), redeemed=1) CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, @@ -380,7 +368,7 @@ class CheckoutTestCase(TestCase): self.assertIn("has already been", doc.select(".alert-danger")[0].text) def test_voucher_multiuse_redeemed(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, valid_until=now() + timedelta(days=2), max_usages=3, redeemed=3) CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, @@ -392,7 +380,7 @@ class CheckoutTestCase(TestCase): self.assertIn("has already been", doc.select(".alert-danger")[0].text) def test_voucher_multiuse_partially(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, valid_until=now() + timedelta(days=2), max_usages=3, redeemed=2) CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, @@ -409,7 +397,7 @@ class CheckoutTestCase(TestCase): 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, + v = Voucher.objects.create(item=self.ticket, value=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, @@ -430,7 +418,7 @@ class CheckoutTestCase(TestCase): assert v.redeemed == 3 def test_voucher_multiuse_in_other_cart_expired(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=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='other', item=self.ticket, @@ -455,7 +443,7 @@ class CheckoutTestCase(TestCase): assert v.redeemed == 3 def test_voucher_multiuse_in_other_cart(self): - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=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='other', item=self.ticket, @@ -478,7 +466,7 @@ class CheckoutTestCase(TestCase): def test_voucher_ignore_quota(self): self.quota_tickets.size = 0 self.quota_tickets.save() - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, valid_until=now() + timedelta(days=2), allow_ignore_quota=True) cr1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, @@ -496,7 +484,7 @@ class CheckoutTestCase(TestCase): def test_voucher_block_quota(self): self.quota_tickets.size = 1 self.quota_tickets.save() - v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, valid_until=now() + timedelta(days=2), block_quota=True) cr1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, @@ -523,7 +511,7 @@ class CheckoutTestCase(TestCase): self.quota_tickets.save() q2 = self.event.quotas.create(name='Testquota', size=0) q2.items.add(self.ticket) - v = Voucher.objects.create(quota=self.quota_tickets, price=Decimal('12.00'), event=self.event, + v = Voucher.objects.create(quota=self.quota_tickets, value=Decimal('12.00'), event=self.event, valid_until=now() + timedelta(days=2), block_quota=True) CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, diff --git a/src/tests/presale/test_event.py b/src/tests/presale/test_event.py index a20933b72..8f5241507 100644 --- a/src/tests/presale/test_event.py +++ b/src/tests/presale/test_event.py @@ -285,19 +285,27 @@ class VoucherRedeemItemDisplayTest(EventTestMixin, SoupTest): html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code)) assert "_voucher_item" in html.rendered_content - def test_special_price(self): - self.v.price = Decimal("10.00") + def test_voucher_price(self): + self.v.value = Decimal("10.00") self.v.save() html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code)) assert "Early-bird" in html.rendered_content assert "10.00" in html.rendered_content - def test_special_price_variations(self): + def test_voucher_price_percentage(self): + self.v.value = Decimal("10.00") + self.v.price_mode = 'percent' + self.v.save() + html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code)) + assert "Early-bird" in html.rendered_content + assert "10.80" in html.rendered_content + + def test_voucher_price_variations(self): var1 = ItemVariation.objects.create(item=self.item, value='Red', default_price=14, position=1) var2 = ItemVariation.objects.create(item=self.item, value='Black', position=2) self.q.variations.add(var1) self.q.variations.add(var2) - self.v.price = Decimal("10.00") + self.v.value = Decimal("10.00") self.v.save() html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code)) assert "Early-bird" in html.rendered_content