1
0
Fork 0

feat: add initial/basic pagination for grids

so far this is only for client-side pagination; which means *all* grid
data is dumped to JSON for Vue access.  backend pagination coming soon
This commit is contained in:
Lance Edgar 2024-08-16 18:19:24 -05:00
parent 7feaa844af
commit dd3d640b1c
5 changed files with 207 additions and 30 deletions

View file

@ -31,6 +31,7 @@ classifiers = [
requires-python = ">= 3.8" requires-python = ">= 3.8"
dependencies = [ dependencies = [
"ColanderAlchemy", "ColanderAlchemy",
"paginate",
"pyramid>=2", "pyramid>=2",
"pyramid_beaker", "pyramid_beaker",
"pyramid_deform", "pyramid_deform",

View file

@ -61,6 +61,11 @@ class Grid:
Presumably unique key for the grid; used to track per-grid Presumably unique key for the grid; used to track per-grid
sort/filter settings etc. sort/filter settings etc.
.. attribute:: vue_tagname
String name for Vue component tag. By default this is
``'wutta-grid'``. See also :meth:`render_vue_tag()`.
.. attribute:: model_class .. attribute:: model_class
Model class for the grid, if applicable. When set, this is Model class for the grid, if applicable. When set, this is
@ -106,15 +111,43 @@ class Grid:
See also :meth:`set_link()` and :meth:`is_linked()`. See also :meth:`set_link()` and :meth:`is_linked()`.
.. attribute:: vue_tagname .. attribute:: paginated
String name for Vue component tag. By default this is Boolean indicating whether the grid data should be paginated
``'wutta-grid'``. See also :meth:`render_vue_tag()`. vs. all data is shown at once. Default is ``False``.
See also :attr:`pagesize` and :attr:`page`.
.. attribute:: pagesize_options
List of "page size" options for the grid. See also
:attr:`pagesize`.
Only relevant if :attr:`paginated` is true. If not specified,
constructor will call :meth:`get_pagesize_options()` to get the
value.
.. attribute:: pagesize
Number of records to show in a data page. See also
:attr:`pagesize_options` and :attr:`page`.
Only relevant if :attr:`paginated` is true. If not specified,
constructor will call :meth:`get_pagesize()` to get the value.
.. attribute:: page
The current page number (of data) to display in the grid. See
also :attr:`pagesize`.
Only relevant if :attr:`paginated` is true. If not specified,
constructor will assume ``1`` (first page).
""" """
def __init__( def __init__(
self, self,
request, request,
vue_tagname='wutta-grid',
model_class=None, model_class=None,
key=None, key=None,
columns=None, columns=None,
@ -123,9 +156,13 @@ class Grid:
renderers={}, renderers={},
actions=[], actions=[],
linked_columns=[], linked_columns=[],
vue_tagname='wutta-grid', paginated=False,
pagesize_options=None,
pagesize=None,
page=1,
): ):
self.request = request self.request = request
self.vue_tagname = vue_tagname
self.model_class = model_class self.model_class = model_class
self.key = key self.key = key
self.data = data self.data = data
@ -133,13 +170,17 @@ class Grid:
self.renderers = renderers or {} self.renderers = renderers or {}
self.actions = actions or [] self.actions = actions or []
self.linked_columns = linked_columns or [] self.linked_columns = linked_columns or []
self.vue_tagname = vue_tagname
self.config = self.request.wutta_config self.config = self.request.wutta_config
self.app = self.config.get_app() self.app = self.config.get_app()
self.set_columns(columns or self.get_columns()) self.set_columns(columns or self.get_columns())
self.paginated = paginated
self.pagesize_options = pagesize_options or self.get_pagesize_options()
self.pagesize = pagesize or self.get_pagesize()
self.page = page
def get_columns(self): def get_columns(self):
""" """
Returns the official list of column names for the grid, or Returns the official list of column names for the grid, or
@ -340,6 +381,64 @@ class Grid:
return True return True
return False return False
##############################
# paging methods
##############################
def get_pagesize_options(self, default=None):
"""
Returns a list of default page size options for the grid.
It will check config but if no setting exists, will fall
back to::
[5, 10, 20, 50, 100, 200]
:param default: Alternate default value to return if none is
configured.
This method is intended for use in the constructor. Code can
instead access :attr:`pagesize_options` directly.
"""
options = self.config.get_list('wuttaweb.grids.default_pagesize_options')
if options:
options = [int(size) for size in options
if size.isdigit()]
if options:
return options
return default or [5, 10, 20, 50, 100, 200]
def get_pagesize(self, default=None):
"""
Returns the default page size for the grid.
It will check config but if no setting exists, will fall back
to a value from :attr:`pagesize_options` (will return ``20`` if
that is listed; otherwise the "first" option).
:param default: Alternate default value to return if none is
configured.
This method is intended for use in the constructor. Code can
instead access :attr:`pagesize` directly.
"""
size = self.config.get_int('wuttaweb.grids.default_pagesize')
if size:
return size
if default:
return default
if 20 in self.pagesize_options:
return 20
return self.pagesize_options[0]
##############################
# rendering methods
##############################
def render_vue_tag(self, **kwargs): def render_vue_tag(self, **kwargs):
""" """
Render the Vue component tag for the grid. Render the Vue component tag for the grid.

View file

@ -2,8 +2,20 @@
<script type="text/x-template" id="${grid.vue_tagname}-template"> <script type="text/x-template" id="${grid.vue_tagname}-template">
<${b}-table :data="data" <${b}-table :data="data"
:loading="loading"
narrowed
hoverable hoverable
:loading="loading"> icon-pack="fas"
## paging
% if grid.paginated:
paginated
pagination-size="is-small"
:per-page="perPage"
:current-page="currentPage"
% endif
>
% for column in grid.get_vue_columns(): % for column in grid.get_vue_columns():
<${b}-table-column field="${column['field']}" <${b}-table-column field="${column['field']}"
@ -34,21 +46,51 @@
</${b}-table-column> </${b}-table-column>
% endif % endif
% if grid.paginated:
<template #footer>
<div style="display: flex; justify-content: space-between;">
<div></div>
<div style="display: flex; gap: 0.5rem; align-items: center;">
<span>
showing
</span>
<b-select v-model="perPage"
size="is-small">
<option v-for="size in pageSizeOptions"
:value="size">
{{ size }}
</option>
</b-select>
<span>
per page
</span>
</div>
</div>
</template>
% endif
</${b}-table> </${b}-table>
</script> </script>
<script> <script>
let ${grid.vue_component} = { const ${grid.vue_component}CurrentData = ${json.dumps(grid.get_vue_data())|n}
const ${grid.vue_component}Data = {
data: ${grid.vue_component}CurrentData,
loading: false,
## paging
% if grid.paginated:
pageSizeOptions: ${json.dumps(grid.pagesize_options)|n},
perPage: ${json.dumps(grid.pagesize)|n},
currentPage: ${json.dumps(grid.page)|n},
% endif
}
const ${grid.vue_component} = {
template: '#${grid.vue_tagname}-template', template: '#${grid.vue_tagname}-template',
methods: {}, methods: {},
} }
let ${grid.vue_component}CurrentData = ${json.dumps(grid.get_vue_data())|n}
let ${grid.vue_component}Data = {
data: ${grid.vue_component}CurrentData,
loading: false,
}
</script> </script>

View file

@ -181,6 +181,14 @@ class MasterView(View):
This is optional; see also :meth:`get_grid_columns()`. This is optional; see also :meth:`get_grid_columns()`.
.. attribute:: paginated
Boolean indicating whether the grid data for the
:meth:`index()` view should be paginated. Default is ``True``.
This is used by :meth:`make_model_grid()` to set the grid's
:attr:`~wuttaweb.grids.base.Grid.paginated` flag.
.. attribute:: creatable .. attribute:: creatable
Boolean indicating whether the view model supports "creating" - Boolean indicating whether the view model supports "creating" -
@ -229,6 +237,7 @@ class MasterView(View):
# features # features
listable = True listable = True
has_grid = True has_grid = True
paginated = True
creatable = True creatable = True
viewable = True viewable = True
editable = True editable = True
@ -1061,6 +1070,8 @@ class MasterView(View):
kwargs['actions'] = actions kwargs['actions'] = actions
kwargs.setdefault('paginated', self.paginated)
grid = self.make_grid(**kwargs) grid = self.make_grid(**kwargs)
self.configure_grid(grid) self.configure_grid(grid)
return grid return grid

View file

@ -8,24 +8,10 @@ from pyramid import testing
from wuttjamaican.conf import WuttaConfig from wuttjamaican.conf import WuttaConfig
from wuttaweb.grids import base from wuttaweb.grids import base
from wuttaweb.forms import FieldList from wuttaweb.forms import FieldList
from tests.util import WebTestCase
class TestGrid(TestCase): class TestGrid(WebTestCase):
def setUp(self):
self.config = WuttaConfig(defaults={
'wutta.web.menus.handler_spec': 'tests.util:NullMenuHandler',
})
self.app = self.config.get_app()
self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False)
self.pyramid_config = testing.setUp(request=self.request, settings={
'mako.directories': ['wuttaweb:templates'],
})
def tearDown(self):
testing.tearDown()
def make_grid(self, request=None, **kwargs): def make_grid(self, request=None, **kwargs):
return base.Grid(request or self.request, **kwargs) return base.Grid(request or self.request, **kwargs)
@ -144,6 +130,44 @@ class TestGrid(TestCase):
self.assertFalse(grid.is_linked('foo')) self.assertFalse(grid.is_linked('foo'))
self.assertTrue(grid.is_linked('bar')) self.assertTrue(grid.is_linked('bar'))
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 config
self.config.setdefault('wuttaweb.grids.default_pagesize_options', '1 2 3')
options = grid.get_pagesize_options()
self.assertEqual(options, [1, 2, 3])
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 config
self.config.setdefault('wuttaweb.grids.default_pagesize', '15')
size = grid.get_pagesize()
self.assertEqual(size, 15)
def test_render_vue_tag(self): def test_render_vue_tag(self):
grid = self.make_grid(columns=['foo', 'bar']) grid = self.make_grid(columns=['foo', 'bar'])
html = grid.render_vue_tag() html = grid.render_vue_tag()