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:
parent
7feaa844af
commit
dd3d640b1c
|
@ -31,6 +31,7 @@ classifiers = [
|
|||
requires-python = ">= 3.8"
|
||||
dependencies = [
|
||||
"ColanderAlchemy",
|
||||
"paginate",
|
||||
"pyramid>=2",
|
||||
"pyramid_beaker",
|
||||
"pyramid_deform",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue