From adf167e611eca34ec4f588ad056b4db258709b8c Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Fri, 17 Apr 2026 02:09:57 +0200 Subject: [PATCH] untested draft --- src/pretix/control/forms/vouchers.py | 293 ++++++++++++++++-- .../pretixcontrol/vouchers/bulk_edit.html | 87 ++++++ .../pretixcontrol/vouchers/index.html | 2 +- src/pretix/control/urls.py | 2 +- src/pretix/control/views/vouchers.py | 60 ++-- 5 files changed, 386 insertions(+), 58 deletions(-) create mode 100644 src/pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html diff --git a/src/pretix/control/forms/vouchers.py b/src/pretix/control/forms/vouchers.py index 3bff507591..552503ccd1 100644 --- a/src/pretix/control/forms/vouchers.py +++ b/src/pretix/control/forms/vouchers.py @@ -33,16 +33,18 @@ # License for the specific language governing permissions and limitations under the License. import csv -from collections import namedtuple +from collections import namedtuple, Counter from io import StringIO from django import forms from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.validators import EmailValidator +from django.db.models import Max, Sum, Count, Q, F from django.db.models.functions import Upper from django.forms.utils import ErrorDict from django.urls import reverse -from django.utils.translation import gettext_lazy as _ +from django.utils.timezone import now +from django.utils.translation import gettext_lazy as _, pgettext_lazy from django_scopes.forms import SafeModelChoiceField from pretix.base.email import get_available_placeholders @@ -51,7 +53,9 @@ from pretix.base.forms import ( ) from pretix.base.forms.widgets import format_placeholders_help_text from pretix.base.i18n import language -from pretix.base.models import Item, Voucher +from pretix.base.models import Item, Voucher, Quota, SubEvent, ItemVariation +from pretix.base.services.locking import lock_objects +from pretix.base.services.quotas import QuotaAvailability from pretix.control.forms import SplitDateTimeField, SplitDateTimePickerWidget from pretix.control.forms.widgets import Select2, Select2ItemVarQuota from pretix.control.signals import voucher_form_validation @@ -107,14 +111,14 @@ class VoucherForm(I18nModelForm): pass super().__init__(*args, **kwargs) - if instance.event.has_subevents: - self.fields['subevent'].queryset = instance.event.subevents.all() + if self.event.has_subevents: + self.fields['subevent'].queryset = self.event.subevents.all() self.fields['subevent'].widget = Select2( attrs={ 'data-model-select2': 'event', 'data-select2-url': reverse('control:event.subevents.select2', kwargs={ - 'event': instance.event.slug, - 'organizer': instance.event.organizer.slug, + 'event': self.event.slug, + 'organizer': self.event.organizer.slug, }), } ) @@ -127,15 +131,15 @@ class VoucherForm(I18nModelForm): if 'itemvar' in initial or (self.data and 'itemvar' in self.data): iv = self.data.get('itemvar') or initial.get('itemvar', '') if iv.startswith('q-'): - q = self.instance.event.quotas.get(pk=iv[2:]) + q = self.event.quotas.get(pk=iv[2:]) choices.append(('q-%d' % q.pk, _('Any product in quota "{quota}"').format(quota=q))) elif '-' in iv: itemid, varid = iv.split('-') - i = self.instance.event.items.get(pk=itemid) + i = self.event.items.get(pk=itemid) v = i.variations.get(pk=varid) choices.append(('%d-%d' % (i.pk, v.pk), '%s – %s' % (str(i), v.value))) elif iv: - i = self.instance.event.items.get(pk=iv) + i = self.event.items.get(pk=iv) if i.variations.exists(): choices.append((str(i.pk), _('{product} – Any variation').format(product=i))) else: @@ -146,8 +150,8 @@ class VoucherForm(I18nModelForm): attrs={ 'data-model-select2': 'generic', 'data-select2-url': reverse('control:event.vouchers.itemselect2', kwargs={ - 'event': instance.event.slug, - 'organizer': instance.event.organizer.slug, + 'event': self.event.slug, + 'organizer': self.event.organizer.slug, }), 'data-placeholder': _('All products') } @@ -155,7 +159,7 @@ class VoucherForm(I18nModelForm): 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(): + if self.event.seating_plan or self.event.subevents.filter(seating_plan__isnull=False).exists(): self.fields['seat'] = forms.CharField( label=_("Specific seat ID"), max_length=255, @@ -182,14 +186,14 @@ class VoucherForm(I18nModelForm): itemid, varid = None, None if itemid: - self.instance.item = self.instance.event.items.get(pk=itemid) + self.instance.item = self.event.items.get(pk=itemid) if varid: self.instance.variation = self.instance.item.variations.get(pk=varid) else: self.instance.variation = None self.instance.quota = None elif quotaid: - self.instance.quota = self.instance.event.quotas.get(pk=quotaid) + self.instance.quota = self.event.quotas.get(pk=quotaid) self.instance.item = None self.instance.variation = None else: @@ -210,7 +214,7 @@ class VoucherForm(I18nModelForm): try: Voucher.clean_item_properties( - data, self.instance.event, + data, self.event, self.instance.quota, self.instance.item, self.instance.variation, seats_given=data.get('seat') or data.get('seats'), block_quota=data.get('block_quota') @@ -230,7 +234,7 @@ class VoucherForm(I18nModelForm): try: Voucher.clean_subevent( - data, self.instance.event + data, self.event ) except ValidationError as e: raise ValidationError({"subevent": e.message}) @@ -246,19 +250,19 @@ class VoucherForm(I18nModelForm): if check_quota: Voucher.clean_quota_check( data, cnt, self.initial_instance_data, - self.instance.event, self.instance.quota, self.instance.item, self.instance.variation + self.event, self.instance.quota, self.instance.item, self.instance.variation ) - Voucher.clean_voucher_code(data, self.instance.event, self.instance.pk) + Voucher.clean_voucher_code(data, self.event, self.instance.pk) if 'seat' in self.fields: if data.get('seat'): self.instance.seat = Voucher.clean_seat_id( - data, self.instance.item, self.instance.quota, self.instance.event, self.instance.pk + data, self.instance.item, self.instance.quota, self.event, self.instance.pk ) self.instance.item = self.instance.seat.product else: self.instance.seat = None - voucher_form_validation.send(sender=self.instance.event, form=self, data=data) + voucher_form_validation.send(sender=self.event, form=self, data=data) return data @@ -267,33 +271,253 @@ class VoucherForm(I18nModelForm): class VoucherBulkEditForm(VoucherForm): - # TODO: clean quota changes! - def __init__(self, *args, **kwargs): self.mixed_values = kwargs.pop('mixed_values') self.queryset = kwargs.pop('queryset') super().__init__(**kwargs) + del self.fields["code"] + self.fields.pop("seat", None) + + def clean(self): + # We skip the parent class because it's not suited for bulk editing and implement custom validation here. + # This does not validate everything we validate in VoucherForm. For example, we skip validation that one does + # not create a voucher for an add-on product to save on complexity. This is a UX validation only anyways, since + # one could first create the voucher and then make the product an add-on product. However, we need to validate + # everything that we don't want violated in the database. + data = super(VoucherForm, self).clean() + + if self.prefix + "itemvar" in self.data.getlist('_bulk'): + try: + itemid = quotaid = None + iv = self.data.get('itemvar', '') + if iv.startswith('q-'): + quotaid = iv[2:] + elif '-' in iv: + itemid, varid = iv.split('-') + elif iv: + itemid, varid = iv, None + else: + itemid, varid = None, None + + if itemid: + data["item"] = self.event.items.get(pk=itemid) + if varid: + data["variation"] = self.instance.item.variations.get(pk=varid) + else: + data["variation"] = None + data["quota"] = None + elif quotaid: + data["quota"] = self.event.quotas.get(pk=quotaid) + data["item"] = None + data["variation"] = None + else: + data["quota"] = None + data["item"] = None + data["variation"] = None + + except ObjectDoesNotExist: + raise ValidationError(_("Invalid product selected.")) + + if self.prefix + "max_usages" in self.data.getlist('_bulk'): + max_redeemed = self.queryset.aggregate(m=Max("redeemed"))["m"] + if data["max_usages"] > max_redeemed: + raise ValidationError(_( + "You cannot reduce the maximum number of redemptions to %(max_usages)s, because at least one " + "of the selected vouchers has already been redeemed %(max_redeemed)s times." + ) % {"max_usages": data["max_usages"], "max_redeemed": max_redeemed}) + + # Check diff on product and quota usage based on old groups of vouchers + if any(self.prefix + k in self.data.getlist('_bulk') for k in ("max_usages", "itemvar", "block_quota", "valid_until", "subevent")): + quota_diff = Counter() + + current_vouchers = self.queryset.order_by().values( + "item", "variation", "quota", "block_quota", "valid_until", "subevent", "redeemed", "max_usages" + ).annotate(c=Count("*")) + item_cache = {i.pk: i for i in Item.objects.filter(pk__in=[c["item"] for c in current_vouchers])} + var_cache = {v.pk: v for v in ItemVariation.objects.filter(pk__in=[c["variation"] for c in current_vouchers])} + quota_cache = {q.pk: q for q in Quota.objects.filter(pk__in=[c["quota"] for c in current_vouchers])} + subevent_cache = {s.pk: s for s in SubEvent.objects.filter(pk__in=[c["subevent"] for c in current_vouchers])} + + for current in current_vouchers: + was_valid = current["valid_until"] is None or current["valid_until"] >= now() + + # Get quotas that are currently used + if current["item"]: + current["item"] = item_cache[current["item"]] + if current["variation"]: + current["variation"] = var_cache[current["variation"]] + if current["quota"]: + current["quota"] = quota_cache[current["quota"]] + if current["subevent"]: + current["subevent"] = subevent_cache[current["subevent"]] + + old_quotas = set() + if was_valid and current["block_quota"] and current["max_usages"] > current["redeemed"]: + if current["quota"]: + old_quotas.add(current["quota"]) + elif current["variation"]: + old_quotas |= set(current["variation"].quotas.filter(subevent=current["subevent"])) + elif current["item"]: + if current["item"].has_variations: + old_quotas |= set( + Quota.objects.filter(pk__in=Quota.variations.through.objects.filter( + itemvariation__item=current["item"], + quota__subevent=current["subevent"], + ).values('quota_id')) + ) + else: + old_quotas |= set(current["item"].quotas.filter(subevent=current["subevent"])) + old_amount = max(current["max_usages"] - current["redeemed"], 0) + + # Predict state after change + after_change = dict(current) + if self.prefix + "itemvar" in self.data.getlist('_bulk'): + after_change["item"] = data["item"] + after_change["variation"] = data["variation"] + after_change["quota"] = data["quota"] + if self.prefix + "subevent" in self.data.getlist('_bulk'): + after_change["subevent"] = data["subevent"] + if self.prefix + "max_usages" in self.data.getlist('_bulk'): + after_change["max_usages"] = data["max_usages"] + if self.prefix + "block_quota" in self.data.getlist('_bulk'): + after_change["block_quota"] = data["block_quota"] + if self.prefix + "valid_until" in self.data.getlist('_bulk'): + after_change["valid_until"] = data["valid_until"] + if self.prefix + "allow_ignore_quota" in self.data.getlist('_bulk'): + after_change["allow_ignore_quota"] = data["allow_ignore_quota"] + + if after_change["quota"] and self.event.has_subevents and not after_change["subevent"]: + raise _("You cannot create a voucher that allows selection of a quota but has no date selected.") + + if after_change["quota"] and after_change["subevent"] and after_change["quota"].subevent_id != after_change["subevent"].pk: + raise _("The selected quota does not match the selected subevent.") + + if after_change["block_quota"] and self.event.has_subevents and not after_change["subevent"]: + raise ValidationError( + _('If you want this voucher to block quota, you need to select a specific date.')) + + if after_change["block_quota"] and not after_change["item"] and not after_change["quota"]: + raise ValidationError( + _('You need to select a specific product or quota if this voucher should reserve ' + 'tickets.') + ) + + if after_change["allow_ignore_quota"]: + # todo: is this the most useful way to do this? + continue + + will_be_valid = current["valid_until"] is None or current["valid_until"] >= now() + new_quotas = set() + if will_be_valid and after_change["block_quota"] and after_change["max_usages"] > current["redeemed"]: + if after_change["quota"]: + new_quotas.add(after_change["quota"]) + elif after_change["variation"]: + new_quotas |= set(after_change["variation"].quotas.filter(subevent=after_change["subevent"])) + elif after_change["item"]: + if after_change["item"].has_variations: + new_quotas |= set( + Quota.objects.filter(pk__in=Quota.variations.through.objects.filter( + itemvariation__item=after_change["item"], + quota__subevent=after_change["subevent"], + ).values('quota_id')) + ) + else: + new_quotas |= set(after_change["item"].quotas.filter(subevent=after_change["subevent"])) + + new_amount = max(current["max_usages"] - current["redeemed"], 0) + + if new_quotas != old_quotas or new_amount != old_amount: + for q in old_quotas: + quota_diff[q] -= old_amount + for q in new_quotas: + quota_diff[q] += new_quotas + + if any(v > 0 for q, v in quota_diff.items()): + lock_objects([q for q, in quota_diff.items() if q.size is not None and v > 0], shared_lock_objects=[self.event]) + qa = QuotaAvailability(count_waitinglist=False) + qa.queue(*(q for q, v in quota_diff.items() if v > 0)) + qa.compute() + + if any(r[0] != Quota.AVAILABILITY_OK or (r[1] is not None and r[1] < cnt) for r in qa.results.values()): + raise ValidationError(_( + 'There is no sufficient quota available to perform this change.' + )) + + has_seat = self.queryset.filter(seat__isnull=False).exists() + if has_seat: + if self.prefix + "max_usages" in self.data.getlist('_bulk'): + raise ValidationError(_( + 'Changing the maximum number of usages in bulk is not supported if any of the selected vouchers ' + 'is assigned a seat.' + )) + if self.prefix + "subevent" in self.data.getlist('_bulk'): + raise ValidationError(pgettext_lazy( + 'subevent', + 'Changing the date in bulk is not supported if any of the selected vouchers ' + 'is assigned a seat.' + )) + if self.prefix + "itemvar" in self.data.getlist('_bulk') and data["quota"]: + raise ValidationError(_( + 'Changing the product to a quota is not supported if any of the selected vouchers ' + 'is assigned a seat.' + )) + + if self.prefix + "valid_until" in self.data.getlist('_bulk'): + if data["valid_until"] is None or data["valid_until"] >= now(): + currently_not_blocked_seats = self.queryset.filter( + seat__isnull=False, + max_usages__gt=F("redeemed"), + valid_until__lt=now(), + ) + if self.event.has_subevents: + conflicts = currently_not_blocked_seats.exclude( + seat_id__in=self.event.free_seats.values(pk) + ) + if conflicts: + raise ValidationError(_( + 'This change cannot be completed because not all assigned seats of the vouchers are ' + 'still available' + )) + else: + subevents = self.event.subevents.filter(pk__in=currently_not_blocked_seats.values_list("subevent")) + for se in subevents: + conflicts = currently_not_blocked_seats.filter( + subevent=se + ).exclude( + seat_id__in=se.free_seats.values(pk) + ) + if conflicts: + raise ValidationError(_( + 'This change cannot be completed because not all assigned seats of the vouchers are ' + 'still available' + )) + + return data def save(self, commit=True): objs = list(self.queryset) fields = set() + check_map = { + 'price_mode': '__price', + 'value': '__price', + } for k in self.fields: - cb_val = self.prefix + k + cb_val = self.prefix + check_map.get(k, k) if cb_val not in self.data.getlist('_bulk'): continue - fields.add(k) + if k == 'itemvar': + fields.add("item") + fields.add("variation") + fields.add("quota") + else: + fields.add(k) for obj in objs: if k == 'itemvar': - selected_items = set(list(self.event.items.filter(id__in=[ - i.split('-')[0] for i in self.cleaned_data['itemvars'] - ]))) - selected_variations = list(ItemVariation.objects.filter(item__event=self.event, id__in=[ - i.split('-')[1] for i in self.cleaned_data['itemvars'] if '-' in i - ])) - obj.items.set(selected_items) - obj.variations.set(selected_variations) + obj.item = self.cleaned_data["item"] + obj.variation = self.cleaned_data["variation"] + obj.quota = self.cleaned_data["quota"] else: setattr(obj, k, self.cleaned_data[k]) @@ -308,6 +532,9 @@ class VoucherBulkEditForm(VoucherForm): return super().full_clean() + def _post_clean(self): + pass # skip model-level clean + class VoucherBulkForm(VoucherForm): codes = forms.CharField( diff --git a/src/pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html b/src/pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html new file mode 100644 index 0000000000..80aaf07dc4 --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html @@ -0,0 +1,87 @@ +{% extends "pretixcontrol/items/base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% load eventsignal %} +{% load eventurl %} +{% block title %}{% trans "Change multiple vouchers" %}{% endblock %} +{% block inside %} +

+ {% trans "Change multiple vouchers" %} + + {% blocktrans trimmed with number=vouchers.count %} + {{ number }} selected + {% endblocktrans %} + +

+
+ {% csrf_token %} + {% bootstrap_form_errors form %} +
+ {% trans "Voucher details" %} + {% bootstrap_field form.max_usages layout="bulkedit" %} + {% bootstrap_field form.valid_until layout="bulkedit" %} + {% bootstrap_field form.itemvar layout="bulkedit" %} + +
+ +
+
+ +
+ {% bootstrap_field form.price_mode show_label=False form_group_class="" %} +
+
+ {% bootstrap_field form.value show_label=False form_group_class="" %} +
+
+
+
+ +
+
+
+
+ {% blocktrans trimmed %} + If you choose "any product" for a specific quota and choose to reserve quota for this + voucher above, the product can still be unavailable to the voucher holder if another quota + associated with the product is sold out! + {% endblocktrans %} +
+
+
+
+ {% if form.subevent %} + {% bootstrap_field form.subevent layout="bulkedit" %} + {% endif %} +
+
+ {% trans "Advanced settings" %} + {% bootstrap_field form.block_quota layout="bulkedit" %} + {% bootstrap_field form.allow_ignore_quota layout="bulkedit" %} + {% bootstrap_field form.min_usages layout="bulkedit" %} + {% bootstrap_field form.budget addon_after=request.event.currency layout="bulkedit" %} + {% bootstrap_field form.tag layout="bulkedit" %} + {% bootstrap_field form.comment layout="bulkedit" %} + {% bootstrap_field form.show_hidden_items layout="bulkedit" %} + {% bootstrap_field form.all_addons_included layout="bulkedit" %} + {% bootstrap_field form.all_bundles_included layout="bulkedit" %} +
+
+ + {% if voucher.pk %} + + {% endif %} +
+
+{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/vouchers/index.html b/src/pretix/control/templates/pretixcontrol/vouchers/index.html index 8894b5d9a5..d51ab46103 100644 --- a/src/pretix/control/templates/pretixcontrol/vouchers/index.html +++ b/src/pretix/control/templates/pretixcontrol/vouchers/index.html @@ -224,7 +224,7 @@ {% trans "Delete selected" %} diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index 48cb9d0834..fce8917ed6 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -379,7 +379,7 @@ urlpatterns = [ re_path(r'^vouchers/bulk_add$', vouchers.VoucherBulkCreate.as_view(), name='event.vouchers.bulk'), re_path(r'^vouchers/bulk_add/mail_preview$', vouchers.VoucherBulkMailPreview.as_view(), name='event.vouchers.bulk.mail_preview'), re_path(r'^vouchers/bulk_action$', vouchers.VoucherBulkAction.as_view(), name='event.vouchers.bulkaction'), - #re_path(r'^vouchers/bulk_edit$', vouchers.VoucherBulkUpdateView.as_view(), name='event.vouchers.bulkedit'), + re_path(r'^vouchers/bulk_edit$', vouchers.VoucherBulkUpdateView.as_view(), name='event.vouchers.bulkedit'), re_path(r'^vouchers/import/$', modelimport.VoucherImportView.as_view(), name='event.vouchers.import'), re_path(r'^vouchers/import/(?P[^/]+)/$', modelimport.VoucherProcessView.as_view(), name='event.vouchers.import.process'), re_path(r'^orders/(?P[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(), diff --git a/src/pretix/control/views/vouchers.py b/src/pretix/control/views/vouchers.py index 0081dff710..8d9f782e0d 100644 --- a/src/pretix/control/views/vouchers.py +++ b/src/pretix/control/views/vouchers.py @@ -42,7 +42,7 @@ from django.conf import settings from django.contrib import messages from django.core.exceptions import PermissionDenied, ValidationError from django.db import connection, transaction -from django.db.models import Exists, OuterRef, Sum, Subquery +from django.db.models import Exists, OuterRef, Sum, Subquery, Count from django.http import ( Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect, JsonResponse, @@ -70,7 +70,7 @@ from pretix.base.services.vouchers import vouchers_send from pretix.base.templatetags.rich_text import markdown_compile_email from pretix.base.views.tasks import AsyncFormView from pretix.control.forms.filter import VoucherFilterForm, VoucherTagFilterForm -from pretix.control.forms.vouchers import VoucherBulkForm, VoucherForm +from pretix.control.forms.vouchers import VoucherBulkForm, VoucherForm, VoucherBulkEditForm from pretix.control.permissions import EventPermissionRequiredMixin from pretix.control.signals import voucher_form_class from pretix.control.views import PaginationMixin @@ -311,6 +311,12 @@ class VoucherUpdate(EventPermissionRequiredMixin, UpdateView): f.disabled = True return form + def get_form_kwargs(self): + return { + **super().get_form_kwargs(), + "event": self.request.event, + } + def get_object(self, queryset=None) -> VoucherForm: url = resolve(self.request.path_info) try: @@ -669,7 +675,7 @@ class VoucherBulkAction(VoucherQueryMixin, EventPermissionRequiredMixin, View): class VoucherBulkUpdateView(VoucherQueryMixin, EventPermissionRequiredMixin, FormView): template_name = 'pretixcontrol/vouchers/bulk_edit.html' permission = 'event.vouchers:write' - context_object_name = 'voucher' + context_object_name = 'vouchers' form_class = VoucherBulkEditForm def get_queryset(self): @@ -690,28 +696,36 @@ class VoucherBulkUpdateView(VoucherQueryMixin, EventPermissionRequiredMixin, For mixed_values = set() qs = self.get_queryset().annotate() - fields = { - 'name': 'name', - 'size': 'size', - 'subevent': 'subevent', - 'close_when_sold_out': 'close_when_sold_out', - 'release_after_exit': 'release_after_exit', - 'ignore_for_event_availability': 'ignore_for_event_availability', - } - for k, f in fields.items(): + fields = ( + 'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag', 'comment', 'max_usages', + 'min_usages', 'price_mode', 'subevent', 'show_hidden_items', 'all_addons_included', 'all_bundles_included', + 'budget', + ) + for f in fields: existing_values = list(qs.order_by(f).values(f).annotate(c=Count('*'))) if len(existing_values) == 1: - initial[k] = existing_values[0][f] + initial[f] = existing_values[0][f] elif len(existing_values) > 1: - mixed_values.add(k) - initial[k] = None + mixed_values.add(f) + if f == "max_usages": + initial[f] = 1 + else: + initial[f] = None - item_values = list(qs.order_by("items_list").values("items_list").annotate(c=Count('*'))) - var_values = list(qs.order_by("vars_list").values("vars_list").annotate(c=Count('*'))) - if len(item_values) > 1 or len(var_values) > 1: - mixed_values.add("itemvars") - else: - initial["itemvars"] = [iv for iv in (item_values[0]["items_list"] or "").split(",") + (var_values[0]["vars_list"] or "").split(",") if iv] + existing_values = list(qs.order_by("item", "variation", "quota").values("item", "variation", "quota").annotate(c=Count('*'))) + if len(existing_values) == 1: + i = existing_values[0] + if i["quota"]: + initial["itemvar"] = f'q-{i["quota"]}' + elif i["variation"]: + initial["itemvar"] = f'{i["item"]}-{i["variation"]}' + elif i["item"]: + initial["itemvar"] = f'{i["item"]}' + else: + initial["itemvar"] = None + elif len(existing_values) > 1: + mixed_values.add("itemvar") + initial["itemvar"] = None kwargs = super().get_form_kwargs() kwargs['event'] = self.request.event @@ -725,7 +739,7 @@ class VoucherBulkUpdateView(VoucherQueryMixin, EventPermissionRequiredMixin, For return kwargs def get_success_url(self): - return reverse('control:event.items.quotas', kwargs={ + return reverse('control:event.vouchers', kwargs={ 'organizer': self.request.event.organizer.slug, 'event': self.request.event.slug, }) @@ -754,7 +768,7 @@ class VoucherBulkUpdateView(VoucherQueryMixin, EventPermissionRequiredMixin, For def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) - ctx['quotas'] = self.get_queryset() + ctx['vouchers'] = self.get_queryset() ctx['bulk_selected'] = self.request.POST.getlist("_bulk") return ctx