Compare commits

..

1 Commits

Author SHA1 Message Date
Phin Wolkwitz
8164f469d3 Prefetch program times, add test for query count 2026-01-19 18:11:12 +01:00
27 changed files with 1355 additions and 1113 deletions

View File

@@ -65,7 +65,7 @@ dependencies = [
"kombu==5.6.*",
"libsass==0.23.*",
"lxml",
"markdown==3.10.1", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
"markdown==3.10", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
"mt-940==4.30.*",
"oauthlib==3.3.*",
@@ -80,7 +80,7 @@ dependencies = [
"protobuf==6.33.*",
"psycopg2-binary",
"pycountry",
"pycparser==3.0",
"pycparser==2.23",
"pycryptodome==3.23.*",
"pypdf==6.5.*",
"python-bidi==0.6.*", # Support for Arabic in reportlab
@@ -92,7 +92,7 @@ dependencies = [
"redis==7.1.*",
"reportlab==4.4.*",
"requests==2.32.*",
"sentry-sdk==2.50.*",
"sentry-sdk==2.49.*",
"sepaxml==2.7.*",
"stripe==7.9.*",
"text-unidecode==1.*",

View File

@@ -966,7 +966,6 @@ class CheckinRPCSearchView(ListAPIView):
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['expand'] = self.request.query_params.getlist('expand')
ctx['organizer'] = self.request.organizer
ctx['pdf_data'] = False
return ctx

View File

@@ -106,7 +106,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
'variations', 'addons', 'bundles', 'meta_values', 'meta_values__property',
'variations__meta_values', 'variations__meta_values__property',
'require_membership_types', 'variations__require_membership_types',
'limit_sales_channels', 'variations__limit_sales_channels',
'limit_sales_channels', 'variations__limit_sales_channels', 'program_times'
).all()
def perform_create(self, serializer):

View File

@@ -364,7 +364,7 @@ class OrderListExporter(MultiSheetListExporter):
order.invoice_address.city,
order.invoice_address.country if order.invoice_address.country else
order.invoice_address.country_old,
order.invoice_address.state_for_address,
order.invoice_address.state,
order.invoice_address.custom_field,
order.invoice_address.vat_id,
]
@@ -515,7 +515,7 @@ class OrderListExporter(MultiSheetListExporter):
order.invoice_address.city,
order.invoice_address.country if order.invoice_address.country else
order.invoice_address.country_old,
order.invoice_address.state_for_address,
order.invoice_address.state,
order.invoice_address.vat_id,
]
except InvoiceAddress.DoesNotExist:
@@ -617,7 +617,6 @@ class OrderListExporter(MultiSheetListExporter):
_('Country'),
pgettext('address', 'State'),
_('Voucher'),
_('Voucher budget usage'),
_('Pseudonymization ID'),
_('Ticket secret'),
_('Seat ID'),
@@ -733,9 +732,8 @@ class OrderListExporter(MultiSheetListExporter):
op.zipcode or '',
op.city or '',
op.country if op.country else '',
op.state_for_address or '',
op.state or '',
op.voucher.code if op.voucher else '',
op.voucher_budget_use if op.voucher_budget_use else '',
op.pseudonymization_id,
op.secret,
]
@@ -799,7 +797,7 @@ class OrderListExporter(MultiSheetListExporter):
order.invoice_address.city,
order.invoice_address.country if order.invoice_address.country else
order.invoice_address.country_old,
order.invoice_address.state_for_address,
order.invoice_address.state,
order.invoice_address.vat_id,
]
except InvoiceAddress.DoesNotExist:

View File

@@ -141,7 +141,7 @@ def get_babel_locale():
for locale in try_locales:
if localedata.exists(locale):
return localedata.normalize_locale(locale)
return locale
return "en"

View File

@@ -349,7 +349,7 @@ class AttendeeProfile(models.Model):
def state_name(self):
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
if sd:
return _(sd.name)
return sd.name
return self.state
@property

View File

@@ -594,11 +594,10 @@ class Item(LoggedModel):
on_delete=models.SET_NULL,
verbose_name=_("Only show after sellout of"),
help_text=_("If you select a product here, this product will only be shown when that product is "
"no longer available. This will happen either because the other product has sold out or because "
"the time is outside of the sales window for the other product. If combined with the option "
"to hide sold-out products, this allows you to swap out products for more expensive ones once "
"the cheaper option is sold out. There might be a short period in which both products are visible "
"while all tickets of the referenced product are reserved, but not yet sold.")
"sold out. If combined with the option to hide sold-out products, this allows you to "
"swap out products for more expensive ones once the cheaper option is sold out. There might "
"be a short period in which both products are visible while all tickets of the referenced "
"product are reserved, but not yet sold.")
)
hidden_if_item_available_mode = models.CharField(
choices=UNAVAIL_MODES,

View File

@@ -1675,7 +1675,7 @@ class AbstractPosition(RoundingCorrectionMixin, models.Model):
def state_name(self):
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
if sd:
return _(sd.name)
return sd.name
return self.state
@property
@@ -3480,7 +3480,7 @@ class InvoiceAddress(models.Model):
def state_name(self):
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
if sd:
return _(sd.name)
return sd.name
return self.state
@property

View File

@@ -159,7 +159,6 @@ class WaitingListEntry(LoggedModel):
if availability[1] is None or availability[1] < 1:
raise WaitingListException(_('This product is currently not available.'))
event = self.event
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
@@ -192,7 +191,6 @@ class WaitingListEntry(LoggedModel):
with transaction.atomic():
locked_wle = WaitingListEntry.objects.select_for_update(of=OF_SELF).get(pk=self.pk)
locked_wle.event = event
if locked_wle.voucher:
raise WaitingListException(_('A voucher has already been sent to this person.'))
e = locked_wle.email
@@ -229,7 +227,6 @@ class WaitingListEntry(LoggedModel):
locked_wle.save()
self.refresh_from_db()
self.event = event
with language(self.locale, self.event.settings.region):
self.send_mail(

View File

@@ -112,8 +112,7 @@ def dictsum(*dicts) -> dict:
def order_overview(
event: Event, subevent: SubEvent=None, date_filter='', date_from=None, date_until=None, fees=False,
admission_only=False, base_qs=None, base_fees_qs=None, subevent_date_from=None, subevent_date_until=None,
skip_empty_lines=False,
admission_only=False, base_qs=None, base_fees_qs=None, subevent_date_from=None, subevent_date_until=None
) -> Tuple[List[Tuple[ItemCategory, List[Item]]], Dict[str, Tuple[Decimal, Decimal]]]:
items = event.items.all().select_related(
'category', # for re-grouping
@@ -206,21 +205,13 @@ def order_overview(
for l in states.keys():
var.num[l] = num[l].get((item.id, variid), (0, 0, 0))
var.num['total'] = num['total'].get((item.id, variid), (0, 0, 0))
var._skip = all(v[0] == 0 for v in var.num.values())
for l in states.keys():
item.num[l] = tuplesum(var.num[l] for var in item.all_variations)
item.num['total'] = tuplesum(var.num['total'] for var in item.all_variations)
if skip_empty_lines:
item.all_variations = [v for v in item.all_variations if not v._skip]
item._skip = not item.all_variations
else:
for l in states.keys():
item.num[l] = num[l].get((item.id, None), (0, 0, 0))
item.num['total'] = num['total'].get((item.id, None), (0, 0, 0))
item._skip = all(v[0] == 0 for v in item.num.values())
if skip_empty_lines:
items = [i for i in items if not i._skip]
nonecat = ItemCategory(name=_('Uncategorized'))
# Regroup those by category

View File

@@ -82,7 +82,7 @@ def _info(cc):
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
return {
'data': [
{'name': gettext(s.name), 'code': s.code[3:]}
{'name': s.name, 'code': s.code[3:]}
for s in sorted(statelist, key=lambda s: s.name)
],
**info,

View File

@@ -1315,10 +1315,10 @@ class QuestionAnswerFilterForm(forms.Form):
if date_range is not None:
d_start, d_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), date_range, self.event.timezone)
if d_start:
opqs = opqs.filter(subevent__date_from__gte=d_start)
if d_end:
opqs = opqs.filter(subevent__date_from__lt=d_end)
opqs = opqs.filter(
subevent__date_from__gte=d_start,
subevent__date_from__lt=d_end
)
s = fdata.get("status", Order.STATUS_PENDING + Order.STATUS_PAID)
if s != "":

View File

@@ -363,7 +363,7 @@ class Forgot(TemplateView):
else:
messages.info(request, _('If the address is registered to valid account, then we have sent you an email containing further instructions.'))
return redirect('control:auth.forgot')
return redirect('control:auth.forgot')
else:
return self.get(request, *args, **kwargs)

View File

@@ -2790,16 +2790,7 @@ class ExportView(EventPermissionRequiredMixin, ExportMixin, ListView):
@transaction.atomic()
def post(self, request, *args, **kwargs):
if request.POST.get("schedule") == "save":
if not self.has_permission():
messages.error(
self.request,
_(
"Your user account does not have sufficient permission to run this report, therefore "
"you cannot schedule it."
)
)
return super().get(request, *args, **kwargs)
elif self.exporter.form.is_valid() and self.rrule_form.is_valid() and self.schedule_form.is_valid():
if self.exporter.form.is_valid() and self.rrule_form.is_valid() and self.schedule_form.is_valid():
self.schedule_form.instance.export_identifier = self.exporter.identifier
self.schedule_form.instance.export_form_data = self.exporter.form.cleaned_data
self.schedule_form.instance.schedule_rrule = str(self.rrule_form.to_rrule())
@@ -2878,8 +2869,17 @@ class ExportView(EventPermissionRequiredMixin, ExportMixin, ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
if "schedule" in self.request.POST or self.scheduled:
ctx['schedule_form'] = self.schedule_form
ctx['rrule_form'] = self.rrule_form
if "schedule" in self.request.POST and not self.has_permission():
messages.error(
self.request,
_(
"Your user account does not have sufficient permission to run this report, therefore "
"you cannot schedule it."
)
)
else:
ctx['schedule_form'] = self.schedule_form
ctx['rrule_form'] = self.rrule_form
elif not self.exporter:
for s in ctx['scheduled']:
try:

View File

@@ -2113,16 +2113,7 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, ListView):
@transaction.atomic()
def post(self, request, *args, **kwargs):
if request.POST.get("schedule") == "save":
if not self.has_permission():
messages.error(
self.request,
_(
"Your user account does not have sufficient permission to run this report, therefore "
"you cannot schedule it."
)
)
return super().get(request, *args, **kwargs)
elif self.exporter.form.is_valid() and self.rrule_form.is_valid() and self.schedule_form.is_valid():
if self.exporter.form.is_valid() and self.rrule_form.is_valid() and self.schedule_form.is_valid():
self.schedule_form.instance.export_identifier = self.exporter.identifier
self.schedule_form.instance.export_form_data = self.exporter.form.cleaned_data
self.schedule_form.instance.schedule_rrule = str(self.rrule_form.to_rrule())
@@ -2209,8 +2200,17 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
if "schedule" in self.request.POST or self.scheduled:
ctx['schedule_form'] = self.schedule_form
ctx['rrule_form'] = self.rrule_form
if "schedule" in self.request.POST and not self.has_permission():
messages.error(
self.request,
_(
"Your user account does not have sufficient permission to run this report, therefore "
"you cannot schedule it."
)
)
else:
ctx['schedule_form'] = self.schedule_form
ctx['rrule_form'] = self.rrule_form
elif not self.exporter:
for s in ctx['scheduled']:
try:

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-05 12:12+0000\n"
"PO-Revision-Date: 2026-01-21 21:00+0000\n"
"PO-Revision-Date: 2026-01-11 22:00+0000\n"
"Last-Translator: Jiří Pastrňák <jiri@pastrnak.email>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/>"
"\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"X-Generator: Weblate 5.15.2\n"
"X-Generator: Weblate 5.15.1\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -1623,7 +1623,7 @@ msgstr "Čas konce události"
#: pretix/base/models/event.py:1536 pretix/control/forms/subevents.py:494
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:274
msgid "Admission time"
msgstr "Vstup"
msgstr "Vstupní čas"
#: pretix/base/exporters/events.py:65 pretix/base/models/event.py:634
#: pretix/base/models/event.py:1545 pretix/control/forms/subevents.py:93
@@ -14592,7 +14592,7 @@ msgstr "Zaplaceno do"
#: pretix/plugins/reports/exporters.py:396
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:23
msgid "Paid"
msgstr "Zaplaceno"
msgstr "Placené"
#: pretix/control/forms/filter.py:1304
msgctxt "subevent"
@@ -35218,8 +35218,10 @@ msgid "Save selection"
msgstr "Uložit volbu"
#: pretix/presale/templates/pretixpresale/fragment_modals.html:145
#, fuzzy
#| msgid "You did not select any products."
msgid "You didn't select any ticket."
msgstr "Nevybrali jste žádné vstupenky."
msgstr "Nevybrali jste žádné produkty."
#: pretix/presale/templates/pretixpresale/fragment_modals.html:146
msgid ""

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,8 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-05 12:13+0000\n"
"PO-Revision-Date: 2026-01-22 19:00+0000\n"
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
"PO-Revision-Date: 2025-06-10 04:00+0000\n"
"Last-Translator: Tim Maurizio Dullaart <Tim.maurizio@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
"nl/>\n"
"Language: nl\n"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.15.2\n"
"X-Generator: Weblate 5.11.4\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -63,7 +63,7 @@ msgstr "iDEAL"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
msgid "SEPA Direct Debit"
msgstr "SEPA-incasso"
msgstr "SEPA Automatische Incasso"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:43
msgid "Bancontact"
@@ -160,7 +160,7 @@ msgstr "Betaalde bestellingen"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
msgid "Total revenue"
msgstr "Totale omzet"
msgstr "Totaal omzet"
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:15
msgid "Contacting Stripe …"
@@ -180,7 +180,7 @@ msgstr "Kies een inchecklijst"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:31
msgid "No active check-in lists found."
msgstr "Geen actieve check-inlijsten gevonden."
msgstr "Geen actieve inchecklijsten gevonden."
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:32
msgid "Switch check-in list"
@@ -245,7 +245,7 @@ msgstr "Goedkeuring in afwachting"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
msgid "Redeemed"
msgstr "Ingewisseld"
msgstr "Gebruikt"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49
msgid "Cancel"
@@ -564,11 +564,11 @@ msgstr "afwezig"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:289
msgid "Error: Product not found!"
msgstr "Fout: product niet gevonden!"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:296
msgid "Error: Variation not found!"
msgstr "Fout: variant niet gevonden!"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/editor.js:171
msgid "Check-in QR"
@@ -1125,31 +1125,31 @@ msgstr "Zo"
#: pretix/static/pretixpresale/js/widget/widget.js:85
msgid "Monday"
msgstr "maandag"
msgstr "Maandag"
#: pretix/static/pretixpresale/js/widget/widget.js:86
msgid "Tuesday"
msgstr "dinsdag"
msgstr "Dinsdag"
#: pretix/static/pretixpresale/js/widget/widget.js:87
msgid "Wednesday"
msgstr "woensdag"
msgstr "Woensdag"
#: pretix/static/pretixpresale/js/widget/widget.js:88
msgid "Thursday"
msgstr "donderdag"
msgstr "Donderdag"
#: pretix/static/pretixpresale/js/widget/widget.js:89
msgid "Friday"
msgstr "vrijdag"
msgstr "Vrijdag"
#: pretix/static/pretixpresale/js/widget/widget.js:90
msgid "Saturday"
msgstr "zaterdag"
msgstr "Zaterdag"
#: pretix/static/pretixpresale/js/widget/widget.js:91
msgid "Sunday"
msgstr "zondag"
msgstr "Zondag"
#: pretix/static/pretixpresale/js/widget/widget.js:94
msgid "January"

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-05 12:12+0000\n"
"PO-Revision-Date: 2026-01-16 22:00+0000\n"
"Last-Translator: Linnea Thelander <linnea@coeo.events>\n"
"PO-Revision-Date: 2026-01-16 09:22+0000\n"
"Last-Translator: Richard Schreiber <schreiber@rami.io>\n"
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix/"
"sv/>\n"
"Language: sv\n"
@@ -35839,11 +35839,9 @@ msgid "A payment of %(total)s is still pending for this order."
msgstr "Tack för din bokning på %(total)s."
#: pretix/presale/templates/pretixpresale/event/order.html:97
#, python-format
#, fuzzy, python-format
msgid "Please complete your payment before %(date)s"
msgstr ""
"Om din betalning inte gick igenom, se till att uppdatera din "
"betalningsinformation innan %(date)s via knappen nedan."
msgstr "Tack för din bokning!"
#: pretix/presale/templates/pretixpresale/event/order.html:108
msgid "Re-try payment or choose another payment method"

View File

@@ -337,8 +337,7 @@ class OverviewReport(Report):
date_until=d_end,
subevent_date_from=sd_start,
subevent_date_until=sd_end,
fees=True,
skip_empty_lines=form_data.get("skip_empty_lines")
fees=True
)
def _table_story(self, doc, form_data, net=False):
@@ -479,10 +478,6 @@ class OverviewReport(Report):
'Use the "Accounting report" in the export section instead.'
))
)
f.fields['skip_empty_lines'] = forms.BooleanField(
label=_("Skip empty lines"),
required=False,
)
return f.fields

View File

@@ -519,7 +519,7 @@
<dialog role="alertdialog" id="cart-extend-confirmation-dialog" class="inline-dialog" aria-labelledby="cart-deadline">
<form method="dialog">
<p>
<button class="btn btn-success" value="OK">
<button class="btn btn-success" autofocus value="OK">
<span role="img" aria-label="{% trans "OK" %}.">
{% icon "check" %}
</span>

View File

@@ -311,8 +311,7 @@ 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)
time_available = item.hidden_if_item_available.is_available()
item._dependency_available = (q[0] == Quota.AVAILABILITY_OK) and time_available
item._dependency_available = q[0] == Quota.AVAILABILITY_OK
if item._dependency_available and item.hidden_if_item_available_mode == Item.UNAVAIL_MODE_HIDDEN:
item._remove = True
continue

View File

@@ -932,12 +932,6 @@ def test_search(token_client, organizer, event, clist, clist_all, item, other_it
assert resp.status_code == 200
assert [p1] == resp.data['results']
with django_assert_max_num_queries(25):
resp = token_client.get(
'/api/v1/organizers/{}/checkinrpc/search/?list={}&search=z3fsn8jyu&expand=item'.format(organizer.slug, clist_all.pk))
assert resp.status_code == 200
assert resp.data['results'][0]['item']['name']
@pytest.mark.django_db
def test_search_no_list(token_client, organizer, event, clist, clist_all, item, other_item, order):

View File

@@ -50,6 +50,7 @@ from pretix.base.models import (
QuestionOption, Quota,
)
from pretix.base.models.orders import OrderFee
from pretix.testutils.queries import assert_num_queries
@pytest.fixture
@@ -422,6 +423,13 @@ def test_item_list(token_client, organizer, event, team, item):
assert [] == resp.data['results']
@pytest.mark.django_db
def test_item_list_queries(token_client, organizer, event, team, item, item3):
with assert_num_queries(18):
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug))
assert resp.status_code == 200
@pytest.mark.django_db
def test_item_detail(token_client, organizer, event, team, item):
res = dict(TEST_ITEM_RES)

View File

@@ -59,7 +59,6 @@ def test_get_locale():
("pt-pt", "PT", "pt-pt", "pt-pt", "pt_PT"),
("es", "MX", "es-mx", "es", "es_MX"),
("es-419", "MX", "es-419", "es-419", "es_MX"),
("zh-hans", "US", "zh-hans", "zh-hans", "zh_Hans"),
("zh-hans", "CN", "zh-hans", "zh-hans", "zh_Hans_CN"),
("zh-hant", "TW", "zh-hant", "zh-hant", "zh_Hant_TW"),
],

View File

@@ -46,7 +46,6 @@ 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
@@ -273,48 +272,6 @@ 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()