mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Check-in rules: Allow to check for time of day (#2061)
* Add "customtime" option * Fix time picker output format * Fix bug in bool_alg * Fix test
This commit is contained in:
@@ -67,6 +67,14 @@ from pretix.helpers.jsonlogic_query import (
|
||||
def _build_time(t=None, value=None, ev=None):
|
||||
if t == "custom":
|
||||
return dateutil.parser.parse(value)
|
||||
elif t == "customtime":
|
||||
parsed = dateutil.parser.parse(value)
|
||||
return now().astimezone(ev.timezone).replace(
|
||||
hour=parsed.hour,
|
||||
minute=parsed.minute,
|
||||
second=parsed.second,
|
||||
microsecond=parsed.microsecond,
|
||||
)
|
||||
elif t == 'date_from':
|
||||
return ev.date_from
|
||||
elif t == 'date_to':
|
||||
@@ -354,7 +362,15 @@ class SQLLogic:
|
||||
|
||||
if operator == 'buildTime':
|
||||
if values[0] == "custom":
|
||||
return Value(dateutil.parser.parse(values[1]))
|
||||
return Value(dateutil.parser.parse(values[1]).astimezone(pytz.UTC))
|
||||
elif values[0] == "customtime":
|
||||
parsed = dateutil.parser.parse(values[1])
|
||||
return Value(now().astimezone(self.list.event.timezone).replace(
|
||||
hour=parsed.hour,
|
||||
minute=parsed.minute,
|
||||
second=parsed.second,
|
||||
microsecond=parsed.microsecond,
|
||||
).astimezone(pytz.UTC))
|
||||
elif values[0] == 'date_from':
|
||||
return Coalesce(
|
||||
F(f'subevent__date_from'),
|
||||
@@ -382,7 +398,7 @@ class SQLLogic:
|
||||
return int(values[1])
|
||||
elif operator == 'var':
|
||||
if values[0] == 'now':
|
||||
return Value(now())
|
||||
return Value(now().astimezone(pytz.UTC))
|
||||
elif values[0] == 'product':
|
||||
return F('item_id')
|
||||
elif values[0] == 'variation':
|
||||
|
||||
@@ -119,6 +119,7 @@
|
||||
<script type="text/javascript" src="{% static "d3/d3-drag.v2.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "d3/d3-zoom.v2.js" %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/datetimefield.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/timefield.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/lookup-select2.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/checkin-rule.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/checkin-rules-editor.vue' %}"></script>
|
||||
|
||||
@@ -75,12 +75,14 @@ def convert_to_dnf(rules):
|
||||
rules = _distribute_or_over_and(rules)
|
||||
operator = list(rules.keys())[0]
|
||||
values = rules[operator]
|
||||
no_list = False
|
||||
if not isinstance(values, list):
|
||||
values = [values]
|
||||
no_list = True
|
||||
rules = {
|
||||
operator: [
|
||||
convert_to_dnf(v) for v in values
|
||||
] if len(values) > 1 else convert_to_dnf(values[0])
|
||||
] if not no_list else convert_to_dnf(values[0])
|
||||
}
|
||||
if old_rules == rules:
|
||||
break
|
||||
|
||||
@@ -99,7 +99,8 @@ $(document).ready(function () {
|
||||
date_from: gettext('Event start'),
|
||||
date_to: gettext('Event end'),
|
||||
date_admission: gettext('Event admission'),
|
||||
date_custom: gettext('custom time'),
|
||||
date_custom: gettext('custom date and time'),
|
||||
date_customtime: gettext('custom time'),
|
||||
date_tolerance: gettext('Tolerance (minutes)'),
|
||||
condition_add: gettext('Add condition'),
|
||||
minutes: gettext('minutes'),
|
||||
@@ -119,4 +120,4 @@ $(document).ready(function () {
|
||||
},
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,11 +27,14 @@
|
||||
<option value="date_to">{{texts.date_to}}</option>
|
||||
<option value="date_admission">{{texts.date_admission}}</option>
|
||||
<option value="custom">{{texts.date_custom}}</option>
|
||||
<option value="customtime">{{texts.date_customtime}}</option>
|
||||
</select>
|
||||
<datetimefield v-if="vartype == 'datetime' && timeType == 'custom'" :value="timeValue"
|
||||
v-on:input="setTimeValue"></datetimefield>
|
||||
<timefield v-if="vartype == 'datetime' && timeType == 'customtime'" :value="timeValue"
|
||||
v-on:input="setTimeValue"></timefield>
|
||||
<input class="form-control" required type="number"
|
||||
v-if="vartype == 'datetime' && timeType && timeType != 'custom'" v-bind:value="timeTolerance"
|
||||
v-if="vartype == 'datetime' && timeType && timeType != 'customtime' && timeType != 'custom'" v-bind:value="timeTolerance"
|
||||
v-on:input="setTimeTolerance" :placeholder="texts.date_tolerance">
|
||||
<input class="form-control" required type="number" v-if="vartype == 'int' && cardinality > 1"
|
||||
v-bind:value="rightoperand" v-on:input="setRightOperandNumber">
|
||||
@@ -56,6 +59,7 @@
|
||||
components: {
|
||||
LookupSelect2: LookupSelect2.default,
|
||||
Datetimefield: Datetimefield.default,
|
||||
Timefield: Timefield.default,
|
||||
},
|
||||
props: {
|
||||
rule: Object,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<input class="form-control">
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ["required", "value"],
|
||||
template: (''),
|
||||
mounted: function () {
|
||||
var vm = this;
|
||||
var multiple = this.multiple;
|
||||
$(this.$el)
|
||||
.datetimepicker(this.opts())
|
||||
.trigger("change")
|
||||
.on("dp.change", function (e) {
|
||||
vm.$emit("input", $(this).data('DateTimePicker').date().format("HH:mm:ss"));
|
||||
});
|
||||
if (!vm.value) {
|
||||
$(this.$el).data("DateTimePicker").viewDate(moment().hour(0).minute(0).second(0).millisecond(0));
|
||||
} else {
|
||||
$(this.$el).data("DateTimePicker").date(vm.value);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
opts: function () {
|
||||
return {
|
||||
format: $("body").attr("data-timeformat"),
|
||||
locale: $("body").attr("data-datetimelocale"),
|
||||
useCurrent: false,
|
||||
showClear: this.required,
|
||||
icons: {
|
||||
time: 'fa fa-clock-o',
|
||||
date: 'fa fa-calendar',
|
||||
up: 'fa fa-chevron-up',
|
||||
down: 'fa fa-chevron-down',
|
||||
previous: 'fa fa-chevron-left',
|
||||
next: 'fa fa-chevron-right',
|
||||
today: 'fa fa-screenshot',
|
||||
clear: 'fa fa-trash',
|
||||
close: 'fa fa-remove'
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: function (val) {
|
||||
$(this.$el).data('DateTimePicker').date(val);
|
||||
},
|
||||
},
|
||||
destroyed: function () {
|
||||
$(this.$el)
|
||||
.off()
|
||||
.datetimepicker("destroy");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -22,6 +22,9 @@
|
||||
<span v-if="rightoperand.buildTime[0] === 'custom'">
|
||||
{{ df(rightoperand.buildTime[1]) }}
|
||||
</span>
|
||||
<span v-else-if="rightoperand.buildTime[0] === 'customtime'">
|
||||
{{ tf(rightoperand.buildTime[1]) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ this.$root.texts[rightoperand.buildTime[0]] }}
|
||||
</span>
|
||||
@@ -139,6 +142,10 @@
|
||||
df (val) {
|
||||
const format = $("body").attr("data-datetimeformat")
|
||||
return moment(val).format(format)
|
||||
},
|
||||
tf (val) {
|
||||
const format = $("body").attr("data-timeformat")
|
||||
return moment(val).format(format)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
from django.utils.timezone import now
|
||||
from django.utils.timezone import now, override
|
||||
from django_scopes import scope
|
||||
from freezegun import freeze_time
|
||||
|
||||
@@ -621,16 +621,33 @@ def test_rules_time_isbefore_with_tolerance(event, position, clist):
|
||||
def test_rules_time_isafter_custom_time(event, position, clist):
|
||||
# Ticket is valid starting at a custom time
|
||||
event.settings.timezone = 'Europe/Berlin'
|
||||
clist.rules = {"isAfter": [{"var": "now"}, {"buildTime": ["custom", "2020-01-01T22:00:00.000Z"]}, None]}
|
||||
clist.rules = {"isAfter": [{"var": "now"}, {"buildTime": ["customtime", "22:00:00"]}, None]}
|
||||
clist.save()
|
||||
with freeze_time("2020-01-01 21:55:00"):
|
||||
with freeze_time("2020-01-01 21:55:00+01:00"), override(event.timezone):
|
||||
assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists()
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'rules'
|
||||
assert 'Only allowed after 23:00' in str(excinfo.value)
|
||||
assert 'Only allowed after 22:00' in str(excinfo.value)
|
||||
|
||||
with freeze_time("2020-01-01 22:05:00"):
|
||||
with freeze_time("2020-01-01 22:05:00+01:00"):
|
||||
assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists()
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rules_time_isafter_custom_datetime(event, position, clist):
|
||||
# Ticket is valid starting at a custom time
|
||||
event.settings.timezone = 'Europe/Berlin'
|
||||
clist.rules = {"isAfter": [{"var": "now"}, {"buildTime": ["custom", "2020-01-01T23:00:00.000+01:00"]}, None]}
|
||||
clist.save()
|
||||
with freeze_time("2020-01-01 21:55:00+00:00"):
|
||||
assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists()
|
||||
with pytest.raises(CheckInError) as excinfo:
|
||||
perform_checkin(position, clist, {})
|
||||
assert excinfo.value.code == 'rules'
|
||||
|
||||
with freeze_time("2020-01-01 22:05:00+00:00"):
|
||||
assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists()
|
||||
perform_checkin(position, clist, {})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user