Improve grid filters for datetime fields.

Hopefully this makes these filters more intuitive, by allowing user to
provide a date value but interpret in a datetime context.
This commit is contained in:
Lance Edgar 2016-03-11 13:21:54 -06:00
parent 68f7c418d6
commit cd461aef51
2 changed files with 117 additions and 8 deletions

View file

@ -100,12 +100,14 @@ class AlchemyGrid(Grid):
factory = filters.AlchemyNumericFilter
elif isinstance(column.type, sa.Boolean):
factory = filters.AlchemyBooleanFilter
elif isinstance(column.type, (sa.Date, sa.DateTime)):
elif isinstance(column.type, sa.Date):
factory = filters.AlchemyDateFilter
elif isinstance(column.type, sa.DateTime):
factory = filters.AlchemyDateTimeFilter
elif isinstance(column.type, GPCType):
factory = filters.AlchemyGPCFilter
factory = kwargs.pop('factory', factory)
return factory(key, column=column, **kwargs)
return factory(key, column=column, config=self.request.rattail_config, **kwargs)
def iter_filters(self):
"""

View file

@ -26,6 +26,9 @@ Grid Filters
from __future__ import unicode_literals, absolute_import
import datetime
import logging
import sqlalchemy as sa
from edbob.util import prettify
@ -33,12 +36,16 @@ from edbob.util import prettify
from rattail.gpc import GPC
from rattail.util import OrderedDict
from rattail.core import UNSPECIFIED
from rattail.time import localtime, make_utc
from pyramid_simpleform import Form
from pyramid_simpleform.renderers import FormRenderer
from webhelpers.html import HTML, tags
log = logging.getLogger(__name__)
class FilterValueRenderer(object):
"""
Base class for all filter renderers.
@ -350,6 +357,17 @@ class AlchemyDateFilter(AlchemyGridFilter):
"""
value_renderer_factory = DateValueRenderer
verb_labels = {
'equal': "on",
'not_equal': "not on",
'greater_than': "after",
'greater_equal': "on or after",
'less_than': "before",
'less_equal': "on or before",
'is_null': "is null",
'is_not_null': "is not null",
}
def default_verbs(self):
"""
Expose greater-than / less-than verbs in addition to core.
@ -357,17 +375,106 @@ class AlchemyDateFilter(AlchemyGridFilter):
return ['equal', 'not_equal', 'greater_than', 'greater_equal',
'less_than', 'less_equal', 'is_null', 'is_not_null']
def filter_is_true(self, query, value):
def make_date(self, value):
"""
Filter data with an "is true" query (alias for "is not null").
Convert user input to a proper ``datetime.date`` object.
"""
return self.filter_is_not_null(query, value)
if value:
try:
dt = datetime.datetime.strptime(value, '%Y-%m-%d')
except ValueError:
log.warning("invalid date value: {}".format(value))
else:
return dt.date()
def filter_is_false(self, query, value):
class AlchemyDateTimeFilter(AlchemyDateFilter):
"""
Filter data with an "is false" query (alias for "is null").
SQLAlchemy filter for datetime values.
"""
return self.filter_is_null(query, value)
def filter_equal(self, query, value):
"""
Find all dateimes which fall on the given date.
"""
date = self.make_date(value)
if not date:
return query
start = datetime.datetime.combine(date, datetime.time(0))
start = make_utc(localtime(self.config, start))
stop = datetime.datetime.combine(date + datetime.timedelta(days=1), datetime.time(0))
stop = make_utc(localtime(self.config, stop))
return query.filter(self.column >= start)\
.filter(self.column < stop)
def filter_not_equal(self, query, value):
"""
Find all dateimes which do *not* fall on the given date.
"""
date = self.make_date(value)
if not date:
return query
start = datetime.datetime.combine(date, datetime.time(0))
start = make_utc(localtime(self.config, start))
stop = datetime.datetime.combine(date + datetime.timedelta(days=1), datetime.time(0))
stop = make_utc(localtime(self.config, stop))
return query.filter(sa.or_(
self.column < start,
self.column <= stop))
def filter_greater_than(self, query, value):
"""
Find all datetimes which fall after the given date.
"""
date = self.make_date(value)
if not date:
return query
time = datetime.datetime.combine(date + datetime.timedelta(days=1), datetime.time(0))
time = make_utc(localtime(self.config, time))
return query.filter(self.column >= time)
def filter_greater_equal(self, query, value):
"""
Find all datetimes which fall on or after the given date.
"""
date = self.make_date(value)
if not date:
return query
time = datetime.datetime.combine(date, datetime.time(0))
time = make_utc(localtime(self.config, time))
return query.filter(self.column >= time)
def filter_less_than(self, query, value):
"""
Find all datetimes which fall before the given date.
"""
date = self.make_date(value)
if not date:
return query
time = datetime.datetime.combine(date, datetime.time(0))
time = make_utc(localtime(self.config, time))
return query.filter(self.column < time)
def filter_less_equal(self, query, value):
"""
Find all datetimes which fall on or before the given date.
"""
date = self.make_date(value)
if not date:
return query
time = datetime.datetime.combine(date + datetime.timedelta(days=1), datetime.time(0))
time = make_utc(localtime(self.config, time))
return query.filter(self.column < time)
class AlchemyGPCFilter(AlchemyGridFilter):