diff --git a/src/pretix/api/serializers/voucher.py b/src/pretix/api/serializers/voucher.py index 6f140e5a5..d89c16e34 100644 --- a/src/pretix/api/serializers/voucher.py +++ b/src/pretix/api/serializers/voucher.py @@ -78,7 +78,7 @@ class VoucherSerializer(I18nAwareModelSerializer): if full_data.get('seat'): data['seat'] = Voucher.clean_seat_id( - full_data, full_data.get('item'), self.context.get('event'), + full_data, full_data.get('item'), full_data.get('quota'), self.context.get('event'), self.instance.pk if self.instance else None ) diff --git a/src/pretix/base/models/vouchers.py b/src/pretix/base/models/vouchers.py index 0fe39d715..08b760cb1 100644 --- a/src/pretix/base/models/vouchers.py +++ b/src/pretix/base/models/vouchers.py @@ -217,11 +217,12 @@ class Voucher(LoggedModel): self.event, self.quota, self.item, - self.variation + self.variation, + seats_given=bool(self.seat) ) @staticmethod - def clean_item_properties(data, event, quota, item, variation): + def clean_item_properties(data, event, quota, item, variation, seats_given=False): if quota: if quota.event != event: raise ValidationError(_('You cannot select a quota that belongs to a different event.')) @@ -240,7 +241,7 @@ 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.')) - else: + elif not seats_given: raise ValidationError(_('You need to specify either a quota or a product.')) @staticmethod @@ -342,14 +343,18 @@ class Voucher(LoggedModel): raise ValidationError(_('A voucher with this code already exists.')) @staticmethod - def clean_seat_id(data, item, event, pk): + def clean_seat_id(data, item, quota, event, pk): try: if event.has_subevents: if not data.get('subevent'): raise ValidationError(_('You need to choose a date if you select a seat.')) - seat = event.seats.get(seat_guid=data.get('seat'), subevent=data.get('subevent')) + seat = event.seats.select_related('product').get( + seat_guid=data.get('seat'), subevent=data.get('subevent') + ) else: - seat = event.seats.get(seat_guid=data.get('seat')) + seat = event.seats.select_related('product').get( + seat_guid=data.get('seat') + ) except Seat.DoesNotExist: raise ValidationError(_('The specified seat ID "{id}" does not exist for this event.').format( id=data.get('seat'))) @@ -359,13 +364,13 @@ class Voucher(LoggedModel): 'different voucher).').format( id=seat.seat_guid)) - if not item: + if quota: raise ValidationError(_('You need to choose a specific product if you select a seat.')) if data.get('max_usages', 1) > 1: raise ValidationError(_('Seat-specific vouchers can only be used once.')) - if seat.product != item: + if item and seat.product != item: raise ValidationError(_('You need to choose the product "{prod}" for this seat.').format(prod=seat.product)) if not seat.is_available(ignore_voucher_id=pk): diff --git a/src/pretix/control/forms/vouchers.py b/src/pretix/control/forms/vouchers.py index e79d19d11..9abdf897e 100644 --- a/src/pretix/control/forms/vouchers.py +++ b/src/pretix/control/forms/vouchers.py @@ -113,7 +113,6 @@ class VoucherForm(I18nModelForm): } ) self.fields['itemvar'].widget.choices = self.fields['itemvar'].choices - self.fields['itemvar'].required = True if self.instance.event.seating_plan or self.instance.event.subevents.filter(seating_plan__isnull=False).exists(): self.fields['seat'] = forms.CharField( @@ -124,11 +123,14 @@ 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() - if not self._errors: + if not self._errors and self.data.get('itemvar'): try: itemid = quotaid = None iv = self.data.get('itemvar', '') @@ -162,7 +164,8 @@ class VoucherForm(I18nModelForm): Voucher.clean_item_properties( data, self.instance.event, - self.instance.quota, self.instance.item, self.instance.variation + self.instance.quota, self.instance.item, self.instance.variation, + seats_given=data.get('seat') or data.get('seats') ) 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())) @@ -190,7 +193,10 @@ class VoucherForm(I18nModelForm): ) Voucher.clean_voucher_code(data, self.instance.event, self.instance.pk) if 'seat' in self.fields and data.get('seat'): - self.instance.seat = Voucher.clean_seat_id(data, self.instance.item, self.instance.event, self.instance.pk) + self.instance.seat = Voucher.clean_seat_id( + data, self.instance.item, self.instance.quota, self.instance.event, self.instance.pk + ) + self.instance.item = self.instance.seat.product voucher_form_validation.send(sender=self.instance.event, form=self, data=data) @@ -357,7 +363,10 @@ class VoucherBulkForm(VoucherForm): data['seats'] = [] for s in seatids: data['seat'] = s - data['seats'].append(Voucher.clean_seat_id(data, self.instance.item, self.instance.event, None)) + data['seats'].append(Voucher.clean_seat_id( + data, self.instance.item, self.instance.quota, self.instance.event, None + )) + self.instance.seat = data['seats'][0] # Trick model-level validation else: data['seats'] = [] @@ -371,6 +380,7 @@ class VoucherBulkForm(VoucherForm): obj.code = code try: obj.seat = self.cleaned_data['seats'].pop() + obj.item = obj.seat.product except IndexError: pass data = dict(self.cleaned_data)