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.
This commit is contained in:
Lance Edgar 2024-08-16 18:45:04 -05:00
parent 2a0b6da2f9
commit 9da2a148c6
5 changed files with 195 additions and 47 deletions

View file

@ -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

View file

@ -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 @@
<div></div>
% endif
% if getattr(grid, 'pageable', False):
% if grid.paginated:
<div v-if="firstItem"
style="display: flex; gap: 0.5rem; align-items: center;">
<span>
@ -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

View file

@ -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 = []

View file

@ -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",
}

View file

@ -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