mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
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:
@@ -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',
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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" %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" %}
|
||||
|
||||
@@ -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" %}
|
||||
|
||||
@@ -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" %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user