Add basic support for "between" verb, for date range grid filter

this seems to be complete, but we'll see in practice if i forgot something..
This commit is contained in:
Lance Edgar 2019-08-29 17:23:32 -05:00
parent 14778757d9
commit d97f95fb92
4 changed files with 177 additions and 10 deletions

View file

@ -553,6 +553,7 @@ class AlchemyDateFilter(AlchemyGridFilter):
'greater_equal': "on or after", 'greater_equal': "on or after",
'less_than': "before", 'less_than': "before",
'less_equal': "on or before", 'less_equal': "on or before",
'between': "between",
'is_null': "is null", 'is_null': "is null",
'is_not_null': "is not null", 'is_not_null': "is not null",
'is_any': "is any", 'is_any': "is any",
@ -562,8 +563,18 @@ class AlchemyDateFilter(AlchemyGridFilter):
""" """
Expose greater-than / less-than verbs in addition to core. Expose greater-than / less-than verbs in addition to core.
""" """
return ['equal', 'not_equal', 'greater_than', 'greater_equal', return [
'less_than', 'less_equal', 'is_null', 'is_not_null', 'is_any'] 'equal',
'not_equal',
'greater_than',
'greater_equal',
'less_than',
'less_equal',
'between',
'is_null',
'is_not_null',
'is_any',
]
def make_date(self, value): def make_date(self, value):
""" """
@ -577,6 +588,44 @@ class AlchemyDateFilter(AlchemyGridFilter):
else: else:
return dt.date() return dt.date()
def filter_between(self, query, value):
"""
Filter data with a "between" query. Really this uses ">=" and "<="
(inclusive) logic instead of SQL "between" keyword.
"""
if value is None or value == '':
return query
if '|' not in value:
return query
values = value.split('|')
if len(values) != 2:
return query
start_date, end_date = values
if start_date:
start_date = self.make_date(start_date)
if end_date:
end_date = self.make_date(end_date)
# we'll only filter if we have start and/or end date
if not start_date and not end_date:
return query
return self.filter_date_range(query, start_date, end_date)
def filter_date_range(self, query, start_date, end_date):
"""
This method should actually apply filter(s) to the query, according to
the given date range. Subclasses may override this logic.
"""
if start_date:
query = query.filter(self.column >= start_date)
if end_date:
query = query.filter(self.column <= end_date)
return query
class AlchemyDateTimeFilter(AlchemyDateFilter): class AlchemyDateTimeFilter(AlchemyDateFilter):
""" """
@ -666,6 +715,17 @@ class AlchemyDateTimeFilter(AlchemyDateFilter):
time = make_utc(localtime(self.config, time)) time = make_utc(localtime(self.config, time))
return query.filter(self.column < time) return query.filter(self.column < time)
def filter_date_range(self, query, start_date, end_date):
if start_date:
start_time = datetime.datetime.combine(start_date, datetime.time(0))
start_time = localtime(self.config, start_time)
query = query.filter(self.column >= make_utc(start_time))
if end_date:
end_time = datetime.datetime.combine(end_date + datetime.timedelta(days=1), datetime.time(0))
end_time = localtime(self.config, end_time)
query = query.filter(self.column <= make_utc(end_time))
return query
class AlchemyLocalDateTimeFilter(AlchemyDateTimeFilter): class AlchemyLocalDateTimeFilter(AlchemyDateTimeFilter):
""" """
@ -756,6 +816,17 @@ class AlchemyLocalDateTimeFilter(AlchemyDateTimeFilter):
time = localtime(self.config, time, tzinfo=False) time = localtime(self.config, time, tzinfo=False)
return query.filter(self.column < time) return query.filter(self.column < time)
def filter_date_range(self, query, start_date, end_date):
if start_date:
start_time = datetime.datetime.combine(start_date, datetime.time(0))
start_time = localtime(self.config, start_time, tzinfo=False)
query = query.filter(self.column >= start_time)
if end_date:
end_time = datetime.datetime.combine(end_date + datetime.timedelta(days=1), datetime.time(0))
end_time = localtime(self.config, end_time, tzinfo=False)
query = query.filter(self.column <= end_time)
return query
class AlchemyGPCFilter(AlchemyGridFilter): class AlchemyGPCFilter(AlchemyGridFilter):
""" """

View file

@ -14,6 +14,7 @@ const TailboneDatepicker = {
':value="value ? parseDate(value) : null"', ':value="value ? parseDate(value) : null"',
'@input="dateChanged"', '@input="dateChanged"',
':disabled="disabled"', ':disabled="disabled"',
'ref="trueDatePicker"',
'>', '>',
'</b-datepicker>' '</b-datepicker>'
].join(' '), ].join(' '),
@ -49,7 +50,11 @@ const TailboneDatepicker = {
dateChanged(date) { dateChanged(date) {
this.$emit('input', this.formatDate(date)) this.$emit('input', this.formatDate(date))
} },
focus() {
this.$refs.trueDatePicker.focus()
},
} }
} }

View file

@ -1,4 +1,53 @@
const GridFilterDateValue = {
template: '#grid-filter-date-value-template',
props: {
value: String,
dateRange: Boolean,
},
data() {
return {
startDate: null,
endDate: null,
}
},
mounted() {
if (this.dateRange) {
if (this.value.includes('|')) {
let values = this.value.split('|')
if (values.length == 2) {
this.startDate = values[0]
this.endDate = values[1]
} else {
this.startDate = this.value
}
} else {
this.startDate = this.value
}
} else {
this.startDate = this.value
}
},
methods: {
focus() {
this.$refs.startDate.focus()
},
startDateChanged(value) {
if (this.dateRange) {
value += '|' + this.endDate
}
this.$emit('input', value)
},
endDateChanged(value) {
value = this.startDate + '|' + value
this.$emit('input', value)
},
},
}
Vue.component('grid-filter-date-value', GridFilterDateValue)
const GridFilter = { const GridFilter = {
template: '#grid-filter-template', template: '#grid-filter-template',
props: { props: {
@ -14,6 +63,23 @@ const GridFilter = {
}) })
}, },
valuedVerb() {
/* this returns true if the filter's current verb should expose value input(s) */
// if filter has no "valueless" verbs, then all verbs should expose value inputs
if (!this.filter.valueless_verbs) {
return true
}
// if filter *does* have valueless verbs, check if "current" verb is valueless
if (this.filter.valueless_verbs.includes(this.filter.verb)) {
return false
}
// current verb is *not* valueless
return true
},
focusValue: function() { focusValue: function() {
this.$refs.valueInput.focus() this.$refs.valueInput.focus()
// this.$refs.valueInput.select() // this.$refs.valueInput.select()

View file

@ -1,5 +1,29 @@
## -*- coding: utf-8; -*- ## -*- coding: utf-8; -*-
<script type="text/x-template" id="grid-filter-date-value-template">
<div class="level">
<div class="level-left">
<div class="level-item">
<tailbone-datepicker v-model="startDate"
ref="startDate"
@input="startDateChanged">
</tailbone-datepicker>
</div>
<div v-show="dateRange"
class="level-item">
and
</div>
<div v-show="dateRange"
class="level-item">
<tailbone-datepicker v-model="endDate"
ref="endDate"
@input="endDateChanged">
</tailbone-datepicker>
</div>
</div>
</div>
</script>
<script type="text/x-template" id="grid-filter-template"> <script type="text/x-template" id="grid-filter-template">
<div class="level filter" v-show="filter.visible"> <div class="level filter" v-show="filter.visible">
@ -30,15 +54,16 @@
## only one of the following "value input" elements will be rendered ## only one of the following "value input" elements will be rendered
<tailbone-datepicker v-if="filter.data_type == 'date'" <grid-filter-date-value v-if="filter.data_type == 'date'"
v-model="filter.value" v-model="filter.value"
v-show="! (filter.valueless_verbs && filter.valueless_verbs.includes(filter.verb))" v-show="valuedVerb()"
ref="valueInput"> :date-range="filter.verb == 'between'"
</tailbone-datepicker> ref="valueInput">
</grid-filter-date-value>
<b-select v-if="filter.data_type == 'choice'" <b-select v-if="filter.data_type == 'choice'"
v-model="filter.value" v-model="filter.value"
v-show="! (filter.valueless_verbs && filter.valueless_verbs.includes(filter.verb))" v-show="valuedVerb()"
ref="valueInput"> ref="valueInput">
<option v-for="choice in filter.choices" <option v-for="choice in filter.choices"
:key="choice" :key="choice"
@ -49,7 +74,7 @@
<b-input v-if="filter.data_type == 'string'" <b-input v-if="filter.data_type == 'string'"
v-model="filter.value" v-model="filter.value"
v-show="! (filter.valueless_verbs && filter.valueless_verbs.includes(filter.verb))" v-show="valuedVerb()"
ref="valueInput"> ref="valueInput">
</b-input> </b-input>