Waitinglist: lock entry to mitigate race-conditions when creating the voucher

This commit is contained in:
Lukas Bockstaller
2026-01-15 16:09:41 +01:00
committed by GitHub
parent 06250ef55e
commit dfd53f0ea2

View File

@@ -35,6 +35,7 @@ from pretix.base.email import get_email_context
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import User, Voucher from pretix.base.models import User, Voucher
from pretix.base.services.mail import SendMailException, mail, render_mail from pretix.base.services.mail import SendMailException, mail, render_mail
from pretix.helpers import OF_SELF
from ...helpers.format import format_map from ...helpers.format import format_map
from ...helpers.names import build_name from ...helpers.names import build_name
@@ -185,44 +186,47 @@ class WaitingListEntry(LoggedModel):
if not free_seats: if not free_seats:
raise WaitingListException(_('No seat with this product is currently available.')) 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: if '@' not in self.email:
raise WaitingListException(_('This entry is anonymized and can no longer be used.')) raise WaitingListException(_('This entry is anonymized and can no longer be used.'))
with transaction.atomic(): with transaction.atomic():
e = self.email locked_wle = WaitingListEntry.objects.select_for_update(of=OF_SELF).get(pk=self.pk)
if self.name: if locked_wle.voucher:
e += ' / ' + self.name raise WaitingListException(_('A voucher has already been sent to this person.'))
e = locked_wle.email
if locked_wle.name:
e += ' / ' + locked_wle.name
v = Voucher.objects.create( v = Voucher.objects.create(
event=self.event, event=locked_wle.event,
max_usages=1, max_usages=1,
valid_until=now() + timedelta(hours=self.event.settings.waiting_list_hours), valid_until=now() + timedelta(hours=locked_wle.event.settings.waiting_list_hours),
item=self.item, item=locked_wle.item,
variation=self.variation, variation=locked_wle.variation,
tag='waiting-list', tag='waiting-list',
comment=_('Automatically created from waiting list entry for {email}').format( comment=_('Automatically created from waiting list entry for {email}').format(
email=e email=e
), ),
block_quota=True, block_quota=True,
subevent=self.subevent, subevent=locked_wle.subevent,
) )
v.log_action('pretix.voucher.added', { v.log_action('pretix.voucher.added', {
'item': self.item.pk, 'item': locked_wle.item.pk,
'variation': self.variation.pk if self.variation else None, 'variation': locked_wle.variation.pk if locked_wle.variation else None,
'tag': 'waiting-list', 'tag': 'waiting-list',
'block_quota': True, 'block_quota': True,
'valid_until': v.valid_until.isoformat(), 'valid_until': v.valid_until.isoformat(),
'max_usages': 1, 'max_usages': 1,
'subevent': self.subevent.pk if self.subevent else None, 'subevent': locked_wle.subevent.pk if locked_wle.subevent else None,
'source': 'waitinglist', 'source': 'waitinglist',
}, user=user, auth=auth) }, user=user, auth=auth)
v.log_action('pretix.voucher.added.waitinglist', { v.log_action('pretix.voucher.added.waitinglist', {
'email': self.email, 'email': locked_wle.email,
'waitinglistentry': self.pk, 'waitinglistentry': locked_wle.pk,
}, user=user, auth=auth) }, user=user, auth=auth)
self.voucher = v locked_wle.voucher = v
self.save() locked_wle.save()
self.refresh_from_db()
with language(self.locale, self.event.settings.region): with language(self.locale, self.event.settings.region):
self.send_mail( self.send_mail(