From 9da2a148c65ebde63b39903028bdf77577d53780 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 16 Aug 2024 18:45:04 -0500 Subject: [PATCH] feat: move "basic" grid pagination logic to wuttaweb so far only "simple" pagination is supported by wuttaweb, so basically the main feature flag, page size, current page. in this scenario *all* data is written to client-side JSON and Buefy handles the actual pagination. backend pagination coming soon for wuttaweb but for now tailbone still handles all that. --- tailbone/grids/core.py | 130 +++++++++++++++++-------- tailbone/templates/grids/complete.mako | 18 ++-- tailbone/views/master.py | 4 +- tailbone/views/wutta/people.py | 4 + tests/grids/test_core.py | 86 ++++++++++++++++ 5 files changed, 195 insertions(+), 47 deletions(-) diff --git a/tailbone/grids/core.py b/tailbone/grids/core.py index a5617215..0b23fb78 100644 --- a/tailbone/grids/core.py +++ b/tailbone/grids/core.py @@ -31,6 +31,7 @@ import logging import sqlalchemy as sa from sqlalchemy import orm +from wuttjamaican.util import UNSPECIFIED from rattail.db.types import GPCType from rattail.util import prettify, pretty_boolean @@ -209,9 +210,6 @@ class Grid(WuttaGrid): sorters={}, default_sortkey=None, default_sortdir='asc', - pageable=False, - default_pagesize=None, - default_page=1, checkboxes=False, checked=None, check_handler=None, @@ -233,7 +231,26 @@ class Grid(WuttaGrid): DeprecationWarning, stacklevel=2) kwargs.setdefault('vue_tagname', kwargs.pop('component')) - # TODO: pretty sure this should go away? + if kwargs.get('pageable'): + warnings.warn("component param is deprecated for Grid(); " + "please use vue_tagname param instead", + DeprecationWarning, stacklevel=2) + kwargs.setdefault('paginated', kwargs.pop('pageable')) + + if kwargs.get('default_pagesize'): + warnings.warn("default_pagesize param is deprecated for Grid(); " + "please use pagesize param instead", + DeprecationWarning, stacklevel=2) + kwargs.setdefault('pagesize', kwargs.pop('default_pagesize')) + + if kwargs.get('default_page'): + warnings.warn("default_page param is deprecated for Grid(); " + "please use page param instead", + DeprecationWarning, stacklevel=2) + kwargs.setdefault('page', kwargs.pop('default_page')) + + # TODO: this should not be needed once all templates correctly + # reference grid.vue_component etc. kwargs.setdefault('vue_tagname', 'tailbone-grid') kwargs['key'] = key @@ -272,10 +289,6 @@ class Grid(WuttaGrid): self.default_sortkey = default_sortkey self.default_sortdir = default_sortdir - self.pageable = pageable - self.default_pagesize = default_pagesize - self.default_page = default_page - self.checkboxes = checkboxes self.checked = checked if self.checked is None: @@ -333,6 +346,16 @@ class Grid(WuttaGrid): DeprecationWarning, stacklevel=2) return self.vue_component + def get_pageable(self): + """ """ + return self.paginated + + def set_pageable(self, value): + """ """ + self.paginated = value + + pageable = property(get_pageable, set_pageable) + def hide_column(self, key): """ This *removes* a column from the grid, altogether. @@ -756,18 +779,61 @@ class Grid(WuttaGrid): keyfunc = lambda v: v[key] return lambda q, d: sorted(q, key=keyfunc, reverse=d == 'desc') - def get_default_pagesize(self): + def get_pagesize_options(self, default=None): + """ """ + # let upstream check config + options = super().get_pagesize_options(default=UNSPECIFIED) + if options is not UNSPECIFIED: + return options + + # fallback to legacy config + options = self.config.get_list('tailbone.grid.pagesize_options') + if options: + warnings.warn("tailbone.grid.pagesize_options setting is deprecated; " + "please set wuttaweb.grids.default_pagesize_options instead", + DeprecationWarning) + options = [int(size) for size in options + if size.isdigit()] + if options: + return options + + if default: + return default + + # use upstream default + return super().get_pagesize_options() + + def get_pagesize(self, default=None): + """ """ + # let upstream check config + pagesize = super().get_pagesize(default=UNSPECIFIED) + if pagesize is not UNSPECIFIED: + return pagesize + + # fallback to legacy config + pagesize = self.config.get_int('tailbone.grid.default_pagesize') + if pagesize: + warnings.warn("tailbone.grid.default_pagesize setting is deprecated; " + "please use wuttaweb.grids.default_pagesize instead", + DeprecationWarning) + return pagesize + + if default: + return default + + # use upstream default + return super().get_pagesize() + + def get_default_pagesize(self): # pragma: no cover + """ """ + warnings.warn("Grid.get_default_pagesize() method is deprecated; " + "please use Grid.get_pagesize() of Grid.page instead", + DeprecationWarning, stacklevel=2) + if self.default_pagesize: return self.default_pagesize - pagesize = self.request.rattail_config.getint('tailbone', - 'grid.default_pagesize', - default=0) - if pagesize: - return pagesize - - options = self.get_pagesize_options() - return options[0] + return self.get_pagesize() def load_settings(self, store=True): """ @@ -789,9 +855,9 @@ class Grid(WuttaGrid): settings['sorters.1.dir'] = self.default_sortdir else: settings['sorters.length'] = 0 - if self.pageable: - settings['pagesize'] = self.get_default_pagesize() - settings['page'] = self.default_page + if self.paginated: + settings['pagesize'] = self.pagesize + settings['page'] = self.page if self.filterable: for filtr in self.iter_filters(): settings['filter.{}.active'.format(filtr.key)] = filtr.default_active @@ -867,7 +933,7 @@ class Grid(WuttaGrid): 'field': settings[f'sorters.{i}.key'], 'order': settings[f'sorters.{i}.dir'], }) - if self.pageable: + if self.paginated: self.pagesize = settings['pagesize'] self.page = settings['page'] @@ -971,7 +1037,7 @@ class Grid(WuttaGrid): merge(f'sorters.{i}.key') merge(f'sorters.{i}.dir') - if self.pageable: + if self.paginated: merge('pagesize', int) merge('page', int) @@ -1154,7 +1220,7 @@ class Grid(WuttaGrid): :param settings: Dictionary of initial settings, which is to be updated. """ - if not self.pageable: + if not self.paginated: return pagesize = self.request.GET.get('pagesize') @@ -1231,7 +1297,7 @@ class Grid(WuttaGrid): persist(f'sorters.{i}.key') persist(f'sorters.{i}.dir') - if self.pageable: + if self.paginated: persist('pagesize') persist('page') @@ -1355,7 +1421,7 @@ class Grid(WuttaGrid): data = self.filter_data(data) if self.sortable: data = self.sort_data(data) - if self.pageable: + if self.paginated: self.pager = self.paginate_data(data) data = self.pager return data @@ -1580,18 +1646,6 @@ class Grid(WuttaGrid): return tags.checkbox('checkbox-{}-{}'.format(self.key, self.get_row_key(item)), checked=self.checked(item)) - def get_pagesize_options(self): - - # use values from config, if defined - options = self.request.rattail_config.getlist('tailbone', 'grid.pagesize_options') - if options: - options = [int(size) for size in options - if size.isdigit()] - if options: - return options - - return [5, 10, 20, 50, 100, 200] - def has_static_data(self): """ Should return ``True`` if the grid data can be considered "static" @@ -1734,7 +1788,7 @@ class Grid(WuttaGrid): results['checked_rows_code'] = '[{}]'.format( ', '.join(['{}[{}]'.format(var, i) for i in checked])) - if self.pageable and self.pager is not None: + if self.paginated and self.pager is not None: results['total_items'] = self.pager.item_count results['per_page'] = self.pager.items_per_page results['page'] = self.pager.page diff --git a/tailbone/templates/grids/complete.mako b/tailbone/templates/grids/complete.mako index 93bb6c26..53043803 100644 --- a/tailbone/templates/grids/complete.mako +++ b/tailbone/templates/grids/complete.mako @@ -107,12 +107,14 @@ @cellclick="cellClick" % endif + % if grid.paginated: :paginated="paginated" :per-page="perPage" :current-page="currentPage" backend-pagination :total="total" @page-change="onPageChange" + % endif ## TODO: should let grid (or master view) decide how to set these? icon-pack="fas" @@ -203,7 +205,7 @@
% endif - % if getattr(grid, 'pageable', False): + % if grid.paginated:
@@ -255,12 +257,14 @@ checkedRows: ${grid_data['checked_rows_code']|n}, % endif - paginated: ${json.dumps(getattr(grid, 'pageable', False))|n}, + % if grid.paginated: + paginated: ${json.dumps(grid.paginated)|n}, total: ${len(grid_data['data']) if static_data else (grid_data['total_items'] if grid_data is not Undefined else 0)}, - perPage: ${json.dumps(grid.pagesize if getattr(grid, 'pageable', False) else None)|n}, - currentPage: ${json.dumps(grid.page if getattr(grid, 'pageable', False) else None)|n}, - firstItem: ${json.dumps(grid_data['first_item'] if getattr(grid, 'pageable', False) else None)|n}, - lastItem: ${json.dumps(grid_data['last_item'] if getattr(grid, 'pageable', False) else None)|n}, + perPage: ${json.dumps(grid.pagesize if grid.paginated else None)|n}, + currentPage: ${json.dumps(grid.page if grid.paginated else None)|n}, + firstItem: ${json.dumps(grid_data['first_item'] if grid.paginated else None)|n}, + lastItem: ${json.dumps(grid_data['last_item'] if grid.paginated else None)|n}, + % endif % if getattr(grid, 'sortable', False): @@ -439,7 +443,7 @@ params['sort'+i+'dir'] = this.backendSorters[i-1].order } % endif - % if getattr(grid, 'pageable', False): + % if grid.paginated: params.pagesize = this.perPage params.page = this.currentPage % endif diff --git a/tailbone/views/master.py b/tailbone/views/master.py index 8f65fc88..58b93568 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -439,7 +439,7 @@ class MasterView(View): 'filterable': self.filterable, 'use_byte_string_filters': self.use_byte_string_filters, 'sortable': self.sortable, - 'pageable': self.pageable, + 'paginated': self.pageable, 'extra_row_class': self.grid_extra_class, 'url': lambda obj: self.get_action_url('view', obj), 'checkboxes': checkboxes, @@ -589,7 +589,7 @@ class MasterView(View): } if self.rows_default_pagesize: - defaults['default_pagesize'] = self.rows_default_pagesize + defaults['pagesize'] = self.rows_default_pagesize if self.has_rows and 'actions' not in defaults: actions = [] diff --git a/tailbone/views/wutta/people.py b/tailbone/views/wutta/people.py index c92e34ae..3158b478 100644 --- a/tailbone/views/wutta/people.py +++ b/tailbone/views/wutta/people.py @@ -45,6 +45,10 @@ class PersonView(wutta.PersonView): model_class = Person Session = Session + # TODO: /grids/complete.mako is too aggressive for the + # limited support we have in wuttaweb thus far + paginated = False + labels = { 'display_name': "Full Name", } diff --git a/tests/grids/test_core.py b/tests/grids/test_core.py index 0d0fe112..7cba917a 100644 --- a/tests/grids/test_core.py +++ b/tests/grids/test_core.py @@ -19,6 +19,32 @@ class TestGrid(WebTestCase): grid = self.make_grid('foo') self.assertIsInstance(grid, mod.Grid) + def test_deprecated_params(self): + + # component + grid = self.make_grid() + self.assertEqual(grid.vue_tagname, 'tailbone-grid') + grid = self.make_grid(component='blarg') + self.assertEqual(grid.vue_tagname, 'blarg') + + # pageable + grid = self.make_grid() + self.assertFalse(grid.paginated) + grid = self.make_grid(pageable=True) + self.assertTrue(grid.paginated) + + # default_pagesize + grid = self.make_grid() + self.assertEqual(grid.pagesize, 20) + grid = self.make_grid(default_pagesize=15) + self.assertEqual(grid.pagesize, 15) + + # default_page + grid = self.make_grid() + self.assertEqual(grid.page, 1) + grid = self.make_grid(default_page=42) + self.assertEqual(grid.page, 42) + def test_vue_tagname(self): # default @@ -133,6 +159,66 @@ class TestGrid(WebTestCase): grid.set_action_urls(setting, setting, 0) self.assertEqual(setting['_action_url_view'], '/blarg') + def test_pageable(self): + grid = self.make_grid() + self.assertFalse(grid.paginated) + grid.pageable = True + self.assertTrue(grid.paginated) + grid.paginated = False + self.assertFalse(grid.pageable) + + def test_get_pagesize_options(self): + grid = self.make_grid() + + # default + options = grid.get_pagesize_options() + self.assertEqual(options, [5, 10, 20, 50, 100, 200]) + + # override default + options = grid.get_pagesize_options(default=[42]) + self.assertEqual(options, [42]) + + # from legacy config + self.config.setdefault('tailbone.grid.pagesize_options', '1 2 3') + grid = self.make_grid() + options = grid.get_pagesize_options() + self.assertEqual(options, [1, 2, 3]) + + # from new config + self.config.setdefault('wuttaweb.grids.default_pagesize_options', '4, 5, 6') + grid = self.make_grid() + options = grid.get_pagesize_options() + self.assertEqual(options, [4, 5, 6]) + + def test_get_pagesize(self): + grid = self.make_grid() + + # default + size = grid.get_pagesize() + self.assertEqual(size, 20) + + # override default + size = grid.get_pagesize(default=42) + self.assertEqual(size, 42) + + # override default options + self.config.setdefault('wuttaweb.grids.default_pagesize_options', '10 15 30') + grid = self.make_grid() + size = grid.get_pagesize() + self.assertEqual(size, 10) + + # from legacy config + self.config.setdefault('tailbone.grid.default_pagesize', '12') + grid = self.make_grid() + size = grid.get_pagesize() + self.assertEqual(size, 12) + + # from new config + self.config.setdefault('wuttaweb.grids.default_pagesize', '15') + grid = self.make_grid() + size = grid.get_pagesize() + self.assertEqual(size, 15) + def test_render_vue_tag(self): model = self.app.model