mirror of
https://github.com/pretix/pretix.git
synced 2025-12-13 12:42:26 +00:00
Compare commits
4 Commits
update-dja
...
reldatetim
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2fe2500ba | ||
|
|
13aee2b3bb | ||
|
|
ba9d0823c7 | ||
|
|
87768a2b74 |
@@ -185,48 +185,103 @@ BEFORE_AFTER_CHOICE = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
reldatetimeparts = namedtuple('reldatetimeparts', (
|
||||||
|
"status", # 0
|
||||||
|
"absolute", # 1
|
||||||
|
"rel_days_number", # 2
|
||||||
|
"rel_mins_relationto", # 3
|
||||||
|
"rel_days_timeofday", # 4
|
||||||
|
"rel_mins_number", # 5
|
||||||
|
"rel_days_relationto", # 6
|
||||||
|
"rel_mins_relation", # 7
|
||||||
|
"rel_days_relation" # 8
|
||||||
|
))
|
||||||
|
reldatetimeparts.indizes = reldatetimeparts(*range(9))
|
||||||
|
|
||||||
|
|
||||||
class RelativeDateTimeWidget(forms.MultiWidget):
|
class RelativeDateTimeWidget(forms.MultiWidget):
|
||||||
template_name = 'pretixbase/forms/widgets/reldatetime.html'
|
template_name = 'pretixbase/forms/widgets/reldatetime.html'
|
||||||
|
parts = reldatetimeparts
|
||||||
|
|
||||||
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')
|
||||||
widgets = (
|
widgets = reldatetimeparts(
|
||||||
forms.RadioSelect(choices=self.status_choices),
|
status=forms.RadioSelect(choices=self.status_choices),
|
||||||
forms.DateTimeInput(
|
absolute=forms.DateTimeInput(
|
||||||
attrs={'class': 'datetimepicker'}
|
attrs={'class': 'datetimepicker'}
|
||||||
),
|
),
|
||||||
forms.NumberInput(),
|
rel_days_number=forms.NumberInput(),
|
||||||
forms.Select(choices=base_choices),
|
rel_mins_relationto=forms.Select(choices=base_choices),
|
||||||
forms.TimeInput(attrs={'placeholder': _('Time'), 'class': 'timepickerfield'}),
|
rel_days_timeofday=forms.TimeInput(attrs={'placeholder': _('Time'), 'class': 'timepickerfield'}),
|
||||||
forms.NumberInput(),
|
rel_mins_number=forms.NumberInput(),
|
||||||
forms.Select(choices=base_choices),
|
rel_days_relationto=forms.Select(choices=base_choices),
|
||||||
forms.Select(choices=BEFORE_AFTER_CHOICE),
|
rel_mins_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||||
forms.Select(choices=BEFORE_AFTER_CHOICE),
|
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||||
)
|
)
|
||||||
super().__init__(widgets=widgets, *args, **kwargs)
|
super().__init__(widgets=widgets, *args, **kwargs)
|
||||||
|
|
||||||
def decompress(self, value):
|
def decompress(self, value):
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
value = RelativeDateWrapper.from_string(value)
|
value = RelativeDateWrapper.from_string(value)
|
||||||
|
if isinstance(value, reldatetimeparts):
|
||||||
|
return value
|
||||||
if not value:
|
if not value:
|
||||||
return ['unset', None, 1, 'date_from', None, 0, "date_from", "before", "before"]
|
return reldatetimeparts(
|
||||||
|
status="unset",
|
||||||
|
absolute=None,
|
||||||
|
rel_days_number=1,
|
||||||
|
rel_mins_relationto="date_from",
|
||||||
|
rel_days_timeofday=None,
|
||||||
|
rel_mins_number=0,
|
||||||
|
rel_days_relationto="date_from",
|
||||||
|
rel_mins_relation="before",
|
||||||
|
rel_days_relation="before"
|
||||||
|
)
|
||||||
elif isinstance(value.data, (datetime.datetime, datetime.date)):
|
elif isinstance(value.data, (datetime.datetime, datetime.date)):
|
||||||
return ['absolute', value.data, 1, 'date_from', None, 0, "date_from", "before", "before"]
|
return reldatetimeparts(
|
||||||
|
status="absolute",
|
||||||
|
absolute=value.data,
|
||||||
|
rel_days_number=1,
|
||||||
|
rel_mins_relationto="date_from",
|
||||||
|
rel_days_timeofday=None,
|
||||||
|
rel_mins_number=0,
|
||||||
|
rel_days_relationto="date_from",
|
||||||
|
rel_mins_relation="before",
|
||||||
|
rel_days_relation="before"
|
||||||
|
)
|
||||||
elif value.data.minutes is not None:
|
elif value.data.minutes is not None:
|
||||||
return ['relative_minutes', None, None, value.data.base_date_name, None, value.data.minutes, value.data.base_date_name,
|
return reldatetimeparts(
|
||||||
"after" if value.data.is_after else "before", "after" if value.data.is_after else "before"]
|
status="relative_minutes",
|
||||||
return ['relative', None, value.data.days, value.data.base_date_name, value.data.time, 0, value.data.base_date_name,
|
absolute=None,
|
||||||
"after" if value.data.is_after else "before", "after" if value.data.is_after else "before"]
|
rel_days_number=None,
|
||||||
|
rel_mins_relationto=value.data.base_date_name,
|
||||||
|
rel_days_timeofday=None,
|
||||||
|
rel_mins_number=value.data.minutes,
|
||||||
|
rel_days_relationto=value.data.base_date_name,
|
||||||
|
rel_mins_relation="after" if value.data.is_after else "before",
|
||||||
|
rel_days_relation="after" if value.data.is_after else "before"
|
||||||
|
)
|
||||||
|
return reldatetimeparts(
|
||||||
|
status="relative",
|
||||||
|
absolute=None,
|
||||||
|
rel_days_number=value.data.days,
|
||||||
|
rel_mins_relationto=value.data.base_date_name,
|
||||||
|
rel_days_timeofday=value.data.time,
|
||||||
|
rel_mins_number=0,
|
||||||
|
rel_days_relationto=value.data.base_date_name,
|
||||||
|
rel_mins_relation="after" if value.data.is_after else "before",
|
||||||
|
rel_days_relation="after" if value.data.is_after else "before"
|
||||||
|
)
|
||||||
|
|
||||||
def get_context(self, name, value, attrs):
|
def get_context(self, name, value, attrs):
|
||||||
ctx = super().get_context(name, value, attrs)
|
ctx = super().get_context(name, value, attrs)
|
||||||
ctx['required'] = self.status_choices[0][0] == 'unset'
|
ctx['required'] = self.status_choices[0][0] == 'unset'
|
||||||
|
|
||||||
ctx['rendered_subwidgets'] = [
|
ctx['rendered_subwidgets'] = self.parts(*(
|
||||||
self._render(w['template_name'], {**ctx, 'widget': w})
|
self._render(w['template_name'], {**ctx, 'widget': w})
|
||||||
for w in ctx['widget']['subwidgets']
|
for w in ctx['widget']['subwidgets']
|
||||||
]
|
))._asdict()
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
@@ -245,36 +300,36 @@ class RelativeDateTimeField(forms.MultiValueField):
|
|||||||
choices = BASE_CHOICES
|
choices = BASE_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')))
|
||||||
fields = (
|
fields = reldatetimeparts(
|
||||||
forms.ChoiceField(
|
status=forms.ChoiceField(
|
||||||
choices=status_choices,
|
choices=status_choices,
|
||||||
required=True
|
required=True
|
||||||
),
|
),
|
||||||
forms.DateTimeField(
|
absolute=forms.DateTimeField(
|
||||||
required=False
|
required=False
|
||||||
),
|
),
|
||||||
forms.IntegerField(
|
rel_days_number=forms.IntegerField(
|
||||||
required=False
|
required=False
|
||||||
),
|
),
|
||||||
forms.ChoiceField(
|
rel_mins_relationto=forms.ChoiceField(
|
||||||
choices=choices,
|
choices=choices,
|
||||||
required=False
|
required=False
|
||||||
),
|
),
|
||||||
forms.TimeField(
|
rel_days_timeofday=forms.TimeField(
|
||||||
required=False,
|
required=False,
|
||||||
),
|
),
|
||||||
forms.IntegerField(
|
rel_mins_number=forms.IntegerField(
|
||||||
required=False
|
required=False
|
||||||
),
|
),
|
||||||
forms.ChoiceField(
|
rel_days_relationto=forms.ChoiceField(
|
||||||
choices=choices,
|
choices=choices,
|
||||||
required=False
|
required=False
|
||||||
),
|
),
|
||||||
forms.ChoiceField(
|
rel_mins_relation=forms.ChoiceField(
|
||||||
choices=BEFORE_AFTER_CHOICE,
|
choices=BEFORE_AFTER_CHOICE,
|
||||||
required=False
|
required=False
|
||||||
),
|
),
|
||||||
forms.ChoiceField(
|
rel_days_relation=forms.ChoiceField(
|
||||||
choices=BEFORE_AFTER_CHOICE,
|
choices=BEFORE_AFTER_CHOICE,
|
||||||
required=False
|
required=False
|
||||||
),
|
),
|
||||||
@@ -288,32 +343,36 @@ class RelativeDateTimeField(forms.MultiValueField):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def set_event(self, event):
|
def set_event(self, event):
|
||||||
self.widget.widgets[3].choices = [
|
self.widget.widgets[reldatetimeparts.indizes.rel_days_relationto].choices = [
|
||||||
|
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
|
||||||
|
]
|
||||||
|
self.widget.widgets[reldatetimeparts.indizes.rel_mins_relationto].choices = [
|
||||||
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
|
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
|
||||||
]
|
]
|
||||||
|
|
||||||
def compress(self, data_list):
|
def compress(self, data_list):
|
||||||
if not data_list:
|
if not data_list:
|
||||||
return None
|
return None
|
||||||
if data_list[0] == 'absolute':
|
data = reldatetimeparts(*data_list)
|
||||||
return RelativeDateWrapper(data_list[1])
|
if data.status == 'absolute':
|
||||||
elif data_list[0] == 'unset':
|
return RelativeDateWrapper(data.absolute)
|
||||||
|
elif data.status == 'unset':
|
||||||
return None
|
return None
|
||||||
elif data_list[0] == 'relative_minutes':
|
elif data.status == 'relative_minutes':
|
||||||
return RelativeDateWrapper(RelativeDate(
|
return RelativeDateWrapper(RelativeDate(
|
||||||
days=0,
|
days=0,
|
||||||
base_date_name=data_list[3],
|
base_date_name=data.rel_mins_relationto,
|
||||||
time=None,
|
time=None,
|
||||||
minutes=data_list[5],
|
minutes=data.rel_mins_number,
|
||||||
is_after=data_list[7] == "after",
|
is_after=data.rel_mins_relation == "after",
|
||||||
))
|
))
|
||||||
else:
|
else:
|
||||||
return RelativeDateWrapper(RelativeDate(
|
return RelativeDateWrapper(RelativeDate(
|
||||||
days=data_list[2],
|
days=data.rel_days_number,
|
||||||
base_date_name=data_list[6],
|
base_date_name=data.rel_days_relationto,
|
||||||
time=data_list[4],
|
time=data.rel_days_timeofday,
|
||||||
minutes=None,
|
minutes=None,
|
||||||
is_after=data_list[8] == "after",
|
is_after=data.rel_days_relation == "after",
|
||||||
))
|
))
|
||||||
|
|
||||||
def has_changed(self, initial, data):
|
def has_changed(self, initial, data):
|
||||||
@@ -322,29 +381,41 @@ class RelativeDateTimeField(forms.MultiValueField):
|
|||||||
return super().has_changed(initial, data)
|
return super().has_changed(initial, data)
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
if value[0] == 'absolute' and not value[1]:
|
data = reldatetimeparts(*value)
|
||||||
|
if data.status == 'absolute' and not data.absolute:
|
||||||
raise ValidationError(self.error_messages['incomplete'])
|
raise ValidationError(self.error_messages['incomplete'])
|
||||||
elif value[0] == 'relative' and (value[2] is None or not value[3]):
|
elif data.status == 'relative' and (data.rel_days_number is None or not data.rel_days_relationto):
|
||||||
raise ValidationError(self.error_messages['incomplete'])
|
raise ValidationError(self.error_messages['incomplete'])
|
||||||
elif value[0] == 'relative_minutes' and (value[5] is None or not value[3]):
|
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'])
|
||||||
|
|
||||||
return super().clean(value)
|
return super().clean(value)
|
||||||
|
|
||||||
|
|
||||||
|
reldateparts = namedtuple('reldateparts', (
|
||||||
|
"status", # 0
|
||||||
|
"absolute", # 1
|
||||||
|
"rel_days_number", # 2
|
||||||
|
"rel_days_relationto", # 3
|
||||||
|
"rel_days_relation", # 4
|
||||||
|
))
|
||||||
|
reldateparts.indizes = reldateparts(*range(5))
|
||||||
|
|
||||||
|
|
||||||
class RelativeDateWidget(RelativeDateTimeWidget):
|
class RelativeDateWidget(RelativeDateTimeWidget):
|
||||||
template_name = 'pretixbase/forms/widgets/reldate.html'
|
template_name = 'pretixbase/forms/widgets/reldate.html'
|
||||||
|
parts = reldateparts
|
||||||
|
|
||||||
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 = (
|
widgets = reldateparts(
|
||||||
forms.RadioSelect(choices=self.status_choices),
|
status=forms.RadioSelect(choices=self.status_choices),
|
||||||
forms.DateInput(
|
absolute=forms.DateInput(
|
||||||
attrs={'class': 'datepickerfield'}
|
attrs={'class': 'datepickerfield'}
|
||||||
),
|
),
|
||||||
forms.NumberInput(),
|
rel_days_number=forms.NumberInput(),
|
||||||
forms.Select(choices=kwargs.pop('base_choices')),
|
rel_days_relationto=forms.Select(choices=kwargs.pop('base_choices')),
|
||||||
forms.Select(choices=BEFORE_AFTER_CHOICE),
|
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||||
)
|
)
|
||||||
forms.MultiWidget.__init__(self, widgets=widgets, *args, **kwargs)
|
forms.MultiWidget.__init__(self, widgets=widgets, *args, **kwargs)
|
||||||
|
|
||||||
@@ -352,10 +423,30 @@ class RelativeDateWidget(RelativeDateTimeWidget):
|
|||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
value = RelativeDateWrapper.from_string(value)
|
value = RelativeDateWrapper.from_string(value)
|
||||||
if not value:
|
if not value:
|
||||||
return ['unset', None, 1, 'date_from', 'before']
|
return reldateparts(
|
||||||
|
status="unset",
|
||||||
|
absolute=None,
|
||||||
|
rel_days_number=1,
|
||||||
|
rel_days_relationto="date_from",
|
||||||
|
rel_days_relation="before"
|
||||||
|
)
|
||||||
|
if isinstance(value, reldateparts):
|
||||||
|
return value
|
||||||
elif isinstance(value.data, (datetime.datetime, datetime.date)):
|
elif isinstance(value.data, (datetime.datetime, datetime.date)):
|
||||||
return ['absolute', value.data, 1, 'date_from', 'before']
|
return reldateparts(
|
||||||
return ['relative', None, value.data.days, value.data.base_date_name, "after" if value.data.is_after else "before"]
|
status="absolute",
|
||||||
|
absolute=value.data,
|
||||||
|
rel_days_number=1,
|
||||||
|
rel_days_relationto="date_from",
|
||||||
|
rel_days_relation="before"
|
||||||
|
)
|
||||||
|
return reldateparts(
|
||||||
|
status="relative",
|
||||||
|
absolute=None,
|
||||||
|
rel_days_number=value.data.days,
|
||||||
|
rel_days_relationto=value.data.base_date_name,
|
||||||
|
rel_days_relation="after" if value.data.is_after else "before"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RelativeDateField(RelativeDateTimeField):
|
class RelativeDateField(RelativeDateTimeField):
|
||||||
@@ -367,22 +458,22 @@ 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')))
|
||||||
fields = (
|
fields = reldateparts(
|
||||||
forms.ChoiceField(
|
status=forms.ChoiceField(
|
||||||
choices=status_choices,
|
choices=status_choices,
|
||||||
required=True
|
required=True
|
||||||
),
|
),
|
||||||
forms.DateField(
|
absolute=forms.DateField(
|
||||||
required=False
|
required=False
|
||||||
),
|
),
|
||||||
forms.IntegerField(
|
rel_days_number=forms.IntegerField(
|
||||||
required=False
|
required=False
|
||||||
),
|
),
|
||||||
forms.ChoiceField(
|
rel_days_relationto=forms.ChoiceField(
|
||||||
choices=BASE_CHOICES,
|
choices=BASE_CHOICES,
|
||||||
required=False
|
required=False
|
||||||
),
|
),
|
||||||
forms.ChoiceField(
|
rel_days_relation=forms.ChoiceField(
|
||||||
choices=BEFORE_AFTER_CHOICE,
|
choices=BEFORE_AFTER_CHOICE,
|
||||||
required=False
|
required=False
|
||||||
),
|
),
|
||||||
@@ -393,28 +484,35 @@ class RelativeDateField(RelativeDateTimeField):
|
|||||||
self, fields=fields, require_all_fields=False, *args, **kwargs
|
self, fields=fields, require_all_fields=False, *args, **kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def set_event(self, event):
|
||||||
|
self.widget.widgets[reldateparts.indizes.rel_days_relationto].choices = [
|
||||||
|
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
|
||||||
|
]
|
||||||
|
|
||||||
def compress(self, data_list):
|
def compress(self, data_list):
|
||||||
if not data_list:
|
if not data_list:
|
||||||
return None
|
return None
|
||||||
if data_list[0] == 'absolute':
|
data = reldateparts(*data_list)
|
||||||
return RelativeDateWrapper(data_list[1])
|
if data.status == 'absolute':
|
||||||
elif data_list[0] == 'unset':
|
return RelativeDateWrapper(data.absolute)
|
||||||
|
elif data.status == 'unset':
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return RelativeDateWrapper(RelativeDate(
|
return RelativeDateWrapper(RelativeDate(
|
||||||
days=data_list[2],
|
days=data.rel_days_number,
|
||||||
base_date_name=data_list[3],
|
base_date_name=data.rel_days_relationto,
|
||||||
time=None, minutes=None,
|
time=None, minutes=None,
|
||||||
is_after=data_list[4] == "after"
|
is_after=data.rel_days_relation == "after"
|
||||||
))
|
))
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
if value[0] == 'absolute' and not value[1]:
|
data = reldateparts(*value)
|
||||||
|
if data.status == 'absolute' and not data.absolute:
|
||||||
raise ValidationError(self.error_messages['incomplete'])
|
raise ValidationError(self.error_messages['incomplete'])
|
||||||
elif value[0] == 'relative' and (value[2] is None or not value[3]):
|
elif data.status == 'relative' and (data.rel_days_number is None or not data.rel_days_relationto):
|
||||||
raise ValidationError(self.error_messages['incomplete'])
|
raise ValidationError(self.error_messages['incomplete'])
|
||||||
|
|
||||||
return super().clean(value)
|
return forms.MultiValueField.clean(self, value)
|
||||||
|
|
||||||
|
|
||||||
class ModelRelativeDateTimeField(models.CharField):
|
class ModelRelativeDateTimeField(models.CharField):
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
{{ selopt.label }}
|
{{ selopt.label }}
|
||||||
</label>
|
</label>
|
||||||
{% if selopt.value == "absolute" %}
|
{% if selopt.value == "absolute" %}
|
||||||
{% include widget.subwidgets.1.template_name with widget=widget.subwidgets.1 %}
|
{{ rendered_subwidgets.absolute }}
|
||||||
{% elif selopt.value == "relative" %}
|
{% elif selopt.value == "relative" %}
|
||||||
{% blocktrans trimmed with number=rendered_subwidgets.2 relation=rendered_subwidgets.4 relation_to=rendered_subwidgets.3 %}
|
{% blocktrans trimmed with number=rendered_subwidgets.rel_days_number relation=rendered_subwidgets.rel_days_relation relation_to=rendered_subwidgets.rel_days_relationto %}
|
||||||
{{ number }} days {{ relation }} {{ relation_to }}
|
{{ number }} days {{ relation }} {{ relation_to }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
{{ selopt.label }}
|
{{ selopt.label }}
|
||||||
</label>
|
</label>
|
||||||
{% if selopt.value == "absolute" %}
|
{% if selopt.value == "absolute" %}
|
||||||
{% include widget.subwidgets.1.template_name with widget=widget.subwidgets.1 %}
|
{{ rendered_subwidgets.absolute }}
|
||||||
{% elif selopt.value == "relative_minutes" %}
|
{% elif selopt.value == "relative_minutes" %}
|
||||||
{% blocktrans trimmed with number=rendered_subwidgets.5 relation=rendered_subwidgets.7 relation_to=rendered_subwidgets.3 %}
|
{% blocktrans trimmed with number=rendered_subwidgets.rel_mins_number relation=rendered_subwidgets.rel_mins_relation relation_to=rendered_subwidgets.rel_mins_relationto %}
|
||||||
{{ number }} minutes {{ relation }} {{ relation_to }}
|
{{ number }} minutes {{ relation }} {{ relation_to }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% elif selopt.value == "relative" %}
|
{% elif selopt.value == "relative" %}
|
||||||
{% blocktrans trimmed with number=rendered_subwidgets.2 relation=rendered_subwidgets.8 relation_to=rendered_subwidgets.6 time_of_day=rendered_subwidgets.4 %}
|
{% blocktrans trimmed with number=rendered_subwidgets.rel_days_number relation=rendered_subwidgets.rel_days_relation relation_to=rendered_subwidgets.rel_days_relationto time_of_day=rendered_subwidgets.rel_days_timeofday %}
|
||||||
{{ number }} days {{ relation }} {{ relation_to }} at {{ time_of_day }}
|
{{ number }} days {{ relation }} {{ relation_to }} at {{ time_of_day }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user