WuttaWeb 0.23.2__tar.gz → 0.25.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 (221) hide show
  1. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/CHANGELOG.md +27 -0
  2. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/PKG-INFO +3 -3
  3. wuttaweb-0.25.0/docs/api/wuttaweb.diffs.rst +6 -0
  4. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/index.rst +1 -0
  5. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/pyproject.toml +9 -3
  6. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/db/sess.py +2 -2
  7. wuttaweb-0.25.0/src/wuttaweb/diffs.py +224 -0
  8. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/forms/schema.py +22 -3
  9. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/forms/widgets.py +3 -1
  10. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/grids/base.py +4 -2
  11. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/handler.py +1 -1
  12. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/subscribers.py +12 -0
  13. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/appinfo/configure.mako +78 -0
  14. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/appinfo/index.mako +3 -0
  15. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/base.mako +32 -3
  16. wuttaweb-0.25.0/src/wuttaweb/templates/diff.mako +15 -0
  17. wuttaweb-0.25.0/src/wuttaweb/templates/master/create_row.mako +4 -0
  18. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/view.mako +12 -0
  19. wuttaweb-0.25.0/src/wuttaweb/templates/master/view_version.mako +35 -0
  20. wuttaweb-0.25.0/src/wuttaweb/templates/master/view_versions.mako +20 -0
  21. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/testing.py +67 -1
  22. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/batch.py +87 -5
  23. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/master.py +716 -17
  24. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/settings.py +48 -0
  25. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/upgrades.py +1 -2
  26. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tasks.py +2 -1
  27. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/forms/test_schema.py +74 -19
  28. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/forms/test_widgets.py +29 -11
  29. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/grids/test_base.py +16 -10
  30. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/grids/test_filters.py +0 -4
  31. wuttaweb-0.25.0/tests/test_diffs.py +222 -0
  32. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/test_handler.py +3 -3
  33. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/test_subscribers.py +13 -0
  34. wuttaweb-0.25.0/tests/util.py +15 -0
  35. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test_batch.py +132 -39
  36. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test_master.py +457 -7
  37. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test_settings.py +20 -0
  38. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tox.ini +7 -2
  39. wuttaweb-0.23.2/tests/util.py +0 -43
  40. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/.gitignore +0 -0
  41. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/.hgignore +0 -0
  42. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/.pylintrc +0 -0
  43. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/COPYING.txt +0 -0
  44. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/README.md +0 -0
  45. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/Makefile +0 -0
  46. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/_static/.keepme +0 -0
  47. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.app.rst +0 -0
  48. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.auth.rst +0 -0
  49. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.cli.rst +0 -0
  50. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.cli.webapp.rst +0 -0
  51. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.conf.rst +0 -0
  52. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.db.continuum.rst +0 -0
  53. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.db.rst +0 -0
  54. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.db.sess.rst +0 -0
  55. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.emails.rst +0 -0
  56. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.forms.base.rst +0 -0
  57. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.forms.rst +0 -0
  58. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.forms.schema.rst +0 -0
  59. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.forms.widgets.rst +0 -0
  60. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.grids.base.rst +0 -0
  61. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.grids.filters.rst +0 -0
  62. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.grids.rst +0 -0
  63. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.handler.rst +0 -0
  64. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.helpers.rst +0 -0
  65. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.menus.rst +0 -0
  66. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.progress.rst +0 -0
  67. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.rst +0 -0
  68. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.static.rst +0 -0
  69. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.subscribers.rst +0 -0
  70. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.util.rst +0 -0
  71. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.auth.rst +0 -0
  72. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.base.rst +0 -0
  73. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.batch.rst +0 -0
  74. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.common.rst +0 -0
  75. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.email.rst +0 -0
  76. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.essential.rst +0 -0
  77. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.master.rst +0 -0
  78. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.people.rst +0 -0
  79. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.progress.rst +0 -0
  80. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.reports.rst +0 -0
  81. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.roles.rst +0 -0
  82. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.rst +0 -0
  83. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.settings.rst +0 -0
  84. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.upgrades.rst +0 -0
  85. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.users.rst +0 -0
  86. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/conf.py +0 -0
  87. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/glossary.rst +0 -0
  88. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/make.bat +0 -0
  89. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/narr/cli/builtin.rst +0 -0
  90. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/narr/cli/index.rst +0 -0
  91. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/narr/templates/base.rst +0 -0
  92. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/narr/templates/index.rst +0 -0
  93. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/narr/templates/lookup.rst +0 -0
  94. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/docs/narr/templates/overview.rst +0 -0
  95. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/__init__.py +0 -0
  96. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/_version.py +0 -0
  97. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/app.py +0 -0
  98. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/auth.py +0 -0
  99. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/cli/__init__.py +0 -0
  100. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/cli/webapp.py +0 -0
  101. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/conf.py +0 -0
  102. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/db/__init__.py +0 -0
  103. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/db/continuum.py +0 -0
  104. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/email-templates/feedback.html.mako +0 -0
  105. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/email-templates/feedback.txt.mako +0 -0
  106. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/emails.py +0 -0
  107. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/forms/__init__.py +0 -0
  108. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/forms/base.py +0 -0
  109. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/grids/__init__.py +0 -0
  110. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/grids/filters.py +0 -0
  111. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/helpers.py +0 -0
  112. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/menus.py +0 -0
  113. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/progress.py +0 -0
  114. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/static/__init__.py +0 -0
  115. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/static/img/favicon.ico +0 -0
  116. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/static/img/logo.png +0 -0
  117. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/static/img/testing.png +0 -0
  118. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
  119. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/auth/login.mako +0 -0
  120. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/base_meta.mako +0 -0
  121. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/batch/view.mako +0 -0
  122. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/configure.mako +0 -0
  123. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
  124. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
  125. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
  126. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
  127. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
  128. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
  129. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/password.pt +0 -0
  130. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
  131. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
  132. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/readonly/email_recips.pt +0 -0
  133. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
  134. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
  135. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
  136. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
  137. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
  138. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/select.pt +0 -0
  139. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
  140. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
  141. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/wutta_checked_password.pt +0 -0
  142. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/email/settings/view.mako +0 -0
  143. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/forbidden.mako +0 -0
  144. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/form.mako +0 -0
  145. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
  146. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/grids/table_element.mako +0 -0
  147. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
  148. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/home.mako +0 -0
  149. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/configure.mako +0 -0
  150. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/create.mako +0 -0
  151. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/delete.mako +0 -0
  152. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/edit.mako +0 -0
  153. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/form.mako +0 -0
  154. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/index.mako +0 -0
  155. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/notfound.mako +0 -0
  156. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/page.mako +0 -0
  157. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
  158. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/progress.mako +0 -0
  159. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/reports/view.mako +0 -0
  160. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/setup.mako +0 -0
  161. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/themes/butterfly/base.mako +0 -0
  162. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/themes/butterfly/buefy-components.mako +0 -0
  163. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/themes/butterfly/buefy-plugin.mako +0 -0
  164. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/themes/butterfly/http-plugin.mako +0 -0
  165. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/upgrade.mako +0 -0
  166. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
  167. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/upgrades/view.mako +0 -0
  168. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/users/view.mako +0 -0
  169. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/templates/wutta-components.mako +0 -0
  170. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/util.py +0 -0
  171. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/__init__.py +0 -0
  172. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/auth.py +0 -0
  173. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/base.py +0 -0
  174. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/common.py +0 -0
  175. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/email.py +0 -0
  176. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/essential.py +0 -0
  177. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/people.py +0 -0
  178. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/progress.py +0 -0
  179. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/reports.py +0 -0
  180. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/roles.py +0 -0
  181. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/src/wuttaweb/views/users.py +0 -0
  182. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/__init__.py +0 -0
  183. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/cli/__init__.py +0 -0
  184. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/cli/test_webapp.py +0 -0
  185. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/db/__init__.py +0 -0
  186. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/db/test_continuum.py +0 -0
  187. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/forms/test_base.py +0 -0
  188. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/grids/__init__.py +0 -0
  189. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
  190. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
  191. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/libcache/bb_oruga.js +0 -0
  192. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/libcache/bb_oruga_bulma.css +0 -0
  193. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/libcache/bb_oruga_bulma.js +0 -0
  194. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/libcache/bb_vue.js +0 -0
  195. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
  196. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/libcache/buefy.css +0 -0
  197. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/libcache/buefy.js +0 -0
  198. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/libcache/fontawesome.js +0 -0
  199. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/libcache/vue.js +0 -0
  200. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/libcache/vue_resource.js +0 -0
  201. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/test_app.py +0 -0
  202. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/test_auth.py +0 -0
  203. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/test_emails.py +0 -0
  204. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/test_helpers.py +0 -0
  205. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/test_menus.py +0 -0
  206. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/test_progress.py +0 -0
  207. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/test_static.py +0 -0
  208. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/test_util.py +0 -0
  209. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/__init__.py +0 -0
  210. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test___init__.py +0 -0
  211. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test_auth.py +0 -0
  212. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test_base.py +0 -0
  213. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test_common.py +0 -0
  214. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test_email.py +0 -0
  215. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test_essential.py +0 -0
  216. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test_people.py +0 -0
  217. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test_progress.py +0 -0
  218. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test_reports.py +0 -0
  219. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test_roles.py +0 -0
  220. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test_upgrades.py +0 -0
  221. {wuttaweb-0.23.2 → wuttaweb-0.25.0}/tests/views/test_users.py +0 -0
@@ -5,6 +5,33 @@ 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.25.0 (2025-12-17)
9
+
10
+ ### Feat
11
+
12
+ - add "complete" (sic) timezone support
13
+
14
+ ### Fix
15
+
16
+ - add local timezone awareness for datetime fields
17
+
18
+ ## v0.24.0 (2025-12-15)
19
+
20
+ ### Feat
21
+
22
+ - basic support for displaying version history
23
+
24
+ ### Fix
25
+
26
+ - use UTC when updating timestamp in DB
27
+ - workaround error when 'fanstatic.needed' missing from environ
28
+ - workaround error when 'fanstatic.needed' missing from environ
29
+ - address pylint warnings
30
+ - add basic `create_row()` support, esp. for batch views
31
+ - update dependencies for wuttjamaican, wutta-continuum
32
+ - make master view auto-detect continuum versioning for model class
33
+ - fix 'invalid-name' for pylint
34
+
8
35
  ## v0.23.2 (2025-10-19)
9
36
 
10
37
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: WuttaWeb
3
- Version: 0.23.2
3
+ Version: 0.25.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
@@ -39,10 +39,10 @@ Requires-Dist: pyramid-tm
39
39
  Requires-Dist: pyramid>=2
40
40
  Requires-Dist: waitress
41
41
  Requires-Dist: webhelpers2
42
- Requires-Dist: wuttjamaican[db]>=0.24.0
42
+ Requires-Dist: wuttjamaican[db]>=0.26.0
43
43
  Requires-Dist: zope-sqlalchemy>=1.5
44
44
  Provides-Extra: continuum
45
- Requires-Dist: wutta-continuum; extra == 'continuum'
45
+ Requires-Dist: wutta-continuum>=0.2.2; extra == 'continuum'
46
46
  Provides-Extra: docs
47
47
  Requires-Dist: furo; extra == 'docs'
48
48
  Requires-Dist: sphinx; extra == 'docs'
@@ -0,0 +1,6 @@
1
+
2
+ ``wuttaweb.diffs``
3
+ ==================
4
+
5
+ .. automodule:: wuttaweb.diffs
6
+ :members:
@@ -41,6 +41,7 @@ the narrative docs are pretty scant. That will eventually change.
41
41
  api/wuttaweb.db
42
42
  api/wuttaweb.db.continuum
43
43
  api/wuttaweb.db.sess
44
+ api/wuttaweb.diffs
44
45
  api/wuttaweb.emails
45
46
  api/wuttaweb.forms
46
47
  api/wuttaweb.forms.base
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "WuttaWeb"
9
- version = "0.23.2"
9
+ version = "0.25.0"
10
10
  description = "Web App for Wutta Framework"
11
11
  readme = "README.md"
12
12
  authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
@@ -44,13 +44,13 @@ dependencies = [
44
44
  "pyramid_tm",
45
45
  "waitress",
46
46
  "WebHelpers2",
47
- "WuttJamaican[db]>=0.24.0",
47
+ "WuttJamaican[db]>=0.26.0",
48
48
  "zope.sqlalchemy>=1.5",
49
49
  ]
50
50
 
51
51
 
52
52
  [project.optional-dependencies]
53
- continuum = ["Wutta-Continuum"]
53
+ continuum = ["Wutta-Continuum>=0.2.2"]
54
54
  docs = ["Sphinx", "furo", "sphinxcontrib-programoutput"]
55
55
  tests = ["pylint", "pytest", "pytest-cov", "tox"]
56
56
 
@@ -91,3 +91,9 @@ update_changelog_on_bump = true
91
91
  exclude = [
92
92
  "htmlcov/",
93
93
  ]
94
+
95
+
96
+ [tool.pytest.ini_options]
97
+ markers = [
98
+ "versioned: tests with SQLAlchemy-Continuum versioning feature enabled",
99
+ ]
@@ -2,7 +2,7 @@
2
2
  ################################################################################
3
3
  #
4
4
  # wuttaweb -- Web App for Wutta Framework
5
- # Copyright © 2024 Lance Edgar
5
+ # Copyright © 2024-2025 Lance Edgar
6
6
  #
7
7
  # This file is part of Wutta Framework.
8
8
  #
@@ -61,6 +61,6 @@ from sqlalchemy import orm
61
61
  from zope.sqlalchemy.datamanager import register
62
62
 
63
63
 
64
- Session = orm.scoped_session(orm.sessionmaker())
64
+ Session = orm.scoped_session(orm.sessionmaker()) # pylint: disable=invalid-name
65
65
 
66
66
  register(Session)
@@ -0,0 +1,224 @@
1
+ # -*- coding: utf-8; -*-
2
+ ################################################################################
3
+ #
4
+ # wuttaweb -- Web App for Wutta Framework
5
+ # Copyright © 2024-2025 Lance Edgar
6
+ #
7
+ # This file is part of Wutta Framework.
8
+ #
9
+ # Wutta Framework is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU General Public License as published by the Free
11
+ # Software Foundation, either version 3 of the License, or (at your option) any
12
+ # later version.
13
+ #
14
+ # Wutta Framework is distributed in the hope that it will be useful, but
15
+ # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17
+ # more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License along with
20
+ # Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21
+ #
22
+ ################################################################################
23
+ """
24
+ Tools for displaying simple data diffs
25
+ """
26
+
27
+ import sqlalchemy as sa
28
+
29
+ from pyramid.renderers import render
30
+ from webhelpers2.html import HTML
31
+
32
+
33
+ class Diff:
34
+ """
35
+ Represent / display a basic "diff" between two data records.
36
+
37
+ You must provide both the "old" and "new" data records, when
38
+ constructing an instance of this class. Then call
39
+ :meth:`render_html()` to display the diff table.
40
+
41
+ :param old_data: Dict of "old" data record.
42
+
43
+ :param new_data: Dict of "new" data record.
44
+
45
+ :param fields: Optional list of field names. If not specified,
46
+ will be derived from the data records.
47
+
48
+ :param nature: What sort of diff is being represented; must be one
49
+ of: ``("create", "update", "delete")``
50
+
51
+ :param old_color: Background color to display for "old/deleted"
52
+ field data, when applicable.
53
+
54
+ :param new_color: Background color to display for "new/created"
55
+ field data, when applicable.
56
+ """
57
+
58
+ def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments
59
+ self,
60
+ old_data: dict,
61
+ new_data: dict,
62
+ fields: list = None,
63
+ nature="update",
64
+ old_color="#ffebe9",
65
+ new_color="#dafbe1",
66
+ ):
67
+ self.old_data = old_data
68
+ self.new_data = new_data
69
+ self.columns = ["field name", "old value", "new value"]
70
+ self.fields = fields or self.make_fields()
71
+ self.nature = nature
72
+ self.old_color = old_color
73
+ self.new_color = new_color
74
+
75
+ def make_fields(self): # pylint: disable=missing-function-docstring
76
+ return sorted(set(self.old_data) | set(self.new_data), key=lambda x: x.lower())
77
+
78
+ def old_value(self, field): # pylint: disable=missing-function-docstring
79
+ return self.old_data.get(field)
80
+
81
+ def new_value(self, field): # pylint: disable=missing-function-docstring
82
+ return self.new_data.get(field)
83
+
84
+ def values_differ(self, field): # pylint: disable=missing-function-docstring
85
+ return self.new_value(field) != self.old_value(field)
86
+
87
+ def render_html(self, template="/diff.mako", **kwargs):
88
+ """
89
+ Render the diff as HTML table.
90
+
91
+ :param template: Name of template to render, if you need to
92
+ override the default.
93
+
94
+ :param \\**kwargs: Remaining kwargs are passed as context to
95
+ the template renderer.
96
+
97
+ :returns: HTML literal string
98
+ """
99
+ context = kwargs
100
+ context["diff"] = self
101
+ return HTML.literal(render(template, context))
102
+
103
+ def render_field_row(self, field): # pylint: disable=missing-function-docstring
104
+ is_diff = self.values_differ(field)
105
+
106
+ td_field = HTML.tag("td", class_="field", c=field)
107
+
108
+ td_old_value = HTML.tag(
109
+ "td",
110
+ c=self.render_old_value(field),
111
+ **self.get_old_value_attrs(is_diff),
112
+ )
113
+
114
+ td_new_value = HTML.tag(
115
+ "td",
116
+ c=self.render_new_value(field),
117
+ **self.get_new_value_attrs(is_diff),
118
+ )
119
+
120
+ return HTML.tag("tr", c=[td_field, td_old_value, td_new_value])
121
+
122
+ def render_old_value(self, field): # pylint: disable=missing-function-docstring
123
+ value = self.old_value(field)
124
+ return repr(value)
125
+
126
+ def render_new_value(self, field): # pylint: disable=missing-function-docstring
127
+ value = self.new_value(field)
128
+ return repr(value)
129
+
130
+ def get_old_value_attrs( # pylint: disable=missing-function-docstring
131
+ self, is_diff
132
+ ):
133
+ attrs = {}
134
+ if self.nature == "update" and is_diff:
135
+ attrs["style"] = f"background-color: {self.old_color};"
136
+ elif self.nature == "delete":
137
+ attrs["style"] = f"background-color: {self.old_color};"
138
+ return attrs
139
+
140
+ def get_new_value_attrs( # pylint: disable=missing-function-docstring
141
+ self, is_diff
142
+ ):
143
+ attrs = {}
144
+ if self.nature == "create":
145
+ attrs["style"] = f"background-color: {self.new_color};"
146
+ elif self.nature == "update" and is_diff:
147
+ attrs["style"] = f"background-color: {self.new_color};"
148
+ return attrs
149
+
150
+
151
+ class VersionDiff(Diff):
152
+ """
153
+ Special diff class, for use with version history views. Note that
154
+ while based on :class:`Diff`, this class uses a different
155
+ signature for the constructor.
156
+
157
+ :param version: Reference to a Continuum version record (object).
158
+
159
+ :param \\**kwargs: Remaining kwargs are passed as-is to the
160
+ :class:`Diff` constructor.
161
+ """
162
+
163
+ def __init__(self, version, **kwargs):
164
+ import sqlalchemy_continuum as continuum # pylint: disable=import-outside-toplevel
165
+ from wutta_continuum.util import ( # pylint: disable=import-outside-toplevel
166
+ render_operation_type,
167
+ )
168
+
169
+ self.version = version
170
+ self.model_class = continuum.parent_class(type(self.version))
171
+ self.mapper = sa.inspect(self.model_class)
172
+ self.version_mapper = sa.inspect(type(self.version))
173
+ self.title = kwargs.pop("title", self.model_class.__name__)
174
+
175
+ self.operation_title = render_operation_type(self.version.operation_type)
176
+
177
+ if "nature" not in kwargs:
178
+ if (
179
+ version.previous
180
+ and version.operation_type == continuum.Operation.DELETE
181
+ ):
182
+ kwargs["nature"] = "delete"
183
+ elif version.previous:
184
+ kwargs["nature"] = "update"
185
+ else:
186
+ kwargs["nature"] = "create"
187
+
188
+ if "fields" not in kwargs:
189
+ kwargs["fields"] = self.get_default_fields()
190
+
191
+ old_data = {}
192
+ new_data = {}
193
+ for field in kwargs["fields"]:
194
+ if version.previous:
195
+ old_data[field] = getattr(version.previous, field)
196
+ new_data[field] = getattr(version, field)
197
+
198
+ super().__init__(old_data, new_data, **kwargs)
199
+
200
+ def get_default_fields(self): # pylint: disable=missing-function-docstring
201
+ fields = sorted(self.version_mapper.columns.keys())
202
+
203
+ unwanted = [
204
+ "transaction_id",
205
+ "end_transaction_id",
206
+ "operation_type",
207
+ ]
208
+
209
+ return [field for field in fields if field not in unwanted]
210
+
211
+ def render_version_value(self, value): # pylint: disable=missing-function-docstring
212
+ return HTML.tag("span", c=[repr(value)], style="font-family: monospace;")
213
+
214
+ def render_old_value(self, field):
215
+ if self.nature == "create":
216
+ return ""
217
+ value = self.old_value(field)
218
+ return self.render_version_value(value)
219
+
220
+ def render_new_value(self, field):
221
+ if self.nature == "delete":
222
+ return ""
223
+ value = self.new_value(field)
224
+ return self.render_version_value(value)
@@ -48,10 +48,22 @@ class WuttaDateTime(colander.DateTime):
48
48
  the Buefy datepicker + timepicker widgets.
49
49
  """
50
50
 
51
- def deserialize( # pylint: disable=inconsistent-return-statements,empty-docstring
51
+ def serialize(self, node, appstruct):
52
+ if not appstruct:
53
+ return colander.null
54
+
55
+ request = node.widget.request
56
+ config = request.wutta_config
57
+ app = config.get_app()
58
+
59
+ dt = app.localtime(appstruct)
60
+ if self.format:
61
+ return dt.strftime(self.format)
62
+ return dt.isoformat()
63
+
64
+ def deserialize( # pylint: disable=inconsistent-return-statements
52
65
  self, node, cstruct
53
66
  ):
54
- """ """
55
67
  if not cstruct:
56
68
  return colander.null
57
69
 
@@ -60,9 +72,16 @@ class WuttaDateTime(colander.DateTime):
60
72
  "%Y-%m-%dT%I:%M %p",
61
73
  ]
62
74
 
75
+ request = node.widget.request
76
+ config = request.wutta_config
77
+ app = config.get_app()
78
+
63
79
  for fmt in formats:
64
80
  try:
65
- return datetime.datetime.strptime(cstruct, fmt)
81
+ dt = datetime.datetime.strptime(cstruct, fmt)
82
+ if not dt.tzinfo:
83
+ dt = app.localtime(dt, from_utc=False)
84
+ return app.make_utc(dt)
66
85
  except Exception: # pylint: disable=broad-exception-caught
67
86
  pass
68
87
 
@@ -252,7 +252,9 @@ class WuttaDateTimeWidget(DateTimeInputWidget):
252
252
  def serialize(self, field, cstruct, **kw): # pylint: disable=empty-docstring
253
253
  """ """
254
254
  readonly = kw.get("readonly", self.readonly)
255
- if readonly and cstruct:
255
+ if readonly:
256
+ if not cstruct:
257
+ return ""
256
258
  dt = datetime.datetime.fromisoformat(cstruct)
257
259
  return self.app.render_datetime(dt)
258
260
 
@@ -84,7 +84,8 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth
84
84
  .. attribute:: vue_tagname
85
85
 
86
86
  String name for Vue component tag. By default this is
87
- ``'wutta-grid'``. See also :meth:`render_vue_tag()`.
87
+ ``'wutta-grid'``. See also :meth:`render_vue_tag()`
88
+ and :attr:`vue_component`.
88
89
 
89
90
  .. attribute:: model_class
90
91
 
@@ -841,8 +842,9 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth
841
842
  def set_tools(self, tools):
842
843
  """
843
844
  Set the :attr:`tools` attribute using the given tools collection.
844
-
845
845
  This will normalize the list/dict to desired internal format.
846
+
847
+ See also :meth:`add_tool()`.
846
848
  """
847
849
  if tools and isinstance(tools, list):
848
850
  if not any(isinstance(t, (tuple, list)) for t in tools):
@@ -2,7 +2,7 @@
2
2
  ################################################################################
3
3
  #
4
4
  # wuttaweb -- Web App for Wutta Framework
5
- # Copyright © 2024 Lance Edgar
5
+ # Copyright © 2024-2025 Lance Edgar
6
6
  #
7
7
  # This file is part of Wutta Framework.
8
8
  #
@@ -41,6 +41,7 @@ import logging
41
41
  from collections import OrderedDict
42
42
 
43
43
  from pyramid import threadlocal
44
+ from pyramid.httpexceptions import HTTPFound
44
45
 
45
46
  from wuttaweb import helpers
46
47
  from wuttaweb.db import Session
@@ -123,6 +124,17 @@ def new_request(event):
123
124
  config = request.registry.settings["wutta_config"]
124
125
  app = config.get_app()
125
126
 
127
+ # nb. in rare circumstances i have received unhandled error email,
128
+ # which somehow was triggered by 'fanstatic.needed' being missing
129
+ # from the environ. not sure why that would happen, but it seems
130
+ # to when crawlers request a non-existent filename under the
131
+ # fanstatic path. there isn't a great way to handle it since
132
+ # e.g. can't show the normal error page if JS resources won't
133
+ # load, so we try a hail-mary redirect..
134
+ # (nb. also we skip this if environ is empty, i.e. for tests)
135
+ if request.environ and "fanstatic.needed" not in request.environ:
136
+ raise HTTPFound(location=request.route_url("home"))
137
+
126
138
  request.wutta_config = config
127
139
 
128
140
  def get_referrer(default=None):
@@ -48,6 +48,16 @@
48
48
  </b-checkbox>
49
49
  </b-field>
50
50
 
51
+ <b-field label="Time Zone"
52
+ :message="timezoneFieldMessage"
53
+ :type="timezoneFieldType">
54
+ <b-input name="${app.appname}.timezone.default"
55
+ v-model="simpleSettings['${app.appname}.timezone.default']"
56
+ ## TODO: ideally could use @change here but it does not work..?
57
+ ##@change="timezoneCheck()"
58
+ @input="timezoneCheck(); settingsNeedSaved = true" />
59
+ </b-field>
60
+
51
61
  <b-field label="Menu Handler">
52
62
  <input type="hidden"
53
63
  name="${app.appname}.web.menus.handler.spec"
@@ -270,6 +280,74 @@
270
280
 
271
281
  ThisPageData.menuHandlers = ${json.dumps(menu_handlers)|n}
272
282
 
283
+ ThisPageData.timezoneChecking = false
284
+ ThisPageData.timezoneInvalid = false
285
+ ThisPageData.timezoneError = false
286
+
287
+ ThisPage.computed.timezoneFieldMessage = function() {
288
+ if (this.timezoneChecking) {
289
+ return "Working, please wait..."
290
+ }
291
+ if (this.timezoneInvalid) {
292
+ return this.timezoneInvalid
293
+ }
294
+ if (this.timezoneError) {
295
+ return this.timezoneError
296
+ }
297
+ return "RESTART REQUIRED IF YOU CHANGE THIS. The system (default) timezone is: ${default_timezone}"
298
+ }
299
+
300
+ ThisPage.computed.timezoneFieldType = function() {
301
+ if (this.timezoneChecking) {
302
+ return 'is-warning'
303
+ }
304
+ if (this.timezoneInvalid || this.timezoneError) {
305
+ return 'is-danger'
306
+ }
307
+ }
308
+
309
+ ThisPage.methods.timezoneCheck = function() {
310
+ if (this.timezoneChecking) {
311
+ return
312
+ }
313
+
314
+ this.timezoneError = false
315
+
316
+ if (!this.simpleSettings['${config.appname}.timezone.default']) {
317
+ this.timezoneInvalid = false
318
+
319
+ } else {
320
+ this.timezoneChecking = true
321
+ const url = '${url(f"{route_prefix}.check_timezone")}'
322
+ const params = {
323
+ tzname: this.simpleSettings['${config.appname}.timezone.default'],
324
+ }
325
+ this.wuttaGET(url, params, response => {
326
+ this.timezoneInvalid = response.data.invalid
327
+ this.timezoneChecking = false
328
+ }, response => {
329
+ this.timezoneError = response?.data?.error || "unknown error"
330
+ this.timezoneChecking = false
331
+ })
332
+ }
333
+ }
334
+
335
+ ThisPage.methods.timezoneValidate = function() {
336
+ if (this.timezoneChecking) {
337
+ return "Still checking time zone, please try again in a moment."
338
+ }
339
+
340
+ if (this.timezoneError) {
341
+ return "Error checking time zone! Please reload page and try again."
342
+ }
343
+
344
+ if (this.timezoneInvalid) {
345
+ return "The time zone is invalid!"
346
+ }
347
+ }
348
+
349
+ ThisPageData.validators.push(ThisPage.methods.timezoneValidate)
350
+
273
351
  ThisPageData.weblibs = ${json.dumps(weblibs or [])|n}
274
352
 
275
353
  ThisPageData.editWebLibraryShowDialog = false
@@ -19,6 +19,9 @@
19
19
  <b-field horizontal label="Node Title">
20
20
  <span>${app.get_node_title()}</span>
21
21
  </b-field>
22
+ <b-field horizontal label="Time Zone">
23
+ <span>${app.get_timezone_name()}</span>
24
+ </b-field>
22
25
  <b-field horizontal label="Production Mode">
23
26
  <span>${"Yes" if config.production() else "No"}</span>
24
27
  </b-field>
@@ -152,6 +152,8 @@
152
152
  font-size: 2rem;
153
153
  font-weight: bold;
154
154
  margin-bottom: 0 !important;
155
+ display: flex;
156
+ gap: 0.6rem;
155
157
  }
156
158
 
157
159
  #content-title h1 {
@@ -242,7 +244,9 @@
242
244
  ## nb. this is the index title proper
243
245
  <div class="level-left">
244
246
  <div id="header-index-title" class="level-item">
245
- % if index_title:
247
+ % if index_title_rendered is not Undefined and index_title_rendered:
248
+ <h1 class="title">${index_title_rendered}</h1>
249
+ % elif index_title:
246
250
  % if index_url:
247
251
  <h1 class="title">${h.link_to(index_title, index_url)}</h1>
248
252
  % else:
@@ -279,7 +283,7 @@
279
283
  class="has-background-primary">
280
284
  <div style="display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem;">
281
285
 
282
- <div style="width: 60%; display: flex; gap: 0.5rem;">
286
+ <div style="width: 60%; display: flex; gap: 1rem;">
283
287
 
284
288
  <h1 class="title has-text-white"
285
289
  v-html="contentTitleHTML">
@@ -781,7 +785,32 @@
781
785
  % endif
782
786
  </%def>
783
787
 
784
- <%def name="render_prevnext_header_buttons()"></%def>
788
+ <%def name="render_prevnext_header_buttons()">
789
+ % if show_prev_next is not Undefined and show_prev_next:
790
+ <b-button tag="a"
791
+ % if prev_url:
792
+ href="${prev_url}"
793
+ % else:
794
+ href="#"
795
+ disabled
796
+ % endif
797
+ icon-pack="fas"
798
+ icon-left="arrow-left">
799
+ Older
800
+ </b-button>
801
+ <b-button tag="a"
802
+ % if next_url:
803
+ href="${next_url}"
804
+ % else:
805
+ href="#"
806
+ disabled
807
+ % endif
808
+ icon-pack="fas"
809
+ icon-left="arrow-right">
810
+ Newer
811
+ </b-button>
812
+ % endif
813
+ </%def>
785
814
 
786
815
  ##############################
787
816
  ## vue components + app
@@ -0,0 +1,15 @@
1
+ ## -*- coding: utf-8; -*-
2
+ <table class="table is-fullwidth is-bordered is-narrow">
3
+ <thead>
4
+ <tr>
5
+ % for column in diff.columns:
6
+ <th>${column}</th>
7
+ % endfor
8
+ </tr>
9
+ </thead>
10
+ <tbody>
11
+ % for field in diff.fields:
12
+ ${diff.render_field_row(field)}
13
+ % endfor
14
+ </tbody>
15
+ </table>
@@ -0,0 +1,4 @@
1
+ ## -*- coding: utf-8; -*-
2
+ <%inherit file="/master/form.mako" />
3
+
4
+ <%def name="title()">New ${row_model_title}</%def>
@@ -5,6 +5,18 @@
5
5
 
6
6
  <%def name="content_title()">${instance_title}</%def>
7
7
 
8
+ <%def name="render_instance_header_title_extras()">
9
+ ${parent.render_instance_header_title_extras()}
10
+ % if master.should_expose_versions():
11
+ <b-button tag="a"
12
+ href="${master.get_action_url('versions', instance)}"
13
+ icon-pack="fas"
14
+ icon-left="history">
15
+ View History
16
+ </b-button>
17
+ % endif
18
+ </%def>
19
+
8
20
  <%def name="page_layout()">
9
21
 
10
22
  % if master.has_rows: