forked from CGM_Public/pretix_original
Compare commits
45 Commits
multi-sube
...
back-to-th
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0630b67546 | ||
|
|
9101238743 | ||
|
|
cefbfc1ad1 | ||
|
|
69a798046e | ||
|
|
c7983bf811 | ||
|
|
042be3603b | ||
|
|
1de2320cc5 | ||
|
|
8042d9d3f4 | ||
|
|
44afe9e193 | ||
|
|
46dce1bf43 | ||
|
|
4865879978 | ||
|
|
4c793076b7 | ||
|
|
5294d819a9 | ||
|
|
7bfe94139a | ||
|
|
e5cbaa9246 | ||
|
|
37208286b1 | ||
|
|
6da56291d4 | ||
|
|
2d335cd095 | ||
|
|
569f1719b0 | ||
|
|
00dd1a5b31 | ||
|
|
18bebb6d31 | ||
|
|
77c8e81cd7 | ||
|
|
3d03f30119 | ||
|
|
91b2d685da | ||
|
|
9787ed1820 | ||
|
|
204b8e53de | ||
|
|
64358be4ae | ||
|
|
5b1175ff05 | ||
|
|
e6f56bfdc2 | ||
|
|
9610e9c89f | ||
|
|
c5f4eeeb28 | ||
|
|
b61880fb5b | ||
|
|
b29c7fc11d | ||
|
|
d99bf7437a | ||
|
|
648cc14ae0 | ||
|
|
5d71cb500a | ||
|
|
68d81982ba | ||
|
|
efa0d5f362 | ||
|
|
046898678b | ||
|
|
f38ecd0ec7 | ||
|
|
3dca6c232e | ||
|
|
297bf566ad | ||
|
|
67f09b5ede | ||
|
|
752137ad84 | ||
|
|
100528ad0f |
@@ -19,3 +19,4 @@ Contents:
|
||||
permissions
|
||||
logging
|
||||
locking
|
||||
timemachine
|
||||
|
||||
32
doc/development/implementation/timemachine.rst
Normal file
32
doc/development/implementation/timemachine.rst
Normal file
@@ -0,0 +1,32 @@
|
||||
Time machine mode
|
||||
=================
|
||||
|
||||
In test mode, pretix provides a "time machine" feature which allows event organizers
|
||||
to test their shop as if it were a different date and time. To enable this feature, they can
|
||||
click on the "time machine"-link in the test mode warning box on the event page.
|
||||
|
||||
Internally, this time machine mode is implemented by calling our custom :py:meth:`time_machine_now()`
|
||||
function instead of :py:meth:`django.utils.timezone.now()` in all places where the fake time should be
|
||||
taken into account. If you add code that uses the current date and time for checking whether some
|
||||
product can be bought, you should use :py:meth:`time_machine_now`.
|
||||
|
||||
.. autofunction:: pretix.base.timemachine.time_machine_now
|
||||
|
||||
Background tasks
|
||||
----------------
|
||||
|
||||
The time machine datetime is passed through the request flow via a thread-local variable (ContextVar).
|
||||
Therefore, if you call a background task in the order process, where time_machine_now should be
|
||||
respected, you need to pass it through manually as shown in the example below:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.task()
|
||||
def my_task(self, override_now_dt: datetime=None) -> None:
|
||||
with time_machine_now_assigned(override_now_dt):
|
||||
# ...do something that uses time_machine_now()
|
||||
|
||||
my_task.apply_async(kwargs={'override_now_dt': time_machine_now(default=None)})
|
||||
|
||||
|
||||
.. autofunction:: pretix.base.timemachine.time_machine_now_assigned
|
||||
@@ -90,6 +90,10 @@ as its first argument and can be used like this::
|
||||
<a href="{% eventurl request.event "presale:event.checkout" step="payment" %}">Pay</a>
|
||||
<a href="{% abseventurl request.event "presale:event.checkout" step="payment" %}">Pay</a>
|
||||
|
||||
To generate absolute URLs on the main domain, you can use the ``absurl`` template tag::
|
||||
|
||||
{% load eventurl %}
|
||||
<a href="{% absmainurl "control:event.settings" organizer=request.event.organizer.slug event=request.event.slug %}">Event settings</a>
|
||||
|
||||
Implementation details
|
||||
----------------------
|
||||
|
||||
@@ -211,5 +211,15 @@ with the documentation a lot, you might find it useful to use sphinx-autobuild::
|
||||
Then, go to http://localhost:8081 for a version of the documentation that automatically re-builds
|
||||
whenever you change a source file.
|
||||
|
||||
Working with frontend assets
|
||||
----------------------------
|
||||
|
||||
To update the frontend styles of shops with a custom styling, run the following commands inside
|
||||
your virtual environment.::
|
||||
|
||||
python -m pretix collectstatic --noinput
|
||||
python -m pretix updatestyles
|
||||
|
||||
|
||||
.. _Django's documentation: https://docs.djangoproject.com/en/1.11/ref/django-admin/#runserver
|
||||
.. _pretixdroid: https://github.com/pretix/pretixdroid
|
||||
|
||||
@@ -31,7 +31,7 @@ pretix/
|
||||
Additional code implementing our customized :ref:`URL handling <urlconf>`.
|
||||
|
||||
static/
|
||||
Contains all static files (CSS/SASS, JavaScript, images) of pretix' core
|
||||
Contains all static files (CSS/SASS, JavaScript, images) of pretix' core.
|
||||
We use libsass as a preprocessor for CSS. Our own sass code is built in the same
|
||||
step as Bootstrap and FontAwesome, so their mixins etc. are fully available.
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ dependencies = [
|
||||
"css-inline==0.14.*",
|
||||
"defusedcsv>=1.1.0",
|
||||
"dj-static",
|
||||
"Django[argon2]==4.2.*",
|
||||
"Django==4.2.*",
|
||||
"django-bootstrap3==24.2",
|
||||
"django-compressor==4.4",
|
||||
"django-countries==7.6.*",
|
||||
|
||||
@@ -57,7 +57,7 @@ from django.forms.widgets import FILE_INPUT_CONTRADICTION
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.timezone import get_current_timezone, now
|
||||
from django.utils.timezone import get_current_timezone
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from django_countries import countries
|
||||
from django_countries.fields import Country, CountryField
|
||||
@@ -86,6 +86,7 @@ from pretix.base.settings import (
|
||||
PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS,
|
||||
)
|
||||
from pretix.base.templatetags.rich_text import rich_text
|
||||
from pretix.base.timemachine import time_machine_now
|
||||
from pretix.control.forms import (
|
||||
ExtFileField, ExtValidationMixin, SizeValidationMixin, SplitDateTimeField,
|
||||
)
|
||||
@@ -606,13 +607,13 @@ class BaseQuestionsForm(forms.Form):
|
||||
|
||||
if cartpos and item.validity_mode == Item.VALIDITY_MODE_DYNAMIC and item.validity_dynamic_start_choice:
|
||||
if item.validity_dynamic_start_choice_day_limit:
|
||||
max_date = now().astimezone(event.timezone) + timedelta(days=item.validity_dynamic_start_choice_day_limit)
|
||||
max_date = time_machine_now().astimezone(event.timezone) + timedelta(days=item.validity_dynamic_start_choice_day_limit)
|
||||
else:
|
||||
max_date = None
|
||||
min_date = now()
|
||||
min_date = time_machine_now()
|
||||
initial = None
|
||||
if (item.require_membership or (pos.variation and pos.variation.require_membership)) and pos.used_membership:
|
||||
if pos.used_membership.date_start >= now():
|
||||
if pos.used_membership.date_start >= time_machine_now():
|
||||
initial = min_date = pos.used_membership.date_start
|
||||
max_date = min(max_date, pos.used_membership.date_end) if max_date else pos.used_membership.date_end
|
||||
if item.validity_dynamic_duration_months or item.validity_dynamic_duration_days:
|
||||
|
||||
@@ -44,6 +44,8 @@ class CodeColumn(ImportColumn):
|
||||
super().__init__(*args)
|
||||
|
||||
def clean(self, value, previous_values):
|
||||
if not value:
|
||||
raise ValidationError(_('A voucher cannot be created without a code.'))
|
||||
if value:
|
||||
MinLengthValidator(5)(value)
|
||||
if value and (value in self._cached or Voucher.objects.filter(event=self.event, code=value).exists()):
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
from collections import defaultdict
|
||||
from decimal import Decimal
|
||||
from itertools import groupby
|
||||
from math import ceil
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
@@ -273,7 +272,7 @@ class Discount(LoggedModel):
|
||||
|
||||
# Prevent over-consuming of items, i.e. if our discount is "buy 2, get 1 free", we only
|
||||
# want to match multiples of 3
|
||||
n_groups = min(len(condition_idx_group) // self.condition_min_count, ceil(len(benefit_idx_group) / self.benefit_only_apply_to_cheapest_n_matches))
|
||||
n_groups = min(len(condition_idx_group) // self.condition_min_count, len(benefit_idx_group))
|
||||
consume_idx = condition_idx_group[:n_groups * self.condition_min_count]
|
||||
benefit_idx = benefit_idx_group[:n_groups * self.benefit_only_apply_to_cheapest_n_matches]
|
||||
else:
|
||||
|
||||
@@ -67,6 +67,7 @@ from i18nfield.fields import I18nCharField, I18nTextField
|
||||
from pretix.base.models.base import LoggedModel
|
||||
from pretix.base.models.fields import MultiStringField
|
||||
from pretix.base.reldate import RelativeDateWrapper
|
||||
from pretix.base.timemachine import time_machine_now
|
||||
from pretix.base.validators import EventSlugBanlistValidator
|
||||
from pretix.helpers.database import GroupConcat
|
||||
from pretix.helpers.daterange import daterange
|
||||
@@ -234,7 +235,7 @@ class EventMixin:
|
||||
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 self.settings.waiting_list_auto_disable.datetime(self) > time_machine_now()
|
||||
return True
|
||||
|
||||
@property
|
||||
@@ -243,11 +244,11 @@ class EventMixin:
|
||||
Is true, when ``presale_end`` is set and in the past.
|
||||
"""
|
||||
if self.effective_presale_end:
|
||||
return now() > self.effective_presale_end
|
||||
return time_machine_now() > self.effective_presale_end
|
||||
elif self.date_to:
|
||||
return now() > self.date_to
|
||||
return time_machine_now() > self.date_to
|
||||
else:
|
||||
return now().astimezone(self.timezone).date() > self.date_from.astimezone(self.timezone).date()
|
||||
return time_machine_now().astimezone(self.timezone).date() > self.date_from.astimezone(self.timezone).date()
|
||||
|
||||
@property
|
||||
def effective_presale_start(self):
|
||||
@@ -267,7 +268,7 @@ class EventMixin:
|
||||
Is true, when ``presale_end`` is not set or in the future and ``presale_start`` is not
|
||||
set or in the past.
|
||||
"""
|
||||
if self.effective_presale_start and now() < self.effective_presale_start:
|
||||
if self.effective_presale_start and time_machine_now() < self.effective_presale_start:
|
||||
return False
|
||||
return not self.presale_has_ended
|
||||
|
||||
@@ -315,11 +316,11 @@ class EventMixin:
|
||||
q_variation = (
|
||||
Q(active=True)
|
||||
& Q(sales_channels__contains=channel)
|
||||
& Q(Q(available_from__isnull=True) | Q(available_from__lte=now()))
|
||||
& Q(Q(available_until__isnull=True) | Q(available_until__gte=now()))
|
||||
& Q(Q(available_from__isnull=True) | Q(available_from__lte=time_machine_now()))
|
||||
& Q(Q(available_until__isnull=True) | Q(available_until__gte=time_machine_now()))
|
||||
& Q(item__active=True)
|
||||
& Q(Q(item__available_from__isnull=True) | Q(item__available_from__lte=now()))
|
||||
& Q(Q(item__available_until__isnull=True) | Q(item__available_until__gte=now()))
|
||||
& Q(Q(item__available_from__isnull=True) | Q(item__available_from__lte=time_machine_now()))
|
||||
& Q(Q(item__available_until__isnull=True) | Q(item__available_until__gte=time_machine_now()))
|
||||
& Q(Q(item__category__isnull=True) | Q(item__category__is_addon=False))
|
||||
& Q(item__sales_channels__contains=channel)
|
||||
& Q(item__require_bundling=False)
|
||||
@@ -694,7 +695,7 @@ class Event(EventMixin, LoggedModel):
|
||||
@property
|
||||
def presale_has_ended(self):
|
||||
if self.has_subevents:
|
||||
return self.presale_end and now() > self.presale_end
|
||||
return self.presale_end and time_machine_now() > self.presale_end
|
||||
else:
|
||||
return super().presale_has_ended
|
||||
|
||||
@@ -1187,8 +1188,8 @@ class Event(EventMixin, LoggedModel):
|
||||
)
|
||||
).filter(
|
||||
Q(active=True) & Q(is_public=True) & (
|
||||
Q(Q(date_to__isnull=True) & Q(date_from__gte=now() - timedelta(hours=24)))
|
||||
| Q(date_to__gte=now() - timedelta(hours=24))
|
||||
Q(Q(date_to__isnull=True) & Q(date_from__gte=time_machine_now() - timedelta(hours=24)))
|
||||
| Q(date_to__gte=time_machine_now() - timedelta(hours=24))
|
||||
)
|
||||
) # order_by doesn't make sense with I18nField
|
||||
if ordering in ("date_ascending", "date_descending"):
|
||||
@@ -1508,7 +1509,7 @@ class SubEvent(EventMixin, LoggedModel):
|
||||
disabled_items=Coalesce(
|
||||
Subquery(
|
||||
SubEventItem.objects.filter(
|
||||
Q(disabled=True) | Q(available_from__gt=now()) | Q(available_until__lt=now()),
|
||||
Q(disabled=True) | Q(available_from__gt=time_machine_now()) | Q(available_until__lt=time_machine_now()),
|
||||
subevent=OuterRef('pk'),
|
||||
).order_by().values('subevent').annotate(items=GroupConcat('item_id', delimiter=',')).values('items'),
|
||||
output_field=models.TextField(),
|
||||
@@ -1519,7 +1520,7 @@ class SubEvent(EventMixin, LoggedModel):
|
||||
disabled_vars=Coalesce(
|
||||
Subquery(
|
||||
SubEventItemVariation.objects.filter(
|
||||
Q(disabled=True) | Q(available_from__gt=now()) | Q(available_until__lt=now()),
|
||||
Q(disabled=True) | Q(available_from__gt=time_machine_now()) | Q(available_until__lt=time_machine_now()),
|
||||
subevent=OuterRef('pk'),
|
||||
).order_by().values('subevent').annotate(items=GroupConcat('variation_id', delimiter=',')).values('items'),
|
||||
output_field=models.TextField(),
|
||||
|
||||
@@ -55,7 +55,7 @@ from django.db.models import Q
|
||||
from django.utils import formats
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import is_naive, make_aware, now
|
||||
from django.utils.timezone import is_naive, make_aware
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from django_countries.fields import Country
|
||||
from django_scopes import ScopedManager
|
||||
@@ -65,6 +65,7 @@ from pretix.base.models import fields
|
||||
from pretix.base.models.base import LoggedModel
|
||||
from pretix.base.models.fields import MultiStringField
|
||||
from pretix.base.models.tax import TaxedPrice
|
||||
from pretix.base.timemachine import time_machine_now
|
||||
|
||||
from ...helpers.images import ImageSizeValidator
|
||||
from ..media import MEDIA_TYPES
|
||||
@@ -192,7 +193,7 @@ class SubEventItem(models.Model):
|
||||
self.subevent.event.cache.clear()
|
||||
|
||||
def is_available(self, now_dt: datetime=None) -> bool:
|
||||
now_dt = now_dt or now()
|
||||
now_dt = now_dt or time_machine_now()
|
||||
if self.disabled:
|
||||
return False
|
||||
if self.available_from and self.available_from > now_dt:
|
||||
@@ -248,7 +249,7 @@ class SubEventItemVariation(models.Model):
|
||||
self.subevent.event.cache.clear()
|
||||
|
||||
def is_available(self, now_dt: datetime=None) -> bool:
|
||||
now_dt = now_dt or now()
|
||||
now_dt = now_dt or time_machine_now()
|
||||
if self.disabled:
|
||||
return False
|
||||
if self.available_from and self.available_from > now_dt:
|
||||
@@ -263,8 +264,8 @@ def filter_available(qs, channel='web', voucher=None, allow_addons=False):
|
||||
# IMPORTANT: If this is updated, also update the ItemVariation query
|
||||
# in models/event.py: EventMixin.annotated()
|
||||
Q(active=True)
|
||||
& Q(Q(available_from__isnull=True) | Q(available_from__lte=now()) | Q(available_from_mode='info'))
|
||||
& Q(Q(available_until__isnull=True) | Q(available_until__gte=now()) | Q(available_until_mode='info'))
|
||||
& Q(Q(available_from__isnull=True) | Q(available_from__lte=time_machine_now()) | Q(available_from_mode='info'))
|
||||
& Q(Q(available_until__isnull=True) | Q(available_until__gte=time_machine_now()) | Q(available_until_mode='info'))
|
||||
& Q(sales_channels__contains=channel) & Q(require_bundling=False)
|
||||
)
|
||||
if not allow_addons:
|
||||
@@ -782,7 +783,7 @@ class Item(LoggedModel):
|
||||
return t
|
||||
|
||||
def is_available_by_time(self, now_dt: datetime=None) -> bool:
|
||||
now_dt = now_dt or now()
|
||||
now_dt = now_dt or time_machine_now()
|
||||
if self.available_from and self.available_from > now_dt:
|
||||
return False
|
||||
if self.available_until and self.available_until < now_dt:
|
||||
@@ -794,13 +795,13 @@ class Item(LoggedModel):
|
||||
Returns whether this item is available according to its ``active`` flag
|
||||
and its ``available_from`` and ``available_until`` fields
|
||||
"""
|
||||
now_dt = now_dt or now()
|
||||
now_dt = now_dt or time_machine_now()
|
||||
if not self.active or not self.is_available_by_time(now_dt):
|
||||
return False
|
||||
return True
|
||||
|
||||
def unavailability_reason(self, now_dt: datetime=None, has_voucher=False, subevent=None) -> Optional[str]:
|
||||
now_dt = now_dt or now()
|
||||
now_dt = now_dt or time_machine_now()
|
||||
subevent_item = subevent and subevent.item_overrides.get(self.pk)
|
||||
if not self.active:
|
||||
return 'active'
|
||||
@@ -957,11 +958,11 @@ class Item(LoggedModel):
|
||||
return self.validity_fixed_from, self.validity_fixed_until
|
||||
elif self.validity_mode == Item.VALIDITY_MODE_DYNAMIC:
|
||||
tz = override_tz or self.event.timezone
|
||||
requested_start = requested_start or now()
|
||||
requested_start = requested_start or time_machine_now()
|
||||
if enforce_start_limit and not self.validity_dynamic_start_choice:
|
||||
requested_start = now()
|
||||
requested_start = time_machine_now()
|
||||
if enforce_start_limit and self.validity_dynamic_start_choice_day_limit is not None:
|
||||
requested_start = min(requested_start, now() + timedelta(days=self.validity_dynamic_start_choice_day_limit))
|
||||
requested_start = min(requested_start, time_machine_now() + timedelta(days=self.validity_dynamic_start_choice_day_limit))
|
||||
|
||||
valid_until = requested_start.astimezone(tz)
|
||||
|
||||
@@ -1290,7 +1291,7 @@ class ItemVariation(models.Model):
|
||||
return ItemVariation.objects.filter(item=self.item).count() == 1
|
||||
|
||||
def is_available_by_time(self, now_dt: datetime=None) -> bool:
|
||||
now_dt = now_dt or now()
|
||||
now_dt = now_dt or time_machine_now()
|
||||
if self.available_from and self.available_from > now_dt:
|
||||
return False
|
||||
if self.available_until and self.available_until < now_dt:
|
||||
@@ -1302,13 +1303,13 @@ class ItemVariation(models.Model):
|
||||
Returns whether this item is available according to its ``active`` flag
|
||||
and its ``available_from`` and ``available_until`` fields
|
||||
"""
|
||||
now_dt = now_dt or now()
|
||||
now_dt = now_dt or time_machine_now()
|
||||
if not self.active or not self.is_available_by_time(now_dt):
|
||||
return False
|
||||
return True
|
||||
|
||||
def unavailability_reason(self, now_dt: datetime=None, has_voucher=False, subevent=None) -> Optional[str]:
|
||||
now_dt = now_dt or now()
|
||||
now_dt = now_dt or time_machine_now()
|
||||
subevent_var = subevent and subevent.var_overrides.get(self.pk)
|
||||
if not self.active:
|
||||
return 'active'
|
||||
|
||||
@@ -23,7 +23,6 @@ from django.db import models
|
||||
from django.db.models import Count, OuterRef, Subquery, Value
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_scopes import ScopedManager, scopes_disabled
|
||||
from i18nfield.fields import I18nCharField
|
||||
@@ -31,6 +30,7 @@ from i18nfield.fields import I18nCharField
|
||||
from pretix.base.models import Customer
|
||||
from pretix.base.models.base import LoggedModel
|
||||
from pretix.base.models.organizer import Organizer
|
||||
from pretix.base.timemachine import time_machine_now
|
||||
from pretix.helpers.names import build_name
|
||||
|
||||
|
||||
@@ -165,13 +165,13 @@ class Membership(models.Model):
|
||||
|
||||
def is_valid(self, ev=None, ticket_valid_from=None, valid_from_not_chosen=False):
|
||||
if valid_from_not_chosen:
|
||||
return not self.canceled and self.date_end >= now()
|
||||
return not self.canceled and self.date_end >= time_machine_now()
|
||||
elif ticket_valid_from:
|
||||
dt = ticket_valid_from
|
||||
elif ev:
|
||||
dt = ev.date_from
|
||||
else:
|
||||
dt = now()
|
||||
dt = time_machine_now()
|
||||
|
||||
return not self.canceled and dt >= self.date_start and dt <= self.date_end
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ from pretix.base.models import Customer, User
|
||||
from pretix.base.reldate import RelativeDateWrapper
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.base.signals import allow_ticket_download, order_gracefully_delete
|
||||
from pretix.base.timemachine import time_machine_now
|
||||
|
||||
from ...helpers import OF_SELF
|
||||
from ...helpers.countries import CachedCountries, FastCountryField
|
||||
@@ -681,7 +682,7 @@ class Order(LockModel, LoggedModel):
|
||||
for op in positions:
|
||||
if op.issued_gift_cards.all():
|
||||
return False
|
||||
if self.user_change_deadline and now() > self.user_change_deadline:
|
||||
if self.user_change_deadline and time_machine_now() > self.user_change_deadline:
|
||||
return False
|
||||
|
||||
return (
|
||||
@@ -713,7 +714,7 @@ class Order(LockModel, LoggedModel):
|
||||
return False
|
||||
if op.granted_memberships.with_usages().filter(usages__gt=0):
|
||||
return False
|
||||
if self.user_cancel_deadline and now() > self.user_cancel_deadline:
|
||||
if self.user_cancel_deadline and time_machine_now() > self.user_cancel_deadline:
|
||||
return False
|
||||
|
||||
if self.status == Order.STATUS_PAID:
|
||||
@@ -851,7 +852,7 @@ class Order(LockModel, LoggedModel):
|
||||
return False
|
||||
|
||||
modify_deadline = self.modify_deadline
|
||||
if modify_deadline is not None and now() > modify_deadline:
|
||||
if modify_deadline is not None and time_machine_now() > modify_deadline:
|
||||
return False
|
||||
|
||||
positions = list(
|
||||
@@ -903,7 +904,7 @@ class Order(LockModel, LoggedModel):
|
||||
return self.event.settings.ticket_download and (
|
||||
self.event.settings.ticket_download_date is None
|
||||
or self.ticket_download_date is None
|
||||
or now() > self.ticket_download_date
|
||||
or time_machine_now() > self.ticket_download_date
|
||||
) and (
|
||||
self.status == Order.STATUS_PAID
|
||||
or (
|
||||
@@ -975,7 +976,7 @@ class Order(LockModel, LoggedModel):
|
||||
return error_messages['require_approval']
|
||||
term_last = self.payment_term_last
|
||||
if term_last and not ignore_date:
|
||||
if now() > term_last:
|
||||
if time_machine_now() > term_last:
|
||||
return error_messages['late_lastdate']
|
||||
|
||||
if self.status == self.STATUS_PENDING:
|
||||
@@ -998,7 +999,7 @@ class Order(LockModel, LoggedModel):
|
||||
'voucher_budget': _('The voucher "{voucher}" no longer has sufficient budget.'),
|
||||
'voucher_usages': _('The voucher "{voucher}" has been used in the meantime.'),
|
||||
}
|
||||
now_dt = now_dt or now()
|
||||
now_dt = now_dt or time_machine_now()
|
||||
positions = list(self.positions.all().select_related('item', 'variation', 'seat', 'voucher'))
|
||||
quota_cache = {}
|
||||
v_budget = {}
|
||||
@@ -2535,9 +2536,9 @@ class OrderPosition(AbstractPosition):
|
||||
if cartpos.item.validity_mode:
|
||||
valid_from, valid_until = cartpos.item.compute_validity(
|
||||
requested_start=(
|
||||
max(cartpos.requested_valid_from, now())
|
||||
max(cartpos.requested_valid_from, time_machine_now())
|
||||
if cartpos.requested_valid_from and cartpos.item.validity_dynamic_start_choice
|
||||
else now()
|
||||
else time_machine_now()
|
||||
),
|
||||
enforce_start_limit=True,
|
||||
override_tz=order.event.timezone,
|
||||
@@ -3063,9 +3064,9 @@ class CartPosition(AbstractPosition):
|
||||
def predicted_validity(self):
|
||||
return self.item.compute_validity(
|
||||
requested_start=(
|
||||
max(self.requested_valid_from, now())
|
||||
max(self.requested_valid_from, time_machine_now())
|
||||
if self.requested_valid_from and self.item.validity_dynamic_start_choice
|
||||
else now()
|
||||
else time_machine_now()
|
||||
),
|
||||
override_tz=self.event.timezone,
|
||||
)
|
||||
|
||||
@@ -67,6 +67,7 @@ from pretix.base.settings import SettingsSandbox
|
||||
from pretix.base.signals import register_payment_providers
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
from pretix.base.templatetags.rich_text import rich_text
|
||||
from pretix.base.timemachine import time_machine_now
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.countries import CachedCountries
|
||||
from pretix.helpers.format import format_map
|
||||
@@ -1441,7 +1442,7 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
if not gc.testmode and self.event.testmode:
|
||||
messages.error(request, _("Only test gift cards can be used in test mode."))
|
||||
return
|
||||
if gc.expires and gc.expires < now():
|
||||
if gc.expires and gc.expires < time_machine_now():
|
||||
messages.error(request, _("This gift card is no longer valid."))
|
||||
return
|
||||
if gc.value <= Decimal("0.00"):
|
||||
@@ -1491,7 +1492,7 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
if not gc.testmode and payment.order.testmode:
|
||||
messages.error(request, _("Only test gift cards can be used in test mode."))
|
||||
return
|
||||
if gc.expires and gc.expires < now():
|
||||
if gc.expires and gc.expires < time_machine_now():
|
||||
messages.error(request, _("This gift card is no longer valid."))
|
||||
return
|
||||
if gc.value <= Decimal("0.00"):
|
||||
@@ -1539,7 +1540,7 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
raise PaymentException(_("This gift card can only be used in test mode."))
|
||||
if not gc.testmode and payment.order.testmode:
|
||||
raise PaymentException(_("Only test gift cards can be used in test mode."))
|
||||
if gc.expires and gc.expires < now():
|
||||
if gc.expires and gc.expires < time_machine_now():
|
||||
raise PaymentException(_("This gift card is no longer valid."))
|
||||
|
||||
trans = gc.transactions.create(
|
||||
|
||||
@@ -74,6 +74,7 @@ from pretix.base.services.tasks import ProfiledEventTask
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES, LazyI18nStringList
|
||||
from pretix.base.signals import validate_cart_addons
|
||||
from pretix.base.templatetags.rich_text import rich_text
|
||||
from pretix.base.timemachine import time_machine_now, time_machine_now_assigned
|
||||
from pretix.celery_app import app
|
||||
from pretix.presale.signals import (
|
||||
checkout_confirm_messages, fee_calculation_for_cart,
|
||||
@@ -278,7 +279,7 @@ class CartManager:
|
||||
sales_channel='web'):
|
||||
self.event = event
|
||||
self.cart_id = cart_id
|
||||
self.now_dt = now()
|
||||
self.real_now_dt = now()
|
||||
self._operations = []
|
||||
self._quota_diff = Counter()
|
||||
self._voucher_use_diff = Counter()
|
||||
@@ -305,10 +306,10 @@ class CartManager:
|
||||
return self._seated_cache[item, subevent]
|
||||
|
||||
def _calculate_expiry(self):
|
||||
self._expiry = self.now_dt + timedelta(minutes=self.event.settings.get('reservation_time', as_type=int))
|
||||
self._expiry = self.real_now_dt + timedelta(minutes=self.event.settings.get('reservation_time', as_type=int))
|
||||
|
||||
def _check_presale_dates(self):
|
||||
if self.event.presale_start and self.now_dt < self.event.presale_start:
|
||||
if self.event.presale_start and time_machine_now(self.real_now_dt) < self.event.presale_start:
|
||||
raise CartError(error_messages['not_started'])
|
||||
if self.event.presale_has_ended:
|
||||
raise CartError(error_messages['ended'])
|
||||
@@ -319,13 +320,13 @@ class CartManager:
|
||||
tlv.datetime(self.event).date(),
|
||||
time(hour=23, minute=59, second=59)
|
||||
), self.event.timezone)
|
||||
if term_last < self.now_dt:
|
||||
if term_last < time_machine_now(self.real_now_dt):
|
||||
raise CartError(error_messages['payment_ended'])
|
||||
|
||||
def _extend_expiry_of_valid_existing_positions(self):
|
||||
# Extend this user's cart session to ensure all items in the cart expire at the same time
|
||||
# We can extend the reservation of items which are not yet expired without risk
|
||||
self.positions.filter(expires__gt=self.now_dt).update(expires=self._expiry)
|
||||
self.positions.filter(expires__gt=self.real_now_dt).update(expires=self._expiry)
|
||||
|
||||
def _delete_out_of_timeframe(self):
|
||||
err = None
|
||||
@@ -333,12 +334,12 @@ class CartManager:
|
||||
if not cp.pk:
|
||||
continue
|
||||
|
||||
if cp.subevent and cp.subevent.presale_start and self.now_dt < cp.subevent.presale_start:
|
||||
if cp.subevent and cp.subevent.presale_start and time_machine_now(self.real_now_dt) < cp.subevent.presale_start:
|
||||
err = error_messages['some_subevent_not_started']
|
||||
cp.addons.all().delete()
|
||||
cp.delete()
|
||||
|
||||
if cp.subevent and cp.subevent.presale_end and self.now_dt > cp.subevent.presale_end:
|
||||
if cp.subevent and cp.subevent.presale_end and time_machine_now(self.real_now_dt) > cp.subevent.presale_end:
|
||||
err = error_messages['some_subevent_ended']
|
||||
cp.addons.all().delete()
|
||||
cp.delete()
|
||||
@@ -350,7 +351,7 @@ class CartManager:
|
||||
tlv.datetime(cp.subevent).date(),
|
||||
time(hour=23, minute=59, second=59)
|
||||
), self.event.timezone)
|
||||
if term_last < self.now_dt:
|
||||
if term_last < time_machine_now(self.real_now_dt):
|
||||
err = error_messages['some_subevent_ended']
|
||||
cp.addons.all().delete()
|
||||
cp.delete()
|
||||
@@ -449,7 +450,7 @@ class CartManager:
|
||||
if op.subevent and not op.subevent.active:
|
||||
raise CartError(error_messages['inactive_subevent'])
|
||||
|
||||
if op.subevent and op.subevent.presale_start and self.now_dt < op.subevent.presale_start:
|
||||
if op.subevent and op.subevent.presale_start and time_machine_now(self.real_now_dt) < op.subevent.presale_start:
|
||||
raise CartError(error_messages['not_started'])
|
||||
|
||||
if op.subevent and op.subevent.presale_has_ended:
|
||||
@@ -472,7 +473,7 @@ class CartManager:
|
||||
tlv.datetime(op.subevent).date(),
|
||||
time(hour=23, minute=59, second=59)
|
||||
), self.event.timezone)
|
||||
if term_last < self.now_dt:
|
||||
if term_last < time_machine_now(self.real_now_dt):
|
||||
raise CartError(error_messages['payment_ended'])
|
||||
|
||||
if isinstance(op, self.AddOperation):
|
||||
@@ -509,7 +510,7 @@ class CartManager:
|
||||
)
|
||||
if not self.event.settings.seating_choice:
|
||||
requires_seat = Value(0, output_field=IntegerField())
|
||||
expired = self.positions.filter(expires__lte=self.now_dt).select_related(
|
||||
expired = self.positions.filter(expires__lte=self.real_now_dt).select_related(
|
||||
'item', 'variation', 'voucher', 'addon_to', 'addon_to__item'
|
||||
).annotate(
|
||||
requires_seat=requires_seat
|
||||
@@ -690,7 +691,7 @@ class CartManager:
|
||||
# than either of the possible default assumptions.
|
||||
predicted_redeemed_after = (
|
||||
voucher.redeemed +
|
||||
CartPosition.objects.filter(voucher=voucher, expires__gte=self.now_dt).count() +
|
||||
CartPosition.objects.filter(voucher=voucher, expires__gte=self.real_now_dt).count() +
|
||||
self._voucher_use_diff[voucher] +
|
||||
voucher_use_diff[voucher]
|
||||
)
|
||||
@@ -982,7 +983,7 @@ class CartManager:
|
||||
current_num = len(current_addons[cp].get(k, []))
|
||||
if input_num < current_num:
|
||||
for a in current_addons[cp][k][:current_num - input_num]:
|
||||
if a.expires > self.now_dt:
|
||||
if a.expires > self.real_now_dt:
|
||||
quotas = list(a.quotas)
|
||||
|
||||
for quota in quotas:
|
||||
@@ -996,7 +997,7 @@ class CartManager:
|
||||
|
||||
def _get_voucher_availability(self):
|
||||
vouchers_ok, self._voucher_depend_on_cart = _get_voucher_availability(
|
||||
self.event, self._voucher_use_diff, self.now_dt,
|
||||
self.event, self._voucher_use_diff, self.real_now_dt,
|
||||
exclude_position_ids=[
|
||||
op.position.id for op in self._operations if isinstance(op, self.ExtendOperation)
|
||||
]
|
||||
@@ -1101,7 +1102,7 @@ class CartManager:
|
||||
shared_lock_objects=[self.event]
|
||||
)
|
||||
vouchers_ok = self._get_voucher_availability()
|
||||
quotas_ok = _get_quota_availability(self._quota_diff, self.now_dt)
|
||||
quotas_ok = _get_quota_availability(self._quota_diff, self.real_now_dt)
|
||||
err = None
|
||||
new_cart_positions = []
|
||||
deleted_positions = set()
|
||||
@@ -1118,7 +1119,7 @@ class CartManager:
|
||||
|
||||
for iop, op in enumerate(self._operations):
|
||||
if isinstance(op, self.RemoveOperation):
|
||||
if op.position.expires > self.now_dt:
|
||||
if op.position.expires > self.real_now_dt:
|
||||
for q in op.position.quotas:
|
||||
quotas_ok[q] += 1
|
||||
addons = op.position.addons.all()
|
||||
@@ -1395,7 +1396,7 @@ class CartManager:
|
||||
err = self.extend_expired_positions() or err
|
||||
err = err or self._check_min_per_voucher()
|
||||
|
||||
self.now_dt = now()
|
||||
self.real_now_dt = now()
|
||||
|
||||
self._extend_expiry_of_valid_existing_positions()
|
||||
err = self._perform_operations() or err
|
||||
@@ -1487,7 +1488,7 @@ def get_fees(event, request, total, invoice_address, payments, positions):
|
||||
|
||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||
def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, locale='en',
|
||||
invoice_address: int=None, widget_data=None, sales_channel='web') -> None:
|
||||
invoice_address: int=None, widget_data=None, sales_channel='web', override_now_dt: datetime=None) -> None:
|
||||
"""
|
||||
Adds a list of items to a user's cart.
|
||||
:param event: The event ID in question
|
||||
@@ -1495,7 +1496,7 @@ def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, lo
|
||||
:param cart_id: Session ID of a guest
|
||||
:raises CartError: On any error that occurred
|
||||
"""
|
||||
with language(locale):
|
||||
with language(locale), time_machine_now_assigned(override_now_dt):
|
||||
ia = False
|
||||
if invoice_address:
|
||||
try:
|
||||
@@ -1517,14 +1518,14 @@ def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, lo
|
||||
|
||||
|
||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||
def apply_voucher(self, event: Event, voucher: str, cart_id: str=None, locale='en', sales_channel='web') -> None:
|
||||
def apply_voucher(self, event: Event, voucher: str, cart_id: str=None, locale='en', sales_channel='web', override_now_dt: datetime=None) -> None:
|
||||
"""
|
||||
Removes a list of items from a user's cart.
|
||||
:param event: The event ID in question
|
||||
:param voucher: A voucher code
|
||||
:param session: Session ID of a guest
|
||||
"""
|
||||
with language(locale):
|
||||
with language(locale), time_machine_now_assigned(override_now_dt):
|
||||
try:
|
||||
try:
|
||||
cm = CartManager(event=event, cart_id=cart_id, sales_channel=sales_channel)
|
||||
@@ -1537,14 +1538,14 @@ def apply_voucher(self, event: Event, voucher: str, cart_id: str=None, locale='e
|
||||
|
||||
|
||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||
def remove_cart_position(self, event: Event, position: int, cart_id: str=None, locale='en', sales_channel='web') -> None:
|
||||
def remove_cart_position(self, event: Event, position: int, cart_id: str=None, locale='en', sales_channel='web', override_now_dt: datetime=None) -> None:
|
||||
"""
|
||||
Removes a list of items from a user's cart.
|
||||
:param event: The event ID in question
|
||||
:param position: A cart position ID
|
||||
:param session: Session ID of a guest
|
||||
"""
|
||||
with language(locale):
|
||||
with language(locale), time_machine_now_assigned(override_now_dt):
|
||||
try:
|
||||
try:
|
||||
cm = CartManager(event=event, cart_id=cart_id, sales_channel=sales_channel)
|
||||
@@ -1557,13 +1558,13 @@ def remove_cart_position(self, event: Event, position: int, cart_id: str=None, l
|
||||
|
||||
|
||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||
def clear_cart(self, event: Event, cart_id: str=None, locale='en', sales_channel='web') -> None:
|
||||
def clear_cart(self, event: Event, cart_id: str=None, locale='en', sales_channel='web', override_now_dt: datetime=None) -> None:
|
||||
"""
|
||||
Removes a list of items from a user's cart.
|
||||
:param event: The event ID in question
|
||||
:param session: Session ID of a guest
|
||||
"""
|
||||
with language(locale):
|
||||
with language(locale), time_machine_now_assigned(override_now_dt):
|
||||
try:
|
||||
try:
|
||||
cm = CartManager(event=event, cart_id=cart_id, sales_channel=sales_channel)
|
||||
@@ -1577,14 +1578,14 @@ def clear_cart(self, event: Event, cart_id: str=None, locale='en', sales_channel
|
||||
|
||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||
def set_cart_addons(self, event: Event, addons: List[dict], cart_id: str=None, locale='en',
|
||||
invoice_address: int=None, sales_channel='web') -> None:
|
||||
invoice_address: int=None, sales_channel='web', override_now_dt: datetime=None) -> None:
|
||||
"""
|
||||
Removes a list of items from a user's cart.
|
||||
:param event: The event ID in question
|
||||
:param addons: A list of dicts with the keys addon_to, item, variation
|
||||
:param session: Session ID of a guest
|
||||
"""
|
||||
with language(locale):
|
||||
with language(locale), time_machine_now_assigned(override_now_dt):
|
||||
ia = False
|
||||
if invoice_address:
|
||||
try:
|
||||
|
||||
@@ -25,13 +25,13 @@ from typing import List, Optional
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix.base.models import (
|
||||
AbstractPosition, CartPosition, Customer, Event, Item, Membership, Order,
|
||||
OrderPosition, SubEvent,
|
||||
)
|
||||
from pretix.base.timemachine import time_machine_now
|
||||
from pretix.helpers import OF_SELF
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ def membership_validity(item: Item, subevent: Optional[SubEvent], event: Event):
|
||||
|
||||
else:
|
||||
# Always start at start of day
|
||||
date_start = now().astimezone(tz).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
date_start = time_machine_now().astimezone(tz).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
date_end = date_start
|
||||
|
||||
if item.grant_membership_duration_months:
|
||||
|
||||
@@ -213,8 +213,6 @@ def import_vouchers(event: Event, fileid: str, settings: dict, locale: str, user
|
||||
voucher = Voucher(event=event)
|
||||
vouchers.append(voucher)
|
||||
|
||||
if not record.get("code"):
|
||||
raise ValidationError(_('A voucher cannot be created without a code.'))
|
||||
Voucher.clean_item_properties(
|
||||
record,
|
||||
event,
|
||||
|
||||
@@ -102,6 +102,7 @@ from pretix.base.signals import (
|
||||
order_fee_calculation, order_paid, order_placed, order_split,
|
||||
order_valid_if_pending, periodic_task, validate_order,
|
||||
)
|
||||
from pretix.base.timemachine import time_machine_now, time_machine_now_assigned
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.models import modelcopy
|
||||
@@ -648,10 +649,11 @@ def _check_date(event: Event, now_dt: datetime):
|
||||
raise OrderError(error_messages['ended'])
|
||||
|
||||
|
||||
def _check_positions(event: Event, now_dt: datetime, positions: List[CartPosition], address: InvoiceAddress=None,
|
||||
def _check_positions(event: Event, now_dt: datetime, time_machine_now_dt: datetime, positions: List[CartPosition],
|
||||
address: InvoiceAddress = None,
|
||||
sales_channel='web', customer=None):
|
||||
err = None
|
||||
_check_date(event, now_dt)
|
||||
_check_date(event, time_machine_now_dt)
|
||||
|
||||
products_seen = Counter()
|
||||
q_avail = Counter()
|
||||
@@ -729,7 +731,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
|
||||
delete(cp)
|
||||
continue
|
||||
|
||||
if cp.subevent and cp.subevent.presale_start and now_dt < cp.subevent.presale_start:
|
||||
if cp.subevent and cp.subevent.presale_start and time_machine_now_dt < cp.subevent.presale_start:
|
||||
err = err or error_messages['some_subevent_not_started']
|
||||
delete(cp)
|
||||
break
|
||||
@@ -741,7 +743,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
|
||||
tlv.datetime(cp.subevent).date(),
|
||||
time(hour=23, minute=59, second=59)
|
||||
), event.timezone)
|
||||
if term_last < now_dt:
|
||||
if term_last < time_machine_now_dt:
|
||||
err = err or error_messages['some_subevent_ended']
|
||||
delete(cp)
|
||||
break
|
||||
@@ -787,19 +789,19 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
|
||||
delete(cp)
|
||||
continue
|
||||
|
||||
if cp.subevent and cp.item.pk in cp.subevent.item_overrides and not cp.subevent.item_overrides[cp.item.pk].is_available(now_dt):
|
||||
if cp.subevent and cp.item.pk in cp.subevent.item_overrides and not cp.subevent.item_overrides[cp.item.pk].is_available(time_machine_now_dt):
|
||||
err = err or error_messages['unavailable']
|
||||
delete(cp)
|
||||
continue
|
||||
|
||||
if cp.subevent and cp.variation and cp.variation.pk in cp.subevent.var_overrides and \
|
||||
not cp.subevent.var_overrides[cp.variation.pk].is_available(now_dt):
|
||||
not cp.subevent.var_overrides[cp.variation.pk].is_available(time_machine_now_dt):
|
||||
err = err or error_messages['unavailable']
|
||||
delete(cp)
|
||||
continue
|
||||
|
||||
if cp.voucher:
|
||||
if cp.voucher.valid_until and cp.voucher.valid_until < now_dt:
|
||||
if cp.voucher.valid_until and cp.voucher.valid_until < time_machine_now_dt:
|
||||
err = err or error_messages['voucher_expired']
|
||||
delete(cp)
|
||||
continue
|
||||
@@ -1163,7 +1165,8 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
||||
warnings = []
|
||||
any_payment_failed = False
|
||||
|
||||
now_dt = now()
|
||||
real_now_dt = now()
|
||||
time_machine_now_dt = time_machine_now(real_now_dt)
|
||||
err_out = None
|
||||
with transaction.atomic(durable=True):
|
||||
positions = list(
|
||||
@@ -1175,14 +1178,15 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
||||
if len(position_ids) != len(positions):
|
||||
raise OrderError(error_messages['internal'])
|
||||
try:
|
||||
_check_positions(event, now_dt, positions, address=addr, sales_channel=sales_channel, customer=customer)
|
||||
_check_positions(event, real_now_dt, time_machine_now_dt, positions,
|
||||
address=addr, sales_channel=sales_channel, customer=customer)
|
||||
except OrderError as e:
|
||||
err_out = e # Don't raise directly to make sure transaction is committed, as it might have deleted things
|
||||
else:
|
||||
if 'sleep-after-quota-check' in debugflags_var.get():
|
||||
sleep(2)
|
||||
|
||||
order, payment_objs = _create_order(event, email, positions, now_dt, payment_requests,
|
||||
order, payment_objs = _create_order(event, email, positions, real_now_dt, payment_requests,
|
||||
locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel,
|
||||
shown_total=shown_total, customer=customer, valid_if_pending=valid_if_pending)
|
||||
|
||||
@@ -2849,8 +2853,8 @@ class OrderChangeManager:
|
||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
|
||||
def perform_order(self, event: Event, payments: List[dict], positions: List[str],
|
||||
email: str=None, locale: str=None, address: int=None, meta_info: dict=None,
|
||||
sales_channel: str='web', shown_total=None, customer=None):
|
||||
with language(locale):
|
||||
sales_channel: str='web', shown_total=None, customer=None, override_now_dt: datetime=None):
|
||||
with language(locale), time_machine_now_assigned(override_now_dt):
|
||||
try:
|
||||
try:
|
||||
return _perform_order(event, payments, positions, email, locale, address, meta_info,
|
||||
|
||||
@@ -25,7 +25,6 @@ from typing import List, Optional, Tuple
|
||||
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
from django.utils.timezone import now
|
||||
|
||||
from pretix.base.decimal import round_decimal
|
||||
from pretix.base.models import (
|
||||
@@ -33,6 +32,7 @@ from pretix.base.models import (
|
||||
)
|
||||
from pretix.base.models.event import Event, SubEvent
|
||||
from pretix.base.models.tax import TAXED_ZERO, TaxedPrice, TaxRule
|
||||
from pretix.base.timemachine import time_machine_now
|
||||
|
||||
|
||||
def get_price(item: Item, variation: ItemVariation = None,
|
||||
@@ -167,8 +167,8 @@ def apply_discounts(event: Event, sales_channel: str,
|
||||
new_prices = {}
|
||||
|
||||
discount_qs = event.discounts.filter(
|
||||
Q(available_from__isnull=True) | Q(available_from__lte=now()),
|
||||
Q(available_until__isnull=True) | Q(available_until__gte=now()),
|
||||
Q(available_from__isnull=True) | Q(available_from__lte=time_machine_now()),
|
||||
Q(available_until__isnull=True) | Q(available_until__gte=time_machine_now()),
|
||||
sales_channels__contains=sales_channel,
|
||||
active=True,
|
||||
).prefetch_related('condition_limit_products', 'benefit_limit_products').order_by('position', 'pk')
|
||||
|
||||
85
src/pretix/base/timemachine.py
Normal file
85
src/pretix/base/timemachine.py
Normal file
@@ -0,0 +1,85 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import contextvars
|
||||
from contextlib import contextmanager
|
||||
|
||||
from dateutil.parser import parse
|
||||
from django.utils.timezone import now
|
||||
|
||||
timemachine_now_var = contextvars.ContextVar('timemachine_now', default=None)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def time_machine_now_assigned_from_request(request):
|
||||
if hasattr(request, 'event') and f'timemachine_now_dt:{request.event.pk}' in request.session and \
|
||||
request.event.testmode and has_time_machine_permission(request, request.event):
|
||||
request.now_dt = parse(request.session[f'timemachine_now_dt:{request.event.pk}'])
|
||||
request.now_dt_is_fake = True
|
||||
else:
|
||||
request.now_dt = now()
|
||||
request.now_dt_is_fake = False
|
||||
|
||||
try:
|
||||
timemachine_now_var.set(request.now_dt if request.now_dt_is_fake else None)
|
||||
|
||||
yield
|
||||
finally:
|
||||
timemachine_now_var.set(None)
|
||||
|
||||
|
||||
def time_machine_now(default=False):
|
||||
"""
|
||||
Return the datetime to use as current datetime for checking order restrictions in event
|
||||
index and checkout flow.
|
||||
|
||||
:param default: Value to return if time machine mode is disabled. By default the current datetime is used.
|
||||
"""
|
||||
if default is False:
|
||||
default = now()
|
||||
return timemachine_now_var.get() or default
|
||||
|
||||
|
||||
@contextmanager
|
||||
def time_machine_now_assigned(now_dt):
|
||||
"""
|
||||
Use this context manager to assign current datetime for time machine mode. Useful e.g. for background tasks.
|
||||
|
||||
:param now_dt: The datetime value to assign. May be `None` to disable time machine.
|
||||
"""
|
||||
try:
|
||||
timemachine_now_var.set(now_dt)
|
||||
yield
|
||||
finally:
|
||||
timemachine_now_var.set(None)
|
||||
|
||||
|
||||
def has_time_machine_permission(request, event):
|
||||
permission = 'can_change_event_settings'
|
||||
|
||||
return (
|
||||
request.user.is_authenticated and
|
||||
request.user.has_event_permission(request.organizer, request.event, permission, request=request)
|
||||
) or (
|
||||
getattr(request, 'event_access_user', None) and
|
||||
request.event_access_user.is_authenticated and
|
||||
request.event_access_user.has_event_permission(request.organizer, request.event, permission, request=request)
|
||||
)
|
||||
@@ -46,11 +46,11 @@ from pretix.base.settings import GlobalSettingsObject
|
||||
from pretix.control.navigation import (
|
||||
get_event_navigation, get_global_navigation, get_organizer_navigation,
|
||||
)
|
||||
|
||||
from ..helpers.i18n import (
|
||||
from pretix.helpers.i18n import (
|
||||
get_javascript_format, get_javascript_output_format, get_moment_locale,
|
||||
)
|
||||
from ..multidomain.urlreverse import get_event_domain
|
||||
from pretix.multidomain.urlreverse import get_event_domain
|
||||
|
||||
from .signals import html_head, nav_topbar
|
||||
|
||||
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
|
||||
@@ -106,7 +106,7 @@ def _default_context(request):
|
||||
else:
|
||||
ctx['complain_testmode_orders'] = False
|
||||
|
||||
if not request.event.live and ctx['has_domain']:
|
||||
if (request.event.testmode or not request.event.live) and ctx['has_domain']:
|
||||
child_sess = request.session.get('child_session_{}'.format(request.event.pk))
|
||||
s = SessionStore()
|
||||
if not child_sess or not s.exists(child_sess):
|
||||
@@ -114,10 +114,8 @@ def _default_context(request):
|
||||
s.create()
|
||||
ctx['new_session'] = s.session_key
|
||||
request.session['child_session_{}'.format(request.event.pk)] = s.session_key
|
||||
request.session['event_access'] = True
|
||||
else:
|
||||
ctx['new_session'] = child_sess
|
||||
request.session['event_access'] = True
|
||||
|
||||
if request.GET.get('subevent', ''):
|
||||
# Do not use .get() for lazy evaluation
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="test-right">
|
||||
<div class="text-right">
|
||||
<button type="submit" class="btn btn-primary btn-lg btn-save" disabled>
|
||||
{% trans "Go live" %}
|
||||
</button>
|
||||
@@ -82,10 +82,10 @@
|
||||
<p>
|
||||
{% trans "Your shop is currently in test mode. All orders are not persistent and can be deleted at any point." %}
|
||||
</p>
|
||||
<div class="form-inline">
|
||||
<label class="checkbox">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="delete" value="yes" />
|
||||
{% trans "Permanently delete all orders created in test mode" %}
|
||||
<b>{% trans "Permanently delete all orders created in test mode" %}</b>
|
||||
</label>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{% load eventurl %}
|
||||
{% load static %}
|
||||
<form action="{% eventurl request.event "presale:event.auth" %}{% if request.GET.next %}?next={{ request.GET.next }}{% endif %}" method="post">
|
||||
<input type="hidden" value="{{ new_session }}" name="session">
|
||||
<button type="submit">
|
||||
Continue
|
||||
</button>
|
||||
</form>
|
||||
<script src="{% static "pretixcontrol/js/send_form.js" %}"></script>
|
||||
@@ -247,6 +247,7 @@ urlpatterns = [
|
||||
re_path(r'^widgets.json$', dashboards.event_index_widgets_lazy, name='event.index.widgets'),
|
||||
re_path(r'^logs/embed$', dashboards.event_index_log_lazy, name='event.index.logs'),
|
||||
re_path(r'^live/$', event.EventLive.as_view(), name='event.live'),
|
||||
re_path(r'^transfer_session/$', event.EventTransferSession.as_view(), name='event.transfer_session'),
|
||||
re_path(r'^logs/$', event.EventLog.as_view(), name='event.log'),
|
||||
re_path(r'^delete/$', event.EventDelete.as_view(), name='event.delete'),
|
||||
re_path(r'^comment/$', event.EventComment.as_view(),
|
||||
|
||||
@@ -1017,6 +1017,11 @@ class EventLive(EventPermissionRequiredMixin, TemplateView):
|
||||
})
|
||||
|
||||
|
||||
class EventTransferSession(EventPermissionRequiredMixin, TemplateView):
|
||||
permission = 'can_change_event_settings'
|
||||
template_name = 'pretixcontrol/event/transfer_session.html'
|
||||
|
||||
|
||||
class EventDelete(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixin, FormView):
|
||||
permission = 'can_change_event_settings'
|
||||
template_name = 'pretixcontrol/event/delete.html'
|
||||
|
||||
@@ -4,7 +4,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-26 09:26+0000\n"
|
||||
"PO-Revision-Date: 2024-05-01 01:00+0000\n"
|
||||
"PO-Revision-Date: 2024-04-30 01:00+0000\n"
|
||||
"Last-Translator: Nikolai <nikolai@lengefeldt.de>\n"
|
||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix/da/"
|
||||
">\n"
|
||||
@@ -30298,15 +30298,12 @@ msgid ""
|
||||
"connect your order to your account. This will allow you to see all your "
|
||||
"orders in one place and access them at any time."
|
||||
msgstr ""
|
||||
"Hvis du allerede har oprettet en konto hos %(org)s, kan du tilmelde dig nu "
|
||||
"og knytte din bestilling til din konto. På denne måde vil du kunne se alle "
|
||||
"dine bestillinger ét sted, hvor du kan tilgå dem til enhver tid."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_customer.html:60
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_login.html:44
|
||||
#, fuzzy
|
||||
msgid "Reset password"
|
||||
msgstr "Nulstil adgangskode"
|
||||
msgstr "Gentag adgangskode"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_customer.html:92
|
||||
#, fuzzy
|
||||
@@ -30320,11 +30317,6 @@ msgid ""
|
||||
"password, so you can use the account for future orders at %(org)s. You can "
|
||||
"still go ahead with this purchase before you received the email."
|
||||
msgstr ""
|
||||
"Vi sender dig en mail med et link, som du kan bruge til at aktivere din "
|
||||
"konto, hvorefter du kan vælge en adgangskode. Derefter kan du bruge din "
|
||||
"konto til alle dine bestillinger hos %(org)s i fremtiden. Du kan stadig "
|
||||
"fortsætte med at afslutte denne bestilling, selvom du ikke har modtaget "
|
||||
"mailen."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_customer.html:122
|
||||
#, fuzzy
|
||||
@@ -30332,23 +30324,24 @@ msgid "Continue as a guest"
|
||||
msgstr "Fortsæt som gæst"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_customer.html:130
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"You are not required to create an account. If you proceed as a guest, you "
|
||||
"will be able to access the details and status of your order any time through "
|
||||
"the secret link we will send you via email once the order is complete."
|
||||
msgstr ""
|
||||
"Du behøver ikke at oprette en konto. Hvis du fortsætter som gæst, vil du "
|
||||
"altid have mulighed for at tilgå informationer og status for din bestilling "
|
||||
"via det link, som du får tilsendt per mail, når du har afsluttet din "
|
||||
"bestilling."
|
||||
"altid have mulighed for at tilgå informationer og status for din bestilling "
|
||||
"via det link, som du får tilsendt per mail."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_membership.html:6
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Some of the products in your cart can only be purchased if there is an "
|
||||
"active membership on your account."
|
||||
msgstr ""
|
||||
"Nogle af posterne i din indkøbskurv kan kun købes, hvis du har et aktivt "
|
||||
"medlemskab på din konto."
|
||||
"Du har tilføjet ordrer, hvor det er muligt at lave yderligere indstillinger "
|
||||
"før du forsætter."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_membership.html:37
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_questions.html:114
|
||||
@@ -30877,10 +30870,10 @@ msgid "incl. %(tax_sum)s taxes"
|
||||
msgstr "inkl. %(rate)s%% %(taxname)s"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:482
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "The items in your cart are reserved for you for %(minutes)s minutes."
|
||||
msgstr ""
|
||||
"Posterne i din indkøbskurv er reserveret til dig i %(minutes)s minutter."
|
||||
"Bookingerne i din indkøbskurv er reserveret til dig i %(minutes)s minutter."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:486
|
||||
msgid ""
|
||||
|
||||
@@ -4,8 +4,8 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-26 09:26+0000\n"
|
||||
"PO-Revision-Date: 2024-05-02 14:27+0000\n"
|
||||
"Last-Translator: Raphaël Deux <github@raphael-deux.fr>\n"
|
||||
"PO-Revision-Date: 2023-10-06 03:00+0000\n"
|
||||
"Last-Translator: Ronan LE MEILLAT <ronan.le_meillat@highcanfly.club>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
|
||||
">\n"
|
||||
"Language: fr\n"
|
||||
@@ -13,7 +13,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.4.3\n"
|
||||
"X-Generator: Weblate 5.0.2\n"
|
||||
|
||||
#: pretix/_base_settings.py:78
|
||||
msgid "English"
|
||||
@@ -3218,7 +3218,7 @@ msgstr "Facture TVA"
|
||||
#: pretix/base/invoice.py:597
|
||||
msgctxt "invoice"
|
||||
msgid "Invoice"
|
||||
msgstr "Facture"
|
||||
msgstr "FactureFacture"
|
||||
|
||||
#: pretix/base/invoice.py:598
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:271
|
||||
@@ -3336,7 +3336,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/invoice.py:858
|
||||
msgid "Default invoice renderer (European-style letter)"
|
||||
msgstr "Rendu de la facture par défaut (lettre européenne)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/invoice.py:947
|
||||
msgctxt "invoice"
|
||||
@@ -3345,13 +3345,14 @@ msgstr "(Veuillez mettre entre guillemets systématiquement.)"
|
||||
|
||||
#: pretix/base/invoice.py:994
|
||||
msgid "Simplified invoice renderer"
|
||||
msgstr "Rendu simplifié de la facture"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/invoice.py:1013
|
||||
#, python-brace-format
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Event date range"
|
||||
msgctxt "invoice"
|
||||
msgid "Event date: {date_range}"
|
||||
msgstr "Plage de dates de l'événement : {date_range}"
|
||||
msgstr "Plage de dates de l'événement"
|
||||
|
||||
#: pretix/base/media.py:61
|
||||
msgid "Barcode / QR-Code"
|
||||
@@ -3580,8 +3581,10 @@ msgid "Maximum usages"
|
||||
msgstr "Utilisation maximale"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:81
|
||||
#, fuzzy
|
||||
#| msgid "Maximum number of items per order"
|
||||
msgid "The maximum number of usages must be set."
|
||||
msgstr "Le nombre maximum d'utilisations doit être fixé."
|
||||
msgstr "Nombre maximum d'articles par commande"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:90 pretix/base/models/vouchers.py:205
|
||||
msgid "Minimum usages"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-26 09:26+0000\n"
|
||||
"PO-Revision-Date: 2024-05-03 01:00+0000\n"
|
||||
"PO-Revision-Date: 2024-04-30 01:00+0000\n"
|
||||
"Last-Translator: Serhii Horichenko <m@sgg.im>\n"
|
||||
"Language-Team: Ukrainian <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"uk/>\n"
|
||||
@@ -647,7 +647,7 @@ msgstr "{system} Користувач"
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:299
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_profile.html:26
|
||||
msgid "E-mail"
|
||||
msgstr "Ел.пошта"
|
||||
msgstr "Електронна пошта"
|
||||
|
||||
#: pretix/base/auth.py:154 pretix/base/forms/auth.py:164
|
||||
#: pretix/base/forms/auth.py:218 pretix/base/models/customers.py:96
|
||||
@@ -1853,9 +1853,8 @@ msgstr "Коротка назва події"
|
||||
#: pretix/plugins/reports/exporters.py:561
|
||||
#: pretix/plugins/reports/exporters.py:886
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_profile.html:73
|
||||
#, fuzzy
|
||||
msgid "Order total"
|
||||
msgstr "Сума замовлення"
|
||||
msgstr "Все замовлення"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:260
|
||||
#: pretix/base/exporters/orderlist.py:442
|
||||
@@ -2054,7 +2053,7 @@ msgstr "Правило оподаткування"
|
||||
#: pretix/base/exporters/orderlist.py:624
|
||||
#: pretix/base/exporters/orderlist.py:628 pretix/base/pdf.py:331
|
||||
msgid "Invoice address name"
|
||||
msgstr "Платіжна адреса: Ім'я"
|
||||
msgstr "Ім’я в рахунку-фактурі"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:479
|
||||
#: pretix/base/exporters/orderlist.py:661 pretix/base/models/orders.py:173
|
||||
@@ -2540,7 +2539,7 @@ msgstr "Загальна квота"
|
||||
#: pretix/control/templates/pretixcontrol/event/cancel.html:20
|
||||
#: pretix/control/views/item.py:949
|
||||
msgid "Paid orders"
|
||||
msgstr "Сплачені замовлення"
|
||||
msgstr "Оплачені замовлення"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:1106 pretix/control/views/item.py:954
|
||||
msgid "Pending orders"
|
||||
@@ -3782,7 +3781,7 @@ msgstr "Усі продукти (включаючи новостворені)"
|
||||
#: pretix/base/models/checkin.py:57 pretix/plugins/badges/exporters.py:428
|
||||
#: pretix/plugins/checkinlists/exporters.py:833
|
||||
msgid "Limit to products"
|
||||
msgstr "Обмеження продуктів"
|
||||
msgstr "Обмежити до продуктів"
|
||||
|
||||
#: pretix/base/models/checkin.py:61
|
||||
msgid ""
|
||||
@@ -7165,7 +7164,7 @@ msgstr "Випадкове місто"
|
||||
|
||||
#: pretix/base/pdf.py:336
|
||||
msgid "Invoice address company"
|
||||
msgstr "Платіжна адреса: Компанія"
|
||||
msgstr "Адреса рахунків-фактур компанії"
|
||||
|
||||
#: pretix/base/pdf.py:342
|
||||
msgid "Sesame Street 42"
|
||||
@@ -8455,11 +8454,16 @@ msgstr ""
|
||||
"вашого кошика."
|
||||
|
||||
#: pretix/base/services/orders.py:201
|
||||
#, python-format
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "You cannot delete the product <strong>%(item)s</strong> because it "
|
||||
#| "already has been ordered."
|
||||
msgid ""
|
||||
"You cannot remove the position %(addon)s since it has already been checked "
|
||||
"in."
|
||||
msgstr "Ви не можете вилучити позицію %(addon)s, оскільки вона вже замовлена."
|
||||
msgstr ""
|
||||
"Ви не можете видалити продукт <strong>%(item)s</strong>, оскільки він уже "
|
||||
"замовлений."
|
||||
|
||||
#: pretix/base/services/orders.py:202
|
||||
#, fuzzy
|
||||
@@ -9532,7 +9536,7 @@ msgstr "напр., цим документом ми надсилаємо вам
|
||||
|
||||
#: pretix/base/settings.py:1162
|
||||
msgid "Introductory text"
|
||||
msgstr "Вступний текст"
|
||||
msgstr "вступний текст"
|
||||
|
||||
#: pretix/base/settings.py:1163
|
||||
msgid "Will be printed on every invoice above the invoice rows."
|
||||
@@ -12284,9 +12288,11 @@ msgstr ""
|
||||
#: pretix/base/timeframes.py:71 pretix/base/timeframes.py:80
|
||||
#: pretix/base/timeframes.py:89 pretix/base/timeframes.py:98
|
||||
#: pretix/base/timeframes.py:107
|
||||
#, fuzzy
|
||||
#| msgid "Orders by day"
|
||||
msgctxt "reporting_timeframe"
|
||||
msgid "by day"
|
||||
msgstr "по днях"
|
||||
msgstr "Замовлення по днях"
|
||||
|
||||
#: pretix/base/timeframes.py:58
|
||||
msgctxt "reporting_timeframe"
|
||||
@@ -18417,9 +18423,9 @@ msgid ""
|
||||
"event and only retain the financial information such as the number and type "
|
||||
"of tickets sold."
|
||||
msgstr ""
|
||||
"Ви можете вилучити особисті дані, такі як імена та адреси електронної пошти, "
|
||||
"зі своєї події та зберегти лише таку фінансову інформацію, як кількість та "
|
||||
"тип проданих квитків."
|
||||
"Ви можете видалити особисті дані, такі як імена та адреси електронної пошти, "
|
||||
"зі своєї події та зберегти лише фінансову інформацію, як-от кількість і тип "
|
||||
"проданих квитків."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:76
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:89
|
||||
@@ -18993,7 +18999,7 @@ msgstr ""
|
||||
#: pretix/plugins/ticketoutputpdf/views.py:172
|
||||
#: pretix/presale/views/customer.py:476 pretix/presale/views/customer.py:528
|
||||
msgid "Your changes have been saved."
|
||||
msgstr "Ваші зміни були збережені."
|
||||
msgstr "Ваші зміни збережено."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/plugins.html:36
|
||||
msgid "Top recommendation"
|
||||
@@ -20812,9 +20818,9 @@ msgid ""
|
||||
"informed automatically, but you will have the option to email them "
|
||||
"individually in the next step."
|
||||
msgstr ""
|
||||
"Ви дійсно хочете вилучити цей запит на скасування? Користувачі не будуть "
|
||||
"проінформовані автоматично, але у Вас буде можливість надіслати окремі "
|
||||
"електронні листи на наступному кроці."
|
||||
"Ви дійсно хочете видалити цей запит на скасування? Користувач не буде "
|
||||
"проінформований автоматично, але ви матимете можливість надіслати їм "
|
||||
"електронний лист окремо на наступному кроці."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/cancellation_request_delete.html:26
|
||||
msgid "Yes, delete request"
|
||||
@@ -22896,7 +22902,7 @@ msgstr "Необмеженo"
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_members.html:67
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_members.html:113
|
||||
msgid "Remove"
|
||||
msgstr "Вилучити"
|
||||
msgstr "Видалити"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/giftcard_acceptance_list.html:66
|
||||
msgid "Accept"
|
||||
@@ -23458,7 +23464,7 @@ msgstr "Вставити"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:39
|
||||
msgid "Undo"
|
||||
msgstr "Скасувати дію"
|
||||
msgstr "Скасувати"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:43
|
||||
msgid "Redo"
|
||||
@@ -25079,7 +25085,7 @@ msgstr ""
|
||||
#: pretix/plugins/sendmail/views.py:674 pretix/plugins/stripe/views.py:680
|
||||
#: pretix/plugins/ticketoutputpdf/views.py:132
|
||||
msgid "We could not save your changes. See below for details."
|
||||
msgstr "Нам не вдалося зберегти Ваші зміни. Подробиці дивіться нижче."
|
||||
msgstr "Нам не вдалося зберегти ваші зміни. Подробиці - нижче."
|
||||
|
||||
#: pretix/control/views/checkin.py:416 pretix/control/views/checkin.py:453
|
||||
msgid "The requested list does not exist."
|
||||
@@ -25720,8 +25726,7 @@ msgstr[2] ""
|
||||
#: pretix/presale/views/order.py:1203 pretix/presale/views/order.py:1586
|
||||
#: pretix/presale/views/order.py:1617
|
||||
msgid "Unknown order code or not authorized to access this order."
|
||||
msgstr ""
|
||||
"Невідомий код замовлення або доступ до цього замовлення отримати неможливо."
|
||||
msgstr "Невідомий код замовлення або доступ до цього замовлення не дозволений."
|
||||
|
||||
#: pretix/control/views/orders.py:674 pretix/presale/views/order.py:1035
|
||||
msgid "Ticket download is not enabled for this product."
|
||||
@@ -30770,7 +30775,7 @@ msgstr "Будь ласка, зачекайте, ми завершуємо ва
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:22
|
||||
msgid "Add or remove tickets"
|
||||
msgstr "Додати або вилучити квитки"
|
||||
msgstr "Додати або видалити квитки"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:44
|
||||
msgid "Modify payment"
|
||||
@@ -31388,7 +31393,7 @@ msgstr "Домовились, ми його замінимо…"
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:288
|
||||
#, python-format
|
||||
msgid "Remove %(item)s from your cart"
|
||||
msgstr "Вилучити %(item)s з Вашого кошика"
|
||||
msgstr "Видалити %(item)s з вашого кошика"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:291
|
||||
#, python-format
|
||||
@@ -31401,7 +31406,7 @@ msgid ""
|
||||
"Remove one %(item)s from your cart. You currently have %(count)s in your "
|
||||
"cart."
|
||||
msgstr ""
|
||||
"Вилучити один %(item)s з Вашого кошика. У Вашому кошику залишилось %(count)s."
|
||||
"Видалити один %(item)s з вашого кошика. У Вашому кошику залишилось %(count)s."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:301
|
||||
msgid "We're trying to reserve another one for you!"
|
||||
@@ -31505,19 +31510,23 @@ msgid "Change summary"
|
||||
msgstr "Змінити підсумок"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:19
|
||||
#, python-format
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Change position #%(positionid)s from \"%(old_item)s – %(old_variation)s "
|
||||
#| "\" to \"%(new_item)s – %(new_variation)s\""
|
||||
msgid ""
|
||||
"Change position #%(positionid)s from \"%(old_item)s – %(old_variation)s\" to "
|
||||
"\"%(new_item)s – %(new_variation)s\""
|
||||
msgstr ""
|
||||
"Змінити позицію #%(positionid)s з \"%(old_item)s – %(old_variation)s \" на \""
|
||||
"%(new_item)s – %(new_variation)s\""
|
||||
"Змінити розміщення #%(positionid)s від \"%(old_item)s – %(old_variation)s \" "
|
||||
"до \"%(new_item)s – %(new_variation)s\""
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:23
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Change position #%(positionid)s from \"%(old_item)s\" to \"%(new_item)s\""
|
||||
msgstr "Змінити позицію #%(positionid)s з \"%(old_item)s\" на \"%(new_item)s\""
|
||||
msgstr ""
|
||||
"Змінити розміщення #%(positionid)s від \"%(old_item)s\" до \"%(new_item)s\""
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:30
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:56
|
||||
@@ -31531,32 +31540,32 @@ msgstr "Додатково до позиції #%(posid)s"
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:40
|
||||
#, python-format
|
||||
msgid "Change date of position #%(positionid)s from \"%(old)s\" to \"%(new)s\""
|
||||
msgstr "Змінити дати позиції #%(positionid)s з \"%(old)s\" на \"%(new)s\""
|
||||
msgstr "Змінити дати розміщення #%(positionid)s від \"%(old)s\" до \"%(new)s\""
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:50
|
||||
#, python-format
|
||||
msgid "Change price of position #%(positionid)s from %(old)s to %(new)s"
|
||||
msgstr "Змінити ціну позиції #%(positionid)s з %(old)s на %(new)s"
|
||||
msgstr "Змінити ціну розміщення #%(positionid)s від %(old)s до %(new)s"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:70
|
||||
#, python-format
|
||||
msgid "Add position (%(item)s – %(variation)s)"
|
||||
msgstr "Додати позицію (%(item)s – %(variation)s)"
|
||||
msgstr "Додати розміщення (%(item)s – %(variation)s)"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:74
|
||||
#, python-format
|
||||
msgid "Add position (%(item)s)"
|
||||
msgstr "Додати позицію (%(item)s)"
|
||||
msgstr "Додати розміщення (%(item)s)"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:95
|
||||
#, python-format
|
||||
msgid "Remove position #%(positionid)s (%(item)s – %(variation)s)"
|
||||
msgstr "Вилучити позицію #%(positionid)s (%(item)s – %(variation)s)"
|
||||
msgstr "Видалити розміщення #%(positionid)s (%(item)s – %(variation)s)"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:99
|
||||
#, python-format
|
||||
msgid "Remove position #%(positionid)s (%(item)s)"
|
||||
msgstr "Вилучити позицію #%(positionid)s (%(item)s)"
|
||||
msgstr "Видалити розміщення #%(positionid)s (%(item)s)"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:121
|
||||
msgid "Total price change"
|
||||
@@ -32653,7 +32662,7 @@ msgstr ""
|
||||
#: pretix/presale/templates/pretixpresale/event/waitinglist_remove.html:16
|
||||
msgctxt "waitinglist"
|
||||
msgid "Yes, remove my ticket"
|
||||
msgstr "Так, вилучити мій квиток"
|
||||
msgstr "Так, видалити мій квиток"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/fragment_calendar.html:6
|
||||
msgid "Calendar"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-23 12:41+0000\n"
|
||||
"PO-Revision-Date: 2024-05-01 01:00+0000\n"
|
||||
"PO-Revision-Date: 2024-04-30 01:00+0000\n"
|
||||
"Last-Translator: Serhii Horichenko <m@sgg.im>\n"
|
||||
"Language-Team: Ukrainian <https://translate.pretix.eu/projects/pretix/"
|
||||
"pretix-js/uk/>\n"
|
||||
@@ -537,7 +537,7 @@ msgstr "Додайте умову"
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:191
|
||||
#, fuzzy
|
||||
msgid "minutes"
|
||||
msgstr "хвилини"
|
||||
msgstr "хвилин"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:192
|
||||
msgid "Duplicate"
|
||||
@@ -688,7 +688,7 @@ msgid_plural "({num} more dates)"
|
||||
msgstr[0] "(додати дату)"
|
||||
msgstr[1] "(додати {num} дати)"
|
||||
msgstr[2] "(додати {num} дат)"
|
||||
msgstr[3] "(додати {num} дати)"
|
||||
msgstr[3] "(додати {num} дат)"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:43
|
||||
msgid ""
|
||||
@@ -703,25 +703,22 @@ msgid "Cart expired"
|
||||
msgstr "Термін дії кошика закінчився"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:50
|
||||
#, fuzzy
|
||||
msgid "The items in your cart are reserved for you for one minute."
|
||||
msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] "Товари у Вашому кошику зарезервовані для Вас на одну хвилину."
|
||||
msgstr[1] "Товари у Вашому кошику зарезервовані для Вас на {num} хвилини."
|
||||
msgstr[2] "Товари у Вашому кошику зарезервовані для Вас на {num} хвилин."
|
||||
msgstr[3] "Товари у Вашому кошику зарезервовані для Вас на {num} хвилин."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:203
|
||||
msgid "The organizer keeps %(currency)s %(amount)s"
|
||||
msgstr "Організатор утримує %(amount)s %(currency)s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:211
|
||||
msgid "You get %(currency)s %(amount)s back"
|
||||
msgstr "Ви отримаєте в поверненні %(amount)s %(currency)s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:227
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr "Введіть суму, яку може залишити організатор."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:444
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
@@ -741,45 +738,52 @@ msgid "Your local time:"
|
||||
msgstr "Ваш місцевий час:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/walletdetection.js:39
|
||||
#, fuzzy
|
||||
#| msgid "Apple Pay"
|
||||
msgid "Google Pay"
|
||||
msgstr "Google Pay"
|
||||
msgstr "Apple Pay"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:16
|
||||
msgctxt "widget"
|
||||
msgid "Quantity"
|
||||
msgstr "Кількість"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:17
|
||||
msgctxt "widget"
|
||||
msgid "Decrease quantity"
|
||||
msgstr "Зменшити кількість"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:18
|
||||
msgctxt "widget"
|
||||
msgid "Increase quantity"
|
||||
msgstr "Збільшити кількість"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:19
|
||||
msgctxt "widget"
|
||||
msgid "Price"
|
||||
msgstr "Ціна"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:20
|
||||
#, fuzzy
|
||||
#| msgid "Selected only"
|
||||
msgctxt "widget"
|
||||
msgid "Select"
|
||||
msgstr "Виберіть"
|
||||
msgstr "Тільки вибрані"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||
#, javascript-format
|
||||
#, fuzzy, javascript-format
|
||||
#| msgid "Selected only"
|
||||
msgctxt "widget"
|
||||
msgid "Select %s"
|
||||
msgstr "Виберіть %s"
|
||||
msgstr "Тільки вибрані"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
||||
#, javascript-format
|
||||
#, fuzzy, javascript-format
|
||||
#| msgctxt "widget"
|
||||
#| msgid "See variations"
|
||||
msgctxt "widget"
|
||||
msgid "Select variant %s"
|
||||
msgstr "Виберіть варіант %s"
|
||||
msgstr "Переглянути варіанти"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:23
|
||||
msgctxt "widget"
|
||||
@@ -843,19 +847,25 @@ msgid "Only available with a voucher"
|
||||
msgstr "Доступно лише з ваучером"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "currently available: %s"
|
||||
msgctxt "widget"
|
||||
msgid "Not yet available"
|
||||
msgstr "Зараз не доступно"
|
||||
msgstr "доступно зараз: %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "Not available anymore"
|
||||
msgstr "Більше не доступно"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "currently available: %s"
|
||||
msgctxt "widget"
|
||||
msgid "Currently not available"
|
||||
msgstr "Зараз недоступно"
|
||||
msgstr "доступно зараз: %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
#, javascript-format
|
||||
@@ -879,13 +889,11 @@ msgid ""
|
||||
"There are currently a lot of users in this ticket shop. Please open the shop "
|
||||
"in a new tab to continue."
|
||||
msgstr ""
|
||||
"Зараз у цій касі багато користувачів. Щоб продовжити, відкрийте вікно "
|
||||
"квитків у новій вкладці."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Open ticket shop"
|
||||
msgstr "Відкрити касу"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
@@ -947,14 +955,20 @@ msgid "Continue"
|
||||
msgstr "Продовжити"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "See variations"
|
||||
msgctxt "widget"
|
||||
msgid "Show variants"
|
||||
msgstr "Показати варіанти"
|
||||
msgstr "Переглянути варіанти"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "See variations"
|
||||
msgctxt "widget"
|
||||
msgid "Hide variants"
|
||||
msgstr "Сховати варіанти"
|
||||
msgstr "Переглянути варіанти"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgctxt "widget"
|
||||
@@ -1003,9 +1017,6 @@ msgid ""
|
||||
"add yourself to the waiting list. We will then notify if seats are available "
|
||||
"again."
|
||||
msgstr ""
|
||||
"Деякі або всі категорії квитків зараз розпродані. Якщо хочете, можете "
|
||||
"додатися до списку очікування. Тоді ми Вам повідомимо, коли місця знову "
|
||||
"будуть вільні."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgctxt "widget"
|
||||
|
||||
@@ -27,7 +27,7 @@ from django.urls import NoReverseMatch
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.html import conditional_escape
|
||||
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri, mainreverse
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@@ -45,11 +45,13 @@ class EventURLNode(URLNode):
|
||||
for k, v in self.kwargs.items()
|
||||
}
|
||||
view_name = self.view_name.resolve(context)
|
||||
event = self.event.resolve(context)
|
||||
event = self.event.resolve(context) if self.event is not False else False
|
||||
url = ''
|
||||
try:
|
||||
if self.absolute:
|
||||
url = build_absolute_uri(event, view_name, kwargs=kwargs)
|
||||
elif self.event is False:
|
||||
url = mainreverse(view_name, kwargs)
|
||||
else:
|
||||
url = eventreverse(event, view_name, kwargs=kwargs)
|
||||
except NoReverseMatch:
|
||||
@@ -65,21 +67,34 @@ class EventURLNode(URLNode):
|
||||
return url
|
||||
|
||||
|
||||
@register.tag
|
||||
def eventurl(parser, token, absolute=False):
|
||||
def multidomainurl(parser, token, has_event, absolute):
|
||||
"""
|
||||
Similar to {% url %} in the same way that eventreverse() is similar to reverse().
|
||||
Similar to {% url %}, but multidomain-aware. Used by eventurl, abseventurl and absmainurl.
|
||||
|
||||
Takes an event or organizer object, an url name and optional keyword arguments
|
||||
If has_event=True, takes an event or organizer object as first template tag parameter.
|
||||
Always takes an url name and optional keyword arguments after that.
|
||||
|
||||
Returns an absolute URL in the following cases:
|
||||
- absolute=True
|
||||
- has_event=True and the event has a custom domain
|
||||
Returns a relative URL otherwise.
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
if len(bits) < 3:
|
||||
raise TemplateSyntaxError("'%s' takes at least two arguments, an event and the name of a url()." % bits[0])
|
||||
viewname = parser.compile_filter(bits[2])
|
||||
event = parser.compile_filter(bits[1])
|
||||
tagname = bits[0]
|
||||
if has_event:
|
||||
if len(bits) < 3:
|
||||
raise TemplateSyntaxError("'%s' takes at least two arguments, an event and the name of a url()." % tagname)
|
||||
viewname = parser.compile_filter(bits[2])
|
||||
event = parser.compile_filter(bits[1])
|
||||
bits = bits[3:]
|
||||
else:
|
||||
if len(bits) < 2:
|
||||
raise TemplateSyntaxError("'%s' takes at least one arguments, the name of a url()." % tagname)
|
||||
viewname = parser.compile_filter(bits[1])
|
||||
event = False
|
||||
bits = bits[2:]
|
||||
kwargs = {}
|
||||
asvar = None
|
||||
bits = bits[3:]
|
||||
if len(bits) >= 2 and bits[-2] == 'as':
|
||||
asvar = bits[-1]
|
||||
bits = bits[:-2]
|
||||
@@ -88,16 +103,26 @@ def eventurl(parser, token, absolute=False):
|
||||
for bit in bits:
|
||||
match = kwarg_re.match(bit)
|
||||
if not match:
|
||||
raise TemplateSyntaxError("Malformed arguments to eventurl tag")
|
||||
raise TemplateSyntaxError("Malformed arguments to %s tag" % tagname)
|
||||
name, value = match.groups()
|
||||
if name:
|
||||
kwargs[name] = parser.compile_filter(value)
|
||||
else:
|
||||
raise TemplateSyntaxError('Event urls only have keyword arguments.')
|
||||
raise TemplateSyntaxError('Multidomain urls only have keyword arguments.')
|
||||
|
||||
return EventURLNode(event, viewname, kwargs, asvar, absolute)
|
||||
|
||||
|
||||
@register.tag
|
||||
def eventurl(parser, token):
|
||||
"""
|
||||
Similar to {% url %} in the same way that eventreverse() is similar to reverse().
|
||||
|
||||
Takes an event or organizer object, an url name and optional keyword arguments
|
||||
"""
|
||||
return multidomainurl(parser, token, has_event=True, absolute=False)
|
||||
|
||||
|
||||
@register.tag
|
||||
def abseventurl(parser, token):
|
||||
"""
|
||||
@@ -105,4 +130,12 @@ def abseventurl(parser, token):
|
||||
|
||||
Returns an absolute URL.
|
||||
"""
|
||||
return eventurl(parser, token, absolute=True)
|
||||
return multidomainurl(parser, token, has_event=True, absolute=True)
|
||||
|
||||
|
||||
@register.tag
|
||||
def absmainurl(parser, token):
|
||||
"""
|
||||
Like {% url %}, but always returns an absolute URL on the main domain.
|
||||
"""
|
||||
return multidomainurl(parser, token, has_event=False, absolute=True)
|
||||
|
||||
@@ -180,7 +180,7 @@ def build_absolute_uri(obj, urlname, kwargs=None):
|
||||
"""
|
||||
Works similar to ``eventreverse`` but always returns an absolute URL.
|
||||
|
||||
:param obj: An ``Event`` or ``Organizer`` object
|
||||
:param obj: An ``Event`` or ``Organizer`` object, or ``False`` to generate main domain URLs
|
||||
:param name: The name of the URL route
|
||||
:type name: str
|
||||
:param kwargs: A dictionary of additional keyword arguments that should be used. You do not
|
||||
@@ -188,7 +188,10 @@ def build_absolute_uri(obj, urlname, kwargs=None):
|
||||
needed.
|
||||
:returns: An absolute URL (including scheme and host) as a string
|
||||
"""
|
||||
reversedurl = eventreverse(obj, urlname, kwargs)
|
||||
if obj is False:
|
||||
reversedurl = mainreverse(urlname, kwargs)
|
||||
else:
|
||||
reversedurl = eventreverse(obj, urlname, kwargs)
|
||||
if '://' in reversedurl:
|
||||
return reversedurl
|
||||
return urljoin(settings.SITE_URL, reversedurl)
|
||||
|
||||
@@ -189,14 +189,6 @@ OPTIONS = OrderedDict([
|
||||
'offsets': [46 * mm, 46 * mm],
|
||||
'pagesize': pagesizes.A4,
|
||||
}),
|
||||
('herma_88.9x33.87', {
|
||||
'name': 'HERMA 88,9 x 33,87 mm (4515)',
|
||||
'cols': 2,
|
||||
'rows': 8,
|
||||
'margins': [13.03 * mm, 12.29 * mm, 13.03 * mm, 12.29 * mm],
|
||||
'offsets': [96.52 * mm, 33.87 * mm],
|
||||
'pagesize': pagesizes.A4,
|
||||
}),
|
||||
('lyreco_70x36', {
|
||||
'name': 'Lyreco 70 x 36 mm (143.344)',
|
||||
'cols': 3,
|
||||
|
||||
@@ -234,9 +234,4 @@ TEMPLATES = {
|
||||
"pagesize": (40 * mm, 40 * mm),
|
||||
"layout": _simple_template(40 * mm, 40 * mm),
|
||||
},
|
||||
"88.9x33.87": {
|
||||
"label": format_lazy(_("{width} x {height} mm label"), width=88.9, height=33.87),
|
||||
"pagesize": (88.9 * mm, 33.87 * mm),
|
||||
"layout": _simple_template(88.9 * mm, 33.87 * mm),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ from pretix.base.signals import validate_cart_addons
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
from pretix.base.templatetags.phone_format import phone_format
|
||||
from pretix.base.templatetags.rich_text import rich_text_snippet
|
||||
from pretix.base.timemachine import time_machine_now
|
||||
from pretix.base.views.tasks import AsyncAction
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers.http import redirect_to_url
|
||||
@@ -706,7 +707,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
|
||||
return self.do(self.request.event.id, data, get_or_create_cart_id(self.request),
|
||||
invoice_address=self.invoice_address.pk, locale=get_language(),
|
||||
sales_channel=request.sales_channel.identifier)
|
||||
sales_channel=request.sales_channel.identifier, override_now_dt=time_machine_now(default=None))
|
||||
|
||||
|
||||
class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
@@ -1548,6 +1549,7 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
sales_channel=request.sales_channel.identifier,
|
||||
shown_total=self.cart_session.get('shown_total'),
|
||||
customer=self.cart_session.get('customer'),
|
||||
override_now_dt=time_machine_now(default=None),
|
||||
)
|
||||
|
||||
def get_success_message(self, value):
|
||||
|
||||
@@ -37,6 +37,7 @@ from django.urls import resolve
|
||||
from django_scopes import scope
|
||||
|
||||
from pretix.base.channels import WebshopSalesChannel
|
||||
from pretix.base.timemachine import time_machine_now_assigned_from_request
|
||||
from pretix.presale.signals import process_response
|
||||
|
||||
from .utils import _detect_event
|
||||
@@ -68,7 +69,8 @@ class EventMiddleware:
|
||||
if redirect:
|
||||
return redirect
|
||||
|
||||
with scope(organizer=getattr(request, 'organizer', None)):
|
||||
with scope(organizer=getattr(request, 'organizer', None)), \
|
||||
time_machine_now_assigned_from_request(request):
|
||||
response = self.get_response(request)
|
||||
|
||||
if hasattr(request, '_namespace') and request._namespace == 'presale' and hasattr(request, 'event'):
|
||||
|
||||
@@ -111,9 +111,40 @@
|
||||
{% if request.event.testmode %}
|
||||
{% if request.sales_channel.testmode_supported %}
|
||||
<div class="alert alert-warning">
|
||||
<p><strong><span class="sr-only">{% trans "Warning" context "alert-messages" %}:</span>
|
||||
{% trans "This ticket shop is currently in test mode. Please do not perform any real purchases as your order might be deleted without notice." %}
|
||||
<p><strong>
|
||||
<span class="sr-only">{% trans "Warning" context "alert-messages" %}:</span>
|
||||
{% trans "This ticket shop is currently in test mode." %}
|
||||
</strong></p>
|
||||
<p>
|
||||
{% trans "Please do not perform any real purchases as your order might be deleted without notice." %}
|
||||
</p>
|
||||
{% if request.now_dt_is_fake %}
|
||||
<p>
|
||||
{% blocktrans trimmed with datetime=request.now_dt|date:"SHORT_DATETIME_FORMAT" %}
|
||||
You are currently using the time machine. The ticket shop is rendered as if it were {{ datetime }}.
|
||||
{% endblocktrans %}
|
||||
<a href="{% eventurl event "presale:event.timemachine" %}"><span class="fa fa-clock-o" aria-hidden="true"></span>{% trans "Change" %}</a>
|
||||
</p>
|
||||
{% elif request.user.is_authenticated or request.event_access_user.is_authenticated %}
|
||||
<p>
|
||||
{% eventurl event "presale:event.timemachine" as time_machine_link %}
|
||||
{% blocktrans trimmed with time_machine_link=time_machine_link %}
|
||||
To view your shop at different points in time, you can enable
|
||||
<a href="{{ time_machine_link }}"><span class="fa fa-clock-o" aria-hidden="true"></span>time machine</a>.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% elif request.event_domain or request.organizer_domain %}
|
||||
<p>
|
||||
{% absmainurl "control:event.transfer_session" event=event.slug organizer=event.organizer.slug as transfer_session_link %}
|
||||
{% eventurl event "presale:event.timemachine" as time_machine_link %}
|
||||
{% with time_machine_link_encoded=time_machine_link|urlencode %}
|
||||
{% blocktrans trimmed with time_machine_link=transfer_session_link|add:"?next="|add:time_machine_link_encoded %}
|
||||
To view your shop at different points in time, you can enable
|
||||
<a href="{{ time_machine_link }}"><span class="fa fa-clock-o" aria-hidden="true"></span>time machine</a>.
|
||||
{% endblocktrans %}
|
||||
{% endwith %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-danger">
|
||||
@@ -122,6 +153,8 @@
|
||||
</strong></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
@@ -152,9 +185,13 @@
|
||||
{% if request.event.testmode %}
|
||||
{% if request.sales_channel.testmode_supported %}
|
||||
<div class="alert alert-testmode alert-warning">
|
||||
<p><strong><span class="sr-only">{% trans "Warning" context "alert-messages" %}:</span>
|
||||
{% trans "This ticket shop is currently in test mode. Please do not perform any real purchases as your order might be deleted without notice." %}
|
||||
<p><strong>
|
||||
<span class="sr-only">{% trans "Warning" context "alert-messages" %}:</span>
|
||||
{% trans "This ticket shop is currently in test mode." %}
|
||||
</strong></p>
|
||||
<p>
|
||||
{% trans "Please do not perform any real purchases as your order might be deleted without notice." %}
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-testmode alert-danger">
|
||||
|
||||
@@ -5,17 +5,17 @@
|
||||
{% load thumb %}
|
||||
{% load eventsignal %}
|
||||
{% load rich_text %}
|
||||
{% for tup in items_by_category %}{% with category=tup.0 items=tup.1 id_prefix=tup.2 %}
|
||||
<section {% if category %}aria-labelledby="{{ id_prefix }}category-{{ category.id }}"{% else %}aria-label="{% trans "Uncategorized items" %}"{% endif %}{% if category.description %} aria-describedby="{{ id_prefix }}category-info-{{ category.id }}"{% endif %}>
|
||||
{% if category %}
|
||||
<h3 id="{{ id_prefix }}category-{{ category.id }}">{{ category.name }}</h3>
|
||||
{% if category.description %}
|
||||
<div id="{{ id_prefix }}category-info-{{ category.id }}">{{ category.description|localize|rich_text }}</div>
|
||||
{% for tup in items_by_category %}
|
||||
<section {% if tup.0 %}aria-labelledby="category-{{ tup.0.id }}"{% else %}aria-label="{% trans "Uncategorized items" %}"{% endif %}{% if tup.0.description %} aria-describedby="category-info-{{ tup.0.id }}"{% endif %}>
|
||||
{% if tup.0 %}
|
||||
<h3 id="category-{{ tup.0.id }}">{{ tup.0.name }}</h3>
|
||||
{% if tup.0.description %}
|
||||
<div id="category-info-{{ tup.0.id }}">{{ tup.0.description|localize|rich_text }}</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% for item in items %}
|
||||
{% for item in tup.1 %}
|
||||
{% if item.has_variations %}
|
||||
<article aria-labelledby="{{ id_prefix }}item-{{ item.pk }}-legend"{% if item.description %} aria-describedby="{{ id_prefix }}item-{{ item.pk }}-description"{% endif %} class="item-with-variations{% if event.settings.show_variations_expanded %} details-open{% endif %}" id="{{ id_prefix }}item-{{ item.pk }}">
|
||||
<article aria-labelledby="item-{{ item.pk }}-legend"{% if item.description %} aria-describedby="item-{{ item.pk }}-description"{% endif %} class="item-with-variations{% if event.settings.show_variations_expanded %} details-open{% endif %}" id="item-{{ item.pk }}">
|
||||
<div class="row product-row headline">
|
||||
<div class="col-md-8 col-sm-6 col-xs-12">
|
||||
{% if item.picture %}
|
||||
@@ -29,9 +29,9 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
<div class="product-description {% if item.picture %}with-picture{% endif %}">
|
||||
<h4 id="{{ id_prefix }}item-{{ item.pk }}-legend">{{ item.name }}</h4>
|
||||
<h4 id="item-{{ item.pk }}-legend">{{ item.name }}</h4>
|
||||
{% if item.description %}
|
||||
<div id="{{ id_prefix }}item-{{ item.pk }}-description" class="product-description">
|
||||
<div id="item-{{ item.pk }}-description" class="product-description">
|
||||
{{ item.description|localize|rich_text }}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -98,14 +98,14 @@
|
||||
</div>
|
||||
<div class="variations {% if not event.settings.show_variations_expanded %}variations-collapsed{% endif %}">
|
||||
{% for var in item.available_variations %}
|
||||
<article aria-labelledby="{{ id_prefix }}item-{{ item.pk }}-{{ var.pk }}-legend"{% if var.description %} aria-describedby="{{ id_prefix }}item-{{ item.pk }}-{{ var.pk }}-description"{% endif %} class="row product-row variation" id="{{ id_prefix }}item-{{ item.pk }}-{{ var.pk }}"
|
||||
<article aria-labelledby="item-{{ item.pk }}-{{ var.pk }}-legend"{% if var.description %} aria-describedby="item-{{ item.pk }}-{{ var.pk }}-description"{% endif %} class="row product-row variation" id="item-{{ item.pk }}-{{ var.pk }}"
|
||||
{% if not item.free_price %}
|
||||
data-price="{% if event.settings.display_net_prices %}{{ var.display_price.net|unlocalize }}{% else %}{{ var.display_price.gross|unlocalize }}{% endif %}"
|
||||
{% endif %}>
|
||||
<div class="col-md-8 col-sm-6 col-xs-12">
|
||||
<h5 id="{{ id_prefix }}item-{{ item.pk }}-{{ var.pk }}-legend">{{ var }}</h5>
|
||||
<h5 id="item-{{ item.pk }}-{{ var.pk }}-legend">{{ var }}</h5>
|
||||
{% if var.description %}
|
||||
<div id="{{ id_prefix }}item-{{ item.pk }}-{{ var.pk }}-description" class="variation-description">
|
||||
<div id="item-{{ item.pk }}-{{ var.pk }}-description" class="variation-description">
|
||||
{{ var.description|localize|rich_text }}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -136,11 +136,11 @@
|
||||
<div class="input-group input-group-price">
|
||||
<span class="input-group-addon">{{ event.currency }}</span>
|
||||
<input type="number" class="form-control input-item-price"
|
||||
id="{{ id_prefix }}price-variation-{{ item.pk }}-{{ var.pk }}"
|
||||
id="price-variation-{{ item.pk }}-{{ var.pk }}"
|
||||
{% if not ev.presale_is_running %}disabled{% endif %}
|
||||
placeholder="0"
|
||||
min="{% if event.settings.display_net_prices %}{{ var.display_price.net|money_numberfield:event.currency }}{% else %}{{ var.display_price.gross|money_numberfield:event.currency }}{% endif %}"
|
||||
name="{{ id_prefix }}price_{{ item.id }}_{{ var.id }}"
|
||||
name="price_{{ item.id }}_{{ var.id }}"
|
||||
{% if var.suggested_price.gross != var.display_price.gross %}
|
||||
{% if event.settings.display_net_prices %}
|
||||
title="{% blocktrans trimmed with item=var.value price=var.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
|
||||
@@ -197,16 +197,16 @@
|
||||
data-checked-onchange="price-variation-{{ item.pk }}-{{ var.pk }}"
|
||||
{% endif %}
|
||||
{% if not ev.presale_is_running %}disabled{% endif %}
|
||||
id="{{ id_prefix }}variation_{{ item.id }}_{{ var.id }}"
|
||||
name="{{ id_prefix }}variation_{{ item.id }}_{{ var.id }}"
|
||||
id="variation_{{ item.id }}_{{ var.id }}"
|
||||
name="variation_{{ item.id }}_{{ var.id }}"
|
||||
aria-label="{% blocktrans with item=item.name var=var %}Add {{ item }}, {{ var }} to cart{% endblocktrans %}"
|
||||
{% if var.description %} aria-describedby="{{ id_prefix }}item-{{ item.pk }}-{{ var.pk }}-description"{% endif %}>
|
||||
{% if var.description %} aria-describedby="item-{{ item.pk }}-{{ var.pk }}-description"{% endif %}>
|
||||
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
|
||||
{% trans "Select" context "checkbox" %}
|
||||
</label>
|
||||
{% else %}
|
||||
<div class="input-item-count-group">
|
||||
<button type="button" data-step="-1" data-controls="{{ id_prefix }}variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}"
|
||||
<button type="button" data-step="-1" data-controls="variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}"
|
||||
{% if not ev.presale_is_running %}disabled{% endif %}>-</button>
|
||||
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
|
||||
{% if not ev.presale_is_running %}disabled{% endif %}
|
||||
@@ -214,10 +214,10 @@
|
||||
data-checked-onchange="price-variation-{{ item.pk }}-{{ var.pk }}"
|
||||
{% endif %}
|
||||
max="{{ var.order_max }}"
|
||||
id="{{ id_prefix }}variation_{{ item.id }}_{{ var.id }}"
|
||||
name="{{ id_prefix }}variation_{{ item.id }}_{{ var.id }}"
|
||||
id="variation_{{ item.id }}_{{ var.id }}"
|
||||
name="variation_{{ item.id }}_{{ var.id }}"
|
||||
aria-label="{% blocktrans with item=item.name var=var.name %}Quantity of {{ item }}, {{ var }} to order{% endblocktrans %}">
|
||||
<button type="button" data-step="1" data-controls="{{ id_prefix }}variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}"
|
||||
<button type="button" data-step="1" data-controls="variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}"
|
||||
{% if not ev.presale_is_running %}disabled{% endif %}>+</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -231,7 +231,7 @@
|
||||
</div>
|
||||
</article>
|
||||
{% else %}
|
||||
<article aria-labelledby="{{ id_prefix }}item-{{ item.pk }}-legend"{% if item.description %} aria-describedby="{{ id_prefix }}item-{{ item.pk }}-description"{% endif %} class="row product-row simple" id="{{ id_prefix }}item-{{ item.pk }}"
|
||||
<article aria-labelledby="item-{{ item.pk }}-legend"{% if item.description %} aria-describedby="item-{{ item.pk }}-description"{% endif %} class="row product-row simple" id="item-{{ item.pk }}"
|
||||
{% if not item.free_price %}
|
||||
data-price="{% if event.settings.display_net_prices %}{{ item.display_price.net|unlocalize }}{% else %}{{ item.display_price.gross|unlocalize }}{% endif %}"
|
||||
{% endif %}>
|
||||
@@ -247,9 +247,9 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
<div class="product-description {% if item.picture %}with-picture{% endif %}">
|
||||
<h4 id="{{ id_prefix }}item-{{ item.pk }}-legend">{{ item.name }}</h4>
|
||||
<h4 id="item-{{ item.pk }}-legend">{{ item.name }}</h4>
|
||||
{% if item.description %}
|
||||
<div id="{{ id_prefix }}item-{{ item.pk }}-description" class="product-description">
|
||||
<div id="item-{{ item.pk }}-description" class="product-description">
|
||||
{{ item.description|localize|rich_text }}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -290,10 +290,10 @@
|
||||
<label class="sr-only" for="price-item-{{ item.pk }}">{% blocktrans trimmed with item=item.name currency=event.currency %}Set price in {{ currency }} for {{ item }}{% endblocktrans %}</label>
|
||||
<span class="input-group-addon" aria-hidden="true">{{ event.currency }}</span>
|
||||
<input type="number" class="form-control input-item-price" placeholder="0"
|
||||
id="{{ id_prefix }}price-item-{{ item.pk }}"
|
||||
id="price-item-{{ item.pk }}"
|
||||
{% if not ev.presale_is_running %}disabled{% endif %}
|
||||
min="{% if event.settings.display_net_prices %}{{ item.display_price.net|money_numberfield:event.currency }}{% else %}{{ item.display_price.gross|money_numberfield:event.currency }}{% endif %}"
|
||||
name="{{ id_prefix }}price_{{ item.id }}"
|
||||
name="price_{{ item.id }}"
|
||||
{% if item.suggested_price.gross != item.display_price.gross %}
|
||||
{% if event.settings.display_net_prices %}
|
||||
title="{% blocktrans trimmed with item=item.name price=item.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
|
||||
@@ -349,15 +349,15 @@
|
||||
data-checked-onchange="price-item-{{ item.pk }}"
|
||||
{% endif %}
|
||||
{% if not ev.presale_is_running %}disabled{% endif %}
|
||||
name="{{ id_prefix }}item_{{ item.id }}" id="{{ id_prefix }}item_{{ item.id }}"
|
||||
name="item_{{ item.id }}" id="item_{{ item.id }}"
|
||||
aria-label="{% blocktrans with item=item.name %}Add {{ item }} to cart{% endblocktrans %}"
|
||||
{% if item.description %} aria-describedby="{{ id_prefix }}item-{{ item.id }}-description"{% endif %}>
|
||||
{% if item.description %} aria-describedby="item-{{ item.id }}-description"{% endif %}>
|
||||
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
|
||||
{% trans "Select" context "checkbox" %}
|
||||
</label>
|
||||
{% else %}
|
||||
<div class="input-item-count-group">
|
||||
<button type="button" data-step="-1" data-controls="{{ id_prefix }}item_{{ item.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}"
|
||||
<button type="button" data-step="-1" data-controls="item_{{ item.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}"
|
||||
{% if not ev.presale_is_running %}disabled{% endif %}>-</button>
|
||||
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
|
||||
{% if not ev.presale_is_running %}disabled{% endif %}
|
||||
@@ -366,11 +366,11 @@
|
||||
data-checked-onchange="price-item-{{ item.pk }}"
|
||||
{% endif %}
|
||||
max="{{ item.order_max }}"
|
||||
name="{{ id_prefix }}item_{{ item.id }}"
|
||||
id="{{ id_prefix }}item_{{ item.id }}"
|
||||
name="item_{{ item.id }}"
|
||||
id="item_{{ item.id }}"
|
||||
aria-label="{% blocktrans with item=item.name %}Quantity of {{ item }} to order{% endblocktrans %}"
|
||||
{% if item.description %} aria-describedby="{{ id_prefix }}item-{{ item.id }}-description"{% endif %}>
|
||||
<button type="button" data-step="1" data-controls="{{ id_prefix }}item_{{ item.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}"
|
||||
{% if item.description %} aria-describedby="item-{{ item.id }}-description"{% endif %}>
|
||||
<button type="button" data-step="1" data-controls="item_{{ item.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}"
|
||||
{% if not ev.presale_is_running %}disabled{% endif %}>+</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -383,4 +383,4 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endwith %}{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{% extends "pretixpresale/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load l10n %}
|
||||
{% load money %}
|
||||
{% load eventurl %}
|
||||
{% load eventsignal %}
|
||||
{% load thumb %}
|
||||
{% load rich_text %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Time machine" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="panel {% if request.session.timemachine_now_dt %}panel-success{% else %}panel-default{% endif %}">
|
||||
<div class="panel-heading">
|
||||
{% trans "Time machine" %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form action="" method="post" class="">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors timemachine_form "all" %}
|
||||
|
||||
<p>{% trans "Test your shop as if it were a different date and time." %}</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{% bootstrap_field timemachine_form.now_dt layout="inline" show_label=False %}
|
||||
</div>
|
||||
<div class="col-md-6 text-right">
|
||||
<button type="submit" class="btn btn-primary btn-lg btn-save">
|
||||
{% if request.session.timemachine_now_dt %}{% trans "Change" %}{% else %}{% trans "Enable time machine" %}{% endif %}
|
||||
</button>
|
||||
{% if request.session.timemachine_now_dt %}
|
||||
<button form="disable_form" type="submit" class="btn btn-default btn-lg btn-save">
|
||||
{% trans "Disable" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<form action="" method="post" class="" id="disable_form">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="timemachine_disable" value="true">
|
||||
</form>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -173,6 +173,8 @@ event_patterns = [
|
||||
re_path(r'^(?P<subevent>\d+)/widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(),
|
||||
name='event.widget.productlist'),
|
||||
|
||||
re_path(r'timemachine/$', pretix.presale.views.event.EventTimeMachine.as_view(), name='event.timemachine'),
|
||||
|
||||
# Account management is done on org level, but we at least need a logout
|
||||
re_path(r'^account/logout$', pretix.presale.views.customer.LogoutView.as_view(), name='organizer.customer.logout'),
|
||||
]
|
||||
|
||||
@@ -38,6 +38,9 @@ from importlib import import_module
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import (
|
||||
BACKEND_SESSION_KEY, SESSION_KEY, get_user_model, load_backend,
|
||||
)
|
||||
from django.db.models import Q
|
||||
from django.http import Http404, HttpResponseForbidden
|
||||
from django.middleware.csrf import rotate_token
|
||||
@@ -52,6 +55,7 @@ from django_scopes import scope
|
||||
|
||||
from pretix.base.middleware import LocaleMiddleware
|
||||
from pretix.base.models import Customer, Event, Organizer
|
||||
from pretix.base.timemachine import time_machine_now_assigned_from_request
|
||||
from pretix.helpers.http import redirect_to_url
|
||||
from pretix.multidomain.urlreverse import (
|
||||
get_event_domain, get_organizer_domain,
|
||||
@@ -218,6 +222,17 @@ def customer_logout(request):
|
||||
request._cached_customer = None
|
||||
|
||||
|
||||
def _get_user_from_session_data(sessiondata):
|
||||
if SESSION_KEY not in sessiondata:
|
||||
return None
|
||||
user_id = get_user_model()._meta.pk.to_python(sessiondata[SESSION_KEY])
|
||||
backend_path = sessiondata[BACKEND_SESSION_KEY]
|
||||
if backend_path in settings.AUTHENTICATION_BACKENDS:
|
||||
backend = load_backend(backend_path)
|
||||
user = backend.get_user(user_id)
|
||||
return user
|
||||
|
||||
|
||||
@scope(organizer=None)
|
||||
def _detect_event(request, require_live=True, require_plugin=None):
|
||||
|
||||
@@ -303,14 +318,13 @@ def _detect_event(request, require_live=True, require_plugin=None):
|
||||
# Restrict locales to the ones available for this event
|
||||
LocaleMiddleware(NotImplementedError).process_request(request)
|
||||
|
||||
if require_live and not request.event.live:
|
||||
if require_live and (request.event.testmode or not request.event.live):
|
||||
can_access = (
|
||||
url.url_name == 'event.auth'
|
||||
or (
|
||||
request.user.is_authenticated
|
||||
and request.user.has_event_permission(request.organizer, request.event, request=request)
|
||||
)
|
||||
|
||||
)
|
||||
if not can_access and 'pretix_event_access_{}'.format(request.event.pk) in request.session:
|
||||
sparent = SessionStore(request.session.get('pretix_event_access_{}'.format(request.event.pk)))
|
||||
@@ -319,9 +333,12 @@ def _detect_event(request, require_live=True, require_plugin=None):
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
can_access = 'event_access' in parentdata
|
||||
user = _get_user_from_session_data(parentdata)
|
||||
if user and user.is_authenticated and user.has_event_permission(request.organizer, request.event, request=request):
|
||||
can_access = True
|
||||
request.event_access_user = user
|
||||
|
||||
if not can_access:
|
||||
if not can_access and not request.event.live:
|
||||
# Directly construct view instead of just calling `raise` since this case is so common that we
|
||||
# don't want it to show in our log files.
|
||||
template = loader.get_template("pretixpresale/event/offline.html")
|
||||
@@ -393,7 +410,8 @@ def _event_view(function=None, require_live=True, require_plugin=None):
|
||||
if ret:
|
||||
return ret
|
||||
else:
|
||||
with scope(organizer=getattr(request, 'organizer', None)):
|
||||
with scope(organizer=getattr(request, 'organizer', None)), \
|
||||
time_machine_now_assigned_from_request(request):
|
||||
response = func(request=request, *args, **kwargs)
|
||||
if getattr(request, 'event', None):
|
||||
for receiver, r in process_response.send(request.event, request=request, response=response):
|
||||
|
||||
@@ -63,6 +63,7 @@ from pretix.base.services.cart import (
|
||||
CartError, add_items_to_cart, apply_voucher, clear_cart, error_messages,
|
||||
remove_cart_position,
|
||||
)
|
||||
from pretix.base.timemachine import time_machine_now
|
||||
from pretix.base.views.tasks import AsyncAction
|
||||
from pretix.helpers.http import redirect_to_url
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
@@ -154,25 +155,17 @@ class CartActionMixin:
|
||||
if value.strip() == '' or '_' not in key:
|
||||
return
|
||||
|
||||
subevent = None
|
||||
if key.startswith('subevent_'):
|
||||
try:
|
||||
parts = key.split('_', 2)
|
||||
subevent = int(parts[1])
|
||||
key = parts[2]
|
||||
except ValueError:
|
||||
pass
|
||||
elif 'subevent' in self.request.POST:
|
||||
try:
|
||||
subevent = int(self.request.POST.get('subevent'))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if not key.startswith('item_') and not key.startswith('variation_') and not key.startswith('seat_'):
|
||||
return
|
||||
|
||||
parts = key.split("_")
|
||||
price = self.request.POST.get('price_' + "_".join(parts[1:]), "")
|
||||
subevent = None
|
||||
if 'subevent' in self.request.POST:
|
||||
try:
|
||||
subevent = int(self.request.POST.get('subevent'))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if key.startswith('seat_'):
|
||||
try:
|
||||
@@ -437,7 +430,8 @@ class CartApplyVoucher(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
if 'voucher' in request.POST:
|
||||
return self.do(self.request.event.id, request.POST.get('voucher'), get_or_create_cart_id(self.request),
|
||||
translation.get_language(), request.sales_channel.identifier)
|
||||
translation.get_language(), request.sales_channel.identifier,
|
||||
time_machine_now(default=None))
|
||||
else:
|
||||
if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
|
||||
return JsonResponse({
|
||||
@@ -463,7 +457,8 @@ class CartRemove(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
if 'id' in request.POST:
|
||||
try:
|
||||
return self.do(self.request.event.id, int(request.POST.get('id')), get_or_create_cart_id(self.request),
|
||||
translation.get_language(), request.sales_channel.identifier)
|
||||
translation.get_language(), request.sales_channel.identifier,
|
||||
time_machine_now(default=None))
|
||||
except ValueError:
|
||||
return redirect_to_url(self.get_error_url())
|
||||
else:
|
||||
@@ -486,7 +481,7 @@ class CartClear(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
return self.do(self.request.event.id, get_or_create_cart_id(self.request), translation.get_language(),
|
||||
request.sales_channel.identifier)
|
||||
request.sales_channel.identifier, time_machine_now(default=None))
|
||||
|
||||
|
||||
@method_decorator(allow_cors_if_namespaced, 'dispatch')
|
||||
@@ -542,7 +537,8 @@ class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
items = self._items_from_post_data()
|
||||
if items:
|
||||
return self.do(self.request.event.id, items, cart_id, translation.get_language(),
|
||||
self.invoice_address.pk, widget_data, self.request.sales_channel.identifier)
|
||||
self.invoice_address.pk, widget_data, self.request.sales_channel.identifier,
|
||||
time_machine_now(default=None))
|
||||
else:
|
||||
if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
|
||||
return JsonResponse({
|
||||
|
||||
@@ -42,24 +42,29 @@ from importlib import import_module
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import isoweek
|
||||
from dateutil import parser
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import (
|
||||
Count, Exists, IntegerField, OuterRef, Prefetch, Q, Value,
|
||||
)
|
||||
from django.db.models.lookups import Exact
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.formats import get_format
|
||||
from django.utils.functional import SimpleLazyObject
|
||||
from django.utils.timezone import now
|
||||
from django.utils.http import url_has_allowed_host_and_scheme
|
||||
from django.utils.timezone import get_current_timezone, now
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from django.views import View
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.forms.widgets import SplitDateTimePickerWidget
|
||||
from pretix.base.models import (
|
||||
ItemVariation, Quota, SeatCategoryMapping, Voucher,
|
||||
)
|
||||
@@ -69,7 +74,12 @@ from pretix.base.models.items import (
|
||||
)
|
||||
from pretix.base.services.placeholders import PlaceholderContext
|
||||
from pretix.base.services.quotas import QuotaAvailability
|
||||
from pretix.base.timemachine import has_time_machine_permission
|
||||
from pretix.helpers.compat import date_fromisocalendar
|
||||
from pretix.helpers.formats.en.formats import (
|
||||
SHORT_MONTH_DAY_FORMAT, WEEK_FORMAT,
|
||||
)
|
||||
from pretix.helpers.http import redirect_to_url
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
from pretix.presale.ical import get_public_ical
|
||||
from pretix.presale.signals import item_description
|
||||
@@ -78,8 +88,6 @@ from pretix.presale.views.organizer import (
|
||||
filter_qs_by_attr, has_before_after, weeks_for_template,
|
||||
)
|
||||
|
||||
from ...helpers.formats.en.formats import SHORT_MONTH_DAY_FORMAT, WEEK_FORMAT
|
||||
from ...helpers.http import redirect_to_url
|
||||
from . import (
|
||||
CartMixin, EventViewMixin, allow_frame_if_namespaced, get_cart,
|
||||
iframe_entry_view_wrapper,
|
||||
@@ -913,8 +921,58 @@ class EventAuth(View):
|
||||
except:
|
||||
raise PermissionDenied(_('Please go back and try again.'))
|
||||
else:
|
||||
if 'event_access' not in parentdata:
|
||||
if 'child_session_{}'.format(request.event.pk) not in parentdata:
|
||||
raise PermissionDenied(_('Please go back and try again.'))
|
||||
|
||||
request.session['pretix_event_access_{}'.format(request.event.pk)] = parent
|
||||
return redirect_to_url(eventreverse(request.event, 'presale:event.index'))
|
||||
|
||||
if "next" in self.request.GET and url_has_allowed_host_and_scheme(
|
||||
url=self.request.GET.get("next"), allowed_hosts=request.host, require_https=True):
|
||||
return redirect_to_url(self.request.GET.get('next'))
|
||||
else:
|
||||
return redirect_to_url(eventreverse(request.event, 'presale:event.index'))
|
||||
|
||||
|
||||
class TimemachineForm(forms.Form):
|
||||
now_dt = forms.SplitDateTimeField(
|
||||
label=_('Fake date time'),
|
||||
widget=SplitDateTimePickerWidget(),
|
||||
initial=lambda: now().astimezone(get_current_timezone()),
|
||||
)
|
||||
|
||||
|
||||
class EventTimeMachine(EventViewMixin, TemplateView):
|
||||
template_name = 'pretixpresale/event/timemachine.html'
|
||||
|
||||
def setup(self, request, *args, **kwargs):
|
||||
super().setup(request, *args, **kwargs)
|
||||
if not has_time_machine_permission(request, request.event):
|
||||
raise PermissionDenied(_('You are not allowed to access time machine mode.'))
|
||||
if not request.event.testmode:
|
||||
raise PermissionDenied(_('This feature is only available in test mode.'))
|
||||
self.timemachine_form = TimemachineForm(
|
||||
data=request.method == 'POST' and request.POST or None,
|
||||
initial=(
|
||||
{'now_dt': parser.parse(request.session.get(f'timemachine_now_dt:{request.event.pk}', None))}
|
||||
if request.session.get(f'timemachine_now_dt:{request.event.pk}', None) else {}
|
||||
)
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['timemachine_form'] = self.timemachine_form
|
||||
return ctx
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if request.POST.get("timemachine_disable"):
|
||||
del request.session[f'timemachine_now_dt:{request.event.pk}']
|
||||
messages.success(self.request, _('Time machine disabled!'))
|
||||
return redirect(self.get_success_url())
|
||||
elif self.timemachine_form.is_valid():
|
||||
request.session[f'timemachine_now_dt:{request.event.pk}'] = str(self.timemachine_form.cleaned_data['now_dt'])
|
||||
return redirect(eventreverse(request.event, "presale:event.index"))
|
||||
else:
|
||||
return self.get(request)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return eventreverse(self.request.event, 'presale:event.timemachine')
|
||||
|
||||
@@ -704,13 +704,6 @@ BOOTSTRAP3 = {
|
||||
},
|
||||
}
|
||||
|
||||
PASSWORD_HASHERS = [
|
||||
"django.contrib.auth.hashers.Argon2PasswordHasher",
|
||||
"django.contrib.auth.hashers.PBKDF2PasswordHasher",
|
||||
"django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
|
||||
"django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
|
||||
"django.contrib.auth.hashers.ScryptPasswordHasher",
|
||||
]
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
|
||||
522
src/pretix/static/npm_dir/package-lock.json
generated
522
src/pretix/static/npm_dir/package-lock.json
generated
@@ -8,8 +8,8 @@
|
||||
"name": "pretix",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.24.5",
|
||||
"@babel/preset-env": "^7.24.5",
|
||||
"@babel/core": "^7.24.3",
|
||||
"@babel/preset-env": "^7.24.3",
|
||||
"@rollup/plugin-babel": "^6.0.4",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"rollup": "^2.79.1",
|
||||
@@ -43,28 +43,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz",
|
||||
"integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz",
|
||||
"integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz",
|
||||
"integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==",
|
||||
"version": "7.24.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.3.tgz",
|
||||
"integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.24.2",
|
||||
"@babel/generator": "^7.24.5",
|
||||
"@babel/generator": "^7.24.1",
|
||||
"@babel/helper-compilation-targets": "^7.23.6",
|
||||
"@babel/helper-module-transforms": "^7.24.5",
|
||||
"@babel/helpers": "^7.24.5",
|
||||
"@babel/parser": "^7.24.5",
|
||||
"@babel/helper-module-transforms": "^7.23.3",
|
||||
"@babel/helpers": "^7.24.1",
|
||||
"@babel/parser": "^7.24.1",
|
||||
"@babel/template": "^7.24.0",
|
||||
"@babel/traverse": "^7.24.5",
|
||||
"@babel/types": "^7.24.5",
|
||||
"@babel/traverse": "^7.24.1",
|
||||
"@babel/types": "^7.24.0",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
@@ -99,11 +99,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz",
|
||||
"integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz",
|
||||
"integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.24.5",
|
||||
"@babel/types": "^7.24.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^2.5.1"
|
||||
@@ -184,18 +184,18 @@
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
||||
},
|
||||
"node_modules/@babel/helper-create-class-features-plugin": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz",
|
||||
"integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz",
|
||||
"integrity": "sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA==",
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.22.5",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-member-expression-to-functions": "^7.24.5",
|
||||
"@babel/helper-member-expression-to-functions": "^7.23.0",
|
||||
"@babel/helper-optimise-call-expression": "^7.22.5",
|
||||
"@babel/helper-replace-supers": "^7.24.1",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.24.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -284,11 +284,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz",
|
||||
"integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz",
|
||||
"integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.24.5"
|
||||
"@babel/types": "^7.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -306,15 +306,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz",
|
||||
"integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==",
|
||||
"version": "7.23.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
|
||||
"integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
|
||||
"dependencies": {
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-module-imports": "^7.24.3",
|
||||
"@babel/helper-simple-access": "^7.24.5",
|
||||
"@babel/helper-split-export-declaration": "^7.24.5",
|
||||
"@babel/helper-validator-identifier": "^7.24.5"
|
||||
"@babel/helper-module-imports": "^7.22.15",
|
||||
"@babel/helper-simple-access": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/helper-validator-identifier": "^7.22.20"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -335,9 +335,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-plugin-utils": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz",
|
||||
"integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==",
|
||||
"version": "7.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz",
|
||||
"integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -375,11 +375,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-simple-access": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz",
|
||||
"integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
|
||||
"integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.24.5"
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -397,28 +397,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-split-export-declaration": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz",
|
||||
"integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==",
|
||||
"version": "7.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
|
||||
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.24.5"
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
|
||||
"integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==",
|
||||
"version": "7.23.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
|
||||
"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
|
||||
"integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -445,13 +445,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz",
|
||||
"integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz",
|
||||
"integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.24.0",
|
||||
"@babel/traverse": "^7.24.5",
|
||||
"@babel/types": "^7.24.5"
|
||||
"@babel/traverse": "^7.24.1",
|
||||
"@babel/types": "^7.24.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -472,9 +472,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
|
||||
"integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz",
|
||||
"integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==",
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@@ -482,21 +482,6 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz",
|
||||
"integrity": "sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==",
|
||||
"dependencies": {
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-plugin-utils": "^7.24.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz",
|
||||
@@ -832,11 +817,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-block-scoping": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz",
|
||||
"integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz",
|
||||
"integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.24.5"
|
||||
"@babel/helper-plugin-utils": "^7.24.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -861,11 +846,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-class-static-block": {
|
||||
"version": "7.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz",
|
||||
"integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.1.tgz",
|
||||
"integrity": "sha512-FUHlKCn6J3ERiu8Dv+4eoz7w8+kFLSyeVG4vDAikwADGjUCoHw/JHokyGtr8OR4UjpwPVivyF+h8Q5iv/JmrtA==",
|
||||
"dependencies": {
|
||||
"@babel/helper-create-class-features-plugin": "^7.24.4",
|
||||
"@babel/helper-create-class-features-plugin": "^7.24.1",
|
||||
"@babel/helper-plugin-utils": "^7.24.0",
|
||||
"@babel/plugin-syntax-class-static-block": "^7.14.5"
|
||||
},
|
||||
@@ -877,17 +862,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-classes": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz",
|
||||
"integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz",
|
||||
"integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==",
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.22.5",
|
||||
"@babel/helper-compilation-targets": "^7.23.6",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-plugin-utils": "^7.24.5",
|
||||
"@babel/helper-plugin-utils": "^7.24.0",
|
||||
"@babel/helper-replace-supers": "^7.24.1",
|
||||
"@babel/helper-split-export-declaration": "^7.24.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -913,11 +898,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-destructuring": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz",
|
||||
"integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz",
|
||||
"integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.24.5"
|
||||
"@babel/helper-plugin-utils": "^7.24.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1212,14 +1197,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-object-rest-spread": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz",
|
||||
"integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz",
|
||||
"integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==",
|
||||
"dependencies": {
|
||||
"@babel/helper-compilation-targets": "^7.23.6",
|
||||
"@babel/helper-plugin-utils": "^7.24.5",
|
||||
"@babel/helper-plugin-utils": "^7.24.0",
|
||||
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
|
||||
"@babel/plugin-transform-parameters": "^7.24.5"
|
||||
"@babel/plugin-transform-parameters": "^7.24.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1259,11 +1244,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-optional-chaining": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz",
|
||||
"integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz",
|
||||
"integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.24.5",
|
||||
"@babel/helper-plugin-utils": "^7.24.0",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
|
||||
"@babel/plugin-syntax-optional-chaining": "^7.8.3"
|
||||
},
|
||||
@@ -1275,11 +1260,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-parameters": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz",
|
||||
"integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz",
|
||||
"integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.24.5"
|
||||
"@babel/helper-plugin-utils": "^7.24.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1304,13 +1289,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-private-property-in-object": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz",
|
||||
"integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz",
|
||||
"integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==",
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.22.5",
|
||||
"@babel/helper-create-class-features-plugin": "^7.24.5",
|
||||
"@babel/helper-plugin-utils": "^7.24.5",
|
||||
"@babel/helper-create-class-features-plugin": "^7.24.1",
|
||||
"@babel/helper-plugin-utils": "^7.24.0",
|
||||
"@babel/plugin-syntax-private-property-in-object": "^7.14.5"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1421,11 +1406,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-typeof-symbol": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz",
|
||||
"integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz",
|
||||
"integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.24.5"
|
||||
"@babel/helper-plugin-utils": "^7.24.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1494,15 +1479,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/preset-env": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.5.tgz",
|
||||
"integrity": "sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==",
|
||||
"version": "7.24.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.3.tgz",
|
||||
"integrity": "sha512-fSk430k5c2ff8536JcPvPWK4tZDwehWLGlBp0wrsBUjZVdeQV6lePbwKWZaZfK2vnh/1kQX1PzAJWsnBmVgGJA==",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.24.4",
|
||||
"@babel/compat-data": "^7.24.1",
|
||||
"@babel/helper-compilation-targets": "^7.23.6",
|
||||
"@babel/helper-plugin-utils": "^7.24.5",
|
||||
"@babel/helper-plugin-utils": "^7.24.0",
|
||||
"@babel/helper-validator-option": "^7.23.5",
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.5",
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1",
|
||||
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1",
|
||||
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1",
|
||||
@@ -1529,12 +1513,12 @@
|
||||
"@babel/plugin-transform-async-generator-functions": "^7.24.3",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.24.1",
|
||||
"@babel/plugin-transform-block-scoped-functions": "^7.24.1",
|
||||
"@babel/plugin-transform-block-scoping": "^7.24.5",
|
||||
"@babel/plugin-transform-block-scoping": "^7.24.1",
|
||||
"@babel/plugin-transform-class-properties": "^7.24.1",
|
||||
"@babel/plugin-transform-class-static-block": "^7.24.4",
|
||||
"@babel/plugin-transform-classes": "^7.24.5",
|
||||
"@babel/plugin-transform-class-static-block": "^7.24.1",
|
||||
"@babel/plugin-transform-classes": "^7.24.1",
|
||||
"@babel/plugin-transform-computed-properties": "^7.24.1",
|
||||
"@babel/plugin-transform-destructuring": "^7.24.5",
|
||||
"@babel/plugin-transform-destructuring": "^7.24.1",
|
||||
"@babel/plugin-transform-dotall-regex": "^7.24.1",
|
||||
"@babel/plugin-transform-duplicate-keys": "^7.24.1",
|
||||
"@babel/plugin-transform-dynamic-import": "^7.24.1",
|
||||
@@ -1554,13 +1538,13 @@
|
||||
"@babel/plugin-transform-new-target": "^7.24.1",
|
||||
"@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1",
|
||||
"@babel/plugin-transform-numeric-separator": "^7.24.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.24.5",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.24.1",
|
||||
"@babel/plugin-transform-object-super": "^7.24.1",
|
||||
"@babel/plugin-transform-optional-catch-binding": "^7.24.1",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.24.5",
|
||||
"@babel/plugin-transform-parameters": "^7.24.5",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.24.1",
|
||||
"@babel/plugin-transform-parameters": "^7.24.1",
|
||||
"@babel/plugin-transform-private-methods": "^7.24.1",
|
||||
"@babel/plugin-transform-private-property-in-object": "^7.24.5",
|
||||
"@babel/plugin-transform-private-property-in-object": "^7.24.1",
|
||||
"@babel/plugin-transform-property-literals": "^7.24.1",
|
||||
"@babel/plugin-transform-regenerator": "^7.24.1",
|
||||
"@babel/plugin-transform-reserved-words": "^7.24.1",
|
||||
@@ -1568,7 +1552,7 @@
|
||||
"@babel/plugin-transform-spread": "^7.24.1",
|
||||
"@babel/plugin-transform-sticky-regex": "^7.24.1",
|
||||
"@babel/plugin-transform-template-literals": "^7.24.1",
|
||||
"@babel/plugin-transform-typeof-symbol": "^7.24.5",
|
||||
"@babel/plugin-transform-typeof-symbol": "^7.24.1",
|
||||
"@babel/plugin-transform-unicode-escapes": "^7.24.1",
|
||||
"@babel/plugin-transform-unicode-property-regex": "^7.24.1",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.24.1",
|
||||
@@ -1643,18 +1627,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz",
|
||||
"integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz",
|
||||
"integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.24.2",
|
||||
"@babel/generator": "^7.24.5",
|
||||
"@babel/code-frame": "^7.24.1",
|
||||
"@babel/generator": "^7.24.1",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.24.5",
|
||||
"@babel/parser": "^7.24.5",
|
||||
"@babel/types": "^7.24.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.24.1",
|
||||
"@babel/types": "^7.24.0",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
@@ -1663,12 +1647,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz",
|
||||
"integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==",
|
||||
"version": "7.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
|
||||
"integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.24.1",
|
||||
"@babel/helper-validator-identifier": "^7.24.5",
|
||||
"@babel/helper-string-parser": "^7.23.4",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4174,25 +4158,25 @@
|
||||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
"version": "7.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz",
|
||||
"integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ=="
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz",
|
||||
"integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA=="
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz",
|
||||
"integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==",
|
||||
"version": "7.24.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.3.tgz",
|
||||
"integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==",
|
||||
"requires": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.24.2",
|
||||
"@babel/generator": "^7.24.5",
|
||||
"@babel/generator": "^7.24.1",
|
||||
"@babel/helper-compilation-targets": "^7.23.6",
|
||||
"@babel/helper-module-transforms": "^7.24.5",
|
||||
"@babel/helpers": "^7.24.5",
|
||||
"@babel/parser": "^7.24.5",
|
||||
"@babel/helper-module-transforms": "^7.23.3",
|
||||
"@babel/helpers": "^7.24.1",
|
||||
"@babel/parser": "^7.24.1",
|
||||
"@babel/template": "^7.24.0",
|
||||
"@babel/traverse": "^7.24.5",
|
||||
"@babel/types": "^7.24.5",
|
||||
"@babel/traverse": "^7.24.1",
|
||||
"@babel/types": "^7.24.0",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
@@ -4213,11 +4197,11 @@
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz",
|
||||
"integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz",
|
||||
"integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.24.5",
|
||||
"@babel/types": "^7.24.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^2.5.1"
|
||||
@@ -4284,18 +4268,18 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-create-class-features-plugin": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz",
|
||||
"integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz",
|
||||
"integrity": "sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA==",
|
||||
"requires": {
|
||||
"@babel/helper-annotate-as-pure": "^7.22.5",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-member-expression-to-functions": "^7.24.5",
|
||||
"@babel/helper-member-expression-to-functions": "^7.23.0",
|
||||
"@babel/helper-optimise-call-expression": "^7.22.5",
|
||||
"@babel/helper-replace-supers": "^7.24.1",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.24.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -4358,11 +4342,11 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz",
|
||||
"integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz",
|
||||
"integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.24.5"
|
||||
"@babel/types": "^7.23.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-imports": {
|
||||
@@ -4374,15 +4358,15 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-transforms": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz",
|
||||
"integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==",
|
||||
"version": "7.23.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
|
||||
"integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
|
||||
"requires": {
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-module-imports": "^7.24.3",
|
||||
"@babel/helper-simple-access": "^7.24.5",
|
||||
"@babel/helper-split-export-declaration": "^7.24.5",
|
||||
"@babel/helper-validator-identifier": "^7.24.5"
|
||||
"@babel/helper-module-imports": "^7.22.15",
|
||||
"@babel/helper-simple-access": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/helper-validator-identifier": "^7.22.20"
|
||||
}
|
||||
},
|
||||
"@babel/helper-optimise-call-expression": {
|
||||
@@ -4394,9 +4378,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-plugin-utils": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz",
|
||||
"integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ=="
|
||||
"version": "7.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz",
|
||||
"integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w=="
|
||||
},
|
||||
"@babel/helper-remap-async-to-generator": {
|
||||
"version": "7.22.20",
|
||||
@@ -4419,11 +4403,11 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-simple-access": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz",
|
||||
"integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
|
||||
"integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.24.5"
|
||||
"@babel/types": "^7.22.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-skip-transparent-expression-wrappers": {
|
||||
@@ -4435,22 +4419,22 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-split-export-declaration": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz",
|
||||
"integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==",
|
||||
"version": "7.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
|
||||
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.24.5"
|
||||
"@babel/types": "^7.22.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-string-parser": {
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
|
||||
"integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ=="
|
||||
"version": "7.23.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
|
||||
"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ=="
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
|
||||
"integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA=="
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A=="
|
||||
},
|
||||
"@babel/helper-validator-option": {
|
||||
"version": "7.23.5",
|
||||
@@ -4468,13 +4452,13 @@
|
||||
}
|
||||
},
|
||||
"@babel/helpers": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz",
|
||||
"integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz",
|
||||
"integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==",
|
||||
"requires": {
|
||||
"@babel/template": "^7.24.0",
|
||||
"@babel/traverse": "^7.24.5",
|
||||
"@babel/types": "^7.24.5"
|
||||
"@babel/traverse": "^7.24.1",
|
||||
"@babel/types": "^7.24.0"
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
@@ -4489,18 +4473,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
|
||||
"integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg=="
|
||||
},
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz",
|
||||
"integrity": "sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==",
|
||||
"requires": {
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-plugin-utils": "^7.24.5"
|
||||
}
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz",
|
||||
"integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg=="
|
||||
},
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
|
||||
"version": "7.24.1",
|
||||
@@ -4718,11 +4693,11 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-block-scoping": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz",
|
||||
"integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz",
|
||||
"integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.24.5"
|
||||
"@babel/helper-plugin-utils": "^7.24.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-class-properties": {
|
||||
@@ -4735,27 +4710,27 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-class-static-block": {
|
||||
"version": "7.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz",
|
||||
"integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.1.tgz",
|
||||
"integrity": "sha512-FUHlKCn6J3ERiu8Dv+4eoz7w8+kFLSyeVG4vDAikwADGjUCoHw/JHokyGtr8OR4UjpwPVivyF+h8Q5iv/JmrtA==",
|
||||
"requires": {
|
||||
"@babel/helper-create-class-features-plugin": "^7.24.4",
|
||||
"@babel/helper-create-class-features-plugin": "^7.24.1",
|
||||
"@babel/helper-plugin-utils": "^7.24.0",
|
||||
"@babel/plugin-syntax-class-static-block": "^7.14.5"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-classes": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz",
|
||||
"integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz",
|
||||
"integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==",
|
||||
"requires": {
|
||||
"@babel/helper-annotate-as-pure": "^7.22.5",
|
||||
"@babel/helper-compilation-targets": "^7.23.6",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-plugin-utils": "^7.24.5",
|
||||
"@babel/helper-plugin-utils": "^7.24.0",
|
||||
"@babel/helper-replace-supers": "^7.24.1",
|
||||
"@babel/helper-split-export-declaration": "^7.24.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
@@ -4769,11 +4744,11 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-destructuring": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz",
|
||||
"integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz",
|
||||
"integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.24.5"
|
||||
"@babel/helper-plugin-utils": "^7.24.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-dotall-regex": {
|
||||
@@ -4948,14 +4923,14 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-object-rest-spread": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz",
|
||||
"integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz",
|
||||
"integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==",
|
||||
"requires": {
|
||||
"@babel/helper-compilation-targets": "^7.23.6",
|
||||
"@babel/helper-plugin-utils": "^7.24.5",
|
||||
"@babel/helper-plugin-utils": "^7.24.0",
|
||||
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
|
||||
"@babel/plugin-transform-parameters": "^7.24.5"
|
||||
"@babel/plugin-transform-parameters": "^7.24.1"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-object-super": {
|
||||
@@ -4977,21 +4952,21 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-optional-chaining": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz",
|
||||
"integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz",
|
||||
"integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.24.5",
|
||||
"@babel/helper-plugin-utils": "^7.24.0",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
|
||||
"@babel/plugin-syntax-optional-chaining": "^7.8.3"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-parameters": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz",
|
||||
"integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz",
|
||||
"integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.24.5"
|
||||
"@babel/helper-plugin-utils": "^7.24.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-private-methods": {
|
||||
@@ -5004,13 +4979,13 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-private-property-in-object": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz",
|
||||
"integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz",
|
||||
"integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==",
|
||||
"requires": {
|
||||
"@babel/helper-annotate-as-pure": "^7.22.5",
|
||||
"@babel/helper-create-class-features-plugin": "^7.24.5",
|
||||
"@babel/helper-plugin-utils": "^7.24.5",
|
||||
"@babel/helper-create-class-features-plugin": "^7.24.1",
|
||||
"@babel/helper-plugin-utils": "^7.24.0",
|
||||
"@babel/plugin-syntax-private-property-in-object": "^7.14.5"
|
||||
}
|
||||
},
|
||||
@@ -5073,11 +5048,11 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-typeof-symbol": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz",
|
||||
"integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz",
|
||||
"integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.24.5"
|
||||
"@babel/helper-plugin-utils": "^7.24.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-unicode-escapes": {
|
||||
@@ -5116,15 +5091,14 @@
|
||||
}
|
||||
},
|
||||
"@babel/preset-env": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.5.tgz",
|
||||
"integrity": "sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==",
|
||||
"version": "7.24.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.3.tgz",
|
||||
"integrity": "sha512-fSk430k5c2ff8536JcPvPWK4tZDwehWLGlBp0wrsBUjZVdeQV6lePbwKWZaZfK2vnh/1kQX1PzAJWsnBmVgGJA==",
|
||||
"requires": {
|
||||
"@babel/compat-data": "^7.24.4",
|
||||
"@babel/compat-data": "^7.24.1",
|
||||
"@babel/helper-compilation-targets": "^7.23.6",
|
||||
"@babel/helper-plugin-utils": "^7.24.5",
|
||||
"@babel/helper-plugin-utils": "^7.24.0",
|
||||
"@babel/helper-validator-option": "^7.23.5",
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.5",
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1",
|
||||
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1",
|
||||
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1",
|
||||
@@ -5151,12 +5125,12 @@
|
||||
"@babel/plugin-transform-async-generator-functions": "^7.24.3",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.24.1",
|
||||
"@babel/plugin-transform-block-scoped-functions": "^7.24.1",
|
||||
"@babel/plugin-transform-block-scoping": "^7.24.5",
|
||||
"@babel/plugin-transform-block-scoping": "^7.24.1",
|
||||
"@babel/plugin-transform-class-properties": "^7.24.1",
|
||||
"@babel/plugin-transform-class-static-block": "^7.24.4",
|
||||
"@babel/plugin-transform-classes": "^7.24.5",
|
||||
"@babel/plugin-transform-class-static-block": "^7.24.1",
|
||||
"@babel/plugin-transform-classes": "^7.24.1",
|
||||
"@babel/plugin-transform-computed-properties": "^7.24.1",
|
||||
"@babel/plugin-transform-destructuring": "^7.24.5",
|
||||
"@babel/plugin-transform-destructuring": "^7.24.1",
|
||||
"@babel/plugin-transform-dotall-regex": "^7.24.1",
|
||||
"@babel/plugin-transform-duplicate-keys": "^7.24.1",
|
||||
"@babel/plugin-transform-dynamic-import": "^7.24.1",
|
||||
@@ -5176,13 +5150,13 @@
|
||||
"@babel/plugin-transform-new-target": "^7.24.1",
|
||||
"@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1",
|
||||
"@babel/plugin-transform-numeric-separator": "^7.24.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.24.5",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.24.1",
|
||||
"@babel/plugin-transform-object-super": "^7.24.1",
|
||||
"@babel/plugin-transform-optional-catch-binding": "^7.24.1",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.24.5",
|
||||
"@babel/plugin-transform-parameters": "^7.24.5",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.24.1",
|
||||
"@babel/plugin-transform-parameters": "^7.24.1",
|
||||
"@babel/plugin-transform-private-methods": "^7.24.1",
|
||||
"@babel/plugin-transform-private-property-in-object": "^7.24.5",
|
||||
"@babel/plugin-transform-private-property-in-object": "^7.24.1",
|
||||
"@babel/plugin-transform-property-literals": "^7.24.1",
|
||||
"@babel/plugin-transform-regenerator": "^7.24.1",
|
||||
"@babel/plugin-transform-reserved-words": "^7.24.1",
|
||||
@@ -5190,7 +5164,7 @@
|
||||
"@babel/plugin-transform-spread": "^7.24.1",
|
||||
"@babel/plugin-transform-sticky-regex": "^7.24.1",
|
||||
"@babel/plugin-transform-template-literals": "^7.24.1",
|
||||
"@babel/plugin-transform-typeof-symbol": "^7.24.5",
|
||||
"@babel/plugin-transform-typeof-symbol": "^7.24.1",
|
||||
"@babel/plugin-transform-unicode-escapes": "^7.24.1",
|
||||
"@babel/plugin-transform-unicode-property-regex": "^7.24.1",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.24.1",
|
||||
@@ -5251,29 +5225,29 @@
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz",
|
||||
"integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz",
|
||||
"integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.24.2",
|
||||
"@babel/generator": "^7.24.5",
|
||||
"@babel/code-frame": "^7.24.1",
|
||||
"@babel/generator": "^7.24.1",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.24.5",
|
||||
"@babel/parser": "^7.24.5",
|
||||
"@babel/types": "^7.24.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.24.1",
|
||||
"@babel/types": "^7.24.0",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz",
|
||||
"integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==",
|
||||
"version": "7.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
|
||||
"integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.24.1",
|
||||
"@babel/helper-validator-identifier": "^7.24.5",
|
||||
"@babel/helper-string-parser": "^7.23.4",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
"private": true,
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.24.5",
|
||||
"@babel/preset-env": "^7.24.5",
|
||||
"@babel/core": "^7.24.3",
|
||||
"@babel/preset-env": "^7.24.3",
|
||||
"@rollup/plugin-babel": "^6.0.4",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"vue": "^2.7.16",
|
||||
|
||||
1
src/pretix/static/pretixcontrol/js/send_form.js
Normal file
1
src/pretix/static/pretixcontrol/js/send_form.js
Normal file
@@ -0,0 +1 @@
|
||||
document.forms[0].submit();
|
||||
@@ -194,7 +194,7 @@ div.front-page {
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
a, .btn-link {
|
||||
text-decoration: underline;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ $body-bg: #f5f5f5 !default;
|
||||
h1 a, .btn {
|
||||
text-decoration: none;
|
||||
}
|
||||
a .fa:first-child { margin-right: 0.5ch }
|
||||
/*
|
||||
a, .btn-link {
|
||||
text-decoration: underline;
|
||||
|
||||
@@ -51,11 +51,6 @@ def item2(event):
|
||||
return event.items.create(name='Ticket II', default_price=Decimal('50.00'))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def item3(event):
|
||||
return event.items.create(name='Ticket III', default_price=Decimal('42.00'))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def voucher(event):
|
||||
return event.vouchers.create()
|
||||
@@ -1343,66 +1338,3 @@ def test_multiple_discounts_with_benefit_condition_overlap(event, item, item2):
|
||||
|
||||
new_prices = [p for p, d in apply_discounts(event, 'web', positions)]
|
||||
assert sorted(new_prices) == sorted(expected)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_multiple_discounts_with_same_condition(event, item, item2, item3):
|
||||
# "For every 1 item1, you get three item2 for 10 % off." + "For every 1 item1, you get five item3 for 10 % off."
|
||||
d1 = Discount(
|
||||
event=event,
|
||||
condition_min_count=1,
|
||||
condition_all_products=False,
|
||||
benefit_only_apply_to_cheapest_n_matches=3,
|
||||
benefit_discount_matching_percent=10,
|
||||
benefit_same_products=False,
|
||||
position=1,
|
||||
)
|
||||
d1.save()
|
||||
d1.condition_limit_products.add(item)
|
||||
d1.benefit_limit_products.add(item2)
|
||||
|
||||
d2 = Discount(
|
||||
event=event,
|
||||
condition_min_count=1,
|
||||
condition_all_products=False,
|
||||
benefit_only_apply_to_cheapest_n_matches=5,
|
||||
benefit_discount_matching_percent=10,
|
||||
benefit_same_products=False,
|
||||
position=2,
|
||||
)
|
||||
d2.save()
|
||||
d2.condition_limit_products.add(item)
|
||||
d2.benefit_limit_products.add(item3)
|
||||
|
||||
positions = (
|
||||
(item3.pk, None, Decimal('42.00'), False, False, Decimal('0.00')),
|
||||
(item3.pk, None, Decimal('42.00'), False, False, Decimal('0.00')),
|
||||
(item3.pk, None, Decimal('42.00'), False, False, Decimal('0.00')),
|
||||
(item3.pk, None, Decimal('42.00'), False, False, Decimal('0.00')),
|
||||
(item3.pk, None, Decimal('42.00'), False, False, Decimal('0.00')),
|
||||
(item3.pk, None, Decimal('42.00'), False, False, Decimal('0.00')),
|
||||
(item2.pk, None, Decimal('50.00'), False, False, Decimal('0.00')),
|
||||
(item2.pk, None, Decimal('50.00'), False, False, Decimal('0.00')),
|
||||
(item.pk, None, Decimal('23.00'), False, False, Decimal('0.00')),
|
||||
(item.pk, None, Decimal('23.00'), False, False, Decimal('0.00')),
|
||||
)
|
||||
expected = (
|
||||
# both item1 remain full price
|
||||
Decimal('23.00'),
|
||||
Decimal('23.00'),
|
||||
# 5 item3 discounted
|
||||
Decimal('37.80'),
|
||||
Decimal('37.80'),
|
||||
Decimal('37.80'),
|
||||
Decimal('37.80'),
|
||||
Decimal('37.80'),
|
||||
# 2 item2 discounted
|
||||
Decimal('45.00'),
|
||||
Decimal('45.00'),
|
||||
# 1 item3 remains untouched
|
||||
Decimal('42.00'),
|
||||
)
|
||||
|
||||
new_prices = [p for p, d in apply_discounts(event, 'web', positions)]
|
||||
assert sorted(new_prices) == sorted(expected)
|
||||
|
||||
@@ -41,6 +41,8 @@ def env():
|
||||
|
||||
TEMPLATE_FRONT_PAGE = Template("{% load eventurl %} {% eventurl event 'presale:event.index' %}")
|
||||
TEMPLATE_KWARGS = Template("{% load eventurl %} {% eventurl event 'presale:event.checkout' step='payment' %}")
|
||||
TEMPLATE_ABSEVENTURL = Template("{% load eventurl %} {% abseventurl event 'presale:event.checkout' step='payment' %}")
|
||||
TEMPLATE_ABSMAINURL = Template("{% load eventurl %} {% absmainurl 'control:event.settings' organizer=event.organizer.slug event=event.slug %}")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -77,6 +79,40 @@ def test_event_custom_domain_kwargs(env):
|
||||
assert rendered == 'http://foobar/2015/checkout/payment/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_abseventurl_event_main_domain(env):
|
||||
rendered = TEMPLATE_ABSEVENTURL.render(Context({
|
||||
'event': env[1]
|
||||
})).strip()
|
||||
assert rendered == 'http://example.com/mrmcd/2015/checkout/payment/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_abseventurl_event_custom_domain(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
rendered = TEMPLATE_ABSEVENTURL.render(Context({
|
||||
'event': env[1]
|
||||
})).strip()
|
||||
assert rendered == 'http://foobar/2015/checkout/payment/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_absmainurl_main_domain(env):
|
||||
rendered = TEMPLATE_ABSMAINURL.render(Context({
|
||||
'event': env[1]
|
||||
})).strip()
|
||||
assert rendered == 'http://example.com/control/event/mrmcd/2015/settings/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_absmainurl_custom_domain(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
rendered = TEMPLATE_ABSMAINURL.render(Context({
|
||||
'event': env[1]
|
||||
})).strip()
|
||||
assert rendered == 'http://example.com/control/event/mrmcd/2015/settings/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_only_kwargs(env):
|
||||
with pytest.raises(TemplateSyntaxError):
|
||||
|
||||
@@ -58,6 +58,8 @@ from pretix.base.services.cart import CartError, CartManager, error_messages
|
||||
from pretix.testutils.scope import classscope
|
||||
from pretix.testutils.sessions import get_cart_session_key
|
||||
|
||||
from .test_timemachine import TimemachineTestMixin
|
||||
|
||||
|
||||
class CartTestMixin:
|
||||
@scopes_disabled()
|
||||
@@ -4274,3 +4276,89 @@ class CartSeatingTest(CartTestMixin, TestCase):
|
||||
self.cm.commit()
|
||||
|
||||
assert not CartPosition.objects.filter(cart_id=self.session_key).exists()
|
||||
|
||||
|
||||
class CartTimemachineTest(CartTestMixin, TimemachineTestMixin, TestCase):
|
||||
def test_before_presale_timemachine(self):
|
||||
self._login_with_permission(self.orga)
|
||||
self.event.presale_start = now() + timedelta(days=1)
|
||||
self.event.testmode = True
|
||||
self.event.save()
|
||||
self._set_time_machine_now(now() + timedelta(days=2))
|
||||
|
||||
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
|
||||
'item_%d' % self.ticket.id: '1'
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
assert 'alert-success' in response.rendered_content
|
||||
with scopes_disabled():
|
||||
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
|
||||
self.assertEqual(len(objs), 1)
|
||||
self.assertEqual(objs[0].item, self.ticket)
|
||||
self.assertIsNone(objs[0].variation)
|
||||
self.assertEqual(objs[0].price, 23)
|
||||
self.assertLessEqual(objs[0].expires, now() + timedelta(
|
||||
minutes=self.event.settings.get('reservation_time', as_type=int)))
|
||||
|
||||
def test_after_presale_timemachine(self):
|
||||
self._login_with_permission(self.orga)
|
||||
self.event.presale_end = now() - timedelta(days=1)
|
||||
self.event.testmode = True
|
||||
self.event.save()
|
||||
self._set_time_machine_now(now() - timedelta(days=2))
|
||||
|
||||
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
|
||||
'item_%d' % self.ticket.id: '1'
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
assert 'alert-success' in response.rendered_content
|
||||
with scopes_disabled():
|
||||
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
|
||||
self.assertEqual(len(objs), 1)
|
||||
self.assertEqual(objs[0].item, self.ticket)
|
||||
self.assertIsNone(objs[0].variation)
|
||||
self.assertEqual(objs[0].price, 23)
|
||||
self.assertLessEqual(objs[0].expires, now() + timedelta(
|
||||
minutes=self.event.settings.get('reservation_time', as_type=int)))
|
||||
|
||||
def test_not_yet_available_with_timemachine_in_time(self):
|
||||
self._login_with_permission(self.orga)
|
||||
self._enable_test_mode()
|
||||
self.ticket.available_from = now() + timedelta(days=2)
|
||||
self.ticket.available_until = now() + timedelta(days=4)
|
||||
self.ticket.save()
|
||||
self._set_time_machine_now(now() + timedelta(days=3))
|
||||
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
|
||||
'item_%d' % self.ticket.id: '1',
|
||||
}, follow=True)
|
||||
with scopes_disabled():
|
||||
self.assertEqual(CartPosition.objects.filter(cart_id=self.session_key, event=self.event).count(), 1)
|
||||
|
||||
def test_variation_no_longer_available_with_timemachine_in_time(self):
|
||||
self._login_with_permission(self.orga)
|
||||
self._enable_test_mode()
|
||||
self.shirt_blue.available_from = now() - timedelta(days=4)
|
||||
self.shirt_blue.available_until = now() - timedelta(days=2)
|
||||
self.shirt_blue.save()
|
||||
self._set_time_machine_now(now() - timedelta(days=3))
|
||||
|
||||
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
|
||||
'variation_%d_%d' % (self.shirt.id, self.shirt_blue.id): '1',
|
||||
}, follow=True)
|
||||
with scopes_disabled():
|
||||
self.assertEqual(CartPosition.objects.filter(cart_id=self.session_key, event=self.event).count(), 1)
|
||||
|
||||
def test_variation_no_longer_available_with_timemachine_before(self):
|
||||
self._login_with_permission(self.orga)
|
||||
self._enable_test_mode()
|
||||
self.shirt_blue.available_from = now() - timedelta(days=4)
|
||||
self.shirt_blue.available_until = now() - timedelta(days=2)
|
||||
self.shirt_blue.save()
|
||||
self._set_time_machine_now(now() - timedelta(days=5))
|
||||
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
|
||||
'variation_%d_%d' % (self.shirt.id, self.shirt_blue.id): '1',
|
||||
}, follow=True)
|
||||
with scopes_disabled():
|
||||
self.assertEqual(CartPosition.objects.filter(cart_id=self.session_key, event=self.event).count(), 0)
|
||||
|
||||
@@ -53,9 +53,12 @@ from pretix.base.models.items import (
|
||||
from pretix.base.services.cart import CartManager
|
||||
from pretix.base.services.orders import OrderError, _perform_order
|
||||
from pretix.base.services.tax import VATIDFinalError, VATIDTemporaryError
|
||||
from pretix.base.timemachine import time_machine_now_assigned
|
||||
from pretix.testutils.scope import classscope
|
||||
from pretix.testutils.sessions import get_cart_session_key
|
||||
|
||||
from .test_timemachine import TimemachineTestMixin
|
||||
|
||||
|
||||
class BaseCheckoutTestCase:
|
||||
@scopes_disabled()
|
||||
@@ -122,7 +125,7 @@ class BaseCheckoutTestCase:
|
||||
}]
|
||||
|
||||
|
||||
class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
||||
|
||||
def _enable_reverse_charge(self):
|
||||
self.tr19.eu_reverse_charge = True
|
||||
@@ -2545,6 +2548,98 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
assert op.valid_from.isoformat() == '2023-01-20T11:00:00+00:00'
|
||||
assert op.valid_until.isoformat() == '2023-01-20T13:00:00+00:00'
|
||||
|
||||
@freeze_time("2023-01-18 10:00:00+00:00")
|
||||
def test_validity_requested_with_time_machine(self):
|
||||
self._login_with_permission(self.orga)
|
||||
self._enable_test_mode()
|
||||
self._set_time_machine_now(now() - timedelta(days=10))
|
||||
self.ticket.available_from = now() - timedelta(days=11)
|
||||
self.ticket.available_until = now() - timedelta(days=9)
|
||||
self.ticket.validity_mode = Item.VALIDITY_MODE_DYNAMIC
|
||||
self.ticket.validity_dynamic_duration_days = 1
|
||||
self.ticket.validity_dynamic_start_choice = True
|
||||
self.ticket.validity_dynamic_start_choice_day_limit = 5
|
||||
self.ticket.save()
|
||||
|
||||
with scopes_disabled():
|
||||
cr1 = CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
price=42, listed_price=42, price_after_voucher=42, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
|
||||
# Date too far in the future, expected to fail
|
||||
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'%s-requested_valid_from' % cr1.id: '2024-01-17',
|
||||
'email': 'admin@localhost'
|
||||
}, follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
|
||||
|
||||
# Corrected request
|
||||
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'%s-requested_valid_from' % cr1.id: '2023-01-10',
|
||||
'email': 'admin@localhost'
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
cr1.refresh_from_db()
|
||||
assert cr1.requested_valid_from.isoformat() == '2023-01-10T00:00:00+00:00'
|
||||
|
||||
self._set_payment()
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
with scopes_disabled():
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
self.assertFalse(CartPosition.objects.filter(id=cr1.id).exists())
|
||||
self.assertEqual(Order.objects.count(), 1)
|
||||
self.assertEqual(OrderPosition.objects.count(), 1)
|
||||
op = OrderPosition.objects.get()
|
||||
assert op.valid_from.isoformat() == '2023-01-10T00:00:00+00:00'
|
||||
assert op.valid_until.isoformat() == '2023-01-10T23:59:59+00:00'
|
||||
|
||||
@freeze_time("2023-01-18 10:00:00+00:00")
|
||||
def test_dynamic_validity_with_time_machine(self):
|
||||
self._login_with_permission(self.orga)
|
||||
self._enable_test_mode()
|
||||
self._set_time_machine_now(now() + timedelta(days=10))
|
||||
self.ticket.available_from = now() + timedelta(days=3)
|
||||
self.ticket.validity_mode = Item.VALIDITY_MODE_DYNAMIC
|
||||
self.ticket.validity_dynamic_duration_days = 1
|
||||
self.ticket.validity_dynamic_start_choice = False
|
||||
self.ticket.save()
|
||||
|
||||
with scopes_disabled():
|
||||
cr1 = CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
price=42, listed_price=42, price_after_voucher=42, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'email': 'admin@localhost'
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
cr1.refresh_from_db()
|
||||
with time_machine_now_assigned(now() + timedelta(days=10)):
|
||||
assert cr1.predicted_validity[0].isoformat() == '2023-01-28T10:00:00+00:00'
|
||||
assert cr1.predicted_validity[1].isoformat() == '2023-01-28T23:59:59+00:00'
|
||||
|
||||
self._set_payment()
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
with scopes_disabled():
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
self.assertFalse(CartPosition.objects.filter(id=cr1.id).exists())
|
||||
self.assertEqual(Order.objects.count(), 1)
|
||||
self.assertEqual(OrderPosition.objects.count(), 1)
|
||||
op = OrderPosition.objects.get()
|
||||
assert op.valid_from.isoformat() == '2023-01-28T10:00:00+00:00'
|
||||
assert op.valid_until.isoformat() == '2023-01-28T23:59:59+00:00'
|
||||
|
||||
def test_voucher(self):
|
||||
with scopes_disabled():
|
||||
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, price_mode='set',
|
||||
@@ -3486,6 +3581,28 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
|
||||
def test_before_presale_timemachine(self):
|
||||
self._login_with_permission(self.orga)
|
||||
self._enable_test_mode()
|
||||
self._set_time_machine_now(now() + timedelta(days=4))
|
||||
self.event.presale_start = now() + timedelta(days=3)
|
||||
self.event.save()
|
||||
with scopes_disabled():
|
||||
CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
|
||||
self._set_payment()
|
||||
response = self.client.get('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
assert "test mode" in response.content.decode()
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
with scopes_disabled():
|
||||
assert Order.objects.last().testmode
|
||||
assert Order.objects.last().code[1] == "0"
|
||||
|
||||
def test_create_testmode_order_in_testmode(self):
|
||||
self.event.testmode = True
|
||||
self.event.save()
|
||||
|
||||
43
src/tests/presale/test_timemachine.py
Normal file
43
src/tests/presale/test_timemachine.py
Normal file
@@ -0,0 +1,43 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django_scopes.state import scopes_disabled
|
||||
|
||||
from pretix.base.models import Team, User
|
||||
|
||||
|
||||
class TimemachineTestMixin:
|
||||
@scopes_disabled()
|
||||
def _login_with_permission(self, orga):
|
||||
self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
|
||||
self.team1 = Team.objects.create(organizer=orga, can_create_events=True, can_change_event_settings=True,
|
||||
can_change_items=True, all_events=True)
|
||||
self.team1.members.add(self.user)
|
||||
self.client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
|
||||
def _set_time_machine_now(self, dt):
|
||||
session = self.client.session
|
||||
session[f'timemachine_now_dt:{self.event.pk}'] = str(dt)
|
||||
session.save()
|
||||
|
||||
def _enable_test_mode(self):
|
||||
self.event.testmode = True
|
||||
self.event.save()
|
||||
Reference in New Issue
Block a user