From dd3d640b1c87f5bb9e0b15e6b27162645a173d0b Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 16 Aug 2024 18:19:24 -0500 Subject: [PATCH] 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 --- pyproject.toml | 1 + src/wuttaweb/grids/base.py | 109 +++++++++++++++++- .../templates/grids/vue_template.mako | 60 ++++++++-- src/wuttaweb/views/master.py | 11 ++ tests/grids/test_base.py | 56 ++++++--- 5 files changed, 207 insertions(+), 30 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e07c98b..9570746 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ classifiers = [ requires-python = ">= 3.8" dependencies = [ "ColanderAlchemy", + "paginate", "pyramid>=2", "pyramid_beaker", "pyramid_deform", diff --git a/src/wuttaweb/grids/base.py b/src/wuttaweb/grids/base.py index 740607c..5f5b34e 100644 --- a/src/wuttaweb/grids/base.py +++ b/src/wuttaweb/grids/base.py @@ -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. diff --git a/src/wuttaweb/templates/grids/vue_template.mako b/src/wuttaweb/templates/grids/vue_template.mako index 5e60bab..0505c33 100644 --- a/src/wuttaweb/templates/grids/vue_template.mako +++ b/src/wuttaweb/templates/grids/vue_template.mako @@ -2,8 +2,20 @@ diff --git a/src/wuttaweb/views/master.py b/src/wuttaweb/views/master.py index 1c7518d..0fb050d 100644 --- a/src/wuttaweb/views/master.py +++ b/src/wuttaweb/views/master.py @@ -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 diff --git a/tests/grids/test_base.py b/tests/grids/test_base.py index 2d15689..9e23c02 100644 --- a/tests/grids/test_base.py +++ b/tests/grids/test_base.py @@ -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()