From bbc70447a2b512d0791c0f8788c2f5dfad783b74 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 27 Nov 2019 14:57:09 +0100 Subject: [PATCH] Allow to create vouchers for *all* products (#1504) --- src/pretix/api/serializers/voucher.py | 3 ++- src/pretix/base/models/items.py | 2 -- src/pretix/base/models/vouchers.py | 17 ++++++++++++----- src/pretix/control/forms/vouchers.py | 9 ++++----- src/tests/api/test_permissions.py | 2 +- src/tests/api/test_vouchers.py | 6 +++++- src/tests/base/test_models.py | 8 ++++++++ 7 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/pretix/api/serializers/voucher.py b/src/pretix/api/serializers/voucher.py index d89c16e34..f07877a7e 100644 --- a/src/pretix/api/serializers/voucher.py +++ b/src/pretix/api/serializers/voucher.py @@ -54,7 +54,8 @@ class VoucherSerializer(I18nAwareModelSerializer): Voucher.clean_item_properties( full_data, self.context.get('event'), - full_data.get('quota'), full_data.get('item'), full_data.get('variation') + full_data.get('quota'), full_data.get('item'), full_data.get('variation'), + block_quota=full_data.get('block_quota') ) Voucher.clean_subevent( full_data, self.context.get('event') diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 67a4aaae9..331392ca2 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -175,8 +175,6 @@ def filter_available(qs, channel='web', voucher=None, allow_addons=False): q &= Q(pk=voucher.item_id) elif voucher.quota_id: q &= Q(quotas__in=[voucher.quota_id]) - else: - return qs.none() if not voucher or not voucher.show_hidden_items: q &= Q(hide_without_voucher=False) diff --git a/src/pretix/base/models/vouchers.py b/src/pretix/base/models/vouchers.py index 08b760cb1..042ba2950 100644 --- a/src/pretix/base/models/vouchers.py +++ b/src/pretix/base/models/vouchers.py @@ -222,7 +222,7 @@ class Voucher(LoggedModel): ) @staticmethod - def clean_item_properties(data, event, quota, item, variation, seats_given=False): + def clean_item_properties(data, event, quota, item, variation, block_quota=False, seats_given=False): if quota: if quota.event != event: raise ValidationError(_('You cannot select a quota that belongs to a different event.')) @@ -241,8 +241,12 @@ class Voucher(LoggedModel): 'Otherwise it might be unclear which quotas to block.')) if item.category and item.category.is_addon: raise ValidationError(_('It is currently not possible to create vouchers for add-on products.')) - elif not seats_given: - raise ValidationError(_('You need to specify either a quota or a product.')) + elif block_quota: + raise ValidationError(_('You need to select a specific product or quota if this voucher should reserve ' + 'tickets.')) + elif variation: + raise ValidationError(_('You cannot select a variation without having selected a product that provides ' + 'variations.')) @staticmethod def clean_max_usages(data, redeemed): @@ -331,7 +335,8 @@ class Voucher(LoggedModel): elif item and not item.has_variations: avail = item.check_quotas(ignored_quotas=old_quotas, subevent=data.get('subevent')) else: - raise ValidationError(_('You need to specify either a quota or a product.')) + raise ValidationError(_('You need to select a specific product or quota if this voucher should reserve ' + 'tickets.')) if avail[0] != Quota.AVAILABILITY_OK or (avail[1] is not None and avail[1] < cnt): raise ValidationError(_('You cannot create a voucher that blocks quota as the selected product or ' @@ -410,7 +415,9 @@ class Voucher(LoggedModel): return item.quotas.filter(pk=self.quota_id).exists() if self.item_id and not self.variation_id: return self.item_id == item.pk - return (self.item_id == item.pk) and (variation and self.variation_id == variation.pk) + if self.item_id: + return (self.item_id == item.pk) and (variation and self.variation_id == variation.pk) + return True def is_active(self): """ diff --git a/src/pretix/control/forms/vouchers.py b/src/pretix/control/forms/vouchers.py index 9abdf897e..8d79910d9 100644 --- a/src/pretix/control/forms/vouchers.py +++ b/src/pretix/control/forms/vouchers.py @@ -109,9 +109,10 @@ class VoucherForm(I18nModelForm): 'event': instance.event.slug, 'organizer': instance.event.organizer.slug, }), - 'data-placeholder': '' + 'data-placeholder': _('All products') } ) + self.fields['itemvar'].required = False self.fields['itemvar'].widget.choices = self.fields['itemvar'].choices if self.instance.event.seating_plan or self.instance.event.subevents.filter(seating_plan__isnull=False).exists(): @@ -123,9 +124,6 @@ class VoucherForm(I18nModelForm): initial=self.instance.seat.seat_guid if self.instance.seat else '', help_text=str(self.instance.seat) if self.instance.seat else '', ) - self.fields['itemvar'].required = False - else: - self.fields['itemvar'].required = True def clean(self): data = super().clean() @@ -165,7 +163,8 @@ class VoucherForm(I18nModelForm): Voucher.clean_item_properties( data, self.instance.event, self.instance.quota, self.instance.item, self.instance.variation, - seats_given=data.get('seat') or data.get('seats') + seats_given=data.get('seat') or data.get('seats'), + block_quota=data.get('block_quota') ) if not self.instance.show_hidden_items and ( (self.instance.quota and all(i.hide_without_voucher for i in self.instance.quota.items.all())) diff --git a/src/tests/api/test_permissions.py b/src/tests/api/test_permissions.py index b6c7d193e..08f51885f 100644 --- a/src/tests/api/test_permissions.py +++ b/src/tests/api/test_permissions.py @@ -75,7 +75,7 @@ event_permission_sub_urls = [ ('delete', 'can_change_event_settings', 'taxrules/1/', 404), ('get', 'can_view_vouchers', 'vouchers/', 200), ('get', 'can_view_vouchers', 'vouchers/1/', 404), - ('post', 'can_change_vouchers', 'vouchers/', 400), + ('post', 'can_change_vouchers', 'vouchers/', 201), ('put', 'can_change_vouchers', 'vouchers/1/', 404), ('patch', 'can_change_vouchers', 'vouchers/1/', 404), ('delete', 'can_change_vouchers', 'vouchers/1/', 404), diff --git a/src/tests/api/test_vouchers.py b/src/tests/api/test_vouchers.py index 96c2f65b2..50c8d38f8 100644 --- a/src/tests/api/test_vouchers.py +++ b/src/tests/api/test_vouchers.py @@ -251,9 +251,13 @@ def create_voucher(token_client, organizer, event, data, expected_failure=False) def test_voucher_require_item(token_client, organizer, event, item): create_voucher( token_client, organizer, event, - data={}, + data={'block_quota': True}, expected_failure=True ) + create_voucher( + token_client, organizer, event, + data={}, + ) @pytest.mark.django_db diff --git a/src/tests/base/test_models.py b/src/tests/base/test_models.py index 2e137f8da..4a9360dc6 100644 --- a/src/tests/base/test_models.py +++ b/src/tests/base/test_models.py @@ -717,6 +717,14 @@ class VoucherTestCase(BaseQuotaTestCase): self.assertTrue(v.applies_to(self.var1.item, self.var1)) self.assertFalse(v.applies_to(self.var1.item, self.var2)) + @classscope(attr='o') + def test_voucher_applicability_all(self): + v = Voucher.objects.create(event=self.event) + self.assertTrue(v.applies_to(self.item1)) + self.assertTrue(v.applies_to(self.var1.item)) + self.assertTrue(v.applies_to(self.var1.item, self.var1)) + self.assertTrue(v.applies_to(self.var1.item, self.var2)) + @classscope(attr='o') def test_voucher_applicability_variation_through_quota(self): self.quota.variations.add(self.var1)