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 factory = filters.AlchemyNumericFilter
elif isinstance(column.type, sa.Boolean): elif isinstance(column.type, sa.Boolean):
factory = filters.AlchemyBooleanFilter factory = filters.AlchemyBooleanFilter
elif isinstance(column.type, (sa.Date, sa.DateTime)): elif isinstance(column.type, sa.Date):
factory = filters.AlchemyDateFilter factory = filters.AlchemyDateFilter
elif isinstance(column.type, sa.DateTime):
factory = filters.AlchemyDateTimeFilter
elif isinstance(column.type, GPCType): elif isinstance(column.type, GPCType):
factory = filters.AlchemyGPCFilter factory = filters.AlchemyGPCFilter
factory = kwargs.pop('factory', factory) 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): def iter_filters(self):
""" """

View file

@ -26,6 +26,9 @@ Grid Filters
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
import datetime
import logging
import sqlalchemy as sa import sqlalchemy as sa
from edbob.util import prettify from edbob.util import prettify
@ -33,12 +36,16 @@ from edbob.util import prettify
from rattail.gpc import GPC from rattail.gpc import GPC
from rattail.util import OrderedDict from rattail.util import OrderedDict
from rattail.core import UNSPECIFIED from rattail.core import UNSPECIFIED
from rattail.time import localtime, make_utc
from pyramid_simpleform import Form from pyramid_simpleform import Form
from pyramid_simpleform.renderers import FormRenderer from pyramid_simpleform.renderers import FormRenderer
from webhelpers.html import HTML, tags from webhelpers.html import HTML, tags
log = logging.getLogger(__name__)
class FilterValueRenderer(object): class FilterValueRenderer(object):
""" """
Base class for all filter renderers. Base class for all filter renderers.
@ -350,6 +357,17 @@ class AlchemyDateFilter(AlchemyGridFilter):
""" """
value_renderer_factory = DateValueRenderer 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): def default_verbs(self):
""" """
Expose greater-than / less-than verbs in addition to core. 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', return ['equal', 'not_equal', 'greater_than', 'greater_equal',
'less_than', 'less_equal', 'is_null', 'is_not_null'] '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): class AlchemyGPCFilter(AlchemyGridFilter):