From eab7d81a51d6a866dfa50a616907aa0085417084 Mon Sep 17 00:00:00 2001 From: Lukas Bockstaller Date: Mon, 23 Feb 2026 16:35:24 +0100 Subject: [PATCH] Waiting list: Add edit view for entry (Z#23215496) (#5712) * add edit view for waitinglist entry * add test and fix behaviour when name isn't asked for * fix linting * add testcases for new edit view * fix test * fix linting * add search to the waitinglist view * repair settings check Co-authored-by: Richard Schreiber * make name and phone field optional by removing them * remove item and variation fields from form rather set those values during clean * change label from "Item and Variation" to "Product" * include only products with an enabled waitinglist in the product field * combine edit.html and transfer.html * change transfer to edit * add tests * code style * Update src/pretix/control/forms/waitinglist.py Co-authored-by: Richard Schreiber * Update src/pretix/control/forms/waitinglist.py Co-authored-by: Richard Schreiber * Update src/pretix/control/urls.py Co-authored-by: Richard Schreiber * Update src/pretix/control/templates/pretixcontrol/waitinglist/edit.html Co-authored-by: Richard Schreiber * Update src/pretix/control/templates/pretixcontrol/waitinglist/index.html Co-authored-by: Richard Schreiber * Update src/pretix/control/views/waitinglist.py Co-authored-by: Richard Schreiber * Update src/pretix/control/views/waitinglist.py Co-authored-by: Richard Schreiber * Update src/pretix/control/views/waitinglist.py Co-authored-by: Richard Schreiber * remove validations * remove validations * replace widget * implement small review items * add better assertions * add test for the different edit form variations * add queryset to prefetch only active ItemVariations * add queryset to prefetch only active ItemVariations * propper use of WrappedPhoneNumberPrefixWidget * cleanup * add validation tests * small review changes * handle products with only inactive variations * styling --------- Co-authored-by: Richard Schreiber --- src/pretix/control/forms/waitinglist.py | 90 +++++++- .../pretixcontrol/waitinglist/edit.html | 33 +++ .../pretixcontrol/waitinglist/index.html | 15 +- .../pretixcontrol/waitinglist/transfer.html | 23 --- src/pretix/control/urls.py | 4 +- src/pretix/control/views/waitinglist.py | 28 ++- src/tests/control/test_waitinglist.py | 194 +++++++++++++++--- 7 files changed, 320 insertions(+), 67 deletions(-) create mode 100644 src/pretix/control/templates/pretixcontrol/waitinglist/edit.html delete mode 100644 src/pretix/control/templates/pretixcontrol/waitinglist/transfer.html diff --git a/src/pretix/control/forms/waitinglist.py b/src/pretix/control/forms/waitinglist.py index 7dc33c4a9..cf77380f6 100644 --- a/src/pretix/control/forms/waitinglist.py +++ b/src/pretix/control/forms/waitinglist.py @@ -19,17 +19,44 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # +from django import forms from django.urls import reverse +from django.utils.translation import gettext_lazy as _ from django_scopes.forms import SafeModelChoiceField +from phonenumber_field.formfields import PhoneNumberField from pretix.base.forms import I18nModelForm +from pretix.base.forms.questions import ( + NamePartsFormField, WrappedPhoneNumberPrefixWidget, +) from pretix.base.models import WaitingListEntry from pretix.control.forms.widgets import Select2 -class WaitingListEntryTransferForm(I18nModelForm): +class WaitingListEntryEditForm(I18nModelForm): + itemvar = forms.ChoiceField( + error_messages={ + 'invalid_choice': _("Select a valid choice.") + } + ) def __init__(self, *args, **kwargs): + self.instance = kwargs.get('instance', None) + initial = kwargs.get('initial', {}) + + choices = [] + if self.instance and self.instance.pk and 'itemvar' not in initial: + if self.instance.variation is not None: + initial['itemvar'] = f'{self.instance.item.pk}-{self.instance.variation.pk}' + if self.instance.variation.active is False: + choices.append((initial['itemvar'], str(self.instance.variation))) + else: + initial['itemvar'] = self.instance.item.pk + if self.instance.item.active is False: + choices.append((initial['itemvar'], str(self.instance))) + + kwargs['initial'] = initial + super().__init__(*args, **kwargs) if self.event.has_subevents: @@ -45,12 +72,73 @@ class WaitingListEntryTransferForm(I18nModelForm): } ) self.fields['subevent'].widget.choices = self.fields['subevent'].choices + else: + del self.fields['subevent'] + + if self.event.settings.waiting_list_names_asked: + self.fields['name_parts'] = NamePartsFormField( + max_length=255, + required=self.event.settings.waiting_list_names_required, + scheme=self.event.organizer.settings.name_scheme, + titles=self.event.organizer.settings.name_scheme_titles, + label=_('Name'), + ) + else: + del self.fields['name_parts'] + + if not self.event.settings.waiting_list_phones_asked: + del self.fields['phone'] + + items = self.event.items.filter(active=True).prefetch_related( + 'variations' + ) + + for item in items: + if len(item.variations.all()) > 0: + for variation in item.variations.all(): + if variation.active: + choices.append( + ('{}-{}'.format(item.pk, variation.pk), '{} - {}'.format(str(item), str(variation))) + ) + else: + choices.append(('{}'.format(item.pk), str(item))) + + self.fields['itemvar'].label = _("Product") + self.fields['itemvar'].help_text = _("Only includes active products.") + self.fields['itemvar'].required = True + self.fields['itemvar'].choices = choices + + def clean(self): + cleaned_data = super().clean() + + if self.instance.voucher is not None: + raise forms.ValidationError(_('A voucher for this waiting list entry was already sent out.')) + + itemvar = cleaned_data.get('itemvar') + if itemvar: + self.instance.item = self.event.items.get(pk=itemvar.split('-')[0]) + if '-' in itemvar: + self.instance.variation = self.instance.item.variations.get(pk=itemvar.split('-')[1]) + + if ((self.instance.item and not self.instance.item.active) or + (self.instance.variation and not self.instance.variation.active)): + self.add_error('itemvar', _('The selected product is not active.')) + + return cleaned_data class Meta: model = WaitingListEntry fields = [ + 'email', + 'name_parts', + 'phone', 'subevent', ] field_classes = { 'subevent': SafeModelChoiceField, + 'email': forms.EmailField, + 'phone': PhoneNumberField, + } + widgets = { + 'phone': WrappedPhoneNumberPrefixWidget, } diff --git a/src/pretix/control/templates/pretixcontrol/waitinglist/edit.html b/src/pretix/control/templates/pretixcontrol/waitinglist/edit.html new file mode 100644 index 000000000..d4362f9c0 --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/waitinglist/edit.html @@ -0,0 +1,33 @@ +{% extends "pretixcontrol/event/base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% block title %}{% trans "Edit entry" %}{% endblock %} +{% block content %} +

{% trans "Edit entry" %}

+
+ {% csrf_token %} + + {% if form.subevent %} + {% bootstrap_field form.subevent layout="control" %} + {% endif %} + + {% bootstrap_field form.email layout="control" %} + + {% if form.name_parts %} + {% bootstrap_field form.name_parts layout="control" %} + {% endif %} + + {% if form.phone %} + {% bootstrap_field form.phone layout="control" %} + {% endif %} + {% bootstrap_field form.itemvar layout="control" %} +
+ + {% trans "Cancel" %} + + +
+
+{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/waitinglist/index.html b/src/pretix/control/templates/pretixcontrol/waitinglist/index.html index 1539b2c9d..42c4a9033 100644 --- a/src/pretix/control/templates/pretixcontrol/waitinglist/index.html +++ b/src/pretix/control/templates/pretixcontrol/waitinglist/index.html @@ -124,6 +124,7 @@ {% endfor %} + {% if request.event.has_subevents %}