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:
Raphael Michel
2021-05-07 11:29:39 +02:00
committed by GitHub
parent 366395278e
commit b5e41f4c62
8 changed files with 114 additions and 11 deletions

View File

@@ -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':

View File

@@ -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>

View File

@@ -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

View File

@@ -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 () {
},
}
})
});
});

View File

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

View File

@@ -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>

View File

@@ -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)
}
},
}

View File

@@ -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, {})