From 6d07530d2bbda68f756e01644746d9f45c4d01f8 Mon Sep 17 00:00:00 2001 From: Kian Cross Date: Fri, 10 Apr 2026 08:14:34 +0100 Subject: [PATCH] Waiting list: group product choices by category (#6006) * Group waiting list product choices by category Use optgroups to group products by category in the waiting list selection dropdown. Products are normally separated in the UI by category grouping, but this context is lost in the waiting list form. When multiple products share the same name, this can make it difficult for customers to distinguish between them. * Add tests for waiting list initial selection with optgroups Verify that the initial product selection (via `?item=` and `?var=` query parameters) works correctly when choices are grouped by category into ``s. Covers both plain items and items with variations. --- src/pretix/presale/views/waiting.py | 13 +++++-- src/tests/presale/test_event.py | 59 +++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/pretix/presale/views/waiting.py b/src/pretix/presale/views/waiting.py index 6a8d85e46..b2b101979 100644 --- a/src/pretix/presale/views/waiting.py +++ b/src/pretix/presale/views/waiting.py @@ -66,22 +66,27 @@ class WaitingView(EventViewMixin, FormView): if customer else None ), ) - choices = [] + groups = {} for i in items: if not i.allow_waitinglist: continue + category_name = str(i.category.name) if i.category else '' + group = groups.setdefault(category_name, []) + if i.has_variations: for v in i.available_variations: if v.cached_availability[0] == Quota.AVAILABILITY_OK: continue - choices.append((f'{i.pk}-{v.pk}', f'{i.name} – {v.value}')) + group.append((f'{i.pk}-{v.pk}', f'{i.name} – {v.value}')) else: if i.cached_availability[0] == Quota.AVAILABILITY_OK: continue - choices.append((f'{i.pk}', f'{i.name}')) - return choices + group.append((f'{i.pk}', f'{i.name}')) + + # Remove categories where all items were available (no waiting list choices) + return [(cat, choices) for cat, choices in groups.items() if choices] def get_form_kwargs(self): kwargs = super().get_form_kwargs() diff --git a/src/tests/presale/test_event.py b/src/tests/presale/test_event.py index 64558c413..5ffd28db3 100644 --- a/src/tests/presale/test_event.py +++ b/src/tests/presale/test_event.py @@ -1162,6 +1162,65 @@ class WaitingListTest(EventTestMixin, SoupTest): assert wle.voucher is None assert wle.locale == 'en' + def test_initial_selection(self): + with scopes_disabled(): + cat = ItemCategory.objects.create(event=self.event, name='Tickets') + self.item.category = cat + self.item.save() + + item2 = Item.objects.create( + event=self.event, name='VIP ticket', + default_price=Decimal('25.00'), + active=True, category=cat, + ) + self.q.items.add(item2) + + response = self.client.get( + '/%s/%s/waitinglist/?item=%d' % ( + self.orga.slug, self.event.slug, item2.pk + ) + ) + self.assertEqual(response.status_code, 200) + doc = BeautifulSoup(response.render().content, "lxml") + + select = doc.find('select', {'name': 'itemvar'}) + optgroup = select.find('optgroup') + self.assertIsNotNone(optgroup, 'Choices should be grouped by category') + self.assertEqual(optgroup['label'], 'Tickets') + + selected = select.find_all('option', selected=True) + self.assertEqual(len(selected), 1, 'Exactly one option should be pre-selected') + self.assertEqual(selected[0]['value'], str(item2.pk)) + + def test_initial_selection_with_variation(self): + with scopes_disabled(): + cat = ItemCategory.objects.create(event=self.event, name='Tickets') + self.item.category = cat + self.item.has_variations = True + self.item.save() + + var1 = ItemVariation.objects.create(item=self.item, value='Standard') + var2 = ItemVariation.objects.create(item=self.item, value='Premium') + self.q.variations.add(var1, var2) + + response = self.client.get( + '/%s/%s/waitinglist/?item=%d&var=%d' % ( + self.orga.slug, self.event.slug, + self.item.pk, var2.pk, + ) + ) + self.assertEqual(response.status_code, 200) + doc = BeautifulSoup(response.render().content, "lxml") + + select = doc.find('select', {'name': 'itemvar'}) + optgroup = select.find('optgroup') + self.assertIsNotNone(optgroup, 'Choices should be grouped by category') + self.assertEqual(optgroup['label'], 'Tickets') + + selected = select.find_all('option', selected=True) + self.assertEqual(len(selected), 1, 'Exactly one option should be pre-selected') + self.assertEqual(selected[0]['value'], '%d-%d' % (self.item.pk, var2.pk)) + def test_subevent_valid(self): with scopes_disabled(): self.event.has_subevents = True