From fb4b9a406e8f762f415ebcaa722ff6322a6c0430 Mon Sep 17 00:00:00 2001 From: Lukas Bockstaller Date: Fri, 19 Dec 2025 11:31:24 +0100 Subject: [PATCH] add tiered availability by time --- src/pretix/presale/views/event.py | 3 ++- src/tests/presale/test_event.py | 43 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/pretix/presale/views/event.py b/src/pretix/presale/views/event.py index d5c7b35922..7dffa58123 100644 --- a/src/pretix/presale/views/event.py +++ b/src/pretix/presale/views/event.py @@ -311,7 +311,8 @@ def get_grouped_items(event, *, channel: SalesChannel, subevent=None, voucher=No ) else: q = item.hidden_if_item_available.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True) - item._dependency_available = q[0] == Quota.AVAILABILITY_OK + time_available = item.hidden_if_item_available.is_available() + item._dependency_available = (q[0] == Quota.AVAILABILITY_OK) & time_available if item._dependency_available and item.hidden_if_item_available_mode == Item.UNAVAIL_MODE_HIDDEN: item._remove = True continue diff --git a/src/tests/presale/test_event.py b/src/tests/presale/test_event.py index 078dbb7357..2f389bebe1 100644 --- a/src/tests/presale/test_event.py +++ b/src/tests/presale/test_event.py @@ -46,6 +46,7 @@ from django.core.exceptions import ValidationError from django.test import TestCase from django.utils.timezone import now from django_scopes import scopes_disabled +from freezegun import freeze_time from tests.base import SoupTest from tests.testdummy.signals import FoobarSalesChannel @@ -272,6 +273,48 @@ class ItemDisplayTest(EventTestMixin, SoupTest): resp = self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug)) self.assertNotIn("Early-bird", resp.rendered_content) + def tiered_availability_by_date_and_quota(self, q1_size, q2_size, time_offset, expected_phase): + current_time = now() + + with scopes_disabled(): + q1 = Quota.objects.create(event=self.event, name='Phase 1', size=q1_size) + item1 = Item.objects.create( + event=self.event, + name='Phase 1', + default_price=0, + available_from=current_time, + available_until=current_time + datetime.timedelta(days=1), + available_from_mode=Item.UNAVAIL_MODE_HIDDEN, + available_until_mode=Item.UNAVAIL_MODE_HIDDEN, + hidden_if_item_available_mode=Item.UNAVAIL_MODE_HIDDEN, + ) + q1.items.add(item1) + q2 = Quota.objects.create(event=self.event, name='Phase 2', size=q2_size) + item2 = Item.objects.create( + event=self.event, + name='Phase 2', + default_price=0, + available_from=current_time + datetime.timedelta(days=0), + available_until=current_time + datetime.timedelta(days=2), + available_from_mode=Item.UNAVAIL_MODE_HIDDEN, + available_until_mode=Item.UNAVAIL_MODE_HIDDEN, + hidden_if_item_available_mode=Item.UNAVAIL_MODE_HIDDEN, + hidden_if_item_available=item1 + ) + q2.items.add(item2) + with freeze_time(current_time + time_offset): + resp = self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug)) + self.assertIn(expected_phase, resp.rendered_content) + + def test_tiered_availability_by_date_and_quota_phase1_available(self): + self.tiered_availability_by_date_and_quota(1, 1, datetime.timedelta(seconds=1), "Phase 1") + + def test_tiered_availability_by_date_and_quota_phase1_sold_out(self): + self.tiered_availability_by_date_and_quota(0, 1, datetime.timedelta(seconds=1), "Phase 2") + + def test_tiered_availability_by_date_and_quota_phase1_timed_out(self): + self.tiered_availability_by_date_and_quota(1, 1, datetime.timedelta(days=1, hours=1), "Phase 2") + def test_subevents_inactive_unknown(self): self.event.has_subevents = True self.event.save()