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:
parent
2a0b6da2f9
commit
9da2a148c6
|
@ -31,6 +31,7 @@ import logging
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
from wuttjamaican.util import UNSPECIFIED
|
||||||
from rattail.db.types import GPCType
|
from rattail.db.types import GPCType
|
||||||
from rattail.util import prettify, pretty_boolean
|
from rattail.util import prettify, pretty_boolean
|
||||||
|
|
||||||
|
@ -209,9 +210,6 @@ class Grid(WuttaGrid):
|
||||||
sorters={},
|
sorters={},
|
||||||
default_sortkey=None,
|
default_sortkey=None,
|
||||||
default_sortdir='asc',
|
default_sortdir='asc',
|
||||||
pageable=False,
|
|
||||||
default_pagesize=None,
|
|
||||||
default_page=1,
|
|
||||||
checkboxes=False,
|
checkboxes=False,
|
||||||
checked=None,
|
checked=None,
|
||||||
check_handler=None,
|
check_handler=None,
|
||||||
|
@ -233,7 +231,26 @@ class Grid(WuttaGrid):
|
||||||
DeprecationWarning, stacklevel=2)
|
DeprecationWarning, stacklevel=2)
|
||||||
kwargs.setdefault('vue_tagname', kwargs.pop('component'))
|
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.setdefault('vue_tagname', 'tailbone-grid')
|
||||||
|
|
||||||
kwargs['key'] = key
|
kwargs['key'] = key
|
||||||
|
@ -272,10 +289,6 @@ class Grid(WuttaGrid):
|
||||||
self.default_sortkey = default_sortkey
|
self.default_sortkey = default_sortkey
|
||||||
self.default_sortdir = default_sortdir
|
self.default_sortdir = default_sortdir
|
||||||
|
|
||||||
self.pageable = pageable
|
|
||||||
self.default_pagesize = default_pagesize
|
|
||||||
self.default_page = default_page
|
|
||||||
|
|
||||||
self.checkboxes = checkboxes
|
self.checkboxes = checkboxes
|
||||||
self.checked = checked
|
self.checked = checked
|
||||||
if self.checked is None:
|
if self.checked is None:
|
||||||
|
@ -333,6 +346,16 @@ class Grid(WuttaGrid):
|
||||||
DeprecationWarning, stacklevel=2)
|
DeprecationWarning, stacklevel=2)
|
||||||
return self.vue_component
|
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):
|
def hide_column(self, key):
|
||||||
"""
|
"""
|
||||||
This *removes* a column from the grid, altogether.
|
This *removes* a column from the grid, altogether.
|
||||||
|
@ -756,18 +779,61 @@ class Grid(WuttaGrid):
|
||||||
keyfunc = lambda v: v[key]
|
keyfunc = lambda v: v[key]
|
||||||
return lambda q, d: sorted(q, key=keyfunc, reverse=d == 'desc')
|
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:
|
if self.default_pagesize:
|
||||||
return self.default_pagesize
|
return self.default_pagesize
|
||||||
|
|
||||||
pagesize = self.request.rattail_config.getint('tailbone',
|
return self.get_pagesize()
|
||||||
'grid.default_pagesize',
|
|
||||||
default=0)
|
|
||||||
if pagesize:
|
|
||||||
return pagesize
|
|
||||||
|
|
||||||
options = self.get_pagesize_options()
|
|
||||||
return options[0]
|
|
||||||
|
|
||||||
def load_settings(self, store=True):
|
def load_settings(self, store=True):
|
||||||
"""
|
"""
|
||||||
|
@ -789,9 +855,9 @@ class Grid(WuttaGrid):
|
||||||
settings['sorters.1.dir'] = self.default_sortdir
|
settings['sorters.1.dir'] = self.default_sortdir
|
||||||
else:
|
else:
|
||||||
settings['sorters.length'] = 0
|
settings['sorters.length'] = 0
|
||||||
if self.pageable:
|
if self.paginated:
|
||||||
settings['pagesize'] = self.get_default_pagesize()
|
settings['pagesize'] = self.pagesize
|
||||||
settings['page'] = self.default_page
|
settings['page'] = self.page
|
||||||
if self.filterable:
|
if self.filterable:
|
||||||
for filtr in self.iter_filters():
|
for filtr in self.iter_filters():
|
||||||
settings['filter.{}.active'.format(filtr.key)] = filtr.default_active
|
settings['filter.{}.active'.format(filtr.key)] = filtr.default_active
|
||||||
|
@ -867,7 +933,7 @@ class Grid(WuttaGrid):
|
||||||
'field': settings[f'sorters.{i}.key'],
|
'field': settings[f'sorters.{i}.key'],
|
||||||
'order': settings[f'sorters.{i}.dir'],
|
'order': settings[f'sorters.{i}.dir'],
|
||||||
})
|
})
|
||||||
if self.pageable:
|
if self.paginated:
|
||||||
self.pagesize = settings['pagesize']
|
self.pagesize = settings['pagesize']
|
||||||
self.page = settings['page']
|
self.page = settings['page']
|
||||||
|
|
||||||
|
@ -971,7 +1037,7 @@ class Grid(WuttaGrid):
|
||||||
merge(f'sorters.{i}.key')
|
merge(f'sorters.{i}.key')
|
||||||
merge(f'sorters.{i}.dir')
|
merge(f'sorters.{i}.dir')
|
||||||
|
|
||||||
if self.pageable:
|
if self.paginated:
|
||||||
merge('pagesize', int)
|
merge('pagesize', int)
|
||||||
merge('page', int)
|
merge('page', int)
|
||||||
|
|
||||||
|
@ -1154,7 +1220,7 @@ class Grid(WuttaGrid):
|
||||||
|
|
||||||
:param settings: Dictionary of initial settings, which is to be updated.
|
:param settings: Dictionary of initial settings, which is to be updated.
|
||||||
"""
|
"""
|
||||||
if not self.pageable:
|
if not self.paginated:
|
||||||
return
|
return
|
||||||
|
|
||||||
pagesize = self.request.GET.get('pagesize')
|
pagesize = self.request.GET.get('pagesize')
|
||||||
|
@ -1231,7 +1297,7 @@ class Grid(WuttaGrid):
|
||||||
persist(f'sorters.{i}.key')
|
persist(f'sorters.{i}.key')
|
||||||
persist(f'sorters.{i}.dir')
|
persist(f'sorters.{i}.dir')
|
||||||
|
|
||||||
if self.pageable:
|
if self.paginated:
|
||||||
persist('pagesize')
|
persist('pagesize')
|
||||||
persist('page')
|
persist('page')
|
||||||
|
|
||||||
|
@ -1355,7 +1421,7 @@ class Grid(WuttaGrid):
|
||||||
data = self.filter_data(data)
|
data = self.filter_data(data)
|
||||||
if self.sortable:
|
if self.sortable:
|
||||||
data = self.sort_data(data)
|
data = self.sort_data(data)
|
||||||
if self.pageable:
|
if self.paginated:
|
||||||
self.pager = self.paginate_data(data)
|
self.pager = self.paginate_data(data)
|
||||||
data = self.pager
|
data = self.pager
|
||||||
return data
|
return data
|
||||||
|
@ -1580,18 +1646,6 @@ class Grid(WuttaGrid):
|
||||||
return tags.checkbox('checkbox-{}-{}'.format(self.key, self.get_row_key(item)),
|
return tags.checkbox('checkbox-{}-{}'.format(self.key, self.get_row_key(item)),
|
||||||
checked=self.checked(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):
|
def has_static_data(self):
|
||||||
"""
|
"""
|
||||||
Should return ``True`` if the grid data can be considered "static"
|
Should return ``True`` if the grid data can be considered "static"
|
||||||
|
@ -1734,7 +1788,7 @@ class Grid(WuttaGrid):
|
||||||
results['checked_rows_code'] = '[{}]'.format(
|
results['checked_rows_code'] = '[{}]'.format(
|
||||||
', '.join(['{}[{}]'.format(var, i) for i in checked]))
|
', '.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['total_items'] = self.pager.item_count
|
||||||
results['per_page'] = self.pager.items_per_page
|
results['per_page'] = self.pager.items_per_page
|
||||||
results['page'] = self.pager.page
|
results['page'] = self.pager.page
|
||||||
|
|
|
@ -107,12 +107,14 @@
|
||||||
@cellclick="cellClick"
|
@cellclick="cellClick"
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
|
% if grid.paginated:
|
||||||
:paginated="paginated"
|
:paginated="paginated"
|
||||||
:per-page="perPage"
|
:per-page="perPage"
|
||||||
:current-page="currentPage"
|
:current-page="currentPage"
|
||||||
backend-pagination
|
backend-pagination
|
||||||
:total="total"
|
:total="total"
|
||||||
@page-change="onPageChange"
|
@page-change="onPageChange"
|
||||||
|
% endif
|
||||||
|
|
||||||
## TODO: should let grid (or master view) decide how to set these?
|
## TODO: should let grid (or master view) decide how to set these?
|
||||||
icon-pack="fas"
|
icon-pack="fas"
|
||||||
|
@ -203,7 +205,7 @@
|
||||||
<div></div>
|
<div></div>
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
% if getattr(grid, 'pageable', False):
|
% if grid.paginated:
|
||||||
<div v-if="firstItem"
|
<div v-if="firstItem"
|
||||||
style="display: flex; gap: 0.5rem; align-items: center;">
|
style="display: flex; gap: 0.5rem; align-items: center;">
|
||||||
<span>
|
<span>
|
||||||
|
@ -255,12 +257,14 @@
|
||||||
checkedRows: ${grid_data['checked_rows_code']|n},
|
checkedRows: ${grid_data['checked_rows_code']|n},
|
||||||
% endif
|
% 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)},
|
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},
|
perPage: ${json.dumps(grid.pagesize if grid.paginated else None)|n},
|
||||||
currentPage: ${json.dumps(grid.page if getattr(grid, 'pageable', False) else None)|n},
|
currentPage: ${json.dumps(grid.page if grid.paginated else None)|n},
|
||||||
firstItem: ${json.dumps(grid_data['first_item'] if getattr(grid, 'pageable', False) else None)|n},
|
firstItem: ${json.dumps(grid_data['first_item'] if grid.paginated else None)|n},
|
||||||
lastItem: ${json.dumps(grid_data['last_item'] if getattr(grid, 'pageable', False) else None)|n},
|
lastItem: ${json.dumps(grid_data['last_item'] if grid.paginated else None)|n},
|
||||||
|
% endif
|
||||||
|
|
||||||
% if getattr(grid, 'sortable', False):
|
% if getattr(grid, 'sortable', False):
|
||||||
|
|
||||||
|
@ -439,7 +443,7 @@
|
||||||
params['sort'+i+'dir'] = this.backendSorters[i-1].order
|
params['sort'+i+'dir'] = this.backendSorters[i-1].order
|
||||||
}
|
}
|
||||||
% endif
|
% endif
|
||||||
% if getattr(grid, 'pageable', False):
|
% if grid.paginated:
|
||||||
params.pagesize = this.perPage
|
params.pagesize = this.perPage
|
||||||
params.page = this.currentPage
|
params.page = this.currentPage
|
||||||
% endif
|
% endif
|
||||||
|
|
|
@ -439,7 +439,7 @@ class MasterView(View):
|
||||||
'filterable': self.filterable,
|
'filterable': self.filterable,
|
||||||
'use_byte_string_filters': self.use_byte_string_filters,
|
'use_byte_string_filters': self.use_byte_string_filters,
|
||||||
'sortable': self.sortable,
|
'sortable': self.sortable,
|
||||||
'pageable': self.pageable,
|
'paginated': self.pageable,
|
||||||
'extra_row_class': self.grid_extra_class,
|
'extra_row_class': self.grid_extra_class,
|
||||||
'url': lambda obj: self.get_action_url('view', obj),
|
'url': lambda obj: self.get_action_url('view', obj),
|
||||||
'checkboxes': checkboxes,
|
'checkboxes': checkboxes,
|
||||||
|
@ -589,7 +589,7 @@ class MasterView(View):
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.rows_default_pagesize:
|
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:
|
if self.has_rows and 'actions' not in defaults:
|
||||||
actions = []
|
actions = []
|
||||||
|
|
|
@ -45,6 +45,10 @@ class PersonView(wutta.PersonView):
|
||||||
model_class = Person
|
model_class = Person
|
||||||
Session = Session
|
Session = Session
|
||||||
|
|
||||||
|
# TODO: /grids/complete.mako is too aggressive for the
|
||||||
|
# limited support we have in wuttaweb thus far
|
||||||
|
paginated = False
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
'display_name': "Full Name",
|
'display_name': "Full Name",
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,32 @@ class TestGrid(WebTestCase):
|
||||||
grid = self.make_grid('foo')
|
grid = self.make_grid('foo')
|
||||||
self.assertIsInstance(grid, mod.Grid)
|
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):
|
def test_vue_tagname(self):
|
||||||
|
|
||||||
# default
|
# default
|
||||||
|
@ -133,6 +159,66 @@ class TestGrid(WebTestCase):
|
||||||
grid.set_action_urls(setting, setting, 0)
|
grid.set_action_urls(setting, setting, 0)
|
||||||
self.assertEqual(setting['_action_url_view'], '/blarg')
|
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):
|
def test_render_vue_tag(self):
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue