Compare commits

...
Author SHA1 Message Date
Lukas Bockstaller ac0bf2d5d4 Merge branch 'master' into order-relativedatetime 2026-08-12 15:00:12 +02:00
Lukas Bockstaller 90e4d5305f change event listener to pretix:bind-forms 2026-07-21 10:26:56 +02:00
Lukas Bockstaller 12f279e00f Merge branch 'master' into order-relativedatetime 2026-07-21 10:22:58 +02:00
Lukas Bockstaller 4cfcb9ede1 add depreciation warning 2026-07-20 17:04:32 +02:00
Lukas Bockstaller 003652d564 remove empty docstrings 2026-07-20 17:03:14 +02:00
1f5a66005b Apply suggestions from code review
Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2026-07-20 17:00:03 +02:00
Lukas Bockstaller 1e01c7ff78 various cleanup 2026-06-30 13:18:07 +02:00
Lukas Bockstaller 5e55f53650 remove order.subevent variants 2026-06-23 15:40:23 +02:00
Lukas Bockstaller b9e10251bc Merge branch 'master' into order-relativedatetime 2026-06-23 14:28:38 +02:00
Lukas Bockstaller 0472101c81 use correct choice format 2026-05-28 13:41:57 +02:00
Lukas Bockstaller b0a34fa196 add test for order.subevents cases 2026-05-28 11:21:43 +02:00
Lukas Bockstaller 99d9d65521 general cleanup 2026-05-27 16:54:54 +02:00
Lukas Bockstaller 599318e7c7 rewrite reldate.js use data-attributes 2026-05-27 16:54:54 +02:00
Lukas Bockstaller b7dc8ad75d add a little bit of information to the tests for future reference 2026-05-27 16:54:54 +02:00
Lukas Bockstaller 027aceaf6c add OptionAttrsSelect to allow select options with their own attributes per value 2026-05-27 16:54:54 +02:00
Lukas Bockstaller 23e37ada59 move reldate.js include in correct file 2026-05-27 16:54:48 +02:00
62917da9a5 Apply suggestions from code review
Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2026-05-26 15:29:50 +02:00
Lukas Bockstaller d68c6415f3 test upgrade behaviour 2026-05-20 11:44:35 +02:00
Lukas Bockstaller 4271928df2 fix tests 2026-05-20 11:37:44 +02:00
Lukas Bockstaller 21fc8e4dec moves logic from RelativeDateWrapper into RelativeDate and adds BaseChoice for configuring which models attributes support which relationship 2026-05-19 17:11:06 +02:00
Lukas Bockstaller f0d1d7ca5a add type annotations 2026-05-18 14:57:03 +02:00
Lukas Bockstaller b2fd6a9c1e move js includes of to fragment_js.html 2026-05-18 14:56:40 +02:00
27966969f1 Apply suggestions from code review
Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2026-05-12 10:40:46 +02:00
64e301ef47 Update src/pretix/base/reldate.py
Co-authored-by: Raphael Michel <michel@pretix.eu>
2026-05-12 10:40:02 +02:00
Lukas Bockstaller 582cb90bb0 fix tests 2026-05-07 11:12:15 +02:00
Lukas Bockstaller 4db98def79 add js to prevent illegal inputs 2026-05-07 10:53:52 +02:00
Lukas Bockstaller 8adc649c4c factor out _resolve_base_date 2026-05-07 09:49:08 +02:00
Lukas Bockstaller 8692222bd3 initial implementation 2026-05-06 18:10:41 +02:00
16 changed files with 588 additions and 254 deletions
+15
View File
@@ -314,3 +314,18 @@ class BusinessBooleanRadio(forms.RadioSelect):
'False': False, 'False': False,
False: False, False: False,
}.get(value) }.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
+373 -155
View File
@@ -19,30 +19,245 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # 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/>. # <https://www.gnu.org/licenses/>.
# #
import copy
import datetime import datetime
import os
import warnings
from collections import namedtuple from collections import namedtuple
from typing import Union from dataclasses import dataclass
from typing import (
TYPE_CHECKING, Iterable, List, Literal, Optional, Tuple, Union, cast,
)
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from dateutil import parser from dateutil import parser
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.forms.widgets import Widget
from django.utils.formats import get_format 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.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
BASE_CHOICES = ( from pretix.base.forms.widgets import OptionAttrsSelect
('date_from', _('Event start')),
('date_to', _('Event end')),
('date_admission', _('Event admission')),
('presale_start', _('Presale start')),
('presale_end', _('Presale end')),
)
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"]
attribute: str
text: Promise
supports_before: bool
supports_after: bool
@property
def key(self) -> str:
key = f"{self.base}__{self.attribute}"
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),
]
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'
]
@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_name is not set, the date_from attribute of Event is used.
"""
days: int = 0
minutes: Optional[int] = None
time: Optional[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_date(self, base: "Event | Order | SubEvent") -> Tuple[datetime.datetime, ZoneInfo]:
"""
Resolves the datetime and timezone information of this RelativeDate object in relation to the provided reference.
:param base: the reference which should be used to resolve the relative date
: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 == "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:
"""
Resolves the effective date of this RelativeDate object in relation to the provided reference.
:param base: the reference which should be used to resolve the date
:return: 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_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:
"""
Resolves the effective datetime of this RelativeDate object in relation to the provided reference.
:param base: the reference which should be used to resolve the datetime
:return: datetime.datetime
"""
base_date, tz = self._resolve_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:
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):
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: class RelativeDateWrapper:
@@ -50,130 +265,53 @@ class RelativeDateWrapper:
This contains information on a date that might be relative to an event. This means 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 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. 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]): def __init__(self, data: Union[datetime.datetime, RelativeDate]):
self.data = data self.data = data
def date(self, event) -> datetime.date: def date(self, base: "Event | Order | SubEvent") -> datetime.date:
from .models import SubEvent """
If the RelativeDateWrapper wraps a RelativeDate object:
Resolves the effective date of this object in relation to the provided reference.
If the RelativeDateWrapper wraps an absolute date or datetime:
Returns the wrapped absolute date.
:param base: the reference which should be used to resolve the date in case it is a relative date.
:return: datetime.date
"""
if isinstance(self.data, datetime.datetime): if isinstance(self.data, datetime.datetime):
return self.data.date() return self.data.date()
elif isinstance(self.data, datetime.date): elif isinstance(self.data, datetime.date):
return self.data return self.data
else: else:
if self.data.minutes is not None: return self.data.date(base)
raise ValueError('A minute-based relative datetime can not be used as a date')
tz = ZoneInfo(event.settings.timezone) def datetime(self, base: "Event | Order | SubEvent") -> datetime.datetime:
if isinstance(event, SubEvent): """
base_date = ( If the RelativeDateWrapper wraps a RelativeDate object:
getattr(event, self.data.base_date_name) Resolves the effective datetime of this object in relation to the provided reference.
or getattr(event.event, self.data.base_date_name) If the RelativeDateWrapper wraps an absolute date or datetime:
or event.date_from Returns the wrapped absolute datetime.
)
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
:param base: the reference which should be used to resolve the datetime in case it is a relative datetime.
:return: datetime.datetime
"""
if isinstance(self.data, (datetime.datetime, datetime.date)): if isinstance(self.data, (datetime.datetime, datetime.date)):
return self.data return self.data
else: else:
tz = ZoneInfo(event.settings.timezone) return self.data.datetime(base)
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
def to_string(self) -> str: def to_string(self) -> str:
if isinstance(self.data, (datetime.datetime, datetime.date)): if isinstance(self.data, (datetime.datetime, datetime.date)):
return self.data.isoformat() return self.data.isoformat()
else: else:
if self.data.minutes is not None: return self.data.to_string()
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 '',
)
@classmethod @classmethod
def from_string(cls, input: str): def from_string(cls, input: str):
if input.startswith('RELDATE/'): if input.startswith('RELDATE/'):
parts = input.split('/') data = RelativeDate.from_string(input)
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))
else: else:
data = parser.parse(input) data = parser.parse(input)
return RelativeDateWrapper(data) return RelativeDateWrapper(data)
@@ -187,7 +325,6 @@ BEFORE_AFTER_CHOICE = (
('after', _('after')), ('after', _('after')),
) )
reldatetimeparts = namedtuple('reldatetimeparts', ( reldatetimeparts = namedtuple('reldatetimeparts', (
"status", # 0 "status", # 0
"absolute", # 1 "absolute", # 1
@@ -202,6 +339,14 @@ reldatetimeparts = namedtuple('reldatetimeparts', (
reldatetimeparts.indizes = reldatetimeparts(*range(9)) 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): class RelativeDateTimeWidget(forms.MultiWidget):
template_name = 'pretixbase/forms/widgets/reldatetime.html' template_name = 'pretixbase/forms/widgets/reldatetime.html'
parts = reldatetimeparts parts = reldatetimeparts
@@ -209,6 +354,7 @@ class RelativeDateTimeWidget(forms.MultiWidget):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.status_choices = kwargs.pop('status_choices') self.status_choices = kwargs.pop('status_choices')
base_choices = kwargs.pop('base_choices') base_choices = kwargs.pop('base_choices')
choices = _get_choices(base_choices)
def placeholder_datetime_format(): def placeholder_datetime_format():
df = get_format('DATETIME_INPUT_FORMATS')[0] df = get_format('DATETIME_INPUT_FORMATS')[0]
@@ -220,21 +366,29 @@ class RelativeDateTimeWidget(forms.MultiWidget):
tf = get_format('TIME_INPUT_FORMATS')[0] tf = get_format('TIME_INPUT_FORMATS')[0]
return datetime.time(8, 30, 0).strftime(tf) return datetime.time(8, 30, 0).strftime(tf)
widgets = reldatetimeparts( widgets = cast(dict[str, Widget | type[Widget]], cast(object, reldatetimeparts(
status=forms.RadioSelect(choices=self.status_choices), status=forms.RadioSelect(choices=self.status_choices),
absolute=forms.DateTimeInput( absolute=forms.DateTimeInput(
attrs={'placeholder': lazy(placeholder_datetime_format, str), 'class': 'datetimepicker'} attrs={'placeholder': lazy(placeholder_datetime_format, str), 'class': 'datetimepicker'}
), ),
rel_days_number=forms.NumberInput(), 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( rel_days_timeofday=forms.TimeInput(
attrs={'placeholder': lazy(placeholder_time_format, str), 'class': 'timepickerfield'} attrs={'placeholder': lazy(placeholder_time_format, str), 'class': 'timepickerfield'}
), ),
rel_mins_number=forms.NumberInput(), rel_mins_number=forms.NumberInput(),
rel_days_relationto=forms.Select(choices=base_choices), rel_days_relationto=OptionAttrsSelect(
rel_mins_relation=forms.Select(choices=BEFORE_AFTER_CHOICE), attrs={'data-relative-choice': True},
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE), 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) super().__init__(widgets=widgets, *args, **kwargs)
def decompress(self, value): def decompress(self, value):
@@ -247,10 +401,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
status="unset", status="unset",
absolute=None, absolute=None,
rel_days_number=1, rel_days_number=1,
rel_mins_relationto="date_from", rel_mins_relationto="event__date_from",
rel_days_timeofday=None, rel_days_timeofday=None,
rel_mins_number=0, rel_mins_number=0,
rel_days_relationto="date_from", rel_days_relationto="event__date_from",
rel_mins_relation="before", rel_mins_relation="before",
rel_days_relation="before" rel_days_relation="before"
) )
@@ -259,10 +413,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
status="absolute", status="absolute",
absolute=value.data, absolute=value.data,
rel_days_number=1, rel_days_number=1,
rel_mins_relationto="date_from", rel_mins_relationto="event__date_from",
rel_days_timeofday=None, rel_days_timeofday=None,
rel_mins_number=0, rel_mins_number=0,
rel_days_relationto="date_from", rel_days_relationto="event__date_from",
rel_mins_relation="before", rel_mins_relation="before",
rel_days_relation="before" rel_days_relation="before"
) )
@@ -271,10 +425,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
status="relative_minutes", status="relative_minutes",
absolute=None, absolute=None,
rel_days_number=None, rel_days_number=None,
rel_mins_relationto=value.data.base_date_name, rel_mins_relationto=value.data.key,
rel_days_timeofday=None, rel_days_timeofday=None,
rel_mins_number=value.data.minutes, 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_mins_relation="after" if value.data.is_after else "before",
rel_days_relation="after" if value.data.is_after else "before" rel_days_relation="after" if value.data.is_after else "before"
) )
@@ -282,10 +436,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
status="relative", status="relative",
absolute=None, absolute=None,
rel_days_number=value.data.days, 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_days_timeofday=value.data.time,
rel_mins_number=0, 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_mins_relation="after" if value.data.is_after else "before",
rel_days_relation="after" if value.data.is_after else "before" rel_days_relation="after" if value.data.is_after else "before"
) )
@@ -309,17 +463,41 @@ class RelativeDateTimeField(forms.MultiValueField):
('relative', _('Relative date:')), ('relative', _('Relative date:')),
('relative_minutes', _('Relative time:')), ('relative_minutes', _('Relative time:')),
] ]
self.relative_to_order = kwargs.pop('relative_to_order', False)
possible_choices = copy.deepcopy(EVENT_BASE_CHOICES)
if self.relative_to_order:
possible_choices.extend(ORDER_BASE_CHOICES)
if kwargs.get('limit_choices'): if kwargs.get('limit_choices'):
limit = kwargs.pop('limit_choices') limit = kwargs.pop('limit_choices')
choices = [(k, v) for k, v in BASE_CHOICES if k in limit] if any(["__" not in l for l in limit]):
else: _warn_skips = (str(os.path.dirname(__file__)),)
choices = BASE_CHOICES
warnings.warn(
message="Please prefix limit_choices with the base the attributes refer to, for example event__date_from",
category=DeprecationWarning,
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): if not kwargs.get('required', True):
status_choices.insert(0, ('unset', _('Not set'))) status_choices.insert(0, ('unset', _('Not set')))
choices = _get_choices(possible_choices)
fields = reldatetimeparts( fields = reldatetimeparts(
status=forms.ChoiceField( status=forms.ChoiceField(
choices=status_choices, choices=status_choices,
required=True required=True,
), ),
absolute=forms.DateTimeField( absolute=forms.DateTimeField(
required=False required=False
@@ -329,7 +507,7 @@ class RelativeDateTimeField(forms.MultiValueField):
), ),
rel_mins_relationto=forms.ChoiceField( rel_mins_relationto=forms.ChoiceField(
choices=choices, choices=choices,
required=False required=False,
), ),
rel_days_timeofday=forms.TimeField( rel_days_timeofday=forms.TimeField(
required=False, required=False,
@@ -339,7 +517,7 @@ class RelativeDateTimeField(forms.MultiValueField):
), ),
rel_days_relationto=forms.ChoiceField( rel_days_relationto=forms.ChoiceField(
choices=choices, choices=choices,
required=False required=False,
), ),
rel_mins_relation=forms.ChoiceField( rel_mins_relation=forms.ChoiceField(
choices=BEFORE_AFTER_CHOICE, choices=BEFORE_AFTER_CHOICE,
@@ -350,8 +528,9 @@ class RelativeDateTimeField(forms.MultiValueField):
required=False required=False
), ),
) )
if 'widget' not in kwargs: 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('max_length', 0)
kwargs.pop('empty_value', 0) kwargs.pop('empty_value', 0)
super().__init__( super().__init__(
@@ -359,21 +538,24 @@ class RelativeDateTimeField(forms.MultiValueField):
) )
def set_event(self, event): def set_event(self, event):
self.widget.widgets[reldatetimeparts.indizes.rel_days_relationto].choices = [ possible_choices = copy.deepcopy(EVENT_BASE_CHOICES)
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None) if self.relative_to_order:
] possible_choices.extend(ORDER_BASE_CHOICES)
self.widget.widgets[reldatetimeparts.indizes.rel_mins_relationto].choices = [
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None) 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): def compress(self, data_list):
if not data_list: if not data_list:
return None return None
data = reldatetimeparts(*data_list) data = reldatetimeparts(*data_list)
if data.status == 'absolute': if data.status == 'unset':
return RelativeDateWrapper(data.absolute)
elif data.status == 'unset':
return None return None
elif data.status == 'absolute':
return RelativeDateWrapper(data.absolute)
elif data.status == 'relative_minutes': elif data.status == 'relative_minutes':
return RelativeDateWrapper(RelativeDate( return RelativeDateWrapper(RelativeDate(
days=0, days=0,
@@ -404,6 +586,18 @@ class RelativeDateTimeField(forms.MultiValueField):
raise ValidationError(self.error_messages['incomplete']) raise ValidationError(self.error_messages['incomplete'])
elif data.status == 'relative_minutes' and (data.rel_mins_number is None or not data.rel_mins_relationto): elif data.status == 'relative_minutes' and (data.rel_mins_number is None or not data.rel_mins_relationto):
raise ValidationError(self.error_messages['incomplete']) 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) return super().clean(value)
@@ -424,15 +618,20 @@ class RelativeDateWidget(RelativeDateTimeWidget):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.status_choices = kwargs.pop('status_choices') self.status_choices = kwargs.pop('status_choices')
widgets = reldateparts( self.base_choices = kwargs.pop('base_choices')
widgets = cast(dict[str, Widget | type[Widget]], cast(object, reldateparts(
status=forms.RadioSelect(choices=self.status_choices), status=forms.RadioSelect(choices=self.status_choices),
absolute=forms.DateInput( absolute=forms.DateInput(
attrs={'class': 'datepickerfield'} attrs={'class': 'datepickerfield'}
), ),
rel_days_number=forms.NumberInput(), rel_days_number=forms.NumberInput(),
rel_days_relationto=forms.Select(choices=kwargs.pop('base_choices')), rel_days_relationto=OptionAttrsSelect(
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE), 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) forms.MultiWidget.__init__(self, widgets=widgets, *args, **kwargs)
def decompress(self, value): def decompress(self, value):
@@ -460,7 +659,7 @@ class RelativeDateWidget(RelativeDateTimeWidget):
status="relative", status="relative",
absolute=None, absolute=None,
rel_days_number=value.data.days, 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" rel_days_relation="after" if value.data.is_after else "before"
) )
@@ -474,6 +673,15 @@ class RelativeDateField(RelativeDateTimeField):
] ]
if not kwargs.get('required', True): if not kwargs.get('required', True):
status_choices.insert(0, ('unset', _('Not set'))) status_choices.insert(0, ('unset', _('Not set')))
self.relative_to_order = kwargs.pop('relative_to_order', False)
possible_choices = copy.deepcopy(EVENT_BASE_CHOICES)
if self.relative_to_order:
possible_choices.extend(ORDER_BASE_CHOICES)
choices = _get_choices(possible_choices)
fields = reldateparts( fields = reldateparts(
status=forms.ChoiceField( status=forms.ChoiceField(
choices=status_choices, choices=status_choices,
@@ -486,33 +694,37 @@ class RelativeDateField(RelativeDateTimeField):
required=False required=False
), ),
rel_days_relationto=forms.ChoiceField( rel_days_relationto=forms.ChoiceField(
choices=BASE_CHOICES, choices=choices,
required=False required=False,
), ),
rel_days_relation=forms.ChoiceField( rel_days_relation=forms.ChoiceField(
choices=BEFORE_AFTER_CHOICE, choices=BEFORE_AFTER_CHOICE,
required=False required=False
), ),
) )
if 'widget' not in kwargs: 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__( forms.MultiValueField.__init__(
self, fields=fields, require_all_fields=False, *args, **kwargs self, fields=fields, require_all_fields=False, *args, **kwargs
) )
def set_event(self, event): def set_event(self, event):
self.widget.widgets[reldateparts.indizes.rel_days_relationto].choices = [ choices = [
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None) (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]
self.widget.widgets[reldateparts.indizes.rel_days_relationto].choices = choices
def compress(self, data_list): def compress(self, data_list):
if not data_list: if not data_list:
return None return None
data = reldateparts(*data_list) data = reldateparts(*data_list)
if data.status == 'absolute': if data.status == 'unset':
return RelativeDateWrapper(data.absolute)
elif data.status == 'unset':
return None return None
elif data.status == 'absolute':
return RelativeDateWrapper(data.absolute)
else: else:
return RelativeDateWrapper(RelativeDate( return RelativeDateWrapper(RelativeDate(
days=data.rel_days_number, days=data.rel_days_number,
@@ -525,8 +737,14 @@ class RelativeDateField(RelativeDateTimeField):
data = reldateparts(*value) data = reldateparts(*value)
if data.status == 'absolute' and not data.absolute: if data.status == 'absolute' and not data.absolute:
raise ValidationError(self.error_messages['incomplete']) raise ValidationError(self.error_messages['incomplete'])
elif data.status == 'relative' and (data.rel_days_number is None or not data.rel_days_relationto): if data.status == 'relative':
raise ValidationError(self.error_messages['incomplete']) 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) return forms.MultiValueField.clean(self, value)
+6 -6
View File
@@ -94,14 +94,14 @@ class SubEventBulkForm(SubEventForm):
label=_('Start of presale'), label=_('Start of presale'),
help_text=_('Optional. No products will be sold before this date.'), help_text=_('Optional. No products will be sold before this date.'),
required=False, required=False,
limit_choices=('date_from', 'date_to'), limit_choices=('event__date_from', 'event__date_to'),
) )
rel_presale_end = RelativeDateTimeField( rel_presale_end = RelativeDateTimeField(
label=_('End of presale'), label=_('End of presale'),
help_text=_('Optional. No products will be sold after this date. If you do not set this value, the 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.'), 'will end after the end date of your event.'),
required=False, required=False,
limit_choices=('date_from', 'date_to'), limit_choices=('event__date_from', 'event__date_to'),
) )
skip_if_overlap = forms.BooleanField( skip_if_overlap = forms.BooleanField(
label=pgettext_lazy('subevent', 'Skip dates that overlap with any existing date'), label=pgettext_lazy('subevent', 'Skip dates that overlap with any existing date'),
@@ -333,12 +333,12 @@ class BulkSubEventItemForm(SubEventItemForm):
rel_available_from = RelativeDateTimeField( rel_available_from = RelativeDateTimeField(
label=_('Available from'), label=_('Available from'),
required=False, required=False,
limit_choices=('date_from', 'date_to'), limit_choices=('event__date_from', 'event__date_to'),
) )
rel_available_until = RelativeDateTimeField( rel_available_until = RelativeDateTimeField(
label=_('Available until'), label=_('Available until'),
required=False, required=False,
limit_choices=('date_from', 'date_to'), limit_choices=('event__date_from', 'event__date_to'),
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -355,12 +355,12 @@ class BulkSubEventItemVariationForm(SubEventItemVariationForm):
rel_available_from = RelativeDateTimeField( rel_available_from = RelativeDateTimeField(
label=_('Available from'), label=_('Available from'),
required=False, required=False,
limit_choices=('date_from', 'date_to'), limit_choices=('event__date_from', 'event__date_to'),
) )
rel_available_until = RelativeDateTimeField( rel_available_until = RelativeDateTimeField(
label=_('Available_until'), label=_('Available_until'),
required=False, required=False,
limit_choices=('date_from', 'date_to'), limit_choices=('event__date_from', 'event__date_to'),
) )
def __init__(self, *args, **kwargs): 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 "leaflet/leaflet.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/geo.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/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 "pretixbase/js/asynctask.js" %}"></script>
<script type="text/javascript" src="{% static "sortable/Sortable.js" %}"></script> <script type="text/javascript" src="{% static "sortable/Sortable.js" %}"></script>
<script type="text/javascript" src="{% static "colorpicker/bootstrap-colorpicker.js" %}"></script> <script type="text/javascript" src="{% static "colorpicker/bootstrap-colorpicker.js" %}"></script>
+1 -1
View File
@@ -431,7 +431,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
days = (self.copy_from.date_from.astimezone(tz).date() - value.astimezone(tz).date()).days days = (self.copy_from.date_from.astimezone(tz).date() - value.astimezone(tz).date()).days
return RelativeDateWrapper(RelativeDate( return RelativeDateWrapper(RelativeDate(
days=abs(days), days=abs(days),
base_date_name='date_from', base_date_name='event__date_from',
time=value.astimezone(tz).time(), time=value.astimezone(tz).time(),
minutes=None, minutes=None,
is_after=days < 0, is_after=days < 0,
@@ -0,0 +1,42 @@
$(document).on('pretix:bind-forms', () => {
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()
})
})
})
+14 -14
View File
@@ -473,7 +473,7 @@ class QuotaTestCase(BaseQuotaTestCase):
@classscope(attr='o') @classscope(attr='o')
def test_waitinglist_auto_disable(self): def test_waitinglist_auto_disable(self):
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper( 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.items.add(self.item1)
self.quota.size = 1 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(), (Quota.AVAILABILITY_ORDERED, 0))
self.assertEqual(self.item1.check_quotas(count_waitinglist=False), (Quota.AVAILABILITY_OK, 1)) self.assertEqual(self.item1.check_quotas(count_waitinglist=False), (Quota.AVAILABILITY_OK, 1))
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper( 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(), (Quota.AVAILABILITY_OK, 1))
self.assertEqual(self.item1.check_quotas(count_waitinglist=False), (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.subevent = se2
self.op2.save() self.op2.save()
self.event.settings.set('payment_term_last', RelativeDateWrapper( 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 self.order.status = Order.STATUS_EXPIRED
@@ -1347,7 +1347,7 @@ class OrderTestCase(BaseQuotaTestCase):
self.op2.subevent = se2 self.op2.subevent = se2
self.op2.save() self.op2.save()
self.event.settings.set('last_order_modification_date', RelativeDateWrapper( 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 assert self.order.can_modify_answers
self.op2.subevent = se3 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.date_from = datetime.datetime(2017, 5, 3, 12, 0, 0, tzinfo=datetime.timezone.utc)
self.event.save() self.event.save()
self.event.settings.set('payment_term_last', RelativeDateWrapper( 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) 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.subevent = se2
self.op2.save() self.op2.save()
self.event.settings.set('payment_term_last', RelativeDateWrapper( 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() assert self.order.payment_term_last > now()
self.op2.subevent = se3 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.date_from = datetime.datetime(2017, 5, 3, 12, 0, 0, tzinfo=datetime.timezone.utc)
self.event.save() self.event.save()
self.event.settings.set('ticket_download_date', RelativeDateWrapper( 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) 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.subevent = se2
self.op2.save() self.op2.save()
self.event.settings.set('ticket_download_date', RelativeDateWrapper( 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() assert self.order.ticket_download_date > now()
self.op2.subevent = se3 self.op2.subevent = se3
@@ -1582,13 +1582,13 @@ class OrderTestCase(BaseQuotaTestCase):
assert self.order.user_cancel_deadline is None assert self.order.user_cancel_deadline is None
self.event.settings.set('cancel_allow_user_until', RelativeDateWrapper( 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) self.order = Order.objects.get(pk=self.order.pk)
assert self.order.user_cancel_deadline > now() assert self.order.user_cancel_deadline > now()
assert self.order.user_cancel_allowed assert self.order.user_cancel_allowed
self.event.settings.set('cancel_allow_user_until', RelativeDateWrapper( 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) self.order = Order.objects.get(pk=self.order.pk)
assert self.order.user_cancel_deadline < now() assert self.order.user_cancel_deadline < now()
@@ -1607,7 +1607,7 @@ class OrderTestCase(BaseQuotaTestCase):
self.op2.save() self.op2.save()
self.event.settings.set('cancel_allow_user_until', RelativeDateWrapper( 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) self.order = Order.objects.get(pk=self.order.pk)
assert self.order.user_cancel_deadline < now() assert self.order.user_cancel_deadline < now()
@@ -2018,13 +2018,13 @@ class OrderTestCase(BaseQuotaTestCase):
assert self.order.user_change_deadline is None assert self.order.user_change_deadline is None
self.event.settings.set('change_allow_user_until', RelativeDateWrapper( 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) self.order = Order.objects.get(pk=self.order.pk)
assert self.order.user_change_deadline > now() assert self.order.user_change_deadline > now()
assert self.order.user_change_allowed assert self.order.user_change_allowed
self.event.settings.set('change_allow_user_until', RelativeDateWrapper( 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) self.order = Order.objects.get(pk=self.order.pk)
assert self.order.user_change_deadline < now() assert self.order.user_change_deadline < now()
@@ -2046,7 +2046,7 @@ class OrderTestCase(BaseQuotaTestCase):
self.op2.save() self.op2.save()
self.event.settings.set('change_allow_user_until', RelativeDateWrapper( 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) self.order = Order.objects.get(pk=self.order.pk)
assert self.order.user_change_deadline < now() assert self.order.user_change_deadline < now()
+2 -2
View File
@@ -204,7 +204,7 @@ def test_expiry_last_relative(event):
event.date_from = now() + timedelta(days=5) event.date_from = now() + timedelta(days=5)
event.save() event.save()
event.settings.set('payment_term_last', RelativeDateWrapper( 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=[], order = _create_order(event, email='dummy@example.org', positions=[],
now_dt=today, now_dt=today,
@@ -245,7 +245,7 @@ def test_expiry_last_relative_subevents(event):
) )
event.settings.set('payment_term_last', RelativeDateWrapper( 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], order = _create_order(event, email='dummy@example.org', positions=[cp1, cp2],
now_dt=today, now_dt=today,
+6 -6
View File
@@ -133,7 +133,7 @@ def test_availability_date_relative(event):
event.save() event.save()
prov = DummyPaymentProvider(event) prov = DummyPaymentProvider(event)
prov.settings.set('_availability_date', RelativeDateWrapper( 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 utc = datetime.timezone.utc
@@ -150,7 +150,7 @@ def test_availability_start_relative(event):
event.save() event.save()
prov = DummyPaymentProvider(event) prov = DummyPaymentProvider(event)
prov.settings.set('_availability_start', RelativeDateWrapper( 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 utc = datetime.timezone.utc
@@ -193,12 +193,12 @@ def test_availability_date_cart_relative_subevents(event):
prov = DummyPaymentProvider(event) prov = DummyPaymentProvider(event)
prov.settings.set('_availability_date', RelativeDateWrapper( 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") assert prov._is_available_by_time(cart_id="123")
prov.settings.set('_availability_date', RelativeDateWrapper( 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") 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 = DummyPaymentProvider(event)
prov.settings.set('_availability_date', RelativeDateWrapper( 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) assert prov._is_available_by_time(order=order)
prov.settings.set('_availability_date', RelativeDateWrapper( 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) assert not prov._is_available_by_time(order=order)
+94 -36
View File
@@ -19,13 +19,13 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # 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/>. # <https://www.gnu.org/licenses/>.
# #
from datetime import datetime, time from datetime import datetime, time, timedelta
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
import pytest import pytest
from django_scopes import scope from django_scopes import scope
from pretix.base.models import Event, Organizer from pretix.base.models import Event, Order, Organizer
from pretix.base.reldate import RelativeDate, RelativeDateWrapper from pretix.base.reldate import RelativeDate, RelativeDateWrapper
TOKYO = ZoneInfo('Asia/Tokyo') TOKYO = ZoneInfo('Asia/Tokyo')
@@ -40,7 +40,6 @@ def event():
date_from=datetime(2017, 12, 27, 5, 0, 0, tzinfo=TOKYO), date_from=datetime(2017, 12, 27, 5, 0, 0, tzinfo=TOKYO),
presale_start=datetime(2017, 12, 1, 5, 0, 0, tzinfo=TOKYO), presale_start=datetime(2017, 12, 1, 5, 0, 0, tzinfo=TOKYO),
plugins='pretix.plugins.banktransfer' plugins='pretix.plugins.banktransfer'
) )
event.settings.timezone = "Asia/Tokyo" event.settings.timezone = "Asia/Tokyo"
return event return event
@@ -56,57 +55,57 @@ def test_absolute_date(event):
@pytest.mark.django_db @pytest.mark.django_db
def test_relative_date_without_time(event): 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.datetime(event).astimezone(TOKYO) == datetime(2017, 12, 26, 5, 0, 0, tzinfo=TOKYO)
assert rdw.to_string() == 'RELDATE/1/-/date_from/' assert rdw.to_string() == 'RELDATE/1/-/event__date_from/'
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='date_from', minutes=None, is_after=True)) 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.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 @pytest.mark.django_db
def test_relative_date_other_base_point(event): def test_relative_date_other_base_point(event):
with scope(organizer=event.organizer): 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.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 # 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.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 # subevent base
se = event.subevents.create(name="SE1", date_from=datetime(2017, 11, 27, 5, 0, 0, tzinfo=TOKYO)) 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) assert rdw.datetime(se) == datetime(2017, 11, 26, 5, 0, 0, tzinfo=TOKYO)
# presale_start is unset on subevent, default to event # 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) 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 # 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) assert rdw.datetime(se) == datetime(2017, 11, 26, 5, 0, 0, tzinfo=TOKYO)
@pytest.mark.django_db @pytest.mark.django_db
def test_relative_date_in_minutes(event): def test_relative_date_in_minutes(event):
rdw = RelativeDateWrapper(RelativeDate(days=0, time=None, base_date_name='date_from', minutes=60)) rdw = RelativeDateWrapper(RelativeDate(days=0, time=None, base_date_name='event__date_from', minutes=60))
assert rdw.to_string() == 'RELDATE/minutes/60/date_from/' assert rdw.to_string() == 'RELDATE/minutes/60/event__date_from/'
assert rdw.datetime(event) == datetime(2017, 12, 27, 4, 0, 0, tzinfo=TOKYO) 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)) 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/date_from/after' assert rdw.to_string() == 'RELDATE/minutes/60/event__date_from/after'
assert rdw.datetime(event) == datetime(2017, 12, 27, 6, 0, 0, tzinfo=TOKYO) assert rdw.datetime(event) == datetime(2017, 12, 27, 6, 0, 0, tzinfo=TOKYO)
@pytest.mark.django_db @pytest.mark.django_db
def test_relative_date_with_time(event): def test_relative_date_with_time(event):
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(8, 5, 13), base_date_name='date_from', minutes=None)) 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/date_from/' 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) 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)) 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/date_from/after' 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) assert rdw.datetime(event) == datetime(2017, 12, 28, 8, 5, 13, tzinfo=TOKYO)
@@ -115,35 +114,94 @@ def test_relative_date_with_time_around_dst(event):
event.settings.timezone = "Europe/Berlin" event.settings.timezone = "Europe/Berlin"
event.date_from = datetime(2020, 3, 29, 18, 0, 0, tzinfo=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)) 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/date_from/' 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) 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)) 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/date_from/' 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) 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) 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)) 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/date_from/' 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) 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)) 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/date_from/' 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) 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) d = datetime(2017, 12, 25, 10, 0, 0, tzinfo=TOKYO)
rdw = RelativeDateWrapper.from_string(d.isoformat()) rdw = RelativeDateWrapper.from_string(d.isoformat())
assert rdw.data == d assert rdw.data == d
rdw = RelativeDateWrapper.from_string('RELDATE/1/-/date_from/') 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/') rdw = RelativeDateWrapper.from_string('RELDATE/1/-/date_to/')
assert rdw.data == RelativeDate(days=1, time=time(18, 5, 13), base_date_name='date_from', minutes=None) assert rdw.to_string() == 'RELDATE/1/-/event__date_to/'
rdw = RelativeDateWrapper.from_string('RELDATE/minutes/60/date_from/') rdw = RelativeDateWrapper.from_string('RELDATE/1/-/date_admission/')
assert rdw.data == RelativeDate(days=0, time=None, base_date_name='date_from', minutes=60) 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'
+1 -1
View File
@@ -235,7 +235,7 @@ class WaitingListTestCase(TestCase):
self.event.settings.set('waiting_list_enabled', True) self.event.settings.set('waiting_list_enabled', True)
self.event.settings.set('waiting_list_auto', True) self.event.settings.set('waiting_list_auto', True)
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper( 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() self.event.save()
with scope(organizer=self.o): with scope(organizer=self.o):
+2 -2
View File
@@ -481,7 +481,7 @@ class EventsTest(SoupTest):
'payment_term_last_0': 'absolute', 'payment_term_last_0': 'absolute',
'payment_term_last_1': (self.event1.presale_end - datetime.timedelta(1)).strftime('%Y-%m-%d'), 'payment_term_last_1': (self.event1.presale_end - datetime.timedelta(1)).strftime('%Y-%m-%d'),
'payment_term_last_2': '0', 'payment_term_last_2': '0',
'payment_term_last_3': 'date_from', 'payment_term_last_3': 'event__date_from',
'tax_rule_payment': 'default', 'tax_rule_payment': 'default',
}) })
assert doc.select('.alert-danger') assert doc.select('.alert-danger')
@@ -496,7 +496,7 @@ class EventsTest(SoupTest):
'payment_term_last_0': 'relative', 'payment_term_last_0': 'relative',
'payment_term_last_1': '', 'payment_term_last_1': '',
'payment_term_last_2': '10', 'payment_term_last_2': '10',
'payment_term_last_3': 'date_from', 'payment_term_last_3': 'event__date_from',
'tax_rule_payment': 'default', 'tax_rule_payment': 'default',
}) })
assert doc.select('.alert-danger') assert doc.select('.alert-danger')
+28 -28
View File
@@ -230,19 +230,19 @@ class SubEventsTest(SoupTest):
'rel_presale_start_0': 'unset', 'rel_presale_start_0': 'unset',
'rel_presale_start_1': '', 'rel_presale_start_1': '',
'rel_presale_start_2': '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_4': '',
'rel_presale_start_5': '', '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_7': 'before',
'rel_presale_start_8': 'before', 'rel_presale_start_8': 'before',
'rel_presale_end_1': '', 'rel_presale_end_1': '',
'rel_presale_end_0': 'relative', 'rel_presale_end_0': 'relative',
'rel_presale_end_2': '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_4': '13:29:31',
'rel_presale_end_5': '', '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_7': 'before',
'rel_presale_end_8': 'before', 'rel_presale_end_8': 'before',
'quotas-TOTAL_FORMS': '1', 'quotas-TOTAL_FORMS': '1',
@@ -328,19 +328,19 @@ class SubEventsTest(SoupTest):
'rel_presale_start_0': 'unset', 'rel_presale_start_0': 'unset',
'rel_presale_start_1': '', 'rel_presale_start_1': '',
'rel_presale_start_2': '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_4': '',
'rel_presale_start_5': '', '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_7': 'before',
'rel_presale_start_8': 'before', 'rel_presale_start_8': 'before',
'rel_presale_end_1': '', 'rel_presale_end_1': '',
'rel_presale_end_0': 'relative', 'rel_presale_end_0': 'relative',
'rel_presale_end_2': '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_4': '13:29:31',
'rel_presale_end_5': '', '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_7': 'before',
'rel_presale_end_8': 'before', 'rel_presale_end_8': 'before',
'quotas-TOTAL_FORMS': '1', 'quotas-TOTAL_FORMS': '1',
@@ -403,19 +403,19 @@ class SubEventsTest(SoupTest):
'rel_presale_start_0': 'unset', 'rel_presale_start_0': 'unset',
'rel_presale_start_1': '', 'rel_presale_start_1': '',
'rel_presale_start_2': '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_4': '',
'rel_presale_start_5': '', '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_7': 'before',
'rel_presale_start_8': 'before', 'rel_presale_start_8': 'before',
'rel_presale_end_1': '', 'rel_presale_end_1': '',
'rel_presale_end_0': 'relative', 'rel_presale_end_0': 'relative',
'rel_presale_end_2': '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_4': '13:29:31',
'rel_presale_end_5': '', '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_7': 'before',
'rel_presale_end_8': 'before', 'rel_presale_end_8': 'before',
'quotas-TOTAL_FORMS': '1', 'quotas-TOTAL_FORMS': '1',
@@ -479,19 +479,19 @@ class SubEventsTest(SoupTest):
'rel_presale_start_0': 'unset', 'rel_presale_start_0': 'unset',
'rel_presale_start_1': '', 'rel_presale_start_1': '',
'rel_presale_start_2': '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_4': '',
'rel_presale_start_5': '', '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_7': 'before',
'rel_presale_start_8': 'before', 'rel_presale_start_8': 'before',
'rel_presale_end_1': '', 'rel_presale_end_1': '',
'rel_presale_end_0': 'relative', 'rel_presale_end_0': 'relative',
'rel_presale_end_2': '1', '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_4': '02:30:00',
'rel_presale_end_5': '', '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_7': 'before',
'rel_presale_end_8': 'before', 'rel_presale_end_8': 'before',
'quotas-TOTAL_FORMS': '1', 'quotas-TOTAL_FORMS': '1',
@@ -567,19 +567,19 @@ class SubEventsTest(SoupTest):
'rel_presale_start_0': 'unset', 'rel_presale_start_0': 'unset',
'rel_presale_start_1': '', 'rel_presale_start_1': '',
'rel_presale_start_2': '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_4': '',
'rel_presale_start_5': '', '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_7': 'before',
'rel_presale_start_8': 'before', 'rel_presale_start_8': 'before',
'rel_presale_end_1': '', 'rel_presale_end_1': '',
'rel_presale_end_0': 'relative', 'rel_presale_end_0': 'relative',
'rel_presale_end_2': '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_4': '13:29:31',
'rel_presale_end_5': '', '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_7': 'before',
'rel_presale_end_8': 'before', 'rel_presale_end_8': 'before',
'quotas-TOTAL_FORMS': '1', 'quotas-TOTAL_FORMS': '1',
@@ -639,19 +639,19 @@ class SubEventsTest(SoupTest):
'rel_presale_start_0': 'unset', 'rel_presale_start_0': 'unset',
'rel_presale_start_1': '', 'rel_presale_start_1': '',
'rel_presale_start_2': '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_4': '',
'rel_presale_start_5': '', '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_7': 'before',
'rel_presale_start_8': 'before', 'rel_presale_start_8': 'before',
'rel_presale_end_0': 'unset', 'rel_presale_end_0': 'unset',
'rel_presale_end_1': '', 'rel_presale_end_1': '',
'rel_presale_end_2': '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_4': '13:29:31',
'rel_presale_end_5': '', '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_7': 'before',
'rel_presale_end_8': 'before', 'rel_presale_end_8': 'before',
'quotas-TOTAL_FORMS': '1', 'quotas-TOTAL_FORMS': '1',
@@ -711,19 +711,19 @@ class SubEventsTest(SoupTest):
'rel_presale_start_0': 'unset', 'rel_presale_start_0': 'unset',
'rel_presale_start_1': '', 'rel_presale_start_1': '',
'rel_presale_start_2': '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_4': '',
'rel_presale_start_5': '', '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_7': 'before',
'rel_presale_start_8': 'before', 'rel_presale_start_8': 'before',
'rel_presale_end_0': 'unset', 'rel_presale_end_0': 'unset',
'rel_presale_end_1': '', 'rel_presale_end_1': '',
'rel_presale_end_2': '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_4': '13:29:31',
'rel_presale_end_5': '', '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_7': 'before',
'rel_presale_end_8': 'before', 'rel_presale_end_8': 'before',
'quotas-TOTAL_FORMS': '1', 'quotas-TOTAL_FORMS': '1',
+1 -1
View File
@@ -1132,7 +1132,7 @@ class WaitingListTest(EventTestMixin, SoupTest):
def test_auto_disable(self): def test_auto_disable(self):
self.event.settings.set('waiting_list_enabled', True) self.event.settings.set('waiting_list_enabled', True)
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper( 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( response = self.client.get(
'/%s/%s/' % (self.orga.slug, self.event.slug) '/%s/%s/' % (self.orga.slug, self.event.slug)
+1 -1
View File
@@ -1527,7 +1527,7 @@ class OrderChangeAddonsTest(BaseOrdersTest):
self.order.save() self.order.save()
self._subevent_setup() self._subevent_setup()
self.event.settings.set('payment_term_last', RelativeDateWrapper( 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( response = self.client.get(
+1 -1
View File
@@ -1076,7 +1076,7 @@ class OrdersTest(BaseOrdersTest):
self.event.date_from = now() + datetime.timedelta(days=3) self.event.date_from = now() + datetime.timedelta(days=3)
self.event.save() self.event.save()
self.event.settings.set('ticket_download_date', RelativeDateWrapper(RelativeDate( 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( response = self.client.post(
'/%s/%s/order/%s/%s/download/%d/testdummy' % (self.orga.slug, self.event.slug, self.order.code, '/%s/%s/order/%s/%s/download/%d/testdummy' % (self.orga.slug, self.event.slug, self.order.code,