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.
Files changed (147) hide show
  1. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/CHANGELOG.md +7 -0
  2. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/PKG-INFO +2 -1
  3. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/pyproject.toml +2 -1
  4. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/grids/base.py +289 -9
  5. wuttaweb-0.9.0/src/wuttaweb/templates/grids/vue_template.mako +215 -0
  6. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/base.py +11 -0
  7. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/master.py +33 -1
  8. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/grids/test_base.py +162 -16
  9. wuttaweb-0.9.0/tests/views/test_base.py +56 -0
  10. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test_master.py +8 -0
  11. wuttaweb-0.8.1/src/wuttaweb/templates/grids/vue_template.mako +0 -54
  12. wuttaweb-0.8.1/tests/views/test_base.py +0 -46
  13. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/.gitignore +0 -0
  14. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/COPYING.txt +0 -0
  15. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/README.md +0 -0
  16. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/Makefile +0 -0
  17. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/_static/.keepme +0 -0
  18. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/index.rst +0 -0
  19. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/app.rst +0 -0
  20. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/auth.rst +0 -0
  21. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/db.rst +0 -0
  22. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/forms.base.rst +0 -0
  23. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/forms.rst +0 -0
  24. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/forms.schema.rst +0 -0
  25. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/forms.widgets.rst +0 -0
  26. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/grids.base.rst +0 -0
  27. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/grids.rst +0 -0
  28. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/handler.rst +0 -0
  29. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/helpers.rst +0 -0
  30. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/index.rst +0 -0
  31. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/menus.rst +0 -0
  32. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/static.rst +0 -0
  33. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/subscribers.rst +0 -0
  34. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/util.rst +0 -0
  35. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.auth.rst +0 -0
  36. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.base.rst +0 -0
  37. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.common.rst +0 -0
  38. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.essential.rst +0 -0
  39. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.master.rst +0 -0
  40. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.people.rst +0 -0
  41. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.roles.rst +0 -0
  42. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.rst +0 -0
  43. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.settings.rst +0 -0
  44. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/api/wuttaweb/views.users.rst +0 -0
  45. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/conf.py +0 -0
  46. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/glossary.rst +0 -0
  47. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/index.rst +0 -0
  48. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/make.bat +0 -0
  49. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/docs/narr/index.rst +0 -0
  50. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/__init__.py +0 -0
  51. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/_version.py +0 -0
  52. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/app.py +0 -0
  53. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/auth.py +0 -0
  54. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/db.py +0 -0
  55. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/forms/__init__.py +0 -0
  56. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/forms/base.py +0 -0
  57. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/forms/schema.py +0 -0
  58. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/forms/widgets.py +0 -0
  59. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/grids/__init__.py +0 -0
  60. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/handler.py +0 -0
  61. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/helpers.py +0 -0
  62. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/menus.py +0 -0
  63. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/static/__init__.py +0 -0
  64. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/static/img/favicon.ico +0 -0
  65. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/static/img/logo.png +0 -0
  66. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/static/img/testing.png +0 -0
  67. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/subscribers.py +0 -0
  68. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
  69. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/appinfo/index.mako +0 -0
  70. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
  71. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/auth/login.mako +0 -0
  72. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/base.mako +0 -0
  73. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/base_meta.mako +0 -0
  74. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/configure.mako +0 -0
  75. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
  76. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
  77. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
  78. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/password.pt +0 -0
  79. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
  80. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
  81. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
  82. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
  83. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/select.pt +0 -0
  84. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
  85. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
  86. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/forbidden.mako +0 -0
  87. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/form.mako +0 -0
  88. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
  89. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/home.mako +0 -0
  90. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/master/configure.mako +0 -0
  91. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/master/create.mako +0 -0
  92. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/master/delete.mako +0 -0
  93. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/master/edit.mako +0 -0
  94. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/master/form.mako +0 -0
  95. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/master/index.mako +0 -0
  96. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/master/view.mako +0 -0
  97. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/notfound.mako +0 -0
  98. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/page.mako +0 -0
  99. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
  100. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/setup.mako +0 -0
  101. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/templates/wutta-components.mako +0 -0
  102. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/util.py +0 -0
  103. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/__init__.py +0 -0
  104. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/auth.py +0 -0
  105. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/common.py +0 -0
  106. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/essential.py +0 -0
  107. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/people.py +0 -0
  108. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/roles.py +0 -0
  109. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/settings.py +0 -0
  110. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/src/wuttaweb/views/users.py +0 -0
  111. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tasks.py +0 -0
  112. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/__init__.py +0 -0
  113. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/forms/__init__.py +0 -0
  114. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/forms/test_base.py +0 -0
  115. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/forms/test_schema.py +0 -0
  116. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/forms/test_widgets.py +0 -0
  117. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/grids/__init__.py +0 -0
  118. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
  119. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
  120. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/bb_oruga.js +0 -0
  121. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/bb_oruga_bulma.css +0 -0
  122. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/bb_oruga_bulma.js +0 -0
  123. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/bb_vue.js +0 -0
  124. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
  125. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/buefy.css +0 -0
  126. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/buefy.js +0 -0
  127. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/fontawesome.js +0 -0
  128. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/vue.js +0 -0
  129. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/libcache/vue_resource.js +0 -0
  130. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_app.py +0 -0
  131. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_auth.py +0 -0
  132. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_handler.py +0 -0
  133. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_helpers.py +0 -0
  134. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_menus.py +0 -0
  135. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_static.py +0 -0
  136. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_subscribers.py +0 -0
  137. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/test_util.py +0 -0
  138. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/util.py +0 -0
  139. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/__init__.py +0 -0
  140. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test___init__.py +0 -0
  141. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test_auth.py +0 -0
  142. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test_common.py +0 -0
  143. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test_people.py +0 -0
  144. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test_roles.py +0 -0
  145. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test_settings.py +0 -0
  146. {wuttaweb-0.8.1 → wuttaweb-0.9.0}/tests/views/test_users.py +0 -0
  147. {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.8.1
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.8.1"
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:: vue_tagname
119
+ .. attribute:: paginated
110
120
 
111
- String name for Vue component tag. By default this is
112
- ``'wutta-grid'``. See also :meth:`render_vue_tag()`.
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
- vue_tagname='wutta-grid',
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 uses :attr:`data` as the basis, but may add some extra
422
- values to each record, e.g. URLs for :attr:`actions` etc.
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.data or []
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
+ &nbsp;
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)