mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Improvements around the waiting list (#2219)
* Waiting list: Support for seated events, pre-fill customer email address * Allow people to remove themselves * Update src/pretix/base/settings.py Co-authored-by: Richard Schreiber <schreiber@rami.io> * Update src/pretix/control/views/waitinglist.py Co-authored-by: Richard Schreiber <schreiber@rami.io> * Update src/pretix/control/views/waitinglist.py Co-authored-by: Richard Schreiber <schreiber@rami.io> * Update src/pretix/control/views/waitinglist.py Co-authored-by: Richard Schreiber <schreiber@rami.io> * Update src/pretix/presale/views/waiting.py Co-authored-by: Richard Schreiber <schreiber@rami.io> * Resolve a review note * Review notes * Fix linter issues * Fix import Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
@@ -462,6 +462,16 @@ def base_placeholders(sender, **kwargs):
|
||||
lambda waiting_list_entry, event: (waiting_list_entry.subevent or event).get_date_from_display(),
|
||||
lambda event: (event if not event.has_subevents or not event.subevents.exists() else event.subevents.first()).get_date_from_display()
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'url_remove', ['waiting_list_entry', 'event'],
|
||||
lambda waiting_list_entry, event: build_absolute_uri(
|
||||
event, 'presale:event.waitinglist.remove'
|
||||
) + '?voucher=' + waiting_list_entry.voucher.code,
|
||||
lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.waitinglist.remove',
|
||||
) + '?voucher=68CYU2H6ZTP3WLK5',
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'url', ['waiting_list_entry', 'event'],
|
||||
lambda waiting_list_entry, event: build_absolute_uri(
|
||||
|
||||
@@ -21,8 +21,9 @@
|
||||
#
|
||||
from datetime import timedelta
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
from django.db import models, transaction
|
||||
from django.db.models import F, Q, Sum
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from django_scopes import ScopedManager
|
||||
@@ -114,9 +115,12 @@ class WaitingListEntry(LoggedModel):
|
||||
return '%s waits for %s' % (str(self.email), str(self.item))
|
||||
|
||||
def clean(self):
|
||||
WaitingListEntry.clean_duplicate(self.email, self.item, self.variation, self.subevent, self.pk)
|
||||
WaitingListEntry.clean_itemvar(self.event, self.item, self.variation)
|
||||
WaitingListEntry.clean_subevent(self.event, self.subevent)
|
||||
try:
|
||||
WaitingListEntry.clean_duplicate(self.email, self.item, self.variation, self.subevent, self.pk)
|
||||
WaitingListEntry.clean_itemvar(self.event, self.item, self.variation)
|
||||
WaitingListEntry.clean_subevent(self.event, self.subevent)
|
||||
except ObjectDoesNotExist:
|
||||
raise ValidationError('Invalid input')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
update_fields = kwargs.get('update_fields', [])
|
||||
@@ -147,6 +151,34 @@ class WaitingListEntry(LoggedModel):
|
||||
)
|
||||
if availability[1] is None or availability[1] < 1:
|
||||
raise WaitingListException(_('This product is currently not available.'))
|
||||
|
||||
ev = self.subevent or self.event
|
||||
if ev.seat_category_mappings.filter(product=self.item).exists():
|
||||
# Generally, we advertise the waiting list to be based on quotas only. This makes it dangerous
|
||||
# to use in combination with seating plans. If your event has 50 seats and a quota of 50 and
|
||||
# default settings, everything is fine and the waiting list will work as usual. However, as soon
|
||||
# as those two numbers diverge, either due to misconfiguration or due to intentional features such
|
||||
# as our COVID-19 minimum distance feature, things get ugly. Theoretically, there could be
|
||||
# significant quota available but not a single seat! The waiting list would happily send out vouchers
|
||||
# which do not work at all. Generally, we consider this a "known bug" and not fixable with the current
|
||||
# design of the waiting list and seating features.
|
||||
# However, we've put in a simple safeguard that makes sure the waiting list on its own does not screw
|
||||
# everything up. Specifically, we will not send out vouchers if the number of available seats is less
|
||||
# than the number of valid vouchers *issued through the waiting list*. Things can still go wrong due to
|
||||
# manually created vouchers, manually blocked seats or the minimum distance feature, but this reduces
|
||||
# the possible damage a bit.
|
||||
num_free_seats_for_product = ev.free_seats().filter(product=self.item).count()
|
||||
num_valid_vouchers_for_product = self.event.vouchers.filter(
|
||||
Q(valid_until__isnull=True) | Q(valid_until__gte=now()),
|
||||
block_quota=True,
|
||||
item_id=self.item_id,
|
||||
subevent_id=self.subevent_id,
|
||||
waitinglistentries__isnull=False
|
||||
).aggregate(free=Sum(F('max_usages') - F('redeemed')))['free'] or 0
|
||||
free_seats = num_free_seats_for_product - num_valid_vouchers_for_product
|
||||
if not free_seats:
|
||||
raise WaitingListException(_('No seat with this product is currently available.'))
|
||||
|
||||
if self.voucher:
|
||||
raise WaitingListException(_('A voucher has already been sent to this person.'))
|
||||
if '@' not in self.email:
|
||||
|
||||
@@ -22,12 +22,14 @@
|
||||
import sys
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db.models import Exists, OuterRef, Q
|
||||
from django.db.models import Exists, F, OuterRef, Q, Sum
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import Event, User, WaitingListEntry
|
||||
from pretix.base.models import (
|
||||
Event, SeatCategoryMapping, User, WaitingListEntry,
|
||||
)
|
||||
from pretix.base.models.waitinglist import WaitingListException
|
||||
from pretix.base.services.tasks import EventTask
|
||||
from pretix.base.signals import periodic_task
|
||||
@@ -43,6 +45,19 @@ def assign_automatically(event: Event, user_id: int=None, subevent_id: int=None)
|
||||
|
||||
quota_cache = {}
|
||||
gone = set()
|
||||
seats_available = {}
|
||||
|
||||
for m in SeatCategoryMapping.objects.filter(event=event).select_related('subevent'):
|
||||
# See comment in WaitingListEntry.send_voucher() for rationale
|
||||
num_free_seets_for_product = (m.subevent or event).free_seats().filter(product_id=m.product_id).count()
|
||||
num_valid_vouchers_for_product = event.vouchers.filter(
|
||||
Q(valid_until__isnull=True) | Q(valid_until__gte=now()),
|
||||
block_quota=True,
|
||||
item_id=m.product_id,
|
||||
subevent_id=m.subevent_id,
|
||||
waitinglistentries__isnull=False
|
||||
).aggregate(free=Sum(F('max_usages') - F('redeemed')))['free'] or 0
|
||||
seats_available[(m.product_id, m.subevent_id)] = num_free_seets_for_product - num_valid_vouchers_for_product
|
||||
|
||||
qs = WaitingListEntry.objects.filter(
|
||||
event=event, voucher__isnull=True
|
||||
@@ -70,6 +85,11 @@ def assign_automatically(event: Event, user_id: int=None, subevent_id: int=None)
|
||||
gone.add((wle.item, wle.variation, wle.subevent))
|
||||
continue
|
||||
|
||||
if (wle.item_id, wle.subevent_id) in seats_available:
|
||||
if seats_available[wle.item_id, wle.subevent_id] < 1:
|
||||
gone.add((wle.item, wle.variation, wle.subevent))
|
||||
continue
|
||||
|
||||
quotas = (wle.variation.quotas.filter(subevent=wle.subevent)
|
||||
if wle.variation
|
||||
else wle.item.quotas.filter(subevent=wle.subevent))
|
||||
@@ -91,6 +111,9 @@ def assign_automatically(event: Event, user_id: int=None, subevent_id: int=None)
|
||||
quota_cache[q.pk][0] if quota_cache[q.pk][0] > 1 else 0,
|
||||
quota_cache[q.pk][1] - 1 if quota_cache[q.pk][1] is not None else sys.maxsize
|
||||
)
|
||||
|
||||
if (wle.item_id, wle.subevent_id) in seats_available:
|
||||
seats_available[wle.item_id, wle.subevent_id] -= 1
|
||||
else:
|
||||
gone.add((wle.item, wle.variation, wle.subevent))
|
||||
|
||||
|
||||
@@ -1746,6 +1746,12 @@ Please note that this link is only valid within the next {hours} hours!
|
||||
We will reassign the ticket to the next person on the list if you do not
|
||||
redeem the voucher within that timeframe.
|
||||
|
||||
If you do NOT need a ticket any more, we kindly ask you to click the
|
||||
following link to let us know. This way, we can send the ticket as quickly
|
||||
as possible to the next person on the waiting list:
|
||||
|
||||
{url_remove}
|
||||
|
||||
Best regards,
|
||||
Your {event} team"""))
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user