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",
|
'is_any': "is any",
|
||||||
'equal': "equal to",
|
'equal': "equal to",
|
||||||
'not_equal': "not equal to",
|
'not_equal': "not equal to",
|
||||||
'is_null': "is null",
|
|
||||||
'is_not_null': "is not null",
|
|
||||||
'is_true': "is true",
|
'is_true': "is true",
|
||||||
'is_false': "is false",
|
'is_false': "is false",
|
||||||
|
'is_false_null': "is false or null",
|
||||||
|
'is_null': "is null",
|
||||||
|
'is_not_null': "is not null",
|
||||||
'contains': "contains",
|
'contains': "contains",
|
||||||
'does_not_contain': "does not contain",
|
'does_not_contain': "does not contain",
|
||||||
}
|
}
|
||||||
|
|
||||||
valueless_verbs = [
|
valueless_verbs = [
|
||||||
'is_any',
|
'is_any',
|
||||||
'is_null',
|
|
||||||
'is_not_null',
|
|
||||||
'is_true',
|
'is_true',
|
||||||
'is_false',
|
'is_false',
|
||||||
|
'is_false_null',
|
||||||
|
'is_null',
|
||||||
|
'is_not_null',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -416,6 +418,27 @@ class BooleanAlchemyFilter(AlchemyFilter):
|
||||||
"""
|
"""
|
||||||
default_verbs = ['is_true', 'is_false']
|
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):
|
def coerce_value(self, value):
|
||||||
""" """
|
""" """
|
||||||
if value is not None:
|
if value is not None:
|
||||||
|
@ -435,6 +458,14 @@ class BooleanAlchemyFilter(AlchemyFilter):
|
||||||
"""
|
"""
|
||||||
return query.filter(self.model_property == False)
|
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 = {
|
default_sqlalchemy_filters = {
|
||||||
None: AlchemyFilter,
|
None: AlchemyFilter,
|
||||||
|
|
|
@ -328,9 +328,15 @@ class TestBooleanAlchemyFilter(WebTestCase):
|
||||||
|
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
self.sample_data = [
|
self.sample_data = [
|
||||||
{'username': 'alice', 'active': True},
|
{'username': 'alice',
|
||||||
{'username': 'bob', 'active': True},
|
'prevent_edit': False,
|
||||||
{'username': 'charlie', 'active': False},
|
'active': True},
|
||||||
|
{'username': 'bob',
|
||||||
|
'prevent_edit': True,
|
||||||
|
'active': True},
|
||||||
|
{'username': 'charlie',
|
||||||
|
'active': False,
|
||||||
|
'prevent_edit': None},
|
||||||
]
|
]
|
||||||
for user in self.sample_data:
|
for user in self.sample_data:
|
||||||
user = model.User(**user)
|
user = model.User(**user)
|
||||||
|
@ -343,6 +349,34 @@ class TestBooleanAlchemyFilter(WebTestCase):
|
||||||
kwargs['model_property'] = model_property
|
kwargs['model_property'] = model_property
|
||||||
return factory(self.request, model_property.key, **kwargs)
|
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):
|
def test_coerce_value(self):
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
filtr = self.make_filter(model.User.active)
|
filtr = self.make_filter(model.User.active)
|
||||||
|
@ -377,6 +411,16 @@ class TestBooleanAlchemyFilter(WebTestCase):
|
||||||
self.assertIsNot(filtered_query, self.sample_query)
|
self.assertIsNot(filtered_query, self.sample_query)
|
||||||
self.assertEqual(filtered_query.count(), 1)
|
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):
|
class TestVerbNotSupported(TestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue