moves logic from RelativeDateWrapper into RelativeDate and adds BaseChoice for configuring which models attributes support which relationship

This commit is contained in:
Lukas Bockstaller
2026-05-19 17:11:06 +02:00
parent f0d1d7ca5a
commit 21fc8e4dec
13 changed files with 363 additions and 258 deletions

View File

@@ -20,8 +20,13 @@
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
import datetime import datetime
import os
import pprint
import warnings
from collections import namedtuple from collections import namedtuple
from typing import Tuple, Union, TYPE_CHECKING from typing import (
TYPE_CHECKING, Iterable, List, Literal, Tuple, Union,
)
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from dateutil import parser from dateutil import parser
@@ -29,7 +34,7 @@ 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.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
@@ -37,21 +42,205 @@ from rest_framework import serializers
if TYPE_CHECKING: if TYPE_CHECKING:
from .models import Event, Order, SubEvent from .models import Event, Order, SubEvent
EVENT_CHOICES = (
('date_from', _('Event start')),
('date_to', _('Event end')),
('date_admission', _('Event admission')),
('presale_start', _('Presale start')),
('presale_end', _('Presale end')),
)
# extend NO_BEFORE_VALUES in reldate.js if changed class BaseChoice:
ORDER_CHOICES = ( def __init__(self, base: Literal["event", "order"], attribute: str, text: Promise, supports_before: bool,
('datetime', _('Order creation')), supports_after: bool) -> None:
) self.base = base
ORDER_CHOICES_KEYS = {choice[0] for choice in ORDER_CHOICES} self.attribute = attribute
self.text = text
self.supports_before = supports_before
self.supports_after = supports_after
self.key = f"{self.base}__{self.attribute}"
RelativeDate = namedtuple('RelativeDate', ['days', 'minutes', 'time', 'is_after', 'base_date_name'], defaults=(0, None, None, False, 'date_from')) @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, False),
]
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'
]
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.
"""
def __init__(self, days: int = 0, minutes: int = None, time: datetime.time = None, is_after: bool = False,
base_date_name: str = 'event__date_from') -> None:
choice = BaseChoice.find(BASE_CHOICES, base_date_name)
self.base = choice.base
self.attribute = choice.attribute
if is_after and not choice.supports_after:
raise ValueError(
"The selected base date and attribute combination does not support relative dates placed after the base date")
if not is_after and not choice.supports_before:
raise ValueError(
"The selected base date and attribute combination does not support relative dates placed before the base date")
self.is_after = is_after
self.days = days
self.minutes = minutes
self.time = time
self.key = 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
if self.base == "order" and isinstance(base, Order):
event = base.event
base_date = getattr(base, self.attribute)
elif self.base == "event" and isinstance(base, SubEvent):
event = base.event
base_date = (getattr(base, self.attribute) or
getattr(base.event, self.attribute) or
base.date_from)
elif self.base == "event" and isinstance(base, Event):
event = base
base_date = getattr(base, self.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.key,
'after' if self.is_after else '',
)
return 'RELDATE/{}/{}/{}/{}'.format( #
self.days,
self.time.strftime('%H:%M:%S') if self.time else '-',
self.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: class RelativeDateWrapper:
@@ -59,140 +248,35 @@ 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, as well as a ``datetime`` of an order.
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 _resolve_base_date(self, reference: "Event | Order | SubEvent") -> Tuple[datetime.datetime, ZoneInfo]: def date(self, base: "Event | Order | SubEvent") -> datetime.date:
"""
:param reference:
:return:
"""
from .models import Event, Order, SubEvent
if self.data.base_date_name in ORDER_CHOICES_KEYS:
if not isinstance(reference, Order):
raise ValueError('A order-based relative datetime choice must be used with an order object')
event = reference.event
base_date = getattr(reference, self.data.base_date_name)
elif isinstance(reference, SubEvent):
event = reference.event
base_date = (getattr(reference, self.data.base_date_name) or
getattr(reference.event, self.data.base_date_name) or
reference.date_from)
elif isinstance(reference, Event):
event = reference
base_date = getattr(reference, self.data.base_date_name) or event.date_from
else:
raise TypeError("Only event, subevent or order objects are supported")
tz = ZoneInfo(event.settings.timezone)
return base_date, tz
def date(self, reference: "datetime.date | datetime.datetime | Event | Order | SubEvent" ) -> 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')
base_date, tz = self._resolve_base_date(reference) def datetime(self, base: "Event | Order | SubEvent") -> datetime.datetime:
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, reference: "datetime.date | datetime.datetime | Event | Order | SubEvent" ) -> 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:
base_date, tz = self._resolve_base_date(reference) return self.data.datetime(base)
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 in ORDER_CHOICES_KEYS and parts[4] != "after":
raise ValueError('ORDER_CHOICE: {} cannot be combined with "before"'.format(data.base_date_name))
if data.base_date_name not in [k[0] for k in EVENT_CHOICES + ORDER_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)
@@ -206,7 +290,6 @@ BEFORE_AFTER_CHOICE = (
('after', _('after')), ('after', _('after')),
) )
reldatetimeparts = namedtuple('reldatetimeparts', ( reldatetimeparts = namedtuple('reldatetimeparts', (
"status", # 0 "status", # 0
"absolute", # 1 "absolute", # 1
@@ -266,10 +349,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"
) )
@@ -278,10 +361,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"
) )
@@ -301,10 +384,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"
) )
@@ -328,15 +411,28 @@ 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)
all_choices = EVENT_BASE_CHOICES
if self.relative_to_order:
all_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 EVENT_CHOICES if k in limit] if any(["__" not in l for l in limit]):
else: _warn_skips = (os.path.dirname(__file__),)
choices = EVENT_CHOICES warnings.warn(
"Please prefix limit_choices with the base the attributes refer to, for example event__date_from",
skip_file_prefixes=_warn_skips)
self.relative_to_order = kwargs.pop('relative_to_order', False) choices = [(c.key, c.text) for c in all_choices if
if self.relative_to_order: # new base case as we want limit_choices to be expressed as base__attribute
choices += ORDER_CHOICES (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)]
else:
choices = [(c.key, c.text) for c in all_choices]
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')))
@@ -385,10 +481,10 @@ class RelativeDateTimeField(forms.MultiValueField):
def set_event(self, event): def set_event(self, event):
choices = [ choices = [
(k, v) for k, v in EVENT_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: if self.relative_to_order:
choices += ORDER_CHOICES choices += [(c.key, c.text) for c in ORDER_BASE_CHOICES]
self.widget.widgets[reldateparts.indizes.rel_days_relationto].choices = choices self.widget.widgets[reldateparts.indizes.rel_days_relationto].choices = choices
self.widget.widgets[reldatetimeparts.indizes.rel_mins_relationto].choices = choices self.widget.widgets[reldatetimeparts.indizes.rel_mins_relationto].choices = choices
@@ -396,10 +492,10 @@ class RelativeDateTimeField(forms.MultiValueField):
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,
@@ -430,10 +526,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' and data.rel_days_relationto in ORDER_CHOICES_KEYS and data.rel_days_relation == 'before': elif data.status == 'relative':
raise ValidationError(_('A relative date in relation to an order can only be after the order has been placed')) choice = BaseChoice.find(BASE_CHOICES, data.rel_days_relationto)
elif data.status == 'relative' and data.rel_mins_relationto in ORDER_CHOICES_KEYS and data.rel_mins_relation == 'before': if data.rel_days_relation == "before" and not choice.supports_before:
raise ValidationError(_('A relative date in relation to an order can only be after the order has been placed')) 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)
@@ -506,10 +610,10 @@ 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')))
choices = EVENT_CHOICES choices = [(c.key, c.text) for c in EVENT_BASE_CHOICES]
self.relative_to_order = kwargs.pop('relative_to_order', False) self.relative_to_order = kwargs.pop('relative_to_order', False)
if self.relative_to_order: if self.relative_to_order:
choices += ORDER_CHOICES choices += [(c.key, c.text) for c in ORDER_BASE_CHOICES]
fields = reldateparts( fields = reldateparts(
status=forms.ChoiceField( status=forms.ChoiceField(
@@ -539,20 +643,20 @@ class RelativeDateField(RelativeDateTimeField):
def set_event(self, event): def set_event(self, event):
choices = [ choices = [
(k, v) for k, v in EVENT_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: if self.relative_to_order:
choices += ORDER_CHOICES choices += [(c.key, c.text) for c in ORDER_BASE_CHOICES]
self.widget.widgets[reldateparts.indizes.rel_days_relationto].choices = 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,
@@ -565,10 +669,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)
elif data.status == 'relative' and data.rel_days_relationto in ORDER_CHOICES_KEYS and data.rel_days_relation == 'before': if data.rel_days_number is None or not data.rel_days_relationto:
raise ValidationError(_('A relative date in relation to an order can only be after the order has been placed')) 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)

View File

@@ -93,14 +93,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'),
@@ -332,12 +332,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):
@@ -354,12 +354,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):

View File

@@ -424,7 +424,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,

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()

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,

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)

View File

@@ -20,6 +20,7 @@
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
from datetime import datetime, time, timedelta from datetime import datetime, time, timedelta
from pprint import pprint
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
import pytest import pytest
@@ -56,57 +57,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,22 +116,22 @@ 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)
@@ -140,6 +141,9 @@ def test_unserialize():
assert rdw.data == d assert rdw.data == d
rdw = RelativeDateWrapper.from_string('RELDATE/1/-/date_from/') rdw = RelativeDateWrapper.from_string('RELDATE/1/-/date_from/')
x = RelativeDate(days=1, time=None, base_date_name='date_from', minutes=None)
pprint(rdw.data)
pprint(x)
assert rdw.data == RelativeDate(days=1, time=None, base_date_name='date_from', minutes=None) assert rdw.data == RelativeDate(days=1, time=None, base_date_name='date_from', minutes=None)
rdw = RelativeDateWrapper.from_string('RELDATE/1/18:05:13/date_from/') rdw = RelativeDateWrapper.from_string('RELDATE/1/18:05:13/date_from/')
@@ -163,13 +167,6 @@ def test_relative_to_order(event):
total=23, locale='en' total=23, locale='en'
) )
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='datetime', minutes=None)) 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, 28, 18, 0, 0, tzinfo=TOKYO)
assert rdw.to_string() == 'RELDATE/1/-/datetime/'
# this is expressible as a RelativeDate but the Wrapper should catch it as invalid when parsing
with pytest.raises(ValueError):
rdw.from_string(rdw.to_string())
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='datetime', minutes=None, is_after=True))
assert rdw.datetime(order).astimezone(TOKYO) == datetime(2020, 3, 30, 18, 0, 0, tzinfo=TOKYO) assert rdw.datetime(order).astimezone(TOKYO) == datetime(2020, 3, 30, 18, 0, 0, tzinfo=TOKYO)
assert rdw.to_string() == 'RELDATE/1/-/datetime/after' assert rdw.to_string() == 'RELDATE/1/-/order__datetime/after'

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):

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')

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',

View File

@@ -1122,7 +1122,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)

View File

@@ -1485,7 +1485,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(

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,