diff --git a/tailbone/grids/core.py b/tailbone/grids/core.py index dde02d19..5c8e1c87 100644 --- a/tailbone/grids/core.py +++ b/tailbone/grids/core.py @@ -1044,6 +1044,9 @@ class Grid(object): valueless = [v for v in filtr.valueless_verbs if v in filtr.verbs] + multiple_values = [v for v in filtr.multiple_value_verbs + if v in filtr.verbs] + choices = [] choice_labels = {} if filtr.choices: @@ -1060,6 +1063,7 @@ class Grid(object): 'visible': filtr.active, 'verbs': filtr.verbs, 'valueless_verbs': valueless, + 'multiple_value_verbs': multiple_values, 'verb_labels': filtr.verb_labels, 'verb': filtr.verb or filtr.default_verb or filtr.verbs[0], 'value': six.text_type(filtr.value) if filtr.value is not None else "", diff --git a/tailbone/grids/filters.py b/tailbone/grids/filters.py index 0aa5046d..a8914b79 100644 --- a/tailbone/grids/filters.py +++ b/tailbone/grids/filters.py @@ -144,6 +144,7 @@ class GridFilter(object): 'is_empty_or_null': "is either empty or null", 'contains': "contains", 'does_not_contain': "does not contain", + 'contains_any_of': "contains any of", 'is_me': "is me", 'is_not_me': "is not me", } @@ -162,6 +163,10 @@ class GridFilter(object): 'is_not_me', ] + multiple_value_verbs = [ + 'contains_any_of', + ] + value_renderer_factory = DefaultValueRenderer data_type = 'string' # default, but will be set from value renderer choices = {} @@ -382,6 +387,7 @@ class AlchemyStringFilter(AlchemyGridFilter): Expose contains / does-not-contain verbs in addition to core. """ return ['contains', 'does_not_contain', + 'contains_any_of', 'equal', 'not_equal', 'is_empty', 'is_not_empty', 'is_null', 'is_not_null', @@ -414,6 +420,39 @@ class AlchemyStringFilter(AlchemyGridFilter): for v in value.split()]), )) + def filter_contains_any_of(self, query, value): + """ + This filter expects "multiple values" separated by newline character, + and will add an "OR" condition with each value being checked via + "ILIKE". For instance if the user submits a "value" like this: + + .. code-block:: none + + foo bar + baz + + This will result in SQL condition like this: + + .. code-block:: sql + + (name ILIKE '%foo%' AND name ILIKE '%bar%') OR name ILIKE '%baz%' + """ + if not value: + return query + + values = value.split('\n') + values = [value for value in values if value] + if not values: + return query + + conditions = [] + for value in values: + conditions.append(sa.and_( + *[self.column.ilike(self.encode_value('%{}%'.format(v))) + for v in value.split()])) + + return query.filter(sa.or_(*conditions)) + def filter_is_empty(self, query, value): return query.filter(sa.func.trim(self.column) == self.encode_value('')) diff --git a/tailbone/static/js/tailbone.buefy.grid.js b/tailbone/static/js/tailbone.buefy.grid.js index f4ebf170..a4139bc6 100644 --- a/tailbone/static/js/tailbone.buefy.grid.js +++ b/tailbone/static/js/tailbone.buefy.grid.js @@ -80,6 +80,23 @@ const GridFilter = { return true }, + multiValuedVerb() { + /* this returns true if the filter's current verb should expose a multi-value input */ + + // if filter has no "multi-value" verbs then we safely assume false + if (!this.filter.multiple_value_verbs) { + return false + } + + // if filter *does* have multi-value verbs, see if "current" is one + if (this.filter.multiple_value_verbs.includes(this.filter.verb)) { + return true + } + + // current verb is not multi-value + return false + }, + focusValue: function() { this.$refs.valueInput.focus() // this.$refs.valueInput.select() diff --git a/tailbone/templates/grids/buefy.mako b/tailbone/templates/grids/buefy.mako index 8d075356..5bd0e619 100644 --- a/tailbone/templates/grids/buefy.mako +++ b/tailbone/templates/grids/buefy.mako @@ -27,7 +27,8 @@