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"
dependencies = [
"ColanderAlchemy",
"paginate",
"pyramid>=2",
"pyramid_beaker",
"pyramid_deform",

View file

@ -61,6 +61,11 @@ class Grid:
Presumably unique key for the grid; used to track per-grid
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
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()`.
.. attribute:: vue_tagname
.. attribute:: paginated
String name for Vue component tag. By default this is
``'wutta-grid'``. See also :meth:`render_vue_tag()`.
Boolean indicating whether the grid data should be paginated
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__(
self,
request,
vue_tagname='wutta-grid',
model_class=None,
key=None,
columns=None,
@ -123,9 +156,13 @@ class Grid:
renderers={},
actions=[],
linked_columns=[],
vue_tagname='wutta-grid',
paginated=False,
pagesize_options=None,
pagesize=None,
page=1,
):
self.request = request
self.vue_tagname = vue_tagname
self.model_class = model_class
self.key = key
self.data = data
@ -133,13 +170,17 @@ class Grid:
self.renderers = renderers or {}
self.actions = actions or []
self.linked_columns = linked_columns or []
self.vue_tagname = vue_tagname
self.config = self.request.wutta_config
self.app = self.config.get_app()
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):
"""
Returns the official list of column names for the grid, or
@ -340,6 +381,64 @@ class Grid:
return True
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):
"""
Render the Vue component tag for the grid.

View file

@ -2,8 +2,20 @@
<script type="text/x-template" id="${grid.vue_tagname}-template">
<${b}-table :data="data"
:loading="loading"
narrowed
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():
<${b}-table-column field="${column['field']}"
@ -34,21 +46,51 @@
</${b}-table-column>
% 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>
</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',
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>

View file

@ -181,6 +181,14 @@ class MasterView(View):
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
Boolean indicating whether the view model supports "creating" -
@ -229,6 +237,7 @@ class MasterView(View):
# features
listable = True
has_grid = True
paginated = True
creatable = True
viewable = True
editable = True
@ -1061,6 +1070,8 @@ class MasterView(View):
kwargs['actions'] = actions
kwargs.setdefault('paginated', self.paginated)
grid = self.make_grid(**kwargs)
self.configure_grid(grid)
return grid

View file

@ -8,24 +8,10 @@ from pyramid import testing
from wuttjamaican.conf import WuttaConfig
from wuttaweb.grids import base
from wuttaweb.forms import FieldList
from tests.util import WebTestCase
class TestGrid(TestCase):
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()
class TestGrid(WebTestCase):
def make_grid(self, request=None, **kwargs):
return base.Grid(request or self.request, **kwargs)
@ -144,6 +130,44 @@ class TestGrid(TestCase):
self.assertFalse(grid.is_linked('foo'))
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):
grid = self.make_grid(columns=['foo', 'bar'])
html = grid.render_vue_tag()