WuttaWeb 0.8.1__tar.gz → 0.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/CHANGELOG.md +7 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/PKG-INFO +2 -1
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/pyproject.toml +2 -1
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/grids/base.py +289 -9
- wuttaweb-0.9.0/src/wuttaweb/templates/grids/vue_template.mako +215 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/base.py +11 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/master.py +33 -1
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/grids/test_base.py +162 -16
- wuttaweb-0.9.0/tests/views/test_base.py +56 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test_master.py +8 -0
- wuttaweb-0.8.1/src/wuttaweb/templates/grids/vue_template.mako +0 -54
- wuttaweb-0.8.1/tests/views/test_base.py +0 -46
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/.gitignore +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/COPYING.txt +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/README.md +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/Makefile +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/_static/.keepme +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/index.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/app.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/auth.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/db.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/forms.base.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/forms.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/forms.schema.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/forms.widgets.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/grids.base.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/grids.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/handler.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/helpers.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/index.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/menus.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/static.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/subscribers.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/util.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.auth.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.base.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.common.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.essential.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.master.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.people.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.roles.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.settings.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.users.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/conf.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/glossary.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/index.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/make.bat +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/narr/index.rst +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/__init__.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/_version.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/app.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/auth.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/db.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/forms/__init__.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/forms/base.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/forms/schema.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/forms/widgets.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/grids/__init__.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/handler.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/helpers.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/menus.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/static/__init__.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/static/img/favicon.ico +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/static/img/logo.png +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/static/img/testing.png +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/subscribers.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/appinfo/index.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/auth/login.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/base.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/base_meta.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/configure.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/password.pt +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/select.pt +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/forbidden.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/form.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/home.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/master/configure.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/master/create.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/master/delete.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/master/edit.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/master/form.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/master/index.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/master/view.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/notfound.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/page.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/setup.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/wutta-components.mako +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/util.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/__init__.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/auth.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/common.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/essential.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/people.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/roles.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/settings.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/users.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tasks.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/__init__.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/forms/__init__.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/forms/test_base.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/forms/test_schema.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/forms/test_widgets.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/grids/__init__.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/bb_oruga.js +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/bb_oruga_bulma.css +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/bb_oruga_bulma.js +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/bb_vue.js +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/buefy.css +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/buefy.js +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/fontawesome.js +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/vue.js +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/vue_resource.js +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_app.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_auth.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_handler.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_helpers.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_menus.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_static.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_subscribers.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_util.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/util.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/__init__.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test___init__.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test_auth.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test_common.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test_people.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test_roles.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test_settings.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test_users.py +0 -0
- {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tox.ini +0 -0
|
@@ -5,6 +5,13 @@ All notable changes to wuttaweb will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## v0.9.0 (2024-08-16)
|
|
9
|
+
|
|
10
|
+
### Feat
|
|
11
|
+
|
|
12
|
+
- add backend pagination support for grids
|
|
13
|
+
- add initial/basic pagination for grids
|
|
14
|
+
|
|
8
15
|
## v0.8.1 (2024-08-15)
|
|
9
16
|
|
|
10
17
|
### Fix
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: WuttaWeb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Web App for Wutta Framework
|
|
5
5
|
Project-URL: Homepage, https://wuttaproject.org/
|
|
6
6
|
Project-URL: Repository, https://forgejo.wuttaproject.org/wutta/wuttaweb
|
|
@@ -25,6 +25,7 @@ Classifier: Topic :: Internet :: WWW/HTTP
|
|
|
25
25
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
26
|
Requires-Python: >=3.8
|
|
27
27
|
Requires-Dist: colanderalchemy
|
|
28
|
+
Requires-Dist: paginate
|
|
28
29
|
Requires-Dist: pyramid-beaker
|
|
29
30
|
Requires-Dist: pyramid-deform
|
|
30
31
|
Requires-Dist: pyramid-fanstatic
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "WuttaWeb"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.9.0"
|
|
10
10
|
description = "Web App for Wutta Framework"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
authors = [{name = "Lance Edgar", email = "lance@edbob.org"}]
|
|
@@ -31,6 +31,7 @@ classifiers = [
|
|
|
31
31
|
requires-python = ">= 3.8"
|
|
32
32
|
dependencies = [
|
|
33
33
|
"ColanderAlchemy",
|
|
34
|
+
"paginate",
|
|
34
35
|
"pyramid>=2",
|
|
35
36
|
"pyramid_beaker",
|
|
36
37
|
"pyramid_deform",
|
|
@@ -30,9 +30,11 @@ import logging
|
|
|
30
30
|
|
|
31
31
|
import sqlalchemy as sa
|
|
32
32
|
|
|
33
|
+
import paginate
|
|
33
34
|
from pyramid.renderers import render
|
|
34
35
|
from webhelpers2.html import HTML
|
|
35
36
|
|
|
37
|
+
from wuttaweb.db import Session
|
|
36
38
|
from wuttaweb.util import FieldList, get_model_fields, make_json_safe
|
|
37
39
|
|
|
38
40
|
|
|
@@ -61,6 +63,11 @@ class Grid:
|
|
|
61
63
|
Presumably unique key for the grid; used to track per-grid
|
|
62
64
|
sort/filter settings etc.
|
|
63
65
|
|
|
66
|
+
.. attribute:: vue_tagname
|
|
67
|
+
|
|
68
|
+
String name for Vue component tag. By default this is
|
|
69
|
+
``'wutta-grid'``. See also :meth:`render_vue_tag()`.
|
|
70
|
+
|
|
64
71
|
.. attribute:: model_class
|
|
65
72
|
|
|
66
73
|
Model class for the grid, if applicable. When set, this is
|
|
@@ -82,6 +89,9 @@ class Grid:
|
|
|
82
89
|
model records) or else an object capable of producing such a
|
|
83
90
|
list, e.g. SQLAlchemy query.
|
|
84
91
|
|
|
92
|
+
This is the "full" data set; see also
|
|
93
|
+
:meth:`get_visible_data()`.
|
|
94
|
+
|
|
85
95
|
.. attribute:: labels
|
|
86
96
|
|
|
87
97
|
Dict of column label overrides.
|
|
@@ -106,15 +116,57 @@ class Grid:
|
|
|
106
116
|
|
|
107
117
|
See also :meth:`set_link()` and :meth:`is_linked()`.
|
|
108
118
|
|
|
109
|
-
.. attribute::
|
|
119
|
+
.. attribute:: paginated
|
|
110
120
|
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
Boolean indicating whether the grid data should be paginated
|
|
122
|
+
vs. all data shown at once. Default is ``False`` which means
|
|
123
|
+
the full set of grid data is sent for each request.
|
|
124
|
+
|
|
125
|
+
See also :attr:`pagesize` and :attr:`page`, and
|
|
126
|
+
:attr:`paginate_on_backend`.
|
|
127
|
+
|
|
128
|
+
.. attribute:: paginate_on_backend
|
|
129
|
+
|
|
130
|
+
Boolean indicating whether the grid data should be paginated on
|
|
131
|
+
the backend. Default is ``True`` which means only one "page"
|
|
132
|
+
of data is sent to the client-side component.
|
|
133
|
+
|
|
134
|
+
If this is ``False``, the full set of grid data is sent for
|
|
135
|
+
each request, and the client-side Vue component will handle the
|
|
136
|
+
pagination.
|
|
137
|
+
|
|
138
|
+
Only relevant if :attr:`paginated` is also true.
|
|
139
|
+
|
|
140
|
+
.. attribute:: pagesize_options
|
|
141
|
+
|
|
142
|
+
List of "page size" options for the grid. See also
|
|
143
|
+
:attr:`pagesize`.
|
|
144
|
+
|
|
145
|
+
Only relevant if :attr:`paginated` is true. If not specified,
|
|
146
|
+
constructor will call :meth:`get_pagesize_options()` to get the
|
|
147
|
+
value.
|
|
148
|
+
|
|
149
|
+
.. attribute:: pagesize
|
|
150
|
+
|
|
151
|
+
Number of records to show in a data page. See also
|
|
152
|
+
:attr:`pagesize_options` and :attr:`page`.
|
|
153
|
+
|
|
154
|
+
Only relevant if :attr:`paginated` is true. If not specified,
|
|
155
|
+
constructor will call :meth:`get_pagesize()` to get the value.
|
|
156
|
+
|
|
157
|
+
.. attribute:: page
|
|
158
|
+
|
|
159
|
+
The current page number (of data) to display in the grid. See
|
|
160
|
+
also :attr:`pagesize`.
|
|
161
|
+
|
|
162
|
+
Only relevant if :attr:`paginated` is true. If not specified,
|
|
163
|
+
constructor will assume ``1`` (first page).
|
|
113
164
|
"""
|
|
114
165
|
|
|
115
166
|
def __init__(
|
|
116
167
|
self,
|
|
117
168
|
request,
|
|
169
|
+
vue_tagname='wutta-grid',
|
|
118
170
|
model_class=None,
|
|
119
171
|
key=None,
|
|
120
172
|
columns=None,
|
|
@@ -123,9 +175,14 @@ class Grid:
|
|
|
123
175
|
renderers={},
|
|
124
176
|
actions=[],
|
|
125
177
|
linked_columns=[],
|
|
126
|
-
|
|
178
|
+
paginated=False,
|
|
179
|
+
paginate_on_backend=True,
|
|
180
|
+
pagesize_options=None,
|
|
181
|
+
pagesize=None,
|
|
182
|
+
page=1,
|
|
127
183
|
):
|
|
128
184
|
self.request = request
|
|
185
|
+
self.vue_tagname = vue_tagname
|
|
129
186
|
self.model_class = model_class
|
|
130
187
|
self.key = key
|
|
131
188
|
self.data = data
|
|
@@ -133,13 +190,18 @@ class Grid:
|
|
|
133
190
|
self.renderers = renderers or {}
|
|
134
191
|
self.actions = actions or []
|
|
135
192
|
self.linked_columns = linked_columns or []
|
|
136
|
-
self.vue_tagname = vue_tagname
|
|
137
193
|
|
|
138
194
|
self.config = self.request.wutta_config
|
|
139
195
|
self.app = self.config.get_app()
|
|
140
196
|
|
|
141
197
|
self.set_columns(columns or self.get_columns())
|
|
142
198
|
|
|
199
|
+
self.paginated = paginated
|
|
200
|
+
self.paginate_on_backend = paginate_on_backend
|
|
201
|
+
self.pagesize_options = pagesize_options or self.get_pagesize_options()
|
|
202
|
+
self.pagesize = pagesize or self.get_pagesize()
|
|
203
|
+
self.page = page
|
|
204
|
+
|
|
143
205
|
def get_columns(self):
|
|
144
206
|
"""
|
|
145
207
|
Returns the official list of column names for the grid, or
|
|
@@ -340,6 +402,207 @@ class Grid:
|
|
|
340
402
|
return True
|
|
341
403
|
return False
|
|
342
404
|
|
|
405
|
+
##############################
|
|
406
|
+
# paging methods
|
|
407
|
+
##############################
|
|
408
|
+
|
|
409
|
+
def get_pagesize_options(self, default=None):
|
|
410
|
+
"""
|
|
411
|
+
Returns a list of default page size options for the grid.
|
|
412
|
+
|
|
413
|
+
It will check config but if no setting exists, will fall
|
|
414
|
+
back to::
|
|
415
|
+
|
|
416
|
+
[5, 10, 20, 50, 100, 200]
|
|
417
|
+
|
|
418
|
+
:param default: Alternate default value to return if none is
|
|
419
|
+
configured.
|
|
420
|
+
|
|
421
|
+
This method is intended for use in the constructor. Code can
|
|
422
|
+
instead access :attr:`pagesize_options` directly.
|
|
423
|
+
"""
|
|
424
|
+
options = self.config.get_list('wuttaweb.grids.default_pagesize_options')
|
|
425
|
+
if options:
|
|
426
|
+
options = [int(size) for size in options
|
|
427
|
+
if size.isdigit()]
|
|
428
|
+
if options:
|
|
429
|
+
return options
|
|
430
|
+
|
|
431
|
+
return default or [5, 10, 20, 50, 100, 200]
|
|
432
|
+
|
|
433
|
+
def get_pagesize(self, default=None):
|
|
434
|
+
"""
|
|
435
|
+
Returns the default page size for the grid.
|
|
436
|
+
|
|
437
|
+
It will check config but if no setting exists, will fall back
|
|
438
|
+
to a value from :attr:`pagesize_options` (will return ``20`` if
|
|
439
|
+
that is listed; otherwise the "first" option).
|
|
440
|
+
|
|
441
|
+
:param default: Alternate default value to return if none is
|
|
442
|
+
configured.
|
|
443
|
+
|
|
444
|
+
This method is intended for use in the constructor. Code can
|
|
445
|
+
instead access :attr:`pagesize` directly.
|
|
446
|
+
"""
|
|
447
|
+
size = self.config.get_int('wuttaweb.grids.default_pagesize')
|
|
448
|
+
if size:
|
|
449
|
+
return size
|
|
450
|
+
|
|
451
|
+
if default:
|
|
452
|
+
return default
|
|
453
|
+
|
|
454
|
+
if 20 in self.pagesize_options:
|
|
455
|
+
return 20
|
|
456
|
+
|
|
457
|
+
return self.pagesize_options[0]
|
|
458
|
+
|
|
459
|
+
##############################
|
|
460
|
+
# configuration methods
|
|
461
|
+
##############################
|
|
462
|
+
|
|
463
|
+
def load_settings(self, store=True):
|
|
464
|
+
"""
|
|
465
|
+
Load all effective settings for the grid, from the following
|
|
466
|
+
places:
|
|
467
|
+
|
|
468
|
+
* request params
|
|
469
|
+
* user session
|
|
470
|
+
|
|
471
|
+
The first value found for a given setting will be applied to
|
|
472
|
+
the grid.
|
|
473
|
+
|
|
474
|
+
.. note::
|
|
475
|
+
|
|
476
|
+
As of now, "pagination" settings are the only type
|
|
477
|
+
supported by this logic. Filter/sort coming soon...
|
|
478
|
+
|
|
479
|
+
The overall logic for this method is as follows:
|
|
480
|
+
|
|
481
|
+
* collect settings
|
|
482
|
+
* apply settings to current grid
|
|
483
|
+
* optionally save settings to user session
|
|
484
|
+
|
|
485
|
+
Saving the settings to user session will allow the grid to
|
|
486
|
+
"remember" its current settings when user refreshes the page.
|
|
487
|
+
|
|
488
|
+
:param store: Flag indicating whether the collected settings
|
|
489
|
+
should then be saved to the user session.
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
# initial default settings
|
|
493
|
+
settings = {}
|
|
494
|
+
if self.paginated and self.paginate_on_backend:
|
|
495
|
+
settings['pagesize'] = self.pagesize
|
|
496
|
+
settings['page'] = self.page
|
|
497
|
+
|
|
498
|
+
# grab settings from request and/or user session
|
|
499
|
+
if self.paginated and self.paginate_on_backend:
|
|
500
|
+
self.update_page_settings(settings)
|
|
501
|
+
|
|
502
|
+
else:
|
|
503
|
+
# no settings were found in request or user session, so
|
|
504
|
+
# nothing needs to be saved
|
|
505
|
+
store = False
|
|
506
|
+
|
|
507
|
+
# maybe store settings in user session, for next time
|
|
508
|
+
if store:
|
|
509
|
+
self.persist_settings(settings)
|
|
510
|
+
|
|
511
|
+
# update ourself to reflect settings
|
|
512
|
+
if self.paginated and self.paginate_on_backend:
|
|
513
|
+
self.pagesize = settings['pagesize']
|
|
514
|
+
self.page = settings['page']
|
|
515
|
+
|
|
516
|
+
def request_has_settings(self):
|
|
517
|
+
""" """
|
|
518
|
+
for key in ['pagesize', 'page']:
|
|
519
|
+
if key in self.request.GET:
|
|
520
|
+
return True
|
|
521
|
+
return False
|
|
522
|
+
|
|
523
|
+
def update_page_settings(self, settings):
|
|
524
|
+
""" """
|
|
525
|
+
# update the settings dict from request and/or user session
|
|
526
|
+
|
|
527
|
+
# pagesize
|
|
528
|
+
pagesize = self.request.GET.get('pagesize')
|
|
529
|
+
if pagesize is not None:
|
|
530
|
+
if pagesize.isdigit():
|
|
531
|
+
settings['pagesize'] = int(pagesize)
|
|
532
|
+
else:
|
|
533
|
+
pagesize = self.request.session.get(f'grid.{self.key}.pagesize')
|
|
534
|
+
if pagesize is not None:
|
|
535
|
+
settings['pagesize'] = pagesize
|
|
536
|
+
|
|
537
|
+
# page
|
|
538
|
+
page = self.request.GET.get('page')
|
|
539
|
+
if page is not None:
|
|
540
|
+
if page.isdigit():
|
|
541
|
+
settings['page'] = int(page)
|
|
542
|
+
else:
|
|
543
|
+
page = self.request.session.get(f'grid.{self.key}.page')
|
|
544
|
+
if page is not None:
|
|
545
|
+
settings['page'] = int(page)
|
|
546
|
+
|
|
547
|
+
def persist_settings(self, settings):
|
|
548
|
+
""" """
|
|
549
|
+
model = self.app.model
|
|
550
|
+
session = Session()
|
|
551
|
+
|
|
552
|
+
# func to save a setting value to user session
|
|
553
|
+
def persist(key, value=lambda k: settings.get(k)):
|
|
554
|
+
skey = f'grid.{self.key}.{key}'
|
|
555
|
+
self.request.session[skey] = value(key)
|
|
556
|
+
|
|
557
|
+
if self.paginated and self.paginate_on_backend:
|
|
558
|
+
persist('pagesize')
|
|
559
|
+
persist('page')
|
|
560
|
+
|
|
561
|
+
##############################
|
|
562
|
+
# data methods
|
|
563
|
+
##############################
|
|
564
|
+
|
|
565
|
+
def get_visible_data(self):
|
|
566
|
+
"""
|
|
567
|
+
Returns the "effective" visible data for the grid.
|
|
568
|
+
|
|
569
|
+
This uses :attr:`data` as the starting point but may morph it
|
|
570
|
+
for pagination etc. per the grid settings.
|
|
571
|
+
|
|
572
|
+
Code can either access :attr:`data` directly, or call this
|
|
573
|
+
method to get only the data for current view (e.g. assuming
|
|
574
|
+
pagination is used), depending on the need.
|
|
575
|
+
|
|
576
|
+
See also these methods which may be called by this one:
|
|
577
|
+
|
|
578
|
+
* :meth:`paginate_data()`
|
|
579
|
+
"""
|
|
580
|
+
data = self.data or []
|
|
581
|
+
|
|
582
|
+
if self.paginated and self.paginate_on_backend:
|
|
583
|
+
self.pager = self.paginate_data(data)
|
|
584
|
+
data = self.pager
|
|
585
|
+
|
|
586
|
+
return data
|
|
587
|
+
|
|
588
|
+
def paginate_data(self, data):
|
|
589
|
+
"""
|
|
590
|
+
Apply pagination to the given data set, based on grid settings.
|
|
591
|
+
|
|
592
|
+
This returns a "pager" object which can then be used as a
|
|
593
|
+
"data replacement" in subsequent logic.
|
|
594
|
+
|
|
595
|
+
This method is called by :meth:`get_visible_data()`.
|
|
596
|
+
"""
|
|
597
|
+
pager = paginate.Page(data,
|
|
598
|
+
items_per_page=self.pagesize,
|
|
599
|
+
page=self.page)
|
|
600
|
+
return pager
|
|
601
|
+
|
|
602
|
+
##############################
|
|
603
|
+
# rendering methods
|
|
604
|
+
##############################
|
|
605
|
+
|
|
343
606
|
def render_vue_tag(self, **kwargs):
|
|
344
607
|
"""
|
|
345
608
|
Render the Vue component tag for the grid.
|
|
@@ -418,17 +681,18 @@ class Grid:
|
|
|
418
681
|
"""
|
|
419
682
|
Returns a list of Vue-compatible data records.
|
|
420
683
|
|
|
421
|
-
This
|
|
422
|
-
|
|
684
|
+
This calls :meth:`get_visible_data()` but then may modify the
|
|
685
|
+
result, e.g. to add URLs for :attr:`actions` etc.
|
|
423
686
|
|
|
424
687
|
Importantly, this also ensures each value in the dict is
|
|
425
688
|
JSON-serializable, using
|
|
426
689
|
:func:`~wuttaweb.util.make_json_safe()`.
|
|
427
690
|
|
|
428
691
|
:returns: List of data record dicts for use with Vue table
|
|
429
|
-
component.
|
|
692
|
+
component. May be the full set of data, or just the
|
|
693
|
+
current page, per :attr:`paginate_on_backend`.
|
|
430
694
|
"""
|
|
431
|
-
original_data = self.
|
|
695
|
+
original_data = self.get_visible_data()
|
|
432
696
|
|
|
433
697
|
# TODO: at some point i thought it was useful to wrangle the
|
|
434
698
|
# columns here, but now i can't seem to figure out why..?
|
|
@@ -479,6 +743,22 @@ class Grid:
|
|
|
479
743
|
|
|
480
744
|
return data
|
|
481
745
|
|
|
746
|
+
def get_vue_pager_stats(self):
|
|
747
|
+
"""
|
|
748
|
+
Returns a simple dict with current grid pager stats.
|
|
749
|
+
|
|
750
|
+
This is used when :attr:`paginate_on_backend` is in effect.
|
|
751
|
+
"""
|
|
752
|
+
pager = self.pager
|
|
753
|
+
return {
|
|
754
|
+
'item_count': pager.item_count,
|
|
755
|
+
'items_per_page': pager.items_per_page,
|
|
756
|
+
'page': pager.page,
|
|
757
|
+
'page_count': pager.page_count,
|
|
758
|
+
'first_item': pager.first_item,
|
|
759
|
+
'last_item': pager.last_item,
|
|
760
|
+
}
|
|
761
|
+
|
|
482
762
|
|
|
483
763
|
class GridAction:
|
|
484
764
|
"""
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
## -*- coding: utf-8; -*-
|
|
2
|
+
|
|
3
|
+
<script type="text/x-template" id="${grid.vue_tagname}-template">
|
|
4
|
+
<${b}-table :data="data"
|
|
5
|
+
:loading="loading"
|
|
6
|
+
|
|
7
|
+
narrowed
|
|
8
|
+
hoverable
|
|
9
|
+
icon-pack="fas"
|
|
10
|
+
|
|
11
|
+
## paging
|
|
12
|
+
% if grid.paginated:
|
|
13
|
+
paginated
|
|
14
|
+
pagination-size="is-small"
|
|
15
|
+
:per-page="perPage"
|
|
16
|
+
:current-page="currentPage"
|
|
17
|
+
@page-change="onPageChange"
|
|
18
|
+
% if grid.paginate_on_backend:
|
|
19
|
+
backend-pagination
|
|
20
|
+
:total="pagerStats.item_count"
|
|
21
|
+
% endif
|
|
22
|
+
% endif
|
|
23
|
+
>
|
|
24
|
+
|
|
25
|
+
% for column in grid.get_vue_columns():
|
|
26
|
+
<${b}-table-column field="${column['field']}"
|
|
27
|
+
label="${column['label']}"
|
|
28
|
+
v-slot="props"
|
|
29
|
+
cell-class="c_${column['field']}">
|
|
30
|
+
% if grid.is_linked(column['field']):
|
|
31
|
+
<a :href="props.row._action_url_view"
|
|
32
|
+
v-html="props.row.${column['field']}" />
|
|
33
|
+
% else:
|
|
34
|
+
<span v-html="props.row.${column['field']}"></span>
|
|
35
|
+
% endif
|
|
36
|
+
</${b}-table-column>
|
|
37
|
+
% endfor
|
|
38
|
+
|
|
39
|
+
% if grid.actions:
|
|
40
|
+
<${b}-table-column field="actions"
|
|
41
|
+
label="Actions"
|
|
42
|
+
v-slot="props">
|
|
43
|
+
% for action in grid.actions:
|
|
44
|
+
<a v-if="props.row._action_url_${action.key}"
|
|
45
|
+
:href="props.row._action_url_${action.key}"
|
|
46
|
+
class="${action.link_class}">
|
|
47
|
+
${action.render_icon_and_label()}
|
|
48
|
+
</a>
|
|
49
|
+
|
|
50
|
+
% endfor
|
|
51
|
+
</${b}-table-column>
|
|
52
|
+
% endif
|
|
53
|
+
|
|
54
|
+
% if grid.paginated:
|
|
55
|
+
<template #footer>
|
|
56
|
+
<div style="display: flex; justify-content: space-between;">
|
|
57
|
+
<div></div>
|
|
58
|
+
<div style="display: flex; gap: 0.5rem; align-items: center;">
|
|
59
|
+
<span>
|
|
60
|
+
showing
|
|
61
|
+
{{ renderNumber(pagerStats.first_item) }}
|
|
62
|
+
- {{ renderNumber(pagerStats.last_item) }}
|
|
63
|
+
of {{ renderNumber(pagerStats.item_count) }} results;
|
|
64
|
+
</span>
|
|
65
|
+
<b-select v-model="perPage"
|
|
66
|
+
% if grid.paginate_on_backend:
|
|
67
|
+
@input="onPageSizeChange"
|
|
68
|
+
% endif
|
|
69
|
+
size="is-small">
|
|
70
|
+
<option v-for="size in pageSizeOptions"
|
|
71
|
+
:value="size">
|
|
72
|
+
{{ size }}
|
|
73
|
+
</option>
|
|
74
|
+
</b-select>
|
|
75
|
+
<span>
|
|
76
|
+
per page
|
|
77
|
+
</span>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</template>
|
|
81
|
+
% endif
|
|
82
|
+
|
|
83
|
+
</${b}-table>
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<script>
|
|
87
|
+
|
|
88
|
+
let ${grid.vue_component}CurrentData = ${json.dumps(grid.get_vue_data())|n}
|
|
89
|
+
|
|
90
|
+
const ${grid.vue_component}Data = {
|
|
91
|
+
data: ${grid.vue_component}CurrentData,
|
|
92
|
+
loading: false,
|
|
93
|
+
|
|
94
|
+
## paging
|
|
95
|
+
% if grid.paginated:
|
|
96
|
+
pageSizeOptions: ${json.dumps(grid.pagesize_options)|n},
|
|
97
|
+
perPage: ${json.dumps(grid.pagesize)|n},
|
|
98
|
+
currentPage: ${json.dumps(grid.page)|n},
|
|
99
|
+
% if grid.paginate_on_backend:
|
|
100
|
+
pagerStats: ${json.dumps(grid.get_vue_pager_stats())|n},
|
|
101
|
+
% endif
|
|
102
|
+
% endif
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const ${grid.vue_component} = {
|
|
106
|
+
template: '#${grid.vue_tagname}-template',
|
|
107
|
+
computed: {
|
|
108
|
+
|
|
109
|
+
% if not grid.paginate_on_backend:
|
|
110
|
+
|
|
111
|
+
pagerStats() {
|
|
112
|
+
let last = this.currentPage * this.perPage
|
|
113
|
+
let first = last - this.perPage + 1
|
|
114
|
+
if (last > this.data.length) {
|
|
115
|
+
last = this.data.length
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
'item_count': this.data.length,
|
|
119
|
+
'items_per_page': this.perPage,
|
|
120
|
+
'page': this.currentPage,
|
|
121
|
+
'first_item': first,
|
|
122
|
+
'last_item': last,
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
% endif
|
|
127
|
+
},
|
|
128
|
+
methods: {
|
|
129
|
+
|
|
130
|
+
renderNumber(value) {
|
|
131
|
+
if (value != undefined) {
|
|
132
|
+
return value.toLocaleString('en')
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
getBasicParams() {
|
|
137
|
+
return {
|
|
138
|
+
% if grid.paginated and grid.paginate_on_backend:
|
|
139
|
+
pagesize: this.perPage,
|
|
140
|
+
page: this.currentPage,
|
|
141
|
+
% endif
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
async fetchData(params, success, failure) {
|
|
146
|
+
|
|
147
|
+
if (params === undefined || params === null) {
|
|
148
|
+
params = new URLSearchParams(this.getBasicParams())
|
|
149
|
+
} else {
|
|
150
|
+
params = new URLSearchParams(params)
|
|
151
|
+
}
|
|
152
|
+
if (!params.has('partial')) {
|
|
153
|
+
params.append('partial', true)
|
|
154
|
+
}
|
|
155
|
+
params = params.toString()
|
|
156
|
+
|
|
157
|
+
this.loading = true
|
|
158
|
+
this.$http.get(`${request.path_url}?${'$'}{params}`).then(response => {
|
|
159
|
+
console.log(response)
|
|
160
|
+
console.log(response.data)
|
|
161
|
+
if (!response.data.error) {
|
|
162
|
+
${grid.vue_component}CurrentData = response.data.data
|
|
163
|
+
this.data = ${grid.vue_component}CurrentData
|
|
164
|
+
% if grid.paginated and grid.paginate_on_backend:
|
|
165
|
+
this.pagerStats = response.data.pager_stats
|
|
166
|
+
% endif
|
|
167
|
+
this.loading = false
|
|
168
|
+
if (success) {
|
|
169
|
+
success()
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
this.$buefy.toast.open({
|
|
173
|
+
message: data.error,
|
|
174
|
+
type: 'is-danger',
|
|
175
|
+
duration: 2000, // 4 seconds
|
|
176
|
+
})
|
|
177
|
+
this.loading = false
|
|
178
|
+
if (failure) {
|
|
179
|
+
failure()
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
.catch((error) => {
|
|
184
|
+
this.data = []
|
|
185
|
+
% if grid.paginated and grid.paginate_on_backend:
|
|
186
|
+
this.pagerStats = {}
|
|
187
|
+
% endif
|
|
188
|
+
this.loading = false
|
|
189
|
+
if (failure) {
|
|
190
|
+
failure()
|
|
191
|
+
}
|
|
192
|
+
throw error
|
|
193
|
+
})
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
% if grid.paginated:
|
|
197
|
+
|
|
198
|
+
% if grid.paginate_on_backend:
|
|
199
|
+
onPageSizeChange(size) {
|
|
200
|
+
this.fetchData()
|
|
201
|
+
},
|
|
202
|
+
% endif
|
|
203
|
+
|
|
204
|
+
onPageChange(page) {
|
|
205
|
+
this.currentPage = page
|
|
206
|
+
% if grid.paginate_on_backend:
|
|
207
|
+
this.fetchData()
|
|
208
|
+
% endif
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
% endif
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
</script>
|
|
@@ -25,6 +25,7 @@ Base Logic for Views
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
from pyramid import httpexceptions
|
|
28
|
+
from pyramid.renderers import render_to_response
|
|
28
29
|
|
|
29
30
|
from wuttaweb import forms, grids
|
|
30
31
|
|
|
@@ -117,3 +118,13 @@ class View:
|
|
|
117
118
|
correctly no matter what.
|
|
118
119
|
"""
|
|
119
120
|
return httpexceptions.HTTPFound(location=url, **kwargs)
|
|
121
|
+
|
|
122
|
+
def json_response(self, context):
|
|
123
|
+
"""
|
|
124
|
+
Convenience method to return a JSON response.
|
|
125
|
+
|
|
126
|
+
:param context: Context data to be rendered as JSON.
|
|
127
|
+
|
|
128
|
+
:returns: A :term:`response` with JSON content type.
|
|
129
|
+
"""
|
|
130
|
+
return render_to_response('json', context, request=self.request)
|