diff --git a/src/pretix/base/models/vouchers.py b/src/pretix/base/models/vouchers.py index 6049e0ebf..053937d1e 100644 --- a/src/pretix/base/models/vouchers.py +++ b/src/pretix/base/models/vouchers.py @@ -344,6 +344,8 @@ class Voucher(LoggedModel): a variation). """ if self.quota_id: + if variation: + return variation.quotas.filter(pk=self.quota_id).exists() return item.quotas.filter(pk=self.quota_id).exists() if self.item_id and not self.variation_id: return self.item_id == item.pk diff --git a/src/pretix/presale/views/event.py b/src/pretix/presale/views/event.py index fe241090f..c7c23917b 100644 --- a/src/pretix/presale/views/event.py +++ b/src/pretix/presale/views/event.py @@ -94,6 +94,11 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web'): item_price_override = {} var_price_override = {} + restrict_vars = set() + if voucher and voucher.quota_id: + # If a voucher is set to a specific quota, we need to filter out on that level + restrict_vars = set(voucher.quota.variations.all()) + for item in items: if voucher and voucher.item_id and voucher.variation_id: # Restrict variations if the voucher only allows one @@ -163,8 +168,11 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web'): display_add_to_cart = display_add_to_cart or var.order_max > 0 item.available_variations = [ - v for v in item.available_variations if v._subevent_quotas + v for v in item.available_variations if v._subevent_quotas and ( + not voucher or not voucher.quota_id or v in restrict_vars + ) ] + if voucher and voucher.variation_id: item.available_variations = [v for v in item.available_variations if v.pk == voucher.variation_id] diff --git a/src/tests/base/test_models.py b/src/tests/base/test_models.py index 94a2ad73b..6068a47ea 100644 --- a/src/tests/base/test_models.py +++ b/src/tests/base/test_models.py @@ -650,6 +650,15 @@ class VoucherTestCase(BaseQuotaTestCase): self.assertTrue(v.applies_to(self.var1.item, self.var1)) self.assertFalse(v.applies_to(self.var1.item, self.var2)) + def test_voucher_applicability_variation_through_quota(self): + self.quota.variations.add(self.var1) + self.quota.items.add(self.var1.item) + v = Voucher.objects.create(quota=self.quota, event=self.event) + self.assertFalse(v.applies_to(self.item1)) + self.assertTrue(v.applies_to(self.var1.item)) # semantics unclear + self.assertTrue(v.applies_to(self.var1.item, self.var1)) + self.assertFalse(v.applies_to(self.var1.item, self.var2)) + def test_voucher_no_item_with_quota(self): with self.assertRaises(ValidationError): v = Voucher(quota=self.quota, item=self.item1, event=self.event) diff --git a/src/tests/presale/test_widget.py b/src/tests/presale/test_widget.py index 82452f140..e544bd8cf 100644 --- a/src/tests/presale/test_widget.py +++ b/src/tests/presale/test_widget.py @@ -242,6 +242,67 @@ class WidgetCartTest(CartTestMixin, TestCase): "cart_exists": False } + def test_product_list_view_with_voucher_variation_through_quota(self): + self.event.vouchers.create(quota=self.quota_shirts, code="ABCDE") + self.quota_shirts.variations.remove(self.shirt_blue) + response = self.client.get('/%s/%s/widget/product_list?voucher=ABCDE' % (self.orga.slug, self.event.slug)) + assert response['Access-Control-Allow-Origin'] == '*' + data = json.loads(response.content.decode()) + assert data == { + "name": "30C3", + "currency": "EUR", + "show_variations_expanded": False, + "display_net_prices": False, + "vouchers_exist": True, + "waiting_list_enabled": False, + "error": None, + "items_by_category": [ + { + "items": [ + { + 'id': self.shirt.pk, + 'name': 'T-Shirt', + 'picture': None, + 'description': None, + 'has_variations': 2, + 'require_voucher': False, + 'order_min': None, + 'order_max': None, + 'price': None, + 'min_price': '14.00', + 'max_price': '14.00', + 'free_price': False, + 'avail': None, + 'original_price': None, + 'variations': [ + { + 'id': self.shirt_red.pk, + 'value': 'Red', + 'order_max': 2, + 'description': None, + 'price': { + 'gross': '14.00', + 'net': '11.76', + 'tax': '2.24', + 'rate': '19.00', + 'name': '', + 'includes_mixed_tax_rate': False + }, + 'avail': [100, None] + }, + ] + } + ], + "description": None, + "id": self.category.pk, + "name": "Everything" + } + ], + "itemnum": 1, + "display_add_to_cart": True, + "cart_exists": False + } + def test_product_list_view_with_voucher_expired(self): self.event.vouchers.create(item=self.ticket, code="ABCDE", valid_until=now() - datetime.timedelta(days=1)) response = self.client.get('/%s/%s/widget/product_list?voucher=ABCDE' % (self.orga.slug, self.event.slug))