Waiting list: Allow to set auto-disable date (Z#23141338) (#4004)

* Waiting list: Allow to set auto-disable date (Z#23141338)

* ADd warning on non-esries events
This commit is contained in:
Raphael Michel
2024-03-22 11:17:02 +01:00
committed by GitHub
parent a946c10ab4
commit 273c1ae0a6
19 changed files with 109 additions and 14 deletions

View File

@@ -687,6 +687,7 @@ class EventSettingsSerializer(SettingsSerializer):
'allow_modifications_after_checkin',
'show_quota_left',
'waiting_list_enabled',
'waiting_list_auto_disable',
'waiting_list_hours',
'waiting_list_auto',
'waiting_list_names_asked',

View File

@@ -229,6 +229,14 @@ class EventMixin:
else:
return self.presale_end
@property
def waiting_list_active(self):
if not self.settings.waiting_list_enabled:
return False
if self.settings.waiting_list_auto_disable:
return self.settings.waiting_list_auto_disable.datetime(self) > now()
return True
@property
def presale_has_ended(self):
"""

View File

@@ -446,6 +446,11 @@ class QuotaAvailability:
self.results[q] = Quota.AVAILABILITY_RESERVED, 0
def _compute_waitinglist(self, quotas, q_items, q_vars, size_left):
quotas = [
q for q in quotas
if not q.event.settings.waiting_list_auto_disable or q.event.settings.waiting_list_auto_disable.datetime(q.subevent or q.event) > now()
]
events = {q.event_id for q in quotas}
subevents = {q.subevent_id for q in quotas}
quota_ids = {q.pk for q in quotas}

View File

@@ -110,6 +110,9 @@ def assign_automatically(event: Event, user_id: int=None, subevent_id: int=None)
continue
if wle.subevent and not wle.subevent.presale_is_running:
continue
if event.settings.waiting_list_auto_disable and event.settings.waiting_list_auto_disable.datetime(wle.subevent or event) <= now():
gone.add((wle.item, wle.variation, wle.subevent))
continue
if not wle.item.is_available():
gone.add((wle.item, wle.variation, wle.subevent))
continue

View File

@@ -1397,6 +1397,19 @@ DEFAULTS = {
widget=forms.NumberInput(),
)
},
'waiting_list_auto_disable': {
'default': None,
'type': RelativeDateWrapper,
'form_class': RelativeDateTimeField,
'serializer_class': SerializerRelativeDateTimeField,
'form_kwargs': dict(
label=_("Disable waiting list"),
help_text=_("The waiting list will be fully disabled after this date. This means that nobody can add "
"themselves to the waiting list any more, but also that tickets will be available for sale "
"again if quota permits, even if there are still people on the waiting list. Vouchers that "
"have already been sent remain active."),
)
},
'waiting_list_names_asked': {
'default': 'False',
'type': bool,

View File

@@ -538,6 +538,7 @@ class EventSettingsForm(EventSettingsValidationMixin, FormPlaceholderMixin, Sett
'region',
'show_quota_left',
'waiting_list_enabled',
'waiting_list_auto_disable',
'waiting_list_hours',
'waiting_list_auto',
'waiting_list_names_asked',

View File

@@ -361,6 +361,7 @@
{% bootstrap_field sform.waiting_list_enabled layout="control" %}
{% bootstrap_field sform.waiting_list_auto layout="control" %}
{% bootstrap_field sform.waiting_list_hours layout="control" %}
{% bootstrap_field sform.waiting_list_auto_disable layout="control" %}
{% bootstrap_field sform.waiting_list_names_asked_required layout="control" %}
{% bootstrap_field sform.waiting_list_phones_asked_required layout="control" %}
{% bootstrap_field sform.waiting_list_phones_explanation_text layout="control" %}

View File

@@ -10,6 +10,10 @@
<div class="alert alert-danger">
{% trans "The waiting list is disabled, so if the event is sold out, people cannot add themselves to this list. If you want to enable it, go to the event settings." %}
</div>
{% elif not request.event.waiting_list_active and not request.event.has_subevents %}
<div class="alert alert-danger">
{% trans "The waiting list is no longer active for this event. The waiting list no longer affects quotas and no longer notifies waiting users." %}
</div>
{% endif %}
<div class="row">
{% if 'can_change_orders' in request.eventpermset %}

View File

@@ -31,7 +31,7 @@
<span class="label label-success">{% trans "Book now" %}</span>
{% endif %}
{% endif %}
{% elif event.settings.waiting_list_enabled and subev.best_availability_state >= 0 %}
{% elif event.waiting_list_active and subev.best_availability_state >= 0 %}
<span class="label label-warning">{% trans "Waiting list" %}</span>
{% elif subev.best_availability_state == 20 %}
<span class="label label-danger">{% trans "Reserved" %}</span>

View File

@@ -25,7 +25,7 @@
{% if event.event.presale_is_running and show_avail %}
{% if event.event.best_availability_state == 100 %}
available {% if event.event.best_availability_is_low %} low {% endif %}
{% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %}
{% elif event.event.waiting_list_active and event.event.best_availability_state >= 0 %}
waitinglist
{% elif event.event.best_availability_state == 20 %}
reserved
@@ -74,7 +74,7 @@
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %}
{% endif %}
{% endif %}
{% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %}
{% elif event.event.waiting_list_active and event.event.best_availability_state >= 0 %}
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Waiting list" %}
{% elif event.event.best_availability_state == 20 %}
{% trans "Reserved" %}

View File

@@ -41,7 +41,7 @@
{% if event.event.presale_is_running and show_avail %}
{% if event.event.best_availability_state == 100 %}
available {% if event.event.best_availability_is_low %} low {% endif %}
{% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %}
{% elif event.event.waiting_list_active and event.event.best_availability_state >= 0 %}
waitinglist
{% elif event.event.best_availability_state == 20 %}
reserved
@@ -98,7 +98,7 @@
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %}
{% endif %}
{% endif %}
{% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %}
{% elif event.event.waiting_list_active and event.event.best_availability_state >= 0 %}
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Waiting list" %}
{% elif event.event.best_availability_state == 20 %}
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Reserved" %}

View File

@@ -13,7 +13,7 @@
{% if event.event.presale_is_running and show_avail %}
{% if event.event.best_availability_state == 100 %}
available {% if event.event.best_availability_is_low %} low {% endif %}
{% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %}
{% elif event.event.waiting_list_active and event.event.best_availability_state >= 0 %}
waitinglist
{% elif event.event.best_availability_state == 20 %}
reserved
@@ -62,7 +62,7 @@
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %}
{% endif %}
{% endif %}
{% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %}
{% elif event.event.waiting_list_active and event.event.best_availability_state >= 0 %}
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Waiting list" %}
{% elif event.event.best_availability_state == 20 %}
{% trans "Reserved" %}

View File

@@ -99,7 +99,7 @@
<span class="label label-success">{% trans "Book now" %}</span>
{% endif %}
{% endif %}
{% elif e.settings.waiting_list_enabled and e.best_availability_state >= 0 %}
{% elif e.waiting_list_active and e.best_availability_state >= 0 %}
<span class="label label-warning">{% trans "Waiting list" %}</span>
{% elif e.best_availability_state == 20 %}
<span class="label label-danger">{% trans "Reserved" %}</span>

View File

@@ -153,13 +153,13 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
Prefetch('quotas',
to_attr='_subevent_quotas',
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(
subevent=subevent))
subevent=subevent).select_related("subevent"))
).distinct()
)
prefetch_quotas = Prefetch(
'quotas',
to_attr='_subevent_quotas',
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent).select_related("subevent")
)
prefetch_bundles = Prefetch(
'bundles',
@@ -548,7 +548,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
context['ev'].presale_is_running
)
context['allow_waitinglist'] = self.request.event.settings.waiting_list_enabled and context['ev'].presale_is_running
context['allow_waitinglist'] = context['ev'].waiting_list_active and context['ev'].presale_is_running
if not self.request.event.has_subevents or self.subevent:
# Fetch all items

View File

@@ -118,6 +118,10 @@ class WaitingView(EventViewMixin, FormView):
messages.error(request, pgettext_lazy('subevent', "You need to select a date."))
return redirect(self.get_index_url())
if not (self.subevent or self.request.event).waiting_list_active:
messages.error(request, _("Waiting lists are disabled for this event."))
return redirect(self.get_index_url())
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):

View File

@@ -390,7 +390,7 @@ class WidgetAPIProductList(EventListMixin, View):
else:
availability['text'] = gettext('Book now')
availability['reason'] = 'ok'
elif event.settings.waiting_list_enabled and (ev.best_availability_state is not None and ev.best_availability_state >= 0):
elif event.waiting_list_active and (ev.best_availability_state is not None and ev.best_availability_state >= 0):
availability['color'] = 'orange'
availability['text'] = gettext('Waiting list')
availability['reason'] = 'waitinglist'
@@ -690,7 +690,7 @@ class WidgetAPIProductList(EventListMixin, View):
'display_net_prices': request.event.settings.display_net_prices,
'use_native_spinners': request.event.settings.widget_use_native_spinners,
'show_variations_expanded': request.event.settings.show_variations_expanded,
'waiting_list_enabled': request.event.settings.waiting_list_enabled,
'waiting_list_enabled': request.event.waiting_list_active,
'voucher_explanation_text': str(rich_text(request.event.settings.voucher_explanation_text, safelinks=False)),
'error': None,
'cart_exists': False
@@ -775,7 +775,7 @@ class WidgetAPIProductList(EventListMixin, View):
data['has_seating_plan'] = ev.seating_plan is not None
data['has_seating_plan_waitinglist'] = False
if request.event.settings.waiting_list_enabled and ev.presale_is_running:
if ev.waiting_list_active and ev.presale_is_running:
for i in items:
if not i.allow_waitinglist or not i.requires_seat:
continue

View File

@@ -436,6 +436,25 @@ class QuotaTestCase(BaseQuotaTestCase):
self.assertEqual(qa.count_cart[self.quota], 1)
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
@classscope(attr='o')
def test_waitinglist_auto_disable(self):
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper(
RelativeDate(days=0, time=None, base_date_name='date_from', minutes=20, is_after=True)
)
self.quota.items.add(self.item1)
self.quota.size = 1
self.quota.save()
WaitingListEntry.objects.create(
event=self.event, item=self.item1, email='foo@bar.com'
)
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_ORDERED, 0))
self.assertEqual(self.item1.check_quotas(count_waitinglist=False), (Quota.AVAILABILITY_OK, 1))
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper(
RelativeDate(days=0, time=None, base_date_name='date_from', minutes=20, is_after=False)
)
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
self.assertEqual(self.item1.check_quotas(count_waitinglist=False), (Quota.AVAILABILITY_OK, 1))
@classscope(attr='o')
def test_waitinglist_item_active(self):
self.quota.items.add(self.item1)

View File

@@ -30,6 +30,7 @@ from pretix.base.models import (
Event, Item, ItemVariation, Organizer, Quota, Voucher, WaitingListEntry,
)
from pretix.base.models.waitinglist import WaitingListException
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
from pretix.base.services.waitinglist import (
assign_automatically, process_waitinglist,
)
@@ -210,6 +211,25 @@ class WaitingListTestCase(TestCase):
self.event.presale_end = now() + timedelta(days=1)
self.event.save()
def test_send_periodic_auto_disable(self):
self.event.settings.set('waiting_list_enabled', True)
self.event.settings.set('waiting_list_auto', True)
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper(
RelativeDate(days=0, time=None, base_date_name='date_from', minutes=20, is_after=False)
)
self.event.save()
with scope(organizer=self.o):
for i in range(5):
WaitingListEntry.objects.create(
event=self.event, item=self.item2, variation=self.var1, email='foo{}@bar.com'.format(i)
)
process_waitinglist(None)
with scope(organizer=self.o):
assert WaitingListEntry.objects.filter(voucher__isnull=True).count() == 5
assert Voucher.objects.count() == 0
self.event.presale_end = now() + timedelta(days=1)
self.event.save()
def test_send_periodic(self):
self.event.settings.set('waiting_list_enabled', True)
self.event.settings.set('waiting_list_auto', True)

View File

@@ -54,6 +54,7 @@ from pretix.base.models import (
User, WaitingListEntry,
)
from pretix.base.models.items import SubEventItem, SubEventItemVariation
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
class EventTestMixin:
@@ -1021,6 +1022,21 @@ class WaitingListTest(EventTestMixin, SoupTest):
)
self.assertEqual(response.status_code, 302)
def test_auto_disable(self):
self.event.settings.set('waiting_list_enabled', True)
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper(
RelativeDate(days=900, time=datetime.time(9, 0, 0), base_date_name='date_from', minutes=None, is_after=False)
)
response = self.client.get(
'/%s/%s/' % (self.orga.slug, self.event.slug)
)
self.assertEqual(response.status_code, 200)
self.assertNotIn('waitinglist', response.rendered_content)
response = self.client.get(
'/%s/%s/waitinglist/?item=%d' % (self.orga.slug, self.event.slug, self.item.pk + 1)
)
self.assertEqual(response.status_code, 302)
def test_display_link(self):
response = self.client.get(
'/%s/%s/' % (self.orga.slug, self.event.slug)