mirror of
https://github.com/pretix/pretix.git
synced 2026-06-29 04:16:15 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b9e10251bc | |||
| 0472101c81 | |||
| b0a34fa196 | |||
| 99d9d65521 | |||
| 599318e7c7 | |||
| b7dc8ad75d | |||
| 027aceaf6c | |||
| 23e37ada59 | |||
| 62917da9a5 | |||
| d68c6415f3 | |||
| 4271928df2 | |||
| 21fc8e4dec | |||
| f0d1d7ca5a | |||
| b2fd6a9c1e | |||
| 27966969f1 | |||
| 64e301ef47 | |||
| 582cb90bb0 | |||
| 4db98def79 | |||
| 8adc649c4c | |||
| 8692222bd3 |
@@ -269,3 +269,18 @@ class BusinessBooleanRadio(forms.RadioSelect):
|
||||
'False': False,
|
||||
False: False,
|
||||
}.get(value)
|
||||
|
||||
|
||||
class OptionAttrsSelect(forms.Select):
|
||||
def __init__(self, *args, option_attrs=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.option_attrs = option_attrs or {}
|
||||
|
||||
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
|
||||
option = super().create_option(
|
||||
name, value, label, selected, index, subindex=subindex, attrs=attrs
|
||||
)
|
||||
extra = self.option_attrs.get(str(value))
|
||||
if extra:
|
||||
option["attrs"].update(extra)
|
||||
return option
|
||||
|
||||
+386
-153
@@ -19,9 +19,13 @@
|
||||
# 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 copy
|
||||
import datetime
|
||||
import os
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
from typing import Union
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Iterable, List, Literal, Tuple, Union
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from dateutil import parser
|
||||
@@ -29,20 +33,255 @@ from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.utils.formats import get_format
|
||||
from django.utils.functional import lazy
|
||||
from django.utils.functional import Promise, lazy
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
BASE_CHOICES = (
|
||||
('date_from', _('Event start')),
|
||||
('date_to', _('Event end')),
|
||||
('date_admission', _('Event admission')),
|
||||
('presale_start', _('Presale start')),
|
||||
('presale_end', _('Presale end')),
|
||||
)
|
||||
from pretix.base.forms.widgets import OptionAttrsSelect
|
||||
|
||||
RelativeDate = namedtuple('RelativeDate', ['days', 'minutes', 'time', 'is_after', 'base_date_name'], defaults=(0, None, None, False, 'date_from'))
|
||||
if TYPE_CHECKING:
|
||||
from .models import Event, Order, SubEvent
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BaseChoice:
|
||||
base: Literal["event", "order", "order.subevents"]
|
||||
attribute: str
|
||||
modifier: str
|
||||
text: Promise
|
||||
supports_before: bool
|
||||
supports_after: bool
|
||||
|
||||
@property
|
||||
def key(self) -> str:
|
||||
key = f"{self.base}__{self.attribute}"
|
||||
if self.modifier:
|
||||
key += f"__{self.modifier}"
|
||||
return key
|
||||
|
||||
@staticmethod
|
||||
def find(objects: Iterable["BaseChoice"], key: str) -> "BaseChoice":
|
||||
if "__" in key:
|
||||
choice = next((obj for obj in objects if obj.key == key), None)
|
||||
else:
|
||||
# fallback for RelativeDateFields stored, before support for bases other than event was added
|
||||
choice = next((obj for obj in objects if obj.attribute == key and obj.base == "event"), None)
|
||||
|
||||
if choice is None:
|
||||
raise TypeError(f"key {key} must be a valid key in BASE_CHOICES")
|
||||
|
||||
return choice
|
||||
|
||||
|
||||
BASE_CHOICES: List[BaseChoice] = [
|
||||
BaseChoice('event', 'date_from', "", _('Event start'), True, True),
|
||||
BaseChoice('event', 'date_to', "", _('Event end'), True, True),
|
||||
BaseChoice('event', 'date_admission', "", _('Event admission'), True, True),
|
||||
BaseChoice('event', 'presale_start', "", _('Presale start'), True, True),
|
||||
BaseChoice('event', 'presale_end', "", _('Presale end'), True, True),
|
||||
BaseChoice('order', 'datetime', "", _('Order creation'), False, True),
|
||||
BaseChoice('order', 'expires', "", _('Order expiry'), True, True),
|
||||
BaseChoice('order.subevents', 'date_from', "first", _('Subevent start (first subevent in order)'), True, True),
|
||||
BaseChoice('order.subevents', 'date_from', "last", _('Subevent start (last subevent in order)'), True, True),
|
||||
BaseChoice('order.subevents', 'date_to', "first", _('Subevent end (first subevent in order)'), True, True),
|
||||
BaseChoice('order.subevents', 'date_to', "last", _('Subevent end (last subevent in order)'), True, True),
|
||||
BaseChoice('order.subevents', 'date_admission', "first", _('Subevent admission (first subevent in order)'), True, True),
|
||||
BaseChoice('order.subevents', 'date_admission', "last", _('Subevent admission (last subevent in order)'), True, True),
|
||||
]
|
||||
|
||||
LIMIT_FALLBACKS = ['date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end']
|
||||
|
||||
EVENT_BASE_CHOICES = [
|
||||
x for x in BASE_CHOICES if x.base == 'event'
|
||||
]
|
||||
|
||||
ORDER_BASE_CHOICES = [
|
||||
x for x in BASE_CHOICES if x.base == 'order'
|
||||
]
|
||||
|
||||
SUBEVENT_BASE_CHOICES = [
|
||||
x for x in BASE_CHOICES if x.base == 'order.subevents'
|
||||
]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RelativeDate:
|
||||
"""
|
||||
This contains information on a date that is defined in relation to a fixed base point.
|
||||
This means that the underlying data is a fixed date as the base point and a number of days or a time interval
|
||||
to calculate the date.
|
||||
|
||||
The list of valid base date choices is defined in BASE_CHOICES.
|
||||
If the base_date_key is not set, the date_from attribute of Event is used.
|
||||
"""
|
||||
|
||||
days: int = 0
|
||||
minutes: int = None
|
||||
time: datetime.time = None
|
||||
is_after: bool = False
|
||||
base_date_name: str = 'event__date_from__'
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.is_after and not self._choice.supports_after:
|
||||
raise ValueError(
|
||||
"The selected base date and attribute combination does not support relative dates placed after the base date"
|
||||
)
|
||||
if not self.is_after and not self._choice.supports_before:
|
||||
raise ValueError(
|
||||
"The selected base date and attribute combination does not support relative dates placed before the base date")
|
||||
|
||||
@property
|
||||
def _choice(self):
|
||||
return BaseChoice.find(BASE_CHOICES, self.base_date_name)
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return self._choice.key
|
||||
|
||||
def __eq__(self, o: object) -> bool:
|
||||
if not isinstance(o, RelativeDate):
|
||||
return False
|
||||
return self.to_string() == o.to_string()
|
||||
|
||||
def _resolve_base_date(self, base: "Event | Order | SubEvent") -> Tuple[datetime.datetime, ZoneInfo]:
|
||||
"""
|
||||
|
||||
:param base:
|
||||
:return:
|
||||
"""
|
||||
from .models import Event, Order, SubEvent
|
||||
|
||||
choice = self._choice
|
||||
|
||||
if choice.base == "order" and isinstance(base, Order):
|
||||
event = base.event
|
||||
base_date = getattr(base, choice.attribute)
|
||||
elif choice.base == "order.subevents" and isinstance(base, Order):
|
||||
if not base.event.has_subevents:
|
||||
raise ValueError("The order is for an event without subevents")
|
||||
if choice.modifier == "first":
|
||||
op = base.all_positions.order_by(f"subevent__{choice.attribute}").first()
|
||||
if op is None:
|
||||
raise ValueError("The order has no positions for subevents")
|
||||
event = op.event
|
||||
base_date = getattr(op.subevent, choice.attribute)
|
||||
elif choice.modifier == "last":
|
||||
op = base.all_positions.order_by(f"subevent__{choice.attribute}").last()
|
||||
if op is None:
|
||||
raise ValueError("The order has no positions for subevents")
|
||||
event = op.event
|
||||
base_date = getattr(op.subevent, choice.attribute)
|
||||
else:
|
||||
raise ValueError("The selected modifier does not exist")
|
||||
elif choice.base == "event" and isinstance(base, SubEvent):
|
||||
event = base.event
|
||||
base_date = (getattr(base, choice.attribute) or
|
||||
getattr(base.event, choice.attribute) or
|
||||
base.date_from)
|
||||
elif choice.base == "event" and isinstance(base, Event):
|
||||
event = base
|
||||
base_date = getattr(base, choice.attribute) or event.date_from
|
||||
else:
|
||||
raise TypeError("The base defined by data does not match the passed in base")
|
||||
|
||||
tz = ZoneInfo(event.settings.timezone)
|
||||
return base_date, tz
|
||||
|
||||
def date(self, base: "Event | Order | SubEvent") -> datetime.date:
|
||||
if self.minutes is not None:
|
||||
raise ValueError('A minute-based relative datetime can not be used as a date')
|
||||
|
||||
base_date, tz = self._resolve_base_date(base)
|
||||
|
||||
if self.is_after:
|
||||
new_date = base_date.astimezone(tz) + datetime.timedelta(days=self.days)
|
||||
else:
|
||||
new_date = base_date.astimezone(tz) - datetime.timedelta(days=self.days)
|
||||
return new_date.date()
|
||||
|
||||
def datetime(self, base: "Event | Order | SubEvent") -> datetime.datetime:
|
||||
base_date, tz = self._resolve_base_date(base)
|
||||
|
||||
if self.minutes is not None:
|
||||
if self.is_after:
|
||||
return base_date.astimezone(tz) + datetime.timedelta(minutes=self.minutes)
|
||||
else:
|
||||
return base_date.astimezone(tz) - datetime.timedelta(minutes=self.minutes)
|
||||
else:
|
||||
if self.is_after:
|
||||
new_date = (base_date.astimezone(tz) + datetime.timedelta(days=self.days)).astimezone(tz)
|
||||
else:
|
||||
new_date = (base_date.astimezone(tz) - datetime.timedelta(days=self.days)).astimezone(tz)
|
||||
if self.time:
|
||||
new_date = new_date.replace(
|
||||
hour=self.time.hour,
|
||||
minute=self.time.minute,
|
||||
second=self.time.second
|
||||
)
|
||||
new_date = new_date.astimezone(tz)
|
||||
return new_date
|
||||
|
||||
def to_string(self) -> str:
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
if self.minutes is not None:
|
||||
return 'RELDATE/minutes/{}/{}/{}'.format( #
|
||||
self.minutes,
|
||||
self._choice.key,
|
||||
'after' if self.is_after else '',
|
||||
)
|
||||
return 'RELDATE/{}/{}/{}/{}'.format( #
|
||||
self.days,
|
||||
self.time.strftime('%H:%M:%S') if self.time else '-',
|
||||
self._choice.key,
|
||||
'after' if self.is_after else '',
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, input: str):
|
||||
"""
|
||||
|
||||
:param input:
|
||||
"""
|
||||
if not input.startswith('RELDATE/'):
|
||||
raise TypeError("Invalid input for RelativeDate.from_string()")
|
||||
|
||||
parts = input.split('/')
|
||||
if parts[1] == 'minutes':
|
||||
data = RelativeDate(
|
||||
days=0,
|
||||
minutes=int(parts[2]),
|
||||
base_date_name=parts[3],
|
||||
time=None,
|
||||
is_after=len(parts) > 4 and parts[4] == "after",
|
||||
)
|
||||
else:
|
||||
if parts[2] == '-':
|
||||
time = None
|
||||
else:
|
||||
timeparts = parts[2].split(':')
|
||||
time = datetime.time(hour=int(timeparts[0]), minute=int(timeparts[1]), second=int(timeparts[2]))
|
||||
try:
|
||||
data = RelativeDate(
|
||||
days=int(parts[1] or 0),
|
||||
base_date_name=parts[3],
|
||||
time=time,
|
||||
minutes=None,
|
||||
is_after=len(parts) > 4 and parts[4] == "after",
|
||||
)
|
||||
except ValueError:
|
||||
data = RelativeDate(
|
||||
days=0,
|
||||
base_date_name=parts[3],
|
||||
time=time,
|
||||
minutes=None,
|
||||
is_after=len(parts) > 4 and parts[4] == "after",
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class RelativeDateWrapper:
|
||||
@@ -50,130 +289,35 @@ class RelativeDateWrapper:
|
||||
This contains information on a date that might be relative to an event. This means
|
||||
that the underlying data is either a fixed date or a number of days and a wall clock
|
||||
time to calculate the date based on a base point.
|
||||
|
||||
The base point can be the date_from, date_to, date_admission, presale_start or presale_end
|
||||
attribute of an event or subevent. If the respective attribute is not set, ``date_from``
|
||||
will be used.
|
||||
"""
|
||||
|
||||
def __init__(self, data: Union[datetime.datetime, RelativeDate]):
|
||||
self.data = data
|
||||
|
||||
def date(self, event) -> datetime.date:
|
||||
from .models import SubEvent
|
||||
|
||||
def date(self, base: "Event | Order | SubEvent") -> datetime.date:
|
||||
if isinstance(self.data, datetime.datetime):
|
||||
return self.data.date()
|
||||
elif isinstance(self.data, datetime.date):
|
||||
return self.data
|
||||
else:
|
||||
if self.data.minutes is not None:
|
||||
raise ValueError('A minute-based relative datetime can not be used as a date')
|
||||
|
||||
tz = ZoneInfo(event.settings.timezone)
|
||||
if isinstance(event, SubEvent):
|
||||
base_date = (
|
||||
getattr(event, self.data.base_date_name)
|
||||
or getattr(event.event, self.data.base_date_name)
|
||||
or event.date_from
|
||||
)
|
||||
else:
|
||||
base_date = getattr(event, self.data.base_date_name) or event.date_from
|
||||
|
||||
if self.data.is_after:
|
||||
new_date = base_date.astimezone(tz) + datetime.timedelta(days=self.data.days)
|
||||
else:
|
||||
new_date = base_date.astimezone(tz) - datetime.timedelta(days=self.data.days)
|
||||
return new_date.date()
|
||||
|
||||
def datetime(self, event) -> datetime.datetime:
|
||||
from .models import SubEvent
|
||||
return self.data.date(base)
|
||||
|
||||
def datetime(self, base: "Event | Order | SubEvent") -> datetime.datetime:
|
||||
if isinstance(self.data, (datetime.datetime, datetime.date)):
|
||||
return self.data
|
||||
else:
|
||||
tz = ZoneInfo(event.settings.timezone)
|
||||
if isinstance(event, SubEvent):
|
||||
base_date = (
|
||||
getattr(event, self.data.base_date_name)
|
||||
or getattr(event.event, self.data.base_date_name)
|
||||
or event.date_from
|
||||
)
|
||||
else:
|
||||
base_date = getattr(event, self.data.base_date_name) or event.date_from
|
||||
|
||||
if self.data.minutes is not None:
|
||||
if self.data.is_after:
|
||||
return base_date.astimezone(tz) + datetime.timedelta(minutes=self.data.minutes)
|
||||
else:
|
||||
return base_date.astimezone(tz) - datetime.timedelta(minutes=self.data.minutes)
|
||||
else:
|
||||
if self.data.is_after:
|
||||
new_date = (base_date.astimezone(tz) + datetime.timedelta(days=self.data.days)).astimezone(tz)
|
||||
else:
|
||||
new_date = (base_date.astimezone(tz) - datetime.timedelta(days=self.data.days)).astimezone(tz)
|
||||
if self.data.time:
|
||||
new_date = new_date.replace(
|
||||
hour=self.data.time.hour,
|
||||
minute=self.data.time.minute,
|
||||
second=self.data.time.second
|
||||
)
|
||||
new_date = new_date.astimezone(tz)
|
||||
return new_date
|
||||
return self.data.datetime(base)
|
||||
|
||||
def to_string(self) -> str:
|
||||
if isinstance(self.data, (datetime.datetime, datetime.date)):
|
||||
return self.data.isoformat()
|
||||
else:
|
||||
if self.data.minutes is not None:
|
||||
return 'RELDATE/minutes/{}/{}/{}'.format( #
|
||||
self.data.minutes,
|
||||
self.data.base_date_name,
|
||||
'after' if self.data.is_after else '',
|
||||
)
|
||||
return 'RELDATE/{}/{}/{}/{}'.format( #
|
||||
self.data.days,
|
||||
self.data.time.strftime('%H:%M:%S') if self.data.time else '-',
|
||||
self.data.base_date_name,
|
||||
'after' if self.data.is_after else '',
|
||||
)
|
||||
return self.data.to_string()
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, input: str):
|
||||
if input.startswith('RELDATE/'):
|
||||
parts = input.split('/')
|
||||
if parts[1] == 'minutes':
|
||||
data = RelativeDate(
|
||||
days=0,
|
||||
minutes=int(parts[2]),
|
||||
base_date_name=parts[3],
|
||||
time=None,
|
||||
is_after=len(parts) > 4 and parts[4] == "after",
|
||||
)
|
||||
else:
|
||||
if parts[2] == '-':
|
||||
time = None
|
||||
else:
|
||||
timeparts = parts[2].split(':')
|
||||
time = datetime.time(hour=int(timeparts[0]), minute=int(timeparts[1]), second=int(timeparts[2]))
|
||||
try:
|
||||
data = RelativeDate(
|
||||
days=int(parts[1] or 0),
|
||||
base_date_name=parts[3],
|
||||
time=time,
|
||||
minutes=None,
|
||||
is_after=len(parts) > 4 and parts[4] == "after",
|
||||
)
|
||||
except ValueError:
|
||||
data = RelativeDate(
|
||||
days=0,
|
||||
base_date_name=parts[3],
|
||||
time=time,
|
||||
minutes=None,
|
||||
is_after=len(parts) > 4 and parts[4] == "after",
|
||||
)
|
||||
if data.base_date_name not in [k[0] for k in BASE_CHOICES]:
|
||||
raise ValueError('{} is not a valid base date'.format(data.base_date_name))
|
||||
data = RelativeDate.from_string(input)
|
||||
else:
|
||||
data = parser.parse(input)
|
||||
return RelativeDateWrapper(data)
|
||||
@@ -187,7 +331,6 @@ BEFORE_AFTER_CHOICE = (
|
||||
('after', _('after')),
|
||||
)
|
||||
|
||||
|
||||
reldatetimeparts = namedtuple('reldatetimeparts', (
|
||||
"status", # 0
|
||||
"absolute", # 1
|
||||
@@ -202,6 +345,14 @@ reldatetimeparts = namedtuple('reldatetimeparts', (
|
||||
reldatetimeparts.indizes = reldatetimeparts(*range(9))
|
||||
|
||||
|
||||
def _get_choices(base_choices: List[BaseChoice]) -> List[Tuple[str, Promise]]:
|
||||
return [(c.key, c.text) for c in base_choices]
|
||||
|
||||
|
||||
def _get_choice_validation_obj(choices: List[BaseChoice]):
|
||||
return {c.key: {"data-supports-before": c.supports_before, "data-supports-after": c.supports_after} for c in choices}
|
||||
|
||||
|
||||
class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
template_name = 'pretixbase/forms/widgets/reldatetime.html'
|
||||
parts = reldatetimeparts
|
||||
@@ -209,6 +360,7 @@ class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.status_choices = kwargs.pop('status_choices')
|
||||
base_choices = kwargs.pop('base_choices')
|
||||
choices = _get_choices(base_choices)
|
||||
|
||||
def placeholder_datetime_format():
|
||||
df = get_format('DATETIME_INPUT_FORMATS')[0]
|
||||
@@ -226,14 +378,22 @@ class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
attrs={'placeholder': lazy(placeholder_datetime_format, str), 'class': 'datetimepicker'}
|
||||
),
|
||||
rel_days_number=forms.NumberInput(),
|
||||
rel_mins_relationto=forms.Select(choices=base_choices),
|
||||
rel_mins_relationto=OptionAttrsSelect(
|
||||
attrs={'data-relative-choice': True},
|
||||
choices=choices,
|
||||
option_attrs=_get_choice_validation_obj(base_choices)
|
||||
),
|
||||
rel_days_timeofday=forms.TimeInput(
|
||||
attrs={'placeholder': lazy(placeholder_time_format, str), 'class': 'timepickerfield'}
|
||||
),
|
||||
rel_mins_number=forms.NumberInput(),
|
||||
rel_days_relationto=forms.Select(choices=base_choices),
|
||||
rel_mins_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||
rel_days_relationto=OptionAttrsSelect(
|
||||
attrs={'data-relative-choice': True},
|
||||
choices=choices,
|
||||
option_attrs=_get_choice_validation_obj(base_choices)
|
||||
),
|
||||
rel_mins_relation=forms.Select(attrs={'data-relation-choice': True}, choices=BEFORE_AFTER_CHOICE),
|
||||
rel_days_relation=forms.Select(attrs={'data-relation-choice': True}, choices=BEFORE_AFTER_CHOICE),
|
||||
)
|
||||
super().__init__(widgets=widgets, *args, **kwargs)
|
||||
|
||||
@@ -247,10 +407,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
status="unset",
|
||||
absolute=None,
|
||||
rel_days_number=1,
|
||||
rel_mins_relationto="date_from",
|
||||
rel_mins_relationto="event__date_from",
|
||||
rel_days_timeofday=None,
|
||||
rel_mins_number=0,
|
||||
rel_days_relationto="date_from",
|
||||
rel_days_relationto="event__date_from",
|
||||
rel_mins_relation="before",
|
||||
rel_days_relation="before"
|
||||
)
|
||||
@@ -259,10 +419,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
status="absolute",
|
||||
absolute=value.data,
|
||||
rel_days_number=1,
|
||||
rel_mins_relationto="date_from",
|
||||
rel_mins_relationto="event__date_from",
|
||||
rel_days_timeofday=None,
|
||||
rel_mins_number=0,
|
||||
rel_days_relationto="date_from",
|
||||
rel_days_relationto="event__date_from",
|
||||
rel_mins_relation="before",
|
||||
rel_days_relation="before"
|
||||
)
|
||||
@@ -271,10 +431,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
status="relative_minutes",
|
||||
absolute=None,
|
||||
rel_days_number=None,
|
||||
rel_mins_relationto=value.data.base_date_name,
|
||||
rel_mins_relationto=value.data.key,
|
||||
rel_days_timeofday=None,
|
||||
rel_mins_number=value.data.minutes,
|
||||
rel_days_relationto=value.data.base_date_name,
|
||||
rel_days_relationto=value.data.key,
|
||||
rel_mins_relation="after" if value.data.is_after else "before",
|
||||
rel_days_relation="after" if value.data.is_after else "before"
|
||||
)
|
||||
@@ -282,10 +442,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
status="relative",
|
||||
absolute=None,
|
||||
rel_days_number=value.data.days,
|
||||
rel_mins_relationto=value.data.base_date_name,
|
||||
rel_mins_relationto=value.data.key,
|
||||
rel_days_timeofday=value.data.time,
|
||||
rel_mins_number=0,
|
||||
rel_days_relationto=value.data.base_date_name,
|
||||
rel_days_relationto=value.data.key,
|
||||
rel_mins_relation="after" if value.data.is_after else "before",
|
||||
rel_days_relation="after" if value.data.is_after else "before"
|
||||
)
|
||||
@@ -309,17 +469,42 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
('relative', _('Relative date:')),
|
||||
('relative_minutes', _('Relative time:')),
|
||||
]
|
||||
self.relative_to_order = kwargs.pop('relative_to_order', False)
|
||||
self.relative_to_subevent_positions = kwargs.pop('relative_to_subevent_positions', False)
|
||||
|
||||
possible_choices = copy.deepcopy(EVENT_BASE_CHOICES)
|
||||
if self.relative_to_order:
|
||||
possible_choices.extend(ORDER_BASE_CHOICES)
|
||||
if self.relative_to_subevent_positions:
|
||||
possible_choices.extend(SUBEVENT_BASE_CHOICES)
|
||||
|
||||
if kwargs.get('limit_choices'):
|
||||
limit = kwargs.pop('limit_choices')
|
||||
choices = [(k, v) for k, v in BASE_CHOICES if k in limit]
|
||||
else:
|
||||
choices = BASE_CHOICES
|
||||
if any(["__" not in l for l in limit]):
|
||||
_warn_skips = (os.path.dirname(__file__),)
|
||||
warnings.warn(
|
||||
"Please prefix limit_choices with the base the attributes refer to, for example event__date_from",
|
||||
skip_file_prefixes=_warn_skips
|
||||
)
|
||||
|
||||
possible_choices = [
|
||||
c for c in possible_choices if
|
||||
# new base case as we want limit_choices to be expressed as base__attribute
|
||||
(c.key in limit) or
|
||||
# fallback for old event based entries
|
||||
# if the base is an event, then using only attribute is fine
|
||||
(c.base == "event" and c.attribute in limit)
|
||||
]
|
||||
|
||||
if not kwargs.get('required', True):
|
||||
status_choices.insert(0, ('unset', _('Not set')))
|
||||
|
||||
choices = _get_choices(possible_choices)
|
||||
|
||||
fields = reldatetimeparts(
|
||||
status=forms.ChoiceField(
|
||||
choices=status_choices,
|
||||
required=True
|
||||
required=True,
|
||||
),
|
||||
absolute=forms.DateTimeField(
|
||||
required=False
|
||||
@@ -329,7 +514,7 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
),
|
||||
rel_mins_relationto=forms.ChoiceField(
|
||||
choices=choices,
|
||||
required=False
|
||||
required=False,
|
||||
),
|
||||
rel_days_timeofday=forms.TimeField(
|
||||
required=False,
|
||||
@@ -339,7 +524,7 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
),
|
||||
rel_days_relationto=forms.ChoiceField(
|
||||
choices=choices,
|
||||
required=False
|
||||
required=False,
|
||||
),
|
||||
rel_mins_relation=forms.ChoiceField(
|
||||
choices=BEFORE_AFTER_CHOICE,
|
||||
@@ -350,8 +535,9 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
required=False
|
||||
),
|
||||
)
|
||||
|
||||
if 'widget' not in kwargs:
|
||||
kwargs['widget'] = RelativeDateTimeWidget(status_choices=status_choices, base_choices=choices)
|
||||
kwargs['widget'] = RelativeDateTimeWidget(status_choices=status_choices, base_choices=possible_choices)
|
||||
kwargs.pop('max_length', 0)
|
||||
kwargs.pop('empty_value', 0)
|
||||
super().__init__(
|
||||
@@ -359,21 +545,26 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
)
|
||||
|
||||
def set_event(self, event):
|
||||
self.widget.widgets[reldatetimeparts.indizes.rel_days_relationto].choices = [
|
||||
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
|
||||
]
|
||||
self.widget.widgets[reldatetimeparts.indizes.rel_mins_relationto].choices = [
|
||||
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
|
||||
]
|
||||
possible_choices = copy.deepcopy(EVENT_BASE_CHOICES)
|
||||
if self.relative_to_order:
|
||||
possible_choices.extend(ORDER_BASE_CHOICES)
|
||||
if self.relative_to_subevent_positions and event.has_subevents:
|
||||
possible_choices.extend(SUBEVENT_BASE_CHOICES)
|
||||
|
||||
possible_choices = possible_choices
|
||||
choices = _get_choices(possible_choices)
|
||||
|
||||
self.widget.widgets[reldatetimeparts.indizes.rel_days_relationto].choices = choices
|
||||
self.widget.widgets[reldatetimeparts.indizes.rel_mins_relationto].choices = choices
|
||||
|
||||
def compress(self, data_list):
|
||||
if not data_list:
|
||||
return None
|
||||
data = reldatetimeparts(*data_list)
|
||||
if data.status == 'absolute':
|
||||
return RelativeDateWrapper(data.absolute)
|
||||
elif data.status == 'unset':
|
||||
if data.status == 'unset':
|
||||
return None
|
||||
elif data.status == 'absolute':
|
||||
return RelativeDateWrapper(data.absolute)
|
||||
elif data.status == 'relative_minutes':
|
||||
return RelativeDateWrapper(RelativeDate(
|
||||
days=0,
|
||||
@@ -404,6 +595,18 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
elif data.status == 'relative_minutes' and (data.rel_mins_number is None or not data.rel_mins_relationto):
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
elif data.status == 'relative':
|
||||
choice = BaseChoice.find(BASE_CHOICES, data.rel_days_relationto)
|
||||
if data.rel_days_relation == "before" and not choice.supports_before:
|
||||
raise ValidationError(_('A relative date cannot be expressed as "before" for "{}"'.format(choice.text)))
|
||||
elif data.status == 'relative' and data.rel_days_relation == "after" and not choice.supports_after:
|
||||
raise ValidationError(_('A relative date cannot be expressed as "after" for "{}"'.format(choice.text)))
|
||||
elif data.status == 'relative_minutes':
|
||||
choice = BaseChoice.find(BASE_CHOICES, data.rel_days_relationto)
|
||||
if data.rel_days_relation == "before" and not choice.supports_before:
|
||||
raise ValidationError(_('A relative time cannot be expressed as "before" for "{}"'.format(choice.text)))
|
||||
elif data.rel_days_relation == "after" and not choice.supports_after:
|
||||
raise ValidationError(_('A relative time cannot be expressed as "after" for "{}"'.format(choice.text)))
|
||||
|
||||
return super().clean(value)
|
||||
|
||||
@@ -424,14 +627,20 @@ class RelativeDateWidget(RelativeDateTimeWidget):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.status_choices = kwargs.pop('status_choices')
|
||||
self.base_choices = kwargs.pop('base_choices')
|
||||
|
||||
widgets = reldateparts(
|
||||
status=forms.RadioSelect(choices=self.status_choices),
|
||||
absolute=forms.DateInput(
|
||||
attrs={'class': 'datepickerfield'}
|
||||
),
|
||||
rel_days_number=forms.NumberInput(),
|
||||
rel_days_relationto=forms.Select(choices=kwargs.pop('base_choices')),
|
||||
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||
rel_days_relationto=OptionAttrsSelect(
|
||||
choices=self.base_choices,
|
||||
option_attrs=_get_choice_validation_obj(self.base_choices),
|
||||
attrs={'data-relative-choice': True},
|
||||
),
|
||||
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE, attrs={'data-relation-choice': True},),
|
||||
)
|
||||
forms.MultiWidget.__init__(self, widgets=widgets, *args, **kwargs)
|
||||
|
||||
@@ -460,7 +669,7 @@ class RelativeDateWidget(RelativeDateTimeWidget):
|
||||
status="relative",
|
||||
absolute=None,
|
||||
rel_days_number=value.data.days,
|
||||
rel_days_relationto=value.data.base_date_name,
|
||||
rel_days_relationto=value.data.key,
|
||||
rel_days_relation="after" if value.data.is_after else "before"
|
||||
)
|
||||
|
||||
@@ -474,6 +683,18 @@ class RelativeDateField(RelativeDateTimeField):
|
||||
]
|
||||
if not kwargs.get('required', True):
|
||||
status_choices.insert(0, ('unset', _('Not set')))
|
||||
|
||||
self.relative_to_order = kwargs.pop('relative_to_order', False)
|
||||
self.relative_to_subevent_positions = kwargs.pop('relative_to_subevent_positions', False)
|
||||
|
||||
possible_choices = copy.deepcopy(EVENT_BASE_CHOICES)
|
||||
if self.relative_to_order:
|
||||
possible_choices.extend(ORDER_BASE_CHOICES)
|
||||
if self.relative_to_subevent_positions:
|
||||
possible_choices.extend(SUBEVENT_BASE_CHOICES)
|
||||
|
||||
choices = _get_choices(possible_choices)
|
||||
|
||||
fields = reldateparts(
|
||||
status=forms.ChoiceField(
|
||||
choices=status_choices,
|
||||
@@ -486,33 +707,39 @@ class RelativeDateField(RelativeDateTimeField):
|
||||
required=False
|
||||
),
|
||||
rel_days_relationto=forms.ChoiceField(
|
||||
choices=BASE_CHOICES,
|
||||
required=False
|
||||
choices=choices,
|
||||
required=False,
|
||||
),
|
||||
rel_days_relation=forms.ChoiceField(
|
||||
choices=BEFORE_AFTER_CHOICE,
|
||||
required=False
|
||||
),
|
||||
)
|
||||
|
||||
if 'widget' not in kwargs:
|
||||
kwargs['widget'] = RelativeDateWidget(status_choices=status_choices, base_choices=BASE_CHOICES)
|
||||
kwargs['widget'] = RelativeDateWidget(status_choices=status_choices, base_choices=possible_choices)
|
||||
forms.MultiValueField.__init__(
|
||||
self, fields=fields, require_all_fields=False, *args, **kwargs
|
||||
)
|
||||
|
||||
def set_event(self, event):
|
||||
self.widget.widgets[reldateparts.indizes.rel_days_relationto].choices = [
|
||||
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
|
||||
choices = [
|
||||
(c.key, c.text) for c in EVENT_BASE_CHOICES if getattr(event, c.attribute, None)
|
||||
]
|
||||
if self.relative_to_order:
|
||||
choices += [(c.key, c.text) for c in ORDER_BASE_CHOICES]
|
||||
if self.relative_to_subevent_positions and event.has_subevents:
|
||||
choices += [(c.key, c.text) for c in SUBEVENT_BASE_CHOICES]
|
||||
self.widget.widgets[reldateparts.indizes.rel_days_relationto].choices = choices
|
||||
|
||||
def compress(self, data_list):
|
||||
if not data_list:
|
||||
return None
|
||||
data = reldateparts(*data_list)
|
||||
if data.status == 'absolute':
|
||||
return RelativeDateWrapper(data.absolute)
|
||||
elif data.status == 'unset':
|
||||
if data.status == 'unset':
|
||||
return None
|
||||
elif data.status == 'absolute':
|
||||
return RelativeDateWrapper(data.absolute)
|
||||
else:
|
||||
return RelativeDateWrapper(RelativeDate(
|
||||
days=data.rel_days_number,
|
||||
@@ -525,8 +752,14 @@ class RelativeDateField(RelativeDateTimeField):
|
||||
data = reldateparts(*value)
|
||||
if data.status == 'absolute' and not data.absolute:
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
elif data.status == 'relative' and (data.rel_days_number is None or not data.rel_days_relationto):
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
if data.status == 'relative':
|
||||
choice = BaseChoice.find(BASE_CHOICES, data.rel_days_relationto)
|
||||
if data.rel_days_number is None or not data.rel_days_relationto:
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
elif data.rel_days_relation == "before" and not choice.supports_before:
|
||||
raise ValidationError(_("A relative date cannot be expressed as 'before' for '{}'".format(choice.text)))
|
||||
elif data.rel_days_relation == "after" and not choice.supports_after:
|
||||
raise ValidationError(_("A relative date cannot be expressed as 'after' for '{}'".format(choice.text)))
|
||||
|
||||
return forms.MultiValueField.clean(self, value)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
<div class="reldatetime">
|
||||
{% for group_name, group_choices, group-index in widget.subwidgets.0.optgroups %}
|
||||
{% for selopt in group_choices %}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
<div class="reldatetime">
|
||||
{% for group_name, group_choices, group-index in widget.subwidgets.0.optgroups %}
|
||||
{% for selopt in group_choices %}
|
||||
|
||||
@@ -93,14 +93,14 @@ class SubEventBulkForm(SubEventForm):
|
||||
label=_('Start of presale'),
|
||||
help_text=_('Optional. No products will be sold before this date.'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
limit_choices=('event__date_from', 'event__date_to'),
|
||||
)
|
||||
rel_presale_end = RelativeDateTimeField(
|
||||
label=_('End of presale'),
|
||||
help_text=_('Optional. No products will be sold after this date. If you do not set this value, the presale '
|
||||
'will end after the end date of your event.'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
limit_choices=('event__date_from', 'event__date_to'),
|
||||
)
|
||||
skip_if_overlap = forms.BooleanField(
|
||||
label=pgettext_lazy('subevent', 'Skip dates that overlap with any existing date'),
|
||||
@@ -332,12 +332,12 @@ class BulkSubEventItemForm(SubEventItemForm):
|
||||
rel_available_from = RelativeDateTimeField(
|
||||
label=_('Available from'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
limit_choices=('event__date_from', 'event__date_to'),
|
||||
)
|
||||
rel_available_until = RelativeDateTimeField(
|
||||
label=_('Available until'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
limit_choices=('event__date_from', 'event__date_to'),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -354,12 +354,12 @@ class BulkSubEventItemVariationForm(SubEventItemVariationForm):
|
||||
rel_available_from = RelativeDateTimeField(
|
||||
label=_('Available from'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
limit_choices=('event__date_from', 'event__date_to'),
|
||||
)
|
||||
rel_available_until = RelativeDateTimeField(
|
||||
label=_('Available_until'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
limit_choices=('event__date_from', 'event__date_to'),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
<script type="text/javascript" src="{% static "leaflet/leaflet.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/geo.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/details.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/reldate.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/asynctask.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "sortable/Sortable.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "colorpicker/bootstrap-colorpicker.js" %}"></script>
|
||||
|
||||
@@ -430,7 +430,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
days = (self.copy_from.date_from.astimezone(tz).date() - value.astimezone(tz).date()).days
|
||||
return RelativeDateWrapper(RelativeDate(
|
||||
days=abs(days),
|
||||
base_date_name='date_from',
|
||||
base_date_name='event__date_from',
|
||||
time=value.astimezone(tz).time(),
|
||||
minutes=None,
|
||||
is_after=days < 0,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
if (!window.__reldateInitialized) {
|
||||
window.__reldateInitialized = true;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
document.querySelectorAll('.reldatetime, .reldate').forEach(container => {
|
||||
const groups = container.querySelectorAll('.radio');
|
||||
|
||||
groups.forEach(group => {
|
||||
const referenceSelect = group.querySelector('select[data-relative-choice]');
|
||||
const beforeAfterSelect = group.querySelector('select[data-relation-choice]');
|
||||
if (!referenceSelect || !beforeAfterSelect) return;
|
||||
|
||||
const beforeOption = beforeAfterSelect.querySelector('option[value="before"]');
|
||||
const afterOption = beforeAfterSelect.querySelector('option[value="after"]');
|
||||
|
||||
const updateBeforeOption = () => {
|
||||
let supportsBefore = referenceSelect.selectedOptions[0].hasAttribute('data-supports-before')
|
||||
if (beforeOption){
|
||||
beforeOption.disabled = !beforeOption.disabled && !supportsBefore;
|
||||
}
|
||||
|
||||
let supportsAfter = referenceSelect.selectedOptions[0].hasAttribute('data-supports-after')
|
||||
if (afterOption){
|
||||
afterOption.disabled = !afterOption.disabled && !supportsAfter;
|
||||
}
|
||||
|
||||
let dirty = false;
|
||||
if (beforeOption.disabled && beforeAfterSelect.value === 'before') {
|
||||
beforeAfterSelect.value = 'after';
|
||||
dirty = true;
|
||||
}
|
||||
if (afterOption.disabled && beforeAfterSelect.value === 'after'){
|
||||
beforeAfterSelect.value = 'before';
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (dirty){
|
||||
beforeAfterSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
|
||||
};
|
||||
referenceSelect.addEventListener('change', updateBeforeOption);
|
||||
updateBeforeOption();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -473,7 +473,7 @@ class QuotaTestCase(BaseQuotaTestCase):
|
||||
@classscope(attr='o')
|
||||
def test_waitinglist_auto_disable(self):
|
||||
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper(
|
||||
RelativeDate(days=0, time=None, base_date_name='date_from', minutes=20, is_after=True)
|
||||
RelativeDate(days=0, time=None, base_date_name='event__date_from', minutes=20, is_after=True)
|
||||
)
|
||||
self.quota.items.add(self.item1)
|
||||
self.quota.size = 1
|
||||
@@ -484,7 +484,7 @@ class QuotaTestCase(BaseQuotaTestCase):
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_ORDERED, 0))
|
||||
self.assertEqual(self.item1.check_quotas(count_waitinglist=False), (Quota.AVAILABILITY_OK, 1))
|
||||
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper(
|
||||
RelativeDate(days=0, time=None, base_date_name='date_from', minutes=20, is_after=False)
|
||||
RelativeDate(days=0, time=None, base_date_name='event__date_from', minutes=20, is_after=False)
|
||||
)
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
|
||||
self.assertEqual(self.item1.check_quotas(count_waitinglist=False), (Quota.AVAILABILITY_OK, 1))
|
||||
@@ -1192,7 +1192,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.op2.subevent = se2
|
||||
self.op2.save()
|
||||
self.event.settings.set('payment_term_last', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
|
||||
self.order.status = Order.STATUS_EXPIRED
|
||||
@@ -1347,7 +1347,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.op2.subevent = se2
|
||||
self.op2.save()
|
||||
self.event.settings.set('last_order_modification_date', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert self.order.can_modify_answers
|
||||
self.op2.subevent = se3
|
||||
@@ -1363,7 +1363,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.event.date_from = datetime.datetime(2017, 5, 3, 12, 0, 0, tzinfo=datetime.timezone.utc)
|
||||
self.event.save()
|
||||
self.event.settings.set('payment_term_last', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert self.order.payment_term_last == datetime.datetime(2017, 5, 1, 23, 59, 59, tzinfo=datetime.timezone.utc)
|
||||
|
||||
@@ -1379,7 +1379,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.op2.subevent = se2
|
||||
self.op2.save()
|
||||
self.event.settings.set('payment_term_last', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert self.order.payment_term_last > now()
|
||||
self.op2.subevent = se3
|
||||
@@ -1395,7 +1395,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.event.date_from = datetime.datetime(2017, 5, 3, 12, 0, 0, tzinfo=datetime.timezone.utc)
|
||||
self.event.save()
|
||||
self.event.settings.set('ticket_download_date', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert self.order.ticket_download_date == datetime.datetime(2017, 5, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)
|
||||
|
||||
@@ -1411,7 +1411,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.op2.subevent = se2
|
||||
self.op2.save()
|
||||
self.event.settings.set('ticket_download_date', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert self.order.ticket_download_date > now()
|
||||
self.op2.subevent = se3
|
||||
@@ -1582,13 +1582,13 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
|
||||
assert self.order.user_cancel_deadline is None
|
||||
self.event.settings.set('cancel_allow_user_until', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
self.order = Order.objects.get(pk=self.order.pk)
|
||||
assert self.order.user_cancel_deadline > now()
|
||||
assert self.order.user_cancel_allowed
|
||||
self.event.settings.set('cancel_allow_user_until', RelativeDateWrapper(
|
||||
RelativeDate(days=4, time=datetime.time(14, 0, 0), base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=4, time=datetime.time(14, 0, 0), base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
self.order = Order.objects.get(pk=self.order.pk)
|
||||
assert self.order.user_cancel_deadline < now()
|
||||
@@ -1607,7 +1607,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.op2.save()
|
||||
|
||||
self.event.settings.set('cancel_allow_user_until', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
self.order = Order.objects.get(pk=self.order.pk)
|
||||
assert self.order.user_cancel_deadline < now()
|
||||
@@ -2018,13 +2018,13 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
|
||||
assert self.order.user_change_deadline is None
|
||||
self.event.settings.set('change_allow_user_until', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
self.order = Order.objects.get(pk=self.order.pk)
|
||||
assert self.order.user_change_deadline > now()
|
||||
assert self.order.user_change_allowed
|
||||
self.event.settings.set('change_allow_user_until', RelativeDateWrapper(
|
||||
RelativeDate(days=4, time=datetime.time(14, 0, 0), base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=4, time=datetime.time(14, 0, 0), base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
self.order = Order.objects.get(pk=self.order.pk)
|
||||
assert self.order.user_change_deadline < now()
|
||||
@@ -2046,7 +2046,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.op2.save()
|
||||
|
||||
self.event.settings.set('change_allow_user_until', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
self.order = Order.objects.get(pk=self.order.pk)
|
||||
assert self.order.user_change_deadline < now()
|
||||
|
||||
@@ -204,7 +204,7 @@ def test_expiry_last_relative(event):
|
||||
event.date_from = now() + timedelta(days=5)
|
||||
event.save()
|
||||
event.settings.set('payment_term_last', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
order = _create_order(event, email='dummy@example.org', positions=[],
|
||||
now_dt=today,
|
||||
@@ -245,7 +245,7 @@ def test_expiry_last_relative_subevents(event):
|
||||
)
|
||||
|
||||
event.settings.set('payment_term_last', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
order = _create_order(event, email='dummy@example.org', positions=[cp1, cp2],
|
||||
now_dt=today,
|
||||
|
||||
@@ -133,7 +133,7 @@ def test_availability_date_relative(event):
|
||||
event.save()
|
||||
prov = DummyPaymentProvider(event)
|
||||
prov.settings.set('_availability_date', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
|
||||
utc = datetime.timezone.utc
|
||||
@@ -150,7 +150,7 @@ def test_availability_start_relative(event):
|
||||
event.save()
|
||||
prov = DummyPaymentProvider(event)
|
||||
prov.settings.set('_availability_start', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=datetime.time(12, 0), base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=datetime.time(12, 0), base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
|
||||
utc = datetime.timezone.utc
|
||||
@@ -193,12 +193,12 @@ def test_availability_date_cart_relative_subevents(event):
|
||||
|
||||
prov = DummyPaymentProvider(event)
|
||||
prov.settings.set('_availability_date', RelativeDateWrapper(
|
||||
RelativeDate(days=3, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=3, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert prov._is_available_by_time(cart_id="123")
|
||||
|
||||
prov.settings.set('_availability_date', RelativeDateWrapper(
|
||||
RelativeDate(days=4, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=4, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert not prov._is_available_by_time(cart_id="123")
|
||||
|
||||
@@ -233,11 +233,11 @@ def test_availability_date_order_relative_subevents(event):
|
||||
|
||||
prov = DummyPaymentProvider(event)
|
||||
prov.settings.set('_availability_date', RelativeDateWrapper(
|
||||
RelativeDate(days=3, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=3, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert prov._is_available_by_time(order=order)
|
||||
|
||||
prov.settings.set('_availability_date', RelativeDateWrapper(
|
||||
RelativeDate(days=4, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=4, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert not prov._is_available_by_time(order=order)
|
||||
|
||||
+166
-37
@@ -19,13 +19,14 @@
|
||||
# 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 datetime import datetime, time
|
||||
from datetime import datetime, time, timedelta
|
||||
from decimal import Decimal
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytest
|
||||
from django_scopes import scope
|
||||
from django_scopes import scope, scopes_disabled
|
||||
|
||||
from pretix.base.models import Event, Organizer
|
||||
from pretix.base.models import Event, Order, OrderPosition, Organizer
|
||||
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
|
||||
|
||||
TOKYO = ZoneInfo('Asia/Tokyo')
|
||||
@@ -40,7 +41,6 @@ def event():
|
||||
date_from=datetime(2017, 12, 27, 5, 0, 0, tzinfo=TOKYO),
|
||||
presale_start=datetime(2017, 12, 1, 5, 0, 0, tzinfo=TOKYO),
|
||||
plugins='pretix.plugins.banktransfer'
|
||||
|
||||
)
|
||||
event.settings.timezone = "Asia/Tokyo"
|
||||
return event
|
||||
@@ -56,57 +56,57 @@ def test_absolute_date(event):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_relative_date_without_time(event):
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='date_from', minutes=None))
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='event__date_from', minutes=None))
|
||||
assert rdw.datetime(event).astimezone(TOKYO) == datetime(2017, 12, 26, 5, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='date_from', minutes=None, is_after=True))
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='event__date_from', minutes=None, is_after=True))
|
||||
assert rdw.datetime(event).astimezone(TOKYO) == datetime(2017, 12, 28, 5, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/date_from/after'
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__date_from/after'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_relative_date_other_base_point(event):
|
||||
with scope(organizer=event.organizer):
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='presale_start', minutes=None))
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='event__presale_start', minutes=None))
|
||||
assert rdw.datetime(event) == datetime(2017, 11, 30, 5, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/presale_start/'
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__presale_start/'
|
||||
|
||||
# presale_end is unset, defaults to date_from
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='presale_end', minutes=None))
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='event__presale_end', minutes=None))
|
||||
assert rdw.datetime(event) == datetime(2017, 12, 26, 5, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/presale_end/'
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__presale_end/'
|
||||
|
||||
# subevent base
|
||||
se = event.subevents.create(name="SE1", date_from=datetime(2017, 11, 27, 5, 0, 0, tzinfo=TOKYO))
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='date_from', minutes=None))
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='event__date_from', minutes=None))
|
||||
assert rdw.datetime(se) == datetime(2017, 11, 26, 5, 0, 0, tzinfo=TOKYO)
|
||||
|
||||
# presale_start is unset on subevent, default to event
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='presale_start', minutes=None))
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='event__presale_start', minutes=None))
|
||||
assert rdw.datetime(se) == datetime(2017, 11, 30, 5, 0, 0, tzinfo=TOKYO)
|
||||
|
||||
# presale_end is unset on all, default to date_from of subevent
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='presale_end', minutes=None))
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='event__presale_end', minutes=None))
|
||||
assert rdw.datetime(se) == datetime(2017, 11, 26, 5, 0, 0, tzinfo=TOKYO)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_relative_date_in_minutes(event):
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=None, base_date_name='date_from', minutes=60))
|
||||
assert rdw.to_string() == 'RELDATE/minutes/60/date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=None, base_date_name='event__date_from', minutes=60))
|
||||
assert rdw.to_string() == 'RELDATE/minutes/60/event__date_from/'
|
||||
assert rdw.datetime(event) == datetime(2017, 12, 27, 4, 0, 0, tzinfo=TOKYO)
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=None, base_date_name='date_from', minutes=60, is_after=True))
|
||||
assert rdw.to_string() == 'RELDATE/minutes/60/date_from/after'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=None, base_date_name='event__date_from', minutes=60, is_after=True))
|
||||
assert rdw.to_string() == 'RELDATE/minutes/60/event__date_from/after'
|
||||
assert rdw.datetime(event) == datetime(2017, 12, 27, 6, 0, 0, tzinfo=TOKYO)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_relative_date_with_time(event):
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(8, 5, 13), base_date_name='date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/1/08:05:13/date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(8, 5, 13), base_date_name='event__date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/1/08:05:13/event__date_from/'
|
||||
assert rdw.datetime(event) == datetime(2017, 12, 26, 8, 5, 13, tzinfo=TOKYO)
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(8, 5, 13), base_date_name='date_from', minutes=None, is_after=True))
|
||||
assert rdw.to_string() == 'RELDATE/1/08:05:13/date_from/after'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(8, 5, 13), base_date_name='event__date_from', minutes=None, is_after=True))
|
||||
assert rdw.to_string() == 'RELDATE/1/08:05:13/event__date_from/after'
|
||||
assert rdw.datetime(event) == datetime(2017, 12, 28, 8, 5, 13, tzinfo=TOKYO)
|
||||
|
||||
|
||||
@@ -115,35 +115,164 @@ def test_relative_date_with_time_around_dst(event):
|
||||
event.settings.timezone = "Europe/Berlin"
|
||||
event.date_from = datetime(2020, 3, 29, 18, 0, 0, tzinfo=BERLIN)
|
||||
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(18, 0, 0), base_date_name='date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/1/18:00:00/date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(18, 0, 0), base_date_name='event__date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/1/18:00:00/event__date_from/'
|
||||
assert rdw.datetime(event) == datetime(2020, 3, 28, 18, 0, 0, tzinfo=BERLIN)
|
||||
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=time(2, 30, 0), base_date_name='date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/0/02:30:00/date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=time(2, 30, 0), base_date_name='event__date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/0/02:30:00/event__date_from/'
|
||||
assert rdw.datetime(event) == datetime(2020, 3, 29, 2, 30, 0, tzinfo=BERLIN)
|
||||
|
||||
event.date_from = datetime(2020, 10, 25, 18, 0, 0, tzinfo=BERLIN)
|
||||
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(18, 0, 0), base_date_name='date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/1/18:00:00/date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(18, 0, 0), base_date_name='event__date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/1/18:00:00/event__date_from/'
|
||||
assert rdw.datetime(event) == datetime(2020, 10, 24, 18, 0, 0, tzinfo=BERLIN)
|
||||
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=time(2, 30, 0), base_date_name='date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/0/02:30:00/date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=time(2, 30, 0), base_date_name='event__date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/0/02:30:00/event__date_from/'
|
||||
assert rdw.datetime(event) == datetime(2020, 10, 25, 2, 30, 0, tzinfo=BERLIN)
|
||||
|
||||
|
||||
def test_unserialize():
|
||||
def test_unserialize_backwards_compatibility():
|
||||
d = datetime(2017, 12, 25, 10, 0, 0, tzinfo=TOKYO)
|
||||
rdw = RelativeDateWrapper.from_string(d.isoformat())
|
||||
assert rdw.data == d
|
||||
|
||||
# keeping the test for the old from_string_format to ensure that we don't break anything
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/-/date_from/')
|
||||
assert rdw.data == RelativeDate(days=1, time=None, base_date_name='date_from', minutes=None)
|
||||
|
||||
# keeping the test for the old from_string_format to ensure that we don't break anything
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/18:05:13/date_from/')
|
||||
assert rdw.data == RelativeDate(days=1, time=time(18, 5, 13), base_date_name='date_from', minutes=None)
|
||||
|
||||
# keeping the test for the old from_string_format to ensure that we don't break anything
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/minutes/60/date_from/')
|
||||
assert rdw.data == RelativeDate(days=0, time=None, base_date_name='date_from', minutes=60)
|
||||
|
||||
|
||||
def test_backwards_compatibility():
|
||||
# the data model of RelativeDate had to be extended to support other models as relation target
|
||||
# previously only:
|
||||
# - date_from
|
||||
# - date_to
|
||||
# - date_admission
|
||||
# - presale_start
|
||||
# - presale_end
|
||||
# where valid values for the fourth slot (base_date_names) of the serialized form.
|
||||
# the relationship in this case always pointed at event
|
||||
# so any preexisting base_date_names without __ should continue to work and upgrade to event__{old_base_date_name}
|
||||
d = datetime(2017, 12, 25, 10, 0, 0, tzinfo=TOKYO)
|
||||
rdw = RelativeDateWrapper.from_string(d.isoformat())
|
||||
assert rdw.data == d
|
||||
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/-/date_from/')
|
||||
assert rdw.data == RelativeDate(days=1, time=None, base_date_name='date_from', minutes=None)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__date_from/'
|
||||
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/18:05:13/date_from/')
|
||||
assert rdw.data == RelativeDate(days=1, time=time(18, 5, 13), base_date_name='date_from', minutes=None)
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/-/date_to/')
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__date_to/'
|
||||
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/minutes/60/date_from/')
|
||||
assert rdw.data == RelativeDate(days=0, time=None, base_date_name='date_from', minutes=60)
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/-/date_admission/')
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__date_admission/'
|
||||
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/-/presale_start/')
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__presale_start/'
|
||||
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/-/presale_end/')
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__presale_end/'
|
||||
|
||||
# new order base_date_names should not work without __
|
||||
with pytest.raises(TypeError):
|
||||
RelativeDateWrapper.from_string('RELDATE/1/-/datetime/')
|
||||
with pytest.raises(TypeError):
|
||||
RelativeDateWrapper.from_string('RELDATE/1/-/expires/')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_relative_to_order(event):
|
||||
with scope(organizer=event.organizer):
|
||||
order_moment = datetime(2020, 3, 29, 18, 0, 0, tzinfo=TOKYO)
|
||||
|
||||
order = Order.objects.create(
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, secret="k24fiuwvu8kxz3y1",
|
||||
datetime=order_moment,
|
||||
expires=order_moment + timedelta(days=10),
|
||||
sales_channel=event.organizer.sales_channels.get(identifier="web"),
|
||||
total=23, locale='en'
|
||||
)
|
||||
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='order__datetime', minutes=None, is_after=True))
|
||||
assert rdw.datetime(order).astimezone(TOKYO) == datetime(2020, 3, 30, 18, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/order__datetime/after'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_relative_to_subevent(event):
|
||||
event.has_subevents = True
|
||||
event.save()
|
||||
se1 = event.subevents.create(
|
||||
name="Foobar",
|
||||
date_from=datetime(2023, 6, 27, 5, 0, 0, tzinfo=TOKYO),
|
||||
date_to=datetime(2023, 7, 27, 5, 0, 0, tzinfo=TOKYO),
|
||||
date_admission=datetime(2023, 6, 27, 6, 0, 0, tzinfo=TOKYO),
|
||||
)
|
||||
se2 = event.subevents.create(
|
||||
name="Foobar1",
|
||||
date_from=datetime(2024, 6, 27, 5, 0, 0, tzinfo=TOKYO),
|
||||
date_to=datetime(2024, 7, 27, 5, 0, 0, tzinfo=TOKYO),
|
||||
date_admission=datetime(2024, 6, 27, 6, 0, 0, tzinfo=TOKYO),
|
||||
)
|
||||
|
||||
order_moment = datetime(2020, 3, 29, 18, 0, 0, tzinfo=TOKYO)
|
||||
|
||||
order = Order.objects.create(
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, secret="k24fiuwvu8kxz3y1",
|
||||
datetime=order_moment,
|
||||
expires=order_moment + timedelta(days=10),
|
||||
sales_channel=event.organizer.sales_channels.get(identifier="web"),
|
||||
total=23, locale='en'
|
||||
)
|
||||
item = event.items.create(name="Budget Ticket", default_price=23)
|
||||
OrderPosition.objects.create(
|
||||
order=order,
|
||||
subevent=se1,
|
||||
item=item,
|
||||
tax_value=Decimal("3"),
|
||||
price=Decimal("23"),
|
||||
attendee_name_parts={'full_name': "Peter"},
|
||||
secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w"
|
||||
)
|
||||
OrderPosition.objects.create(
|
||||
order=order,
|
||||
subevent=se2,
|
||||
item=item,
|
||||
tax_value=Decimal("3"),
|
||||
price=Decimal("23"),
|
||||
attendee_name_parts={'full_name': "Peter"},
|
||||
secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w"
|
||||
)
|
||||
|
||||
rdw = RelativeDate(days=1, time=None, base_date_name='order.subevents__date_from__first', minutes=None, is_after=True)
|
||||
assert rdw.datetime(order).astimezone(TOKYO) == datetime(2023, 6, 28, 5, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/order.subevents__date_from__first/after'
|
||||
rdw = RelativeDate(days=1, time=None, base_date_name='order.subevents__date_from__last', minutes=None, is_after=True)
|
||||
assert rdw.datetime(order).astimezone(TOKYO) == datetime(2024, 6, 28, 5, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/order.subevents__date_from__last/after'
|
||||
|
||||
rdw = RelativeDate(days=1, time=None, base_date_name='order.subevents__date_to__first', minutes=None, is_after=True)
|
||||
assert rdw.datetime(order).astimezone(TOKYO) == datetime(2023, 7, 28, 5, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/order.subevents__date_to__first/after'
|
||||
rdw = RelativeDate(days=1, time=None, base_date_name='order.subevents__date_to__last', minutes=None, is_after=True)
|
||||
assert rdw.datetime(order).astimezone(TOKYO) == datetime(2024, 7, 28, 5, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/order.subevents__date_to__last/after'
|
||||
|
||||
rdw = RelativeDate(days=1, time=None, base_date_name='order.subevents__date_admission__first', minutes=None, is_after=True)
|
||||
assert rdw.datetime(order).astimezone(TOKYO) == datetime(2023, 6, 28, 6, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/order.subevents__date_admission__first/after'
|
||||
rdw = RelativeDate(days=1, time=None, base_date_name='order.subevents__date_admission__last', minutes=None, is_after=True)
|
||||
assert rdw.datetime(order).astimezone(TOKYO) == datetime(2024, 6, 28, 6, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/order.subevents__date_admission__last/after'
|
||||
|
||||
@@ -235,7 +235,7 @@ class WaitingListTestCase(TestCase):
|
||||
self.event.settings.set('waiting_list_enabled', True)
|
||||
self.event.settings.set('waiting_list_auto', True)
|
||||
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper(
|
||||
RelativeDate(days=0, time=None, base_date_name='date_from', minutes=20, is_after=False)
|
||||
RelativeDate(days=0, time=None, base_date_name='event__date_from', minutes=20, is_after=False)
|
||||
)
|
||||
self.event.save()
|
||||
with scope(organizer=self.o):
|
||||
|
||||
@@ -481,7 +481,7 @@ class EventsTest(SoupTest):
|
||||
'payment_term_last_0': 'absolute',
|
||||
'payment_term_last_1': (self.event1.presale_end - datetime.timedelta(1)).strftime('%Y-%m-%d'),
|
||||
'payment_term_last_2': '0',
|
||||
'payment_term_last_3': 'date_from',
|
||||
'payment_term_last_3': 'event__date_from',
|
||||
'tax_rule_payment': 'default',
|
||||
})
|
||||
assert doc.select('.alert-danger')
|
||||
@@ -496,7 +496,7 @@ class EventsTest(SoupTest):
|
||||
'payment_term_last_0': 'relative',
|
||||
'payment_term_last_1': '',
|
||||
'payment_term_last_2': '10',
|
||||
'payment_term_last_3': 'date_from',
|
||||
'payment_term_last_3': 'event__date_from',
|
||||
'tax_rule_payment': 'default',
|
||||
})
|
||||
assert doc.select('.alert-danger')
|
||||
|
||||
@@ -230,19 +230,19 @@ class SubEventsTest(SoupTest):
|
||||
'rel_presale_start_0': 'unset',
|
||||
'rel_presale_start_1': '',
|
||||
'rel_presale_start_2': '1',
|
||||
'rel_presale_start_3': 'date_from',
|
||||
'rel_presale_start_3': 'event__date_from',
|
||||
'rel_presale_start_4': '',
|
||||
'rel_presale_start_5': '',
|
||||
'rel_presale_start_6': 'date_from',
|
||||
'rel_presale_start_6': 'event__date_from',
|
||||
'rel_presale_start_7': 'before',
|
||||
'rel_presale_start_8': 'before',
|
||||
'rel_presale_end_1': '',
|
||||
'rel_presale_end_0': 'relative',
|
||||
'rel_presale_end_2': '1',
|
||||
'rel_presale_end_3': 'date_from',
|
||||
'rel_presale_end_3': 'event__date_from',
|
||||
'rel_presale_end_4': '13:29:31',
|
||||
'rel_presale_end_5': '',
|
||||
'rel_presale_end_6': 'date_from',
|
||||
'rel_presale_end_6': 'event__date_from',
|
||||
'rel_presale_end_7': 'before',
|
||||
'rel_presale_end_8': 'before',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
@@ -328,19 +328,19 @@ class SubEventsTest(SoupTest):
|
||||
'rel_presale_start_0': 'unset',
|
||||
'rel_presale_start_1': '',
|
||||
'rel_presale_start_2': '1',
|
||||
'rel_presale_start_3': 'date_from',
|
||||
'rel_presale_start_3': 'event__date_from',
|
||||
'rel_presale_start_4': '',
|
||||
'rel_presale_start_5': '',
|
||||
'rel_presale_start_6': 'date_from',
|
||||
'rel_presale_start_6': 'event__date_from',
|
||||
'rel_presale_start_7': 'before',
|
||||
'rel_presale_start_8': 'before',
|
||||
'rel_presale_end_1': '',
|
||||
'rel_presale_end_0': 'relative',
|
||||
'rel_presale_end_2': '1',
|
||||
'rel_presale_end_3': 'date_from',
|
||||
'rel_presale_end_3': 'event__date_from',
|
||||
'rel_presale_end_4': '13:29:31',
|
||||
'rel_presale_end_5': '',
|
||||
'rel_presale_end_6': 'date_from',
|
||||
'rel_presale_end_6': 'event__date_from',
|
||||
'rel_presale_end_7': 'before',
|
||||
'rel_presale_end_8': 'before',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
@@ -403,19 +403,19 @@ class SubEventsTest(SoupTest):
|
||||
'rel_presale_start_0': 'unset',
|
||||
'rel_presale_start_1': '',
|
||||
'rel_presale_start_2': '1',
|
||||
'rel_presale_start_3': 'date_from',
|
||||
'rel_presale_start_3': 'event__date_from',
|
||||
'rel_presale_start_4': '',
|
||||
'rel_presale_start_5': '',
|
||||
'rel_presale_start_6': 'date_from',
|
||||
'rel_presale_start_6': 'event__date_from',
|
||||
'rel_presale_start_7': 'before',
|
||||
'rel_presale_start_8': 'before',
|
||||
'rel_presale_end_1': '',
|
||||
'rel_presale_end_0': 'relative',
|
||||
'rel_presale_end_2': '1',
|
||||
'rel_presale_end_3': 'date_from',
|
||||
'rel_presale_end_3': 'event__date_from',
|
||||
'rel_presale_end_4': '13:29:31',
|
||||
'rel_presale_end_5': '',
|
||||
'rel_presale_end_6': 'date_from',
|
||||
'rel_presale_end_6': 'event__date_from',
|
||||
'rel_presale_end_7': 'before',
|
||||
'rel_presale_end_8': 'before',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
@@ -479,19 +479,19 @@ class SubEventsTest(SoupTest):
|
||||
'rel_presale_start_0': 'unset',
|
||||
'rel_presale_start_1': '',
|
||||
'rel_presale_start_2': '1',
|
||||
'rel_presale_start_3': 'date_from',
|
||||
'rel_presale_start_3': 'event__date_from',
|
||||
'rel_presale_start_4': '',
|
||||
'rel_presale_start_5': '',
|
||||
'rel_presale_start_6': 'date_from',
|
||||
'rel_presale_start_6': 'event__date_from',
|
||||
'rel_presale_start_7': 'before',
|
||||
'rel_presale_start_8': 'before',
|
||||
'rel_presale_end_1': '',
|
||||
'rel_presale_end_0': 'relative',
|
||||
'rel_presale_end_2': '1',
|
||||
'rel_presale_end_3': 'date_from',
|
||||
'rel_presale_end_3': 'event__date_from',
|
||||
'rel_presale_end_4': '02:30:00',
|
||||
'rel_presale_end_5': '',
|
||||
'rel_presale_end_6': 'date_from',
|
||||
'rel_presale_end_6': 'event__date_from',
|
||||
'rel_presale_end_7': 'before',
|
||||
'rel_presale_end_8': 'before',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
@@ -567,19 +567,19 @@ class SubEventsTest(SoupTest):
|
||||
'rel_presale_start_0': 'unset',
|
||||
'rel_presale_start_1': '',
|
||||
'rel_presale_start_2': '1',
|
||||
'rel_presale_start_3': 'date_from',
|
||||
'rel_presale_start_3': 'event__date_from',
|
||||
'rel_presale_start_4': '',
|
||||
'rel_presale_start_5': '',
|
||||
'rel_presale_start_6': 'date_from',
|
||||
'rel_presale_start_6': 'event__date_from',
|
||||
'rel_presale_start_7': 'before',
|
||||
'rel_presale_start_8': 'before',
|
||||
'rel_presale_end_1': '',
|
||||
'rel_presale_end_0': 'relative',
|
||||
'rel_presale_end_2': '1',
|
||||
'rel_presale_end_3': 'date_from',
|
||||
'rel_presale_end_3': 'event__date_from',
|
||||
'rel_presale_end_4': '13:29:31',
|
||||
'rel_presale_end_5': '',
|
||||
'rel_presale_end_6': 'date_from',
|
||||
'rel_presale_end_6': 'event__date_from',
|
||||
'rel_presale_end_7': 'before',
|
||||
'rel_presale_end_8': 'before',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
@@ -639,19 +639,19 @@ class SubEventsTest(SoupTest):
|
||||
'rel_presale_start_0': 'unset',
|
||||
'rel_presale_start_1': '',
|
||||
'rel_presale_start_2': '1',
|
||||
'rel_presale_start_3': 'date_from',
|
||||
'rel_presale_start_3': 'event__date_from',
|
||||
'rel_presale_start_4': '',
|
||||
'rel_presale_start_5': '',
|
||||
'rel_presale_start_6': 'date_from',
|
||||
'rel_presale_start_6': 'event__date_from',
|
||||
'rel_presale_start_7': 'before',
|
||||
'rel_presale_start_8': 'before',
|
||||
'rel_presale_end_0': 'unset',
|
||||
'rel_presale_end_1': '',
|
||||
'rel_presale_end_2': '1',
|
||||
'rel_presale_end_3': 'date_from',
|
||||
'rel_presale_end_3': 'event__date_from',
|
||||
'rel_presale_end_4': '13:29:31',
|
||||
'rel_presale_end_5': '',
|
||||
'rel_presale_end_6': 'date_from',
|
||||
'rel_presale_end_6': 'event__date_from',
|
||||
'rel_presale_end_7': 'before',
|
||||
'rel_presale_end_8': 'before',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
@@ -711,19 +711,19 @@ class SubEventsTest(SoupTest):
|
||||
'rel_presale_start_0': 'unset',
|
||||
'rel_presale_start_1': '',
|
||||
'rel_presale_start_2': '1',
|
||||
'rel_presale_start_3': 'date_from',
|
||||
'rel_presale_start_3': 'event__date_from',
|
||||
'rel_presale_start_4': '',
|
||||
'rel_presale_start_5': '',
|
||||
'rel_presale_start_6': 'date_from',
|
||||
'rel_presale_start_6': 'event__date_from',
|
||||
'rel_presale_start_7': 'before',
|
||||
'rel_presale_start_8': 'before',
|
||||
'rel_presale_end_0': 'unset',
|
||||
'rel_presale_end_1': '',
|
||||
'rel_presale_end_2': '1',
|
||||
'rel_presale_end_3': 'date_from',
|
||||
'rel_presale_end_3': 'event__date_from',
|
||||
'rel_presale_end_4': '13:29:31',
|
||||
'rel_presale_end_5': '',
|
||||
'rel_presale_end_6': 'date_from',
|
||||
'rel_presale_end_6': 'event__date_from',
|
||||
'rel_presale_end_7': 'before',
|
||||
'rel_presale_end_8': 'before',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
|
||||
@@ -1122,7 +1122,7 @@ class WaitingListTest(EventTestMixin, SoupTest):
|
||||
def test_auto_disable(self):
|
||||
self.event.settings.set('waiting_list_enabled', True)
|
||||
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper(
|
||||
RelativeDate(days=900, time=datetime.time(9, 0, 0), base_date_name='date_from', minutes=None, is_after=False)
|
||||
RelativeDate(days=900, time=datetime.time(9, 0, 0), base_date_name='event__date_from', minutes=None, is_after=False)
|
||||
)
|
||||
response = self.client.get(
|
||||
'/%s/%s/' % (self.orga.slug, self.event.slug)
|
||||
|
||||
@@ -1485,7 +1485,7 @@ class OrderChangeAddonsTest(BaseOrdersTest):
|
||||
self.order.save()
|
||||
self._subevent_setup()
|
||||
self.event.settings.set('payment_term_last', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
|
||||
response = self.client.get(
|
||||
|
||||
@@ -1076,7 +1076,7 @@ class OrdersTest(BaseOrdersTest):
|
||||
self.event.date_from = now() + datetime.timedelta(days=3)
|
||||
self.event.save()
|
||||
self.event.settings.set('ticket_download_date', RelativeDateWrapper(RelativeDate(
|
||||
base_date_name='date_from', days=2, time=None, minutes=None
|
||||
base_date_name='event__date_from', days=2, time=None, minutes=None
|
||||
)))
|
||||
response = self.client.post(
|
||||
'/%s/%s/order/%s/%s/download/%d/testdummy' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
|
||||
Reference in New Issue
Block a user