fix: add "is false or null" grid filter, for nullable bool columns
This commit is contained in:
parent
a9caf3f970
commit
f68fe26ada
|
@ -116,20 +116,22 @@ class GridFilter:
|
|||
'is_any': "is any",
|
||||
'equal': "equal to",
|
||||
'not_equal': "not equal to",
|
||||
'is_null': "is null",
|
||||
'is_not_null': "is not null",
|
||||
'is_true': "is true",
|
||||
'is_false': "is false",
|
||||
'is_false_null': "is false or null",
|
||||
'is_null': "is null",
|
||||
'is_not_null': "is not null",
|
||||
'contains': "contains",
|
||||
'does_not_contain': "does not contain",
|
||||
}
|
||||
|
||||
valueless_verbs = [
|
||||
'is_any',
|
||||
'is_null',
|
||||
'is_not_null',
|
||||
'is_true',
|
||||
'is_false',
|
||||
'is_false_null',
|
||||
'is_null',
|
||||
'is_not_null',
|
||||
]
|
||||
|
||||
def __init__(
|
||||
|
@ -416,6 +418,27 @@ class BooleanAlchemyFilter(AlchemyFilter):
|
|||
"""
|
||||
default_verbs = ['is_true', 'is_false']
|
||||
|
||||
def get_verbs(self):
|
||||
""" """
|
||||
|
||||
# get basic verbs from caller, or default list
|
||||
verbs = getattr(self, 'verbs', self.default_verbs)
|
||||
if callable(verbs):
|
||||
verbs = verbs()
|
||||
verbs = list(verbs)
|
||||
|
||||
# add some more if column is nullable
|
||||
if self.nullable:
|
||||
for verb in ('is_false_null', 'is_null', 'is_not_null'):
|
||||
if verb not in verbs:
|
||||
verbs.append(verb)
|
||||
|
||||
# add wildcard
|
||||
if 'is_any' not in verbs:
|
||||
verbs.append('is_any')
|
||||
|
||||
return verbs
|
||||
|
||||
def coerce_value(self, value):
|
||||
""" """
|
||||
if value is not None:
|
||||
|
@ -435,6 +458,14 @@ class BooleanAlchemyFilter(AlchemyFilter):
|
|||
"""
|
||||
return query.filter(self.model_property == False)
|
||||
|
||||
def filter_is_false_null(self, query, value):
|
||||
"""
|
||||
Filter data with "is false or null" condition. The value is
|
||||
ignored.
|
||||
"""
|
||||
return query.filter(sa.or_(self.model_property == False,
|
||||
self.model_property == None))
|
||||
|
||||
|
||||
default_sqlalchemy_filters = {
|
||||
None: AlchemyFilter,
|
||||
|
|
|
@ -328,9 +328,15 @@ class TestBooleanAlchemyFilter(WebTestCase):
|
|||
|
||||
model = self.app.model
|
||||
self.sample_data = [
|
||||
{'username': 'alice', 'active': True},
|
||||
{'username': 'bob', 'active': True},
|
||||
{'username': 'charlie', 'active': False},
|
||||
{'username': 'alice',
|
||||
'prevent_edit': False,
|
||||
'active': True},
|
||||
{'username': 'bob',
|
||||
'prevent_edit': True,
|
||||
'active': True},
|
||||
{'username': 'charlie',
|
||||
'active': False,
|
||||
'prevent_edit': None},
|
||||
]
|
||||
for user in self.sample_data:
|
||||
user = model.User(**user)
|
||||
|
@ -343,6 +349,34 @@ class TestBooleanAlchemyFilter(WebTestCase):
|
|||
kwargs['model_property'] = model_property
|
||||
return factory(self.request, model_property.key, **kwargs)
|
||||
|
||||
def test_get_verbs(self):
|
||||
model = self.app.model
|
||||
|
||||
# bool field, not nullable
|
||||
filtr = self.make_filter(model.User.active,
|
||||
factory=mod.BooleanAlchemyFilter,
|
||||
nullable=False)
|
||||
self.assertFalse(hasattr(filtr, 'verbs'))
|
||||
self.assertEqual(filtr.default_verbs, ['is_true', 'is_false'])
|
||||
|
||||
# by default, returns default verbs (plus 'is_any')
|
||||
self.assertEqual(filtr.get_verbs(), ['is_true', 'is_false', 'is_any'])
|
||||
|
||||
# default verbs can be a callable
|
||||
filtr.default_verbs = lambda: ['foo', 'bar']
|
||||
self.assertEqual(filtr.get_verbs(), ['foo', 'bar', 'is_any'])
|
||||
|
||||
# bool field, *nullable*
|
||||
filtr = self.make_filter(model.User.active,
|
||||
factory=mod.BooleanAlchemyFilter,
|
||||
nullable=True)
|
||||
self.assertFalse(hasattr(filtr, 'verbs'))
|
||||
self.assertEqual(filtr.default_verbs, ['is_true', 'is_false'])
|
||||
|
||||
# effective verbs also include is_false_null
|
||||
self.assertEqual(filtr.get_verbs(), ['is_true', 'is_false', 'is_false_null',
|
||||
'is_null', 'is_not_null', 'is_any'])
|
||||
|
||||
def test_coerce_value(self):
|
||||
model = self.app.model
|
||||
filtr = self.make_filter(model.User.active)
|
||||
|
@ -377,6 +411,16 @@ class TestBooleanAlchemyFilter(WebTestCase):
|
|||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 1)
|
||||
|
||||
def test_filter_is_false_null(self):
|
||||
model = self.app.model
|
||||
filtr = self.make_filter(model.User.prevent_edit)
|
||||
self.assertEqual(self.sample_query.count(), 3)
|
||||
|
||||
# nb. only one account is marked with "prevent edit"
|
||||
filtered_query = filtr.filter_is_false_null(self.sample_query, None)
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 2)
|
||||
|
||||
|
||||
class TestVerbNotSupported(TestCase):
|
||||
|
||||
|
|
Loading…
Reference in a new issue