diff --git a/tailbone/grids/core.py b/tailbone/grids/core.py index 60374577..72964845 100644 --- a/tailbone/grids/core.py +++ b/tailbone/grids/core.py @@ -69,7 +69,7 @@ class Grid(object): def __init__(self, key, data, columns=None, width='auto', request=None, mobile=False, model_class=None, enums={}, labels={}, renderers={}, extra_row_class=None, linked_columns=[], url='#', - joiners={}, filterable=False, filters={}, + joiners={}, filterable=False, filters={}, use_byte_string_filters=False, sortable=False, sorters={}, default_sortkey=None, default_sortdir='asc', pageable=False, default_pagesize=20, default_page=1, checkboxes=False, checked=None, main_actions=[], more_actions=[], @@ -94,6 +94,7 @@ class Grid(object): self.joiners = joiners or {} self.filterable = filterable + self.use_byte_string_filters = use_byte_string_filters self.filters = self.make_filters(filters) self.sortable = sortable @@ -426,6 +427,7 @@ class Grid(object): factory = gridfilters.AlchemyDateTimeFilter elif isinstance(column.type, GPCType): factory = gridfilters.AlchemyGPCFilter + kwargs.setdefault('encode_values', self.use_byte_string_filters) return factory(key, column=column, config=self.request.rattail_config, **kwargs) def iter_filters(self): diff --git a/tailbone/grids/filters.py b/tailbone/grids/filters.py index 37719275..a3431ef6 100644 --- a/tailbone/grids/filters.py +++ b/tailbone/grids/filters.py @@ -140,7 +140,8 @@ class GridFilter(object): value_renderer_factory = DefaultValueRenderer def __init__(self, key, label=None, verbs=None, value_enum=None, value_renderer=None, - default_active=False, default_verb=None, default_value=None, **kwargs): + default_active=False, default_verb=None, default_value=None, + encode_values=False, value_encoding='utf-8', **kwargs): self.key = key self.label = label or prettify(key) self.verbs = verbs or self.get_default_verbs() @@ -153,6 +154,10 @@ class GridFilter(object): self.default_active = default_active self.default_verb = default_verb self.default_value = default_value + + self.encode_values = encode_values + self.value_encoding = value_encoding + for key, value in kwargs.items(): setattr(self, key, value) @@ -196,6 +201,11 @@ class GridFilter(object): def get_value(self, value=UNSPECIFIED): return value if value is not UNSPECIFIED else self.value + def encode_value(self, value): + if self.encode_values and isinstance(value, six.string_types): + return value.encode('utf-8') + return value + def filter_is_any(self, data, value): """ Special no-op filter which does no actual filtering. Useful in some @@ -241,7 +251,7 @@ class AlchemyGridFilter(GridFilter): """ if value is None or value == '': return query - return query.filter(self.column == value) + return query.filter(self.column == self.encode_value(value)) def filter_not_equal(self, query, value): """ @@ -254,7 +264,7 @@ class AlchemyGridFilter(GridFilter): # include things which are nothing at all, in our result set. return query.filter(sa.or_( self.column == None, - self.column != value, + self.column != self.encode_value(value), )) def filter_is_null(self, query, value): @@ -277,7 +287,7 @@ class AlchemyGridFilter(GridFilter): """ if value is None or value == '': return query - return query.filter(self.column > value) + return query.filter(self.column > self.encode_value(value)) def filter_greater_equal(self, query, value): """ @@ -285,7 +295,7 @@ class AlchemyGridFilter(GridFilter): """ if value is None or value == '': return query - return query.filter(self.column >= value) + return query.filter(self.column >= self.encode_value(value)) def filter_less_than(self, query, value): """ @@ -293,7 +303,7 @@ class AlchemyGridFilter(GridFilter): """ if value is None or value == '': return query - return query.filter(self.column < value) + return query.filter(self.column < self.encode_value(value)) def filter_less_equal(self, query, value): """ @@ -301,7 +311,7 @@ class AlchemyGridFilter(GridFilter): """ if value is None or value == '': return query - return query.filter(self.column <= value) + return query.filter(self.column <= self.encode_value(value)) class AlchemyStringFilter(AlchemyGridFilter): @@ -323,7 +333,8 @@ class AlchemyStringFilter(AlchemyGridFilter): if value is None or value == '': return query return query.filter(sa.and_( - *[self.column.ilike('%{}%'.format(v)) for v in value.split()])) + *[self.column.ilike(self.encode_value('%{}%'.format(v))) + for v in value.split()])) def filter_does_not_contain(self, query, value): """ @@ -337,7 +348,8 @@ class AlchemyStringFilter(AlchemyGridFilter): return query.filter(sa.or_( self.column == None, sa.and_( - *[~self.column.ilike('%{0}%'.format(v)) for v in value.split()]), + *[~self.column.ilike(self.encode_value('%{}%'.format(v))) + for v in value.split()]), )) @@ -350,13 +362,13 @@ class AlchemyEmptyStringFilter(AlchemyStringFilter): return query.filter( sa.or_( self.column == None, - sa.func.trim(self.column) == '')) + sa.func.trim(self.column) == self.encode_value(''))) def filter_is_not_null(self, query, value): return query.filter( sa.and_( self.column != None, - sa.func.trim(self.column) != '')) + sa.func.trim(self.column) != self.encode_value(''))) class AlchemyByteStringFilter(AlchemyStringFilter): diff --git a/tailbone/views/master.py b/tailbone/views/master.py index e1c93f39..0270d055 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -67,6 +67,9 @@ class MasterView(View): pageable = True checkboxes = False + # set to True in order to encode search values as utf-8 + use_byte_string_filters = False + listable = True sortable = True results_downloadable_csv = False @@ -276,6 +279,7 @@ class MasterView(View): 'model_class': getattr(self, 'model_class', None), 'width': 'full', 'filterable': self.filterable, + 'use_byte_string_filters': self.use_byte_string_filters, 'sortable': self.sortable, 'pageable': self.pageable, 'extra_row_class': self.grid_extra_class,