feat: move "most" filtering logic for grid class to wuttaweb

we still define all filters, and the "most important" grid methods for
filtering
This commit is contained in:
Lance Edgar 2024-08-21 20:16:03 -05:00
parent ffa724ef37
commit e52a83751e

View file

@ -196,9 +196,6 @@ class Grid(WuttaGrid):
raw_renderers={}, raw_renderers={},
extra_row_class=None, extra_row_class=None,
url='#', url='#',
joiners={},
filterable=False,
filters={},
use_byte_string_filters=False, use_byte_string_filters=False,
checkboxes=False, checkboxes=False,
checked=None, checked=None,
@ -263,6 +260,8 @@ class Grid(WuttaGrid):
# reference grid.vue_component etc. # reference grid.vue_component etc.
kwargs.setdefault('vue_tagname', 'tailbone-grid') kwargs.setdefault('vue_tagname', 'tailbone-grid')
self.use_byte_string_filters = use_byte_string_filters
kwargs['key'] = key kwargs['key'] = key
kwargs['data'] = data kwargs['data'] = data
super().__init__(request, **kwargs) super().__init__(request, **kwargs)
@ -286,11 +285,6 @@ class Grid(WuttaGrid):
self.invisible = invisible or [] self.invisible = invisible or []
self.extra_row_class = extra_row_class self.extra_row_class = extra_row_class
self.url = url self.url = url
self.joiners = joiners or {}
self.filterable = filterable
self.use_byte_string_filters = use_byte_string_filters
self.filters = self.make_filters(filters)
self.checkboxes = checkboxes self.checkboxes = checkboxes
self.checked = checked self.checked = checked
@ -446,10 +440,14 @@ class Grid(WuttaGrid):
self.remove(oldfield) self.remove(oldfield)
def set_joiner(self, key, joiner): def set_joiner(self, key, joiner):
""" """
if joiner is None: if joiner is None:
self.joiners.pop(key, None) warnings.warn("specifying None is deprecated for Grid.set_joiner(); "
"please use Grid.remove_joiner() instead",
DeprecationWarning, stacklevel=2)
self.remove_joiner(key)
else: else:
self.joiners[key] = joiner super().set_joiner(key, joiner)
def set_sorter(self, key, *args, **kwargs): def set_sorter(self, key, *args, **kwargs):
""" """ """ """
@ -477,33 +475,27 @@ class Grid(WuttaGrid):
self.sorters[key] = self.make_sorter(*args, **kwargs) self.sorters[key] = self.make_sorter(*args, **kwargs)
def set_filter(self, key, *args, **kwargs): def set_filter(self, key, *args, **kwargs):
if len(args) == 1 and args[0] is None: """ """
if len(args) == 1:
if args[0] is None:
warnings.warn("specifying None is deprecated for Grid.set_filter(); "
"please use Grid.remove_filter() instead",
DeprecationWarning, stacklevel=2)
self.remove_filter(key) self.remove_filter(key)
else: else:
if 'label' not in kwargs and key in self.labels: super().set_filter(key, args[0], **kwargs)
kwargs['label'] = self.labels[key]
elif len(args) == 0:
super().set_filter(key, **kwargs)
else:
warnings.warn("multiple args are deprecated for Grid.set_filter(); "
"please refactor your code accordingly",
DeprecationWarning, stacklevel=2)
kwargs.setdefault('label', self.get_label(key))
self.filters[key] = self.make_filter(key, *args, **kwargs) self.filters[key] = self.make_filter(key, *args, **kwargs)
def remove_filter(self, key):
self.filters.pop(key, None)
def set_label(self, key, label, column_only=False):
"""
Set/override the label for a column.
This overrides
:meth:`~wuttaweb:wuttaweb.grids.base.Grid.set_label()` to add
the following params:
:param column_only: Boolean indicating whether the label
should be applied *only* to the column header (if
``True``), vs. applying also to the filter (if ``False``).
"""
super().set_label(key, label)
if not column_only and key in self.filters:
self.filters[key].label = label
def set_click_handler(self, key, handler): def set_click_handler(self, key, handler):
if handler: if handler:
self.click_handlers[key] = handler self.click_handlers[key] = handler
@ -702,6 +694,14 @@ class Grid(WuttaGrid):
def actions_column_format(self, column_number, row_number, item): def actions_column_format(self, column_number, row_number, item):
return HTML.td(self.render_actions(item, row_number), class_='actions') return HTML.td(self.render_actions(item, row_number), class_='actions')
# TODO: upstream should handle this..
def make_backend_filters(self, filters=None):
""" """
final = self.get_default_filters()
if filters:
final.update(filters)
return final
def get_default_filters(self): def get_default_filters(self):
""" """
Returns the default set of filters provided by the grid. Returns the default set of filters provided by the grid.
@ -726,16 +726,6 @@ class Grid(WuttaGrid):
filters[prop.key] = self.make_filter(prop.key, column) filters[prop.key] = self.make_filter(prop.key, column)
return filters return filters
def make_filters(self, filters=None):
"""
Returns an initial set of filters which will be available to the grid.
The grid itself may or may not provide some default filters, and the
``filters`` kwarg may contain additions and/or overrides.
"""
if filters:
return filters
return self.get_default_filters()
def make_filter(self, key, column, **kwargs): def make_filter(self, key, column, **kwargs):
""" """
Make a filter suitable for use with the given column. Make a filter suitable for use with the given column.
@ -888,8 +878,8 @@ class Grid(WuttaGrid):
# If request has filter settings, grab those, then grab sort/pager # If request has filter settings, grab those, then grab sort/pager
# settings from request or session. # settings from request or session.
elif self.filterable and self.request_has_settings('filter'): elif self.request_has_settings('filter'):
self.update_filter_settings(settings, 'request') self.update_filter_settings(settings, src='request')
if self.request_has_settings('sort'): if self.request_has_settings('sort'):
self.update_sort_settings(settings, src='request') self.update_sort_settings(settings, src='request')
else: else:
@ -901,7 +891,7 @@ class Grid(WuttaGrid):
# settings from request or session. # settings from request or session.
elif self.request_has_settings('sort'): elif self.request_has_settings('sort'):
self.update_sort_settings(settings, src='request') self.update_sort_settings(settings, src='request')
self.update_filter_settings(settings, 'session') self.update_filter_settings(settings, src='session')
self.update_page_settings(settings) self.update_page_settings(settings)
# NOTE: These next two are functionally equivalent, but are kept # NOTE: These next two are functionally equivalent, but are kept
@ -911,12 +901,12 @@ class Grid(WuttaGrid):
# grab those, then grab filter/sort settings from session. # grab those, then grab filter/sort settings from session.
elif self.request_has_settings('page'): elif self.request_has_settings('page'):
self.update_page_settings(settings) self.update_page_settings(settings)
self.update_filter_settings(settings, 'session') self.update_filter_settings(settings, src='session')
self.update_sort_settings(settings, src='session') self.update_sort_settings(settings, src='session')
# If request has no settings, grab all from session. # If request has no settings, grab all from session.
elif self.session_has_settings(): elif self.session_has_settings():
self.update_filter_settings(settings, 'session') self.update_filter_settings(settings, src='session')
self.update_sort_settings(settings, src='session') self.update_sort_settings(settings, src='session')
self.update_page_settings(settings) self.update_page_settings(settings)
@ -1056,18 +1046,11 @@ class Grid(WuttaGrid):
merge('page', int) merge('page', int)
def request_has_settings(self, type_): def request_has_settings(self, type_):
""" """ """
Determine if the current request (GET query string) contains any if super().request_has_settings(type_):
filter/sort settings for the grid.
"""
if type_ == 'filter':
for filtr in self.iter_filters():
if filtr.key in self.request.GET:
return True
if 'filter' in self.request.GET: # user may be applying empty filters
return True return True
elif type_ == 'sort': if type_ == 'sort':
# TODO: remove this eventually, but some links in the wild # TODO: remove this eventually, but some links in the wild
# may still include these params, so leave it for now # may still include these params, so leave it for now
@ -1075,14 +1058,6 @@ class Grid(WuttaGrid):
if key in self.request.GET: if key in self.request.GET:
return True return True
if 'sort1key' in self.request.GET:
return True
elif type_ == 'page':
for key in ['pagesize', 'page']:
if key in self.request.GET:
return True
return False return False
def session_has_settings(self): def session_has_settings(self):
@ -1098,72 +1073,6 @@ class Grid(WuttaGrid):
return any([key.startswith(f'{prefix}.filter') return any([key.startswith(f'{prefix}.filter')
for key in self.request.session]) for key in self.request.session])
def update_filter_settings(self, settings, source):
"""
Updates a settings dictionary according to filter settings data found
in either the GET query string, or session storage.
:param settings: Dictionary of initial settings, which is to be updated.
:param source: String identifying the source to consult for settings
data. Must be one of: ``('request', 'session')``.
"""
if not self.filterable:
return
for filtr in self.iter_filters():
prefix = 'filter.{}'.format(filtr.key)
if source == 'request':
# consider filter active if query string contains a value for it
settings['{}.active'.format(prefix)] = filtr.key in self.request.GET
settings['{}.verb'.format(prefix)] = self.get_setting(
settings, f'{filtr.key}.verb', src='request', default='')
settings['{}.value'.format(prefix)] = self.get_setting(
settings, filtr.key, src='request', default='')
else: # source = session
settings['{}.active'.format(prefix)] = self.get_setting(
settings, f'{prefix}.active', src='session',
normalize=lambda v: str(v).lower() == 'true', default=False)
settings['{}.verb'.format(prefix)] = self.get_setting(
settings, f'{prefix}.verb', src='session', default='')
settings['{}.value'.format(prefix)] = self.get_setting(
settings, f'{prefix}.value', src='session', default='')
def update_page_settings(self, settings):
"""
Updates a settings dictionary according to pager settings data found in
either the GET query string, or session storage.
Note that due to how the actual pager functions, the effective settings
will often come from *both* the request and session. This is so that
e.g. the page size will remain constant (coming from the session) while
the user jumps between pages (which only provides the single setting).
:param settings: Dictionary of initial settings, which is to be updated.
"""
if not self.paginated:
return
pagesize = self.request.GET.get('pagesize')
if pagesize is not None:
if pagesize.isdigit():
settings['pagesize'] = int(pagesize)
else:
pagesize = self.request.session.get('grid.{}.pagesize'.format(self.key))
if pagesize is not None:
settings['pagesize'] = pagesize
page = self.request.GET.get('page')
if page is not None:
if page.isdigit():
settings['page'] = int(page)
else:
page = self.request.session.get('grid.{}.page'.format(self.key))
if page is not None:
settings['page'] = int(page)
def persist_settings(self, settings, dest='session'): def persist_settings(self, settings, dest='session'):
""" """ """ """
if dest not in ('defaults', 'session'): if dest not in ('defaults', 'session'):
@ -1251,89 +1160,12 @@ class Grid(WuttaGrid):
return data return data
def sort_data(self, data, sorters=None):
""" """
if sorters is None:
sorters = self.active_sorters
if not sorters:
return data
# nb. when data is a query, we want to apply sorters in the
# requested order, so the final query has order_by() in the
# correct "as-is" sequence. however when data is a list we
# must do the opposite, applying in the reverse order, so the
# final list has the most "important" sort(s) applied last.
if not isinstance(data, orm.Query):
sorters = reversed(sorters)
for sorter in sorters:
sortkey = sorter['key']
sortdir = sorter['dir']
# cannot sort unless we have a sorter callable
sortfunc = self.sorters.get(sortkey)
if not sortfunc:
return data
# join appropriate model if needed
if sortkey in self.joiners and sortkey not in self.joined:
data = self.joiners[sortkey](data)
self.joined.add(sortkey)
# invoke the sorter
data = sortfunc(data, sortdir)
return data
def paginate_data(self, data):
"""
Paginate the given data set according to current settings, and return
the result.
"""
# we of course assume our current page is correct, at first
pager = self.make_pager(data)
# if pager has detected that our current page is outside the valid
# range, we must re-orient ourself around the "new" (valid) page
if pager.page != self.page:
self.page = pager.page
self.request.session['grid.{}.page'.format(self.key)] = self.page
pager = self.make_pager(data)
return pager
def make_pager(self, data):
# TODO: this seems hacky..normally we expect `data` to be a
# query of course, but in some cases it may be a list instead.
# if so then we can't use ORM pager
if isinstance(data, list):
import paginate
return paginate.Page(data,
items_per_page=self.pagesize,
page=self.page)
return SqlalchemyOrmPage(data,
items_per_page=self.pagesize,
page=self.page,
url_maker=URLMaker(self.request))
def make_visible_data(self): def make_visible_data(self):
""" """ """
Apply various settings to the raw data set, to produce a final data warnings.warn("grid.make_visible_data() method is deprecated; "
set. This will page / sort / filter as necessary, according to the "please use grid.get_visible_data() instead",
grid's defaults and the current request etc. DeprecationWarning, stacklevel=2)
""" return self.get_visible_data()
self.joined = set()
data = self.data
if self.filterable:
data = self.filter_data(data)
if self.sortable:
data = self.sort_data(data)
if self.paginated:
self.pager = self.paginate_data(data)
data = self.pager
return data
def render_vue_tag(self, master=None, **kwargs): def render_vue_tag(self, master=None, **kwargs):
""" """ """ """
@ -1356,7 +1188,7 @@ class Grid(WuttaGrid):
includes the context menu items and grid tools. includes the context menu items and grid tools.
""" """
if 'grid_columns' not in kwargs: if 'grid_columns' not in kwargs:
kwargs['grid_columns'] = self.get_table_columns() kwargs['grid_columns'] = self.get_vue_columns()
if 'grid_data' not in kwargs: if 'grid_data' not in kwargs:
kwargs['grid_data'] = self.get_table_data() kwargs['grid_data'] = self.get_table_data()
@ -1379,6 +1211,7 @@ class Grid(WuttaGrid):
return HTML.literal(html) return HTML.literal(html)
def render_buefy(self, **kwargs): def render_buefy(self, **kwargs):
""" """
warnings.warn("Grid.render_buefy() is deprecated; " warnings.warn("Grid.render_buefy() is deprecated; "
"please use Grid.render_complete() instead", "please use Grid.render_complete() instead",
DeprecationWarning, stacklevel=2) DeprecationWarning, stacklevel=2)
@ -1568,23 +1401,19 @@ class Grid(WuttaGrid):
def get_vue_columns(self): def get_vue_columns(self):
""" """ """ """
return self.get_table_columns() columns = super().get_vue_columns()
for column in columns:
column['visible'] = column['field'] not in self.invisible
return columns
def get_table_columns(self): def get_table_columns(self):
""" """ """
Return a list of dicts representing all grid columns. Meant warnings.warn("grid.get_table_columns() method is deprecated; "
for use with the client-side JS table. "please use grid.get_vue_columns() instead",
""" DeprecationWarning, stacklevel=2)
columns = [] return self.get_vue_columns()
for name in self.columns:
columns.append({
'field': name,
'label': self.get_label(name),
'sortable': self.is_sortable(name),
'searchable': self.is_searchable(name),
'visible': name not in self.invisible,
})
return columns
def get_uuid_for_row(self, rowobj): def get_uuid_for_row(self, rowobj):
@ -1610,7 +1439,7 @@ class Grid(WuttaGrid):
return self._table_data return self._table_data
# filter / sort / paginate to get "visible" data # filter / sort / paginate to get "visible" data
raw_data = self.make_visible_data() raw_data = self.get_visible_data()
data = [] data = []
status_map = {} status_map = {}
checked = [] checked = []