diff --git a/docs/api/wuttaweb/grids.filters.rst b/docs/api/wuttaweb/grids.filters.rst deleted file mode 100644 index b929d75..0000000 --- a/docs/api/wuttaweb/grids.filters.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``wuttaweb.grids.filters`` -========================== - -.. automodule:: wuttaweb.grids.filters - :members: diff --git a/docs/api/wuttaweb/index.rst b/docs/api/wuttaweb/index.rst index 9749cae..1410a20 100644 --- a/docs/api/wuttaweb/index.rst +++ b/docs/api/wuttaweb/index.rst @@ -16,7 +16,6 @@ forms.widgets grids grids.base - grids.filters handler helpers menus diff --git a/src/wuttaweb/forms/base.py b/src/wuttaweb/forms/base.py index c3567b5..d5b893a 100644 --- a/src/wuttaweb/forms/base.py +++ b/src/wuttaweb/forms/base.py @@ -313,7 +313,7 @@ class Form: self.set_fields(fields or self.get_fields()) # nb. this tracks grid JSON data for inclusion in page template - self.grid_vue_context = OrderedDict() + self.grid_vue_data = OrderedDict() def __contains__(self, name): """ @@ -826,16 +826,16 @@ class Form: output = render(template, context) return HTML.literal(output) - def add_grid_vue_context(self, grid): + def add_grid_vue_data(self, grid): """ """ if not grid.key: raise ValueError("grid must have a key!") - if grid.key in self.grid_vue_context: + if grid.key in self.grid_vue_data: log.warning("grid data with key '%s' already registered, " "but will be replaced", grid.key) - self.grid_vue_context[grid.key] = grid.get_vue_context() + self.grid_vue_data[grid.key] = grid.get_vue_data() def render_vue_field( self, diff --git a/src/wuttaweb/grids/base.py b/src/wuttaweb/grids/base.py index aa2e413..3e7695c 100644 --- a/src/wuttaweb/grids/base.py +++ b/src/wuttaweb/grids/base.py @@ -27,7 +27,6 @@ Base grid classes import functools import json import logging -import warnings from collections import namedtuple import sqlalchemy as sa @@ -41,7 +40,6 @@ from webhelpers2.html import HTML from wuttaweb.db import Session from wuttaweb.util import FieldList, get_model_fields, make_json_safe from wuttjamaican.util import UNSPECIFIED -from wuttaweb.grids.filters import default_sqlalchemy_filters, VerbNotSupported log = logging.getLogger(__name__) @@ -117,26 +115,6 @@ class Grid: See also :meth:`set_renderer()`. - .. attribute:: row_class - - This represents the CSS ``class`` attribute for a row within - the grid. Default is ``None``. - - This can be a simple string, in which case the same class is - applied to all rows. - - Or it can be a callable, which can then return different - class(es) depending on each row. The callable must take three - args: ``(obj, data, i)`` - for example:: - - def my_row_class(obj, data, i): - if obj.archived: - return 'poser-archived' - - grid = Grid(request, key='foo', row_class=my_row_class) - - See :meth:`get_row_class()` for more info. - .. attribute:: actions List of :class:`GridAction` instances represenging action links @@ -351,7 +329,6 @@ class Grid: data=None, labels={}, renderers={}, - row_class=None, actions=[], linked_columns=[], sortable=False, @@ -377,7 +354,6 @@ class Grid: self.data = data self.labels = labels or {} self.renderers = renderers or {} - self.row_class = row_class self.actions = actions or [] self.linked_columns = linked_columns or [] self.joiners = joiners or {} @@ -553,9 +529,8 @@ class Grid: Depending on the nature of grid data, sometimes a cell's "as-is" value will be undesirable for display purposes. - The logic in :meth:`get_vue_context()` will first "convert" - all grid data as necessary so that it is at least - JSON-compatible. + The logic in :meth:`get_vue_data()` will first "convert" all + grid data as necessary so that it is at least JSON-compatible. But then it also will invoke a renderer override (if defined) to obtain the "final" cell value. @@ -1059,9 +1034,8 @@ class Grid: def make_filter(self, columninfo, **kwargs): """ - Create and return a - :class:`~wuttaweb.grids.filters.GridFilter` instance suitable - for use on the given column. + Create and return a :class:`GridFilter` instance suitable for + use on the given column. Code usually does not need to call this directly. See also :meth:`set_filter()`, which calls this method automatically. @@ -1069,34 +1043,24 @@ class Grid: :param columninfo: Can be either a model property (see below), or a column name. - :returns: A :class:`~wuttaweb.grids.filters.GridFilter` - instance. + :returns: A :class:`GridFilter` instance. """ - - # model_property is required model_property = None - if kwargs.get('model_property'): - model_property = kwargs['model_property'] - elif isinstance(columninfo, str): + if isinstance(columninfo, str): key = columninfo if self.model_class: - model_property = getattr(self.model_class, key, None) + try: + mapper = sa.inspect(self.model_class) + except sa.exc.NoInspectionAvailable: + pass + else: + model_property = mapper.get_property(key) if not model_property: raise ValueError(f"cannot locate model property for key: {key}") else: model_property = columninfo - # optional factory override - factory = kwargs.pop('factory', None) - if not factory: - typ = model_property.type - factory = default_sqlalchemy_filters.get(type(typ)) - if not factory: - factory = default_sqlalchemy_filters[None] - - # make filter - kwargs['model_property'] = model_property - return factory(self.request, model_property.key, **kwargs) + return GridFilter(self.request, model_property, **kwargs) def set_filter(self, key, filterinfo=None, **kwargs): """ @@ -1264,7 +1228,7 @@ class Grid: settings[f'filter.{filtr.key}.active'] = defaults.get('active', filtr.default_active) settings[f'filter.{filtr.key}.verb'] = defaults.get('verb', - filtr.get_default_verb()) + filtr.default_verb) settings[f'filter.{filtr.key}.value'] = defaults.get('value', filtr.default_value) if self.sortable: @@ -1327,7 +1291,7 @@ class Grid: if self.filterable: for filtr in self.filters.values(): filtr.active = settings[f'filter.{filtr.key}.active'] - filtr.verb = settings[f'filter.{filtr.key}.verb'] or filtr.get_default_verb() + filtr.verb = settings[f'filter.{filtr.key}.verb'] or filtr.default_verb filtr.value = settings[f'filter.{filtr.key}.value'] # sorting @@ -1567,8 +1531,8 @@ class Grid: """ Returns the list of currently active filters. - This inspects each :class:`~wuttaweb.grids.filters.GridFilter` - in :attr:`filters` and only returns the ones marked active. + This inspects each :class:`GridFilter` in :attr:`filters` and + only returns the ones marked active. """ return [filtr for filtr in self.filters.values() if filtr.active] @@ -1694,7 +1658,7 @@ class Grid: .. code-block:: html - + @@ -1713,10 +1677,10 @@ class Grid: .. note:: - The above example shows ``gridContext['mykey'].data`` as - the Vue data reference. This should "just work" if you - provide the correct ``form`` arg and the grid is contained - directly by that form's Vue component. + The above example shows ``gridData['mykey']`` as the Vue + data reference. This should "just work" if you provide the + correct ``form`` arg and the grid is contained directly by + that form's Vue component. However, this may not account for all use cases. For now we wait and see what comes up, but know the dust may not @@ -1725,7 +1689,7 @@ class Grid: # nb. must register data for inclusion on page template if form: - form.add_grid_vue_context(self) + form.add_grid_vue_data(self) # otherwise logic is the same, just different template return self.render_vue_template(template=template, **context) @@ -1833,7 +1797,7 @@ class Grid: in its `Table docs `_. - See also :meth:`get_vue_context()`. + See also :meth:`get_vue_data()`. """ if not self.columns: raise ValueError(f"you must define columns for the grid! key = {self.key}") @@ -1884,55 +1848,61 @@ class Grid: 'key': filtr.key, 'active': filtr.active, 'visible': filtr.active, - 'verbs': filtr.get_verbs(), - 'verb_labels': filtr.get_verb_labels(), - 'valueless_verbs': filtr.get_valueless_verbs(), + 'verbs': filtr.verbs, 'verb': filtr.verb, 'value': filtr.value, 'label': filtr.label, }) return filters - def get_vue_context(self): + def get_vue_data(self): """ - Returns a dict of context for the grid, for use with the Vue - component. This contains the following keys: + Returns a list of Vue-compatible data records. - * ``data`` - list of Vue-compatible data records - * ``row_classes`` - dict of per-row CSS classes + This calls :meth:`get_visible_data()` but then may modify the + result, e.g. to add URLs for :attr:`actions` etc. - This first calls :meth:`get_visible_data()` to get the - original data set. Each record is converted to a dict. + Importantly, this also ensures each value in the dict is + JSON-serializable, using + :func:`~wuttaweb.util.make_json_safe()`. - Then it calls :func:`~wuttaweb.util.make_json_safe()` to - ensure each record can be serialized to JSON. - - Then it invokes any :attr:`renderers` which are defined, to - obtain the "final" values for each record. - - Then it adds a URL key/value for each of the :attr:`actions` - defined, to each record. - - Then it calls :meth:`get_row_class()` for each record. If a - value is returned, it is added to the ``row_classes`` dict. - Note that this dict is keyed by "zero-based row sequence as - string" - the Vue component expects that. - - :returns: Dict of grid data/CSS context as described above. + :returns: List of data record dicts for use with Vue table + component. May be the full set of data, or just the + current page, per :attr:`paginate_on_backend`. """ original_data = self.get_visible_data() - # loop thru data + # TODO: at some point i thought it was useful to wrangle the + # columns here, but now i can't seem to figure out why..? + + # # determine which columns are relevant for data set + # columns = None + # if not columns: + # columns = self.get_columns() + # if not columns: + # raise ValueError("cannot determine columns for the grid") + # columns = set(columns) + # if self.model_class: + # mapper = sa.inspect(self.model_class) + # for column in mapper.primary_key: + # columns.add(column.key) + + # # prune data fields for which no column is defined + # for i, record in enumerate(original_data): + # original_data[i]= dict([(key, record[key]) + # for key in columns]) + + # we have action(s), so add URL(s) for each record in data data = [] - row_classes = {} - for i, record in enumerate(original_data, 1): + for i, record in enumerate(original_data): original_record = record - # convert record to new dict record = dict(record) - # make all values safe for json - record = make_json_safe(record, warn=False) + # convert data if needed, for json compat + record = make_json_safe(record, + # TODO: is this a good idea? + warn=False) # customize value rendering where applicable for key in self.renderers: @@ -1947,48 +1917,9 @@ class Grid: if url: record[key] = url - # set row css class if applicable - css_class = self.get_row_class(original_record, record, i) - if css_class: - # nb. use *string* zero-based index, for js compat - row_classes[str(i-1)] = css_class - data.append(record) - return { - 'data': data, - 'row_classes': row_classes, - } - - def get_vue_data(self): - """ """ - warnings.warn("grid.get_vue_data() is deprecated; " - "please use grid.get_vue_context() instead", - DeprecationWarning, stacklevel=2) - return self.get_vue_context()['data'] - - def get_row_class(self, obj, data, i): - """ - Returns the row CSS ``class`` attribute for the given record. - This method is called by :meth:`get_vue_context()`. - - This will inspect/invoke :attr:`row_class` and return the - value obtained from there. - - :param obj: Reference to the original model instance. - - :param data: Dict of record data for the instance; part of the - Vue grid data set in/from :meth:`get_vue_context()`. - - :param i: One-based sequence for this object/record (row) - within the grid. - - :returns: String of CSS class name(s), or ``None``. - """ - if self.row_class: - if callable(self.row_class): - return self.row_class(obj, data, i) - return self.row_class + return data def get_vue_pager_stats(self): """ @@ -2141,7 +2072,7 @@ class GridAction: :param obj: Model instance of whatever type the parent grid is setup to use. - :param i: One-based sequence for the object's row within the + :param i: Zero-based sequence for the object, within the parent grid. See also :attr:`url`. @@ -2150,3 +2081,164 @@ class GridAction: return self.url(obj, i) return self.url + + +class GridFilter: + """ + Filter option for a grid. Represents both the "features" as well + as "state" for the filter. + + :param request: Current :term:`request` object. + + :param model_property: Property of a model class, representing the + column by which to filter. For instance, + ``model.Person.full_name``. + + :param \**kwargs: Any additional kwargs will be set as attributes + on the filter instance. + + Filter instances have the following attributes: + + .. attribute:: key + + Unique key for the filter. This often corresponds to a "column + name" for the grid, but not always. + + .. attribute:: label + + Display label for the filter field. + + .. attribute:: active + + Boolean indicating whether the filter is currently active. + + See also :attr:`verb` and :attr:`value`. + + .. attribute:: verb + + Verb for current filter, if :attr:`active` is true. + + See also :attr:`value`. + + .. attribute:: value + + Value for current filter, if :attr:`active` is true. + + See also :attr:`verb`. + + .. attribute:: default_active + + Boolean indicating whether the filter should be active by + default, i.e. when first displaying the grid. + + See also :attr:`default_verb` and :attr:`default_value`. + + .. attribute:: default_verb + + Filter verb to use by default. This will be auto-selected when + the filter is first activated, or when first displaying the + grid if :attr:`default_active` is true. + + See also :attr:`default_value`. + + .. attribute:: default_value + + Filter value to use by default. This will be auto-populated + when the filter is first activated, or when first displaying + the grid if :attr:`default_active` is true. + + See also :attr:`default_verb`. + """ + + def __init__( + self, + request, + model_property, + label=None, + default_active=False, + default_verb=None, + default_value=None, + **kwargs, + ): + self.request = request + self.config = self.request.wutta_config + self.app = self.config.get_app() + + self.model_property = model_property + self.key = self.model_property.key + self.label = label or self.app.make_title(self.key) + + self.default_active = default_active + self.active = self.default_active + + self.verbs = ['contains'] # TODO + self.default_verb = default_verb or self.verbs[0] + self.verb = self.default_verb + + self.default_value = default_value + self.value = self.default_value + + self.__dict__.update(kwargs) + + def __repr__(self): + return ("GridFilter(" + f"key='{self.key}', " + f"active={self.active}, " + f"verb='{self.verb}', " + f"value={repr(self.value)})") + + def apply_filter(self, data, verb=None, value=UNSPECIFIED): + """ + Filter the given data set according to a verb/value pair. + + If verb and/or value are not specified, will use :attr:`verb` + and/or :attr:`value` instead. + + This method does not directly filter the data; rather it + delegates (based on ``verb``) to some other method. The + latter may choose *not* to filter the data, e.g. if ``value`` + is empty, in which case this may return the original data set + unchanged. + + :returns: The (possibly) filtered data set. + """ + if verb is None: + verb = self.verb + if not verb: + log.warn("missing verb for '%s' filter, will use default verb: %s", + self.key, self.default_verb) + verb = self.default_verb + + if value is UNSPECIFIED: + value = self.value + + func = getattr(self, f'filter_{verb}', None) + if not func: + raise VerbNotSupported(verb) + + return func(data, value) + + def filter_contains(self, query, value): + """ + Filter data with a full 'ILIKE' query. + """ + if value is None or value == '': + return query + + criteria = [] + for val in value.split(): + val = val.replace('_', r'\_') + val = f'%{val}%' + criteria.append(self.model_property.ilike(val)) + + return query.filter(sa.and_(*criteria)) + + +class VerbNotSupported(Exception): + """ """ + + def __init__(self, verb): + self.verb = verb + + def __str__(self): + return f"unknown filter verb not supported: {self.verb}" diff --git a/src/wuttaweb/grids/filters.py b/src/wuttaweb/grids/filters.py deleted file mode 100644 index 0489c22..0000000 --- a/src/wuttaweb/grids/filters.py +++ /dev/null @@ -1,444 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# wuttaweb -- Web App for Wutta Framework -# Copyright © 2024 Lance Edgar -# -# This file is part of Wutta Framework. -# -# Wutta Framework is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# Wutta Framework is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# Wutta Framework. If not, see . -# -################################################################################ -""" -Grid Filters -""" - -import logging - -import sqlalchemy as sa - -from wuttjamaican.util import UNSPECIFIED - - -log = logging.getLogger(__name__) - - -class VerbNotSupported(Exception): - """ """ - - def __init__(self, verb): - self.verb = verb - - def __str__(self): - return f"unknown filter verb not supported: {self.verb}" - - -class GridFilter: - """ - Filter option for a grid. Represents both the "features" as well - as "state" for the filter. - - :param request: Current :term:`request` object. - - :param model_property: Property of a model class, representing the - column by which to filter. For instance, - ``model.Person.full_name``. - - :param \**kwargs: Any additional kwargs will be set as attributes - on the filter instance. - - Filter instances have the following attributes: - - .. attribute:: key - - Unique key for the filter. This often corresponds to a "column - name" for the grid, but not always. - - .. attribute:: label - - Display label for the filter field. - - .. attribute:: active - - Boolean indicating whether the filter is currently active. - - See also :attr:`verb` and :attr:`value`. - - .. attribute:: verb - - Verb for current filter, if :attr:`active` is true. - - See also :attr:`value`. - - .. attribute:: value - - Value for current filter, if :attr:`active` is true. - - See also :attr:`verb`. - - .. attribute:: default_active - - Boolean indicating whether the filter should be active by - default, i.e. when first displaying the grid. - - See also :attr:`default_verb` and :attr:`default_value`. - - .. attribute:: default_verb - - Filter verb to use by default. This will be auto-selected when - the filter is first activated, or when first displaying the - grid if :attr:`default_active` is true. - - See also :attr:`default_value`. - - .. attribute:: default_value - - Filter value to use by default. This will be auto-populated - when the filter is first activated, or when first displaying - the grid if :attr:`default_active` is true. - - See also :attr:`default_verb`. - """ - default_verbs = ['equal', 'not_equal'] - - default_verb_labels = { - '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", - 'contains': "contains", - 'does_not_contain': "does not contain", - } - - valueless_verbs = [ - 'is_any', - 'is_null', - 'is_not_null', - 'is_true', - 'is_false', - ] - - def __init__( - self, - request, - key, - label=None, - verbs=None, - default_active=False, - default_verb=None, - default_value=None, - **kwargs, - ): - self.request = request - self.key = key - self.config = self.request.wutta_config - self.app = self.config.get_app() - self.label = label or self.app.make_title(self.key) - - # active - self.default_active = default_active - self.active = self.default_active - - # verb - if verbs is not None: - self.verbs = verbs - if default_verb: - self.default_verb = default_verb - - # value - self.default_value = default_value - self.value = self.default_value - - self.__dict__.update(kwargs) - - def __repr__(self): - verb = getattr(self, 'verb', None) - return (f"{self.__class__.__name__}(" - f"key='{self.key}', " - f"active={self.active}, " - f"verb={repr(verb)}, " - f"value={repr(self.value)})") - - def get_verbs(self): - """ - Returns the list of verbs supported by the filter. - """ - verbs = None - - if hasattr(self, 'verbs'): - verbs = self.verbs - - else: - verbs = self.default_verbs - - if callable(verbs): - verbs = verbs() - verbs = list(verbs) - - if self.nullable: - if 'is_null' not in verbs: - verbs.append('is_null') - if 'is_not_null' not in verbs: - verbs.append('is_not_null') - - if 'is_any' not in verbs: - verbs.append('is_any') - - return verbs - - def get_verb_labels(self): - """ - Returns a dict of all defined verb labels. - """ - # TODO: should traverse hierarchy - labels = dict([(verb, verb) for verb in self.get_verbs()]) - labels.update(self.default_verb_labels) - return labels - - def get_valueless_verbs(self): - """ - Returns a list of verb names which do not need a value. - """ - return self.valueless_verbs - - def get_default_verb(self): - """ - Returns the default verb for the filter. - """ - verb = None - - if hasattr(self, 'default_verb'): - verb = self.default_verb - - elif hasattr(self, 'verb'): - verb = self.verb - - if not verb: - verbs = self.get_verbs() - if verbs: - verb = verbs[0] - - return verb - - def apply_filter(self, data, verb=None, value=UNSPECIFIED): - """ - Filter the given data set according to a verb/value pair. - - If verb and/or value are not specified, will use :attr:`verb` - and/or :attr:`value` instead. - - This method does not directly filter the data; rather it - delegates (based on ``verb``) to some other method. The - latter may choose *not* to filter the data, e.g. if ``value`` - is empty, in which case this may return the original data set - unchanged. - - :returns: The (possibly) filtered data set. - """ - if verb is None: - verb = self.verb - if not verb: - verb = self.get_default_verb() - log.warn("missing verb for '%s' filter, will use default verb: %s", - self.key, verb) - - # only attempt for known verbs - if verb not in self.get_verbs(): - raise VerbNotSupported(verb) - - # fallback value - if value is UNSPECIFIED: - value = self.value - - # locate filter method - func = getattr(self, f'filter_{verb}', None) - if not func: - raise VerbNotSupported(verb) - - # invoke filter method - return func(data, value) - - def filter_is_any(self, data, value): - """ - This is a no-op which always ignores the value and returns the - data as-is. - """ - return data - - -class AlchemyFilter(GridFilter): - """ - Filter option for a grid with SQLAlchemy query data. - - This is a subclass of :class:`GridFilter`. It requires a - ``model_property`` to know how to filter the query. - - :param model_property: Property of a model class, representing the - column by which to filter. For instance, - ``model.Person.full_name``. - - :param nullable: Boolean indicating whether the filter should - include ``is_null`` and ``is_not_null`` verbs. If not - specified, the column will be inspected and use its nullable - flag. - """ - - def __init__(self, *args, **kwargs): - nullable = kwargs.pop('nullable', None) - super().__init__(*args, **kwargs) - - self.nullable = nullable - if self.nullable is None: - columns = self.model_property.prop.columns - if len(columns) == 1: - self.nullable = columns[0].nullable - - def coerce_value(self, value): - """ - Coerce the given value to the correct type/format for use with - the filter. - - Default logic returns value as-is; subclass may override. - """ - return value - - def filter_equal(self, query, value): - """ - Filter data with an equal (``=``) condition. - """ - value = self.coerce_value(value) - if value is None: - return query - - return query.filter(self.model_property == value) - - def filter_not_equal(self, query, value): - """ - Filter data with a not equal (``!=``) condition. - """ - value = self.coerce_value(value) - if value is None: - return query - - # sql probably excludes null values from results, but user - # probably does not expect that, so explicitly include them. - return query.filter(sa.or_( - self.model_property == None, - self.model_property != value, - )) - - def filter_is_null(self, query, value): - """ - Filter data with an ``IS NULL`` query. The value is ignored. - """ - return query.filter(self.model_property == None) - - def filter_is_not_null(self, query, value): - """ - Filter data with an ``IS NOT NULL`` query. The value is - ignored. - """ - return query.filter(self.model_property != None) - - -class StringAlchemyFilter(AlchemyFilter): - """ - SQLAlchemy filter option for a text data column. - - Subclass of :class:`AlchemyFilter`. - """ - default_verbs = ['contains', 'does_not_contain', - 'equal', 'not_equal'] - - def coerce_value(self, value): - """ """ - if value is not None: - value = str(value) - if value: - return value - - def filter_contains(self, query, value): - """ - Filter data with an ``ILIKE`` condition. - """ - value = self.coerce_value(value) - if not value: - return query - - criteria = [] - for val in value.split(): - val = val.replace('_', r'\_') - val = f'%{val}%' - criteria.append(self.model_property.ilike(val)) - - return query.filter(sa.and_(*criteria)) - - def filter_does_not_contain(self, query, value): - """ - Filter data with a ``NOT ILIKE`` condition. - """ - value = self.coerce_value(value) - if not value: - return query - - criteria = [] - for val in value.split(): - val = val.replace('_', r'\_') - val = f'%{val}%' - criteria.append(~self.model_property.ilike(val)) - - # sql probably excludes null values from results, but user - # probably does not expect that, so explicitly include them. - return query.filter(sa.or_( - self.model_property == None, - sa.and_(*criteria))) - - -class BooleanAlchemyFilter(AlchemyFilter): - """ - SQLAlchemy filter option for a boolean data column. - - Subclass of :class:`AlchemyFilter`. - """ - default_verbs = ['is_true', 'is_false'] - - def coerce_value(self, value): - """ """ - if value is not None: - return bool(value) - - def filter_is_true(self, query, value): - """ - Filter data with an "is true" condition. The value is - ignored. - """ - return query.filter(self.model_property == True) - - def filter_is_false(self, query, value): - """ - Filter data with an "is false" condition. The value is - ignored. - """ - return query.filter(self.model_property == False) - - -default_sqlalchemy_filters = { - None: AlchemyFilter, - sa.String: StringAlchemyFilter, - sa.Text: StringAlchemyFilter, - sa.Boolean: BooleanAlchemyFilter, -} diff --git a/src/wuttaweb/templates/base.mako b/src/wuttaweb/templates/base.mako index a85bc2d..c8557ca 100644 --- a/src/wuttaweb/templates/base.mako +++ b/src/wuttaweb/templates/base.mako @@ -164,9 +164,12 @@ gap: 0.5rem; } + .wutta-filter .button.filter-toggle { + justify-content: left; + } + .wutta-filter .button.filter-toggle, .wutta-filter .filter-verb { - justify-content: left; min-width: 15rem; } diff --git a/src/wuttaweb/templates/form.mako b/src/wuttaweb/templates/form.mako index de7209a..7029463 100644 --- a/src/wuttaweb/templates/form.mako +++ b/src/wuttaweb/templates/form.mako @@ -9,17 +9,13 @@ % endif -<%def name="render_vue_template_form()"> +<%def name="render_vue_templates()"> + ${parent.render_vue_templates()} % if form is not Undefined: ${form.render_vue_template()} % endif -<%def name="render_vue_templates()"> - ${parent.render_vue_templates()} - ${self.render_vue_template_form()} - - <%def name="make_vue_components()"> ${parent.make_vue_components()} % if form is not Undefined: diff --git a/src/wuttaweb/templates/forms/vue_template.mako b/src/wuttaweb/templates/forms/vue_template.mako index 5a4af70..e7d3f2b 100644 --- a/src/wuttaweb/templates/forms/vue_template.mako +++ b/src/wuttaweb/templates/forms/vue_template.mako @@ -69,9 +69,9 @@ % endif - % if form.grid_vue_context: - gridContext: { - % for key, data in form.grid_vue_context.items(): + % if form.grid_vue_data: + gridData: { + % for key, data in form.grid_vue_data.items(): '${key}': ${json.dumps(data)|n}, % endfor }, diff --git a/src/wuttaweb/templates/grids/table_element.mako b/src/wuttaweb/templates/grids/table_element.mako index 1bbf8a9..ba35bf3 100644 --- a/src/wuttaweb/templates/grids/table_element.mako +++ b/src/wuttaweb/templates/grids/table_element.mako @@ -1,5 +1,5 @@ ## -*- coding: utf-8; -*- -<${b}-table :data="gridContext['${grid.key}'].data"> +<${b}-table :data="gridData['${grid.key}']"> % for column in grid.get_vue_columns(): <${b}-table-column field="${column['field']}" diff --git a/src/wuttaweb/templates/grids/vue_template.mako b/src/wuttaweb/templates/grids/vue_template.mako index edcdc24..84dcb58 100644 --- a/src/wuttaweb/templates/grids/vue_template.mako +++ b/src/wuttaweb/templates/grids/vue_template.mako @@ -5,7 +5,8 @@
% if grid.filterable: -
+
- {{ viewResetting ? "Working, please wait..." : "Reset View" }} + Reset View <${b}-table :data="data" - :row-class="getRowClass" :loading="loading" narrowed hoverable @@ -228,12 +227,10 @@