WuttaWeb 0.7.0__tar.gz → 0.8.1__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 (145) hide show
  1. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/CHANGELOG.md +16 -0
  2. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/PKG-INFO +2 -2
  3. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/pyproject.toml +2 -2
  4. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/forms/base.py +2 -4
  5. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/forms/schema.py +6 -3
  6. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/grids/base.py +36 -2
  7. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/util.py +40 -3
  8. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/master.py +69 -0
  9. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/people.py +3 -2
  10. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/forms/test_schema.py +3 -2
  11. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/grids/test_base.py +24 -0
  12. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_util.py +20 -3
  13. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_master.py +39 -0
  14. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/.gitignore +0 -0
  15. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/COPYING.txt +0 -0
  16. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/README.md +0 -0
  17. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/Makefile +0 -0
  18. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/_static/.keepme +0 -0
  19. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/index.rst +0 -0
  20. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/app.rst +0 -0
  21. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/auth.rst +0 -0
  22. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/db.rst +0 -0
  23. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/forms.base.rst +0 -0
  24. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/forms.rst +0 -0
  25. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/forms.schema.rst +0 -0
  26. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/forms.widgets.rst +0 -0
  27. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/grids.base.rst +0 -0
  28. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/grids.rst +0 -0
  29. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/handler.rst +0 -0
  30. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/helpers.rst +0 -0
  31. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/index.rst +0 -0
  32. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/menus.rst +0 -0
  33. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/static.rst +0 -0
  34. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/subscribers.rst +0 -0
  35. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/util.rst +0 -0
  36. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.auth.rst +0 -0
  37. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.base.rst +0 -0
  38. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.common.rst +0 -0
  39. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.essential.rst +0 -0
  40. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.master.rst +0 -0
  41. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.people.rst +0 -0
  42. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.roles.rst +0 -0
  43. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.rst +0 -0
  44. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.settings.rst +0 -0
  45. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.users.rst +0 -0
  46. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/conf.py +0 -0
  47. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/glossary.rst +0 -0
  48. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/index.rst +0 -0
  49. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/make.bat +0 -0
  50. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/narr/index.rst +0 -0
  51. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/__init__.py +0 -0
  52. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/_version.py +0 -0
  53. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/app.py +0 -0
  54. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/auth.py +0 -0
  55. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/db.py +0 -0
  56. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/forms/__init__.py +0 -0
  57. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/forms/widgets.py +0 -0
  58. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/grids/__init__.py +0 -0
  59. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/handler.py +0 -0
  60. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/helpers.py +0 -0
  61. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/menus.py +0 -0
  62. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/static/__init__.py +0 -0
  63. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/static/img/favicon.ico +0 -0
  64. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/static/img/logo.png +0 -0
  65. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/static/img/testing.png +0 -0
  66. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/subscribers.py +0 -0
  67. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
  68. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/appinfo/index.mako +0 -0
  69. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/auth/change_password.mako +0 -0
  70. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/auth/login.mako +0 -0
  71. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/base.mako +0 -0
  72. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/base_meta.mako +0 -0
  73. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/configure.mako +0 -0
  74. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
  75. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
  76. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
  77. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/password.pt +0 -0
  78. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/permissions.pt +0 -0
  79. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
  80. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
  81. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
  82. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/select.pt +0 -0
  83. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/textarea.pt +0 -0
  84. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/textinput.pt +0 -0
  85. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/forbidden.mako +0 -0
  86. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/form.mako +0 -0
  87. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
  88. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
  89. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/home.mako +0 -0
  90. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/master/configure.mako +0 -0
  91. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/master/create.mako +0 -0
  92. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/master/delete.mako +0 -0
  93. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/master/edit.mako +0 -0
  94. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/master/form.mako +0 -0
  95. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/master/index.mako +0 -0
  96. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/master/view.mako +0 -0
  97. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/notfound.mako +0 -0
  98. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/page.mako +0 -0
  99. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/people/view_profile.mako +0 -0
  100. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/setup.mako +0 -0
  101. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/wutta-components.mako +0 -0
  102. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/__init__.py +0 -0
  103. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/auth.py +0 -0
  104. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/base.py +0 -0
  105. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/common.py +0 -0
  106. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/essential.py +0 -0
  107. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/roles.py +0 -0
  108. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/settings.py +0 -0
  109. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/users.py +0 -0
  110. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tasks.py +0 -0
  111. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/__init__.py +0 -0
  112. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/forms/__init__.py +0 -0
  113. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/forms/test_base.py +0 -0
  114. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/forms/test_widgets.py +0 -0
  115. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/grids/__init__.py +0 -0
  116. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
  117. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
  118. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/bb_oruga.js +0 -0
  119. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/bb_oruga_bulma.css +0 -0
  120. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/bb_oruga_bulma.js +0 -0
  121. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/bb_vue.js +0 -0
  122. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/bb_vue_fontawesome.js +0 -0
  123. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/buefy.css +0 -0
  124. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/buefy.js +0 -0
  125. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/fontawesome.js +0 -0
  126. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/vue.js +0 -0
  127. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/vue_resource.js +0 -0
  128. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_app.py +0 -0
  129. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_auth.py +0 -0
  130. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_handler.py +0 -0
  131. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_helpers.py +0 -0
  132. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_menus.py +0 -0
  133. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_static.py +0 -0
  134. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_subscribers.py +0 -0
  135. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/util.py +0 -0
  136. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/__init__.py +0 -0
  137. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test___init__.py +0 -0
  138. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_auth.py +0 -0
  139. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_base.py +0 -0
  140. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_common.py +0 -0
  141. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_people.py +0 -0
  142. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_roles.py +0 -0
  143. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_settings.py +0 -0
  144. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_users.py +0 -0
  145. {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tox.ini +0 -0
@@ -5,6 +5,22 @@ 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.8.1 (2024-08-15)
9
+
10
+ ### Fix
11
+
12
+ - improve backward compat for `util.get_liburl()`
13
+
14
+ ## v0.8.0 (2024-08-15)
15
+
16
+ ### Feat
17
+
18
+ - add form/grid label auto-overrides for master view
19
+
20
+ ### Fix
21
+
22
+ - add `person` to template context for `PersonView.view_profile()`
23
+
8
24
  ## v0.7.0 (2024-08-15)
9
25
 
10
26
  ### Feat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: WuttaWeb
3
- Version: 0.7.0
3
+ Version: 0.8.1
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
@@ -33,7 +33,7 @@ Requires-Dist: pyramid-tm
33
33
  Requires-Dist: pyramid>=2
34
34
  Requires-Dist: waitress
35
35
  Requires-Dist: webhelpers2
36
- Requires-Dist: wuttjamaican[db]>=0.11.1
36
+ Requires-Dist: wuttjamaican[db]>=0.12.0
37
37
  Requires-Dist: zope-sqlalchemy>=1.5
38
38
  Provides-Extra: docs
39
39
  Requires-Dist: furo; extra == 'docs'
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "WuttaWeb"
9
- version = "0.7.0"
9
+ version = "0.8.1"
10
10
  description = "Web App for Wutta Framework"
11
11
  readme = "README.md"
12
12
  authors = [{name = "Lance Edgar", email = "lance@edbob.org"}]
@@ -39,7 +39,7 @@ dependencies = [
39
39
  "pyramid_tm",
40
40
  "waitress",
41
41
  "WebHelpers2",
42
- "WuttJamaican[db]>=0.11.1",
42
+ "WuttJamaican[db]>=0.12.0",
43
43
  "zope.sqlalchemy>=1.5",
44
44
  ]
45
45
 
@@ -435,16 +435,14 @@ class Form:
435
435
 
436
436
  Node overrides are tracked via :attr:`nodes`.
437
437
  """
438
+ from wuttaweb.forms.schema import ObjectNode
439
+
438
440
  if isinstance(nodeinfo, colander.SchemaNode):
439
441
  # assume nodeinfo is a complete node
440
442
  node = nodeinfo
441
443
 
442
444
  else: # assume nodeinfo is a schema type
443
445
  kwargs.setdefault('name', key)
444
-
445
- from wuttaweb.forms.schema import ObjectNode
446
-
447
- # node = colander.SchemaNode(nodeinfo, **kwargs)
448
446
  node = ObjectNode(nodeinfo, **kwargs)
449
447
 
450
448
  self.nodes[key] = node
@@ -59,13 +59,16 @@ class ObjectNode(colander.SchemaNode):
59
59
  :class:`ObjectRef`.
60
60
 
61
61
  If the node's type does not have a ``dictify()`` method, this
62
- will raise ``NotImplementeError``.
62
+ will just convert the object to a string and return that.
63
63
  """
64
64
  if hasattr(self.typ, 'dictify'):
65
65
  return self.typ.dictify(obj)
66
66
 
67
- class_name = self.typ.__class__.__name__
68
- raise NotImplementedError(f"you must define {class_name}.dictify()")
67
+ # TODO: this is better than raising an error, as it previously
68
+ # did, but seems like troubleshooting problems may often lead
69
+ # one here.. i suspect this needs to do something smarter but
70
+ # not sure what that is yet
71
+ return str(obj)
69
72
 
70
73
  def objectify(self, value):
71
74
  """
@@ -82,6 +82,12 @@ class Grid:
82
82
  model records) or else an object capable of producing such a
83
83
  list, e.g. SQLAlchemy query.
84
84
 
85
+ .. attribute:: labels
86
+
87
+ Dict of column label overrides.
88
+
89
+ See also :meth:`get_label()` and :meth:`set_label()`.
90
+
85
91
  .. attribute:: renderers
86
92
 
87
93
  Dict of column (cell) value renderer overrides.
@@ -113,6 +119,7 @@ class Grid:
113
119
  key=None,
114
120
  columns=None,
115
121
  data=None,
122
+ labels={},
116
123
  renderers={},
117
124
  actions=[],
118
125
  linked_columns=[],
@@ -122,6 +129,7 @@ class Grid:
122
129
  self.model_class = model_class
123
130
  self.key = key
124
131
  self.data = data
132
+ self.labels = labels or {}
125
133
  self.renderers = renderers or {}
126
134
  self.actions = actions or []
127
135
  self.linked_columns = linked_columns or []
@@ -220,6 +228,32 @@ class Grid:
220
228
  if key in self.columns:
221
229
  self.columns.remove(key)
222
230
 
231
+ def set_label(self, key, label):
232
+ """
233
+ Set/override the label for a column.
234
+
235
+ :param key: Name of column.
236
+
237
+ :param label: New label for the column header.
238
+
239
+ See also :meth:`get_label()`.
240
+
241
+ Label overrides are tracked via :attr:`labels`.
242
+ """
243
+ self.labels[key] = label
244
+
245
+ def get_label(self, key):
246
+ """
247
+ Returns the label text for a given column.
248
+
249
+ If no override is defined, the label is derived from ``key``.
250
+
251
+ See also :meth:`set_label()`.
252
+ """
253
+ if key in self.labels:
254
+ return self.labels[key]
255
+ return self.app.make_title(key)
256
+
223
257
  def set_renderer(self, key, renderer, **kwargs):
224
258
  """
225
259
  Set/override the value renderer for a column.
@@ -376,7 +410,7 @@ class Grid:
376
410
  for name in self.columns:
377
411
  columns.append({
378
412
  'field': name,
379
- 'label': self.app.make_title(name),
413
+ 'label': self.get_label(name),
380
414
  })
381
415
  return columns
382
416
 
@@ -430,7 +464,7 @@ class Grid:
430
464
 
431
465
  # customize value rendering where applicable
432
466
  for key in self.renderers:
433
- value = record[key]
467
+ value = record.get(key, None)
434
468
  record[key] = self.renderers[key](original_record, key, value)
435
469
 
436
470
  # add action urls to each record
@@ -27,6 +27,7 @@ Web Utilities
27
27
  import importlib
28
28
  import json
29
29
  import logging
30
+ import warnings
30
31
 
31
32
  import sqlalchemy as sa
32
33
 
@@ -153,20 +154,34 @@ def get_libver(
153
154
  config = request.wutta_config
154
155
 
155
156
  # nb. we prefer a setting to be named like: wuttaweb.libver.vue
156
- # but for back-compat this also can work: wuttaweb.vue_version
157
+ # but for back-compat this also can work: tailbone.libver.vue
158
+ # and for more back-compat this can work: wuttaweb.vue_version
157
159
  # however that compat only works for some of the settings...
158
160
 
159
161
  if not default_only:
162
+
160
163
  # nb. new/preferred setting
161
- version = config.get(f'{prefix}.libver.{key}')
164
+ version = config.get(f'wuttaweb.libver.{key}')
162
165
  if version:
163
166
  return version
164
167
 
168
+ # fallback to caller-specified prefix
169
+ if prefix != 'wuttaweb':
170
+ version = config.get(f'{prefix}.libver.{key}')
171
+ if version:
172
+ warnings.warn(f"config for {prefix}.libver.{key} is deprecated; "
173
+ f"please set wuttaweb.libver.{key} instead",
174
+ DeprecationWarning)
175
+ return version
176
+
165
177
  if key == 'buefy':
166
178
  if not default_only:
167
179
  # nb. old/legacy setting
168
180
  version = config.get(f'{prefix}.buefy_version')
169
181
  if version:
182
+ warnings.warn(f"config for {prefix}.buefy_version is deprecated; "
183
+ "please set wuttaweb.libver.buefy instead",
184
+ DeprecationWarning)
170
185
  return version
171
186
  if not configured_only:
172
187
  return 'latest'
@@ -182,6 +197,9 @@ def get_libver(
182
197
  # nb. old/legacy setting
183
198
  version = config.get(f'{prefix}.vue_version')
184
199
  if version:
200
+ warnings.warn(f"config for {prefix}.vue_version is deprecated; "
201
+ "please set wuttaweb.libver.vue instead",
202
+ DeprecationWarning)
185
203
  return version
186
204
  if not configured_only:
187
205
  return '2.6.14'
@@ -293,16 +311,35 @@ def get_liburl(
293
311
  config = request.wutta_config
294
312
 
295
313
  if not default_only:
314
+
315
+ # nb. new/preferred setting
316
+ url = config.get(f'wuttaweb.liburl.{key}')
317
+ if url:
318
+ return url
319
+
320
+ # fallback to caller-specified prefix
296
321
  url = config.get(f'{prefix}.liburl.{key}')
297
322
  if url:
323
+ warnings.warn(f"config for {prefix}.liburl.{key} is deprecated; "
324
+ f"please set wuttaweb.liburl.{key} instead",
325
+ DeprecationWarning)
298
326
  return url
299
327
 
300
328
  if configured_only:
301
329
  return
302
330
 
303
- version = get_libver(request, key, prefix=prefix)
331
+ version = get_libver(request, key, prefix=prefix,
332
+ configured_only=False,
333
+ default_only=default_only)
304
334
 
335
+ # load fanstatic libcache if configured
305
336
  static = config.get('wuttaweb.static_libcache.module')
337
+ if not static:
338
+ static = config.get(f'{prefix}.static_libcache.module')
339
+ if static:
340
+ warnings.warn(f"config for {prefix}.static_libcache.module is deprecated; "
341
+ "please set wuttaweb.static_libcache.module instead",
342
+ DeprecationWarning)
306
343
  if static:
307
344
  static = importlib.import_module(static)
308
345
  needed = request.environ['fanstatic.needed']
@@ -33,6 +33,7 @@ from webhelpers2.html import HTML
33
33
  from wuttaweb.views import View
34
34
  from wuttaweb.util import get_form_data, get_model_fields
35
35
  from wuttaweb.db import Session
36
+ from wuttjamaican.util import get_class_hierarchy
36
37
 
37
38
 
38
39
  class MasterView(View):
@@ -803,6 +804,16 @@ class MasterView(View):
803
804
  # support methods
804
805
  ##############################
805
806
 
807
+ def get_class_hierarchy(self, topfirst=True):
808
+ """
809
+ Convenience to return a list of classes from which the current
810
+ class inherits.
811
+
812
+ This is a wrapper around
813
+ :func:`wuttjamaican.util.get_class_hierarchy()`.
814
+ """
815
+ return get_class_hierarchy(self.__class__, topfirst=topfirst)
816
+
806
817
  def has_perm(self, name):
807
818
  """
808
819
  Shortcut to check if current user has the given permission.
@@ -949,6 +960,60 @@ class MasterView(View):
949
960
  route_prefix = self.get_route_prefix()
950
961
  return self.request.route_url(route_prefix, **kwargs)
951
962
 
963
+ def set_labels(self, obj):
964
+ """
965
+ Set label overrides on a form or grid, based on what is
966
+ defined by the view class and its parent class(es).
967
+
968
+ This is called automatically from :meth:`configure_grid()` and
969
+ :meth:`configure_form()`.
970
+
971
+ This calls :meth:`collect_labels()` to find everything, then
972
+ it assigns the labels using one of (based on ``obj`` type):
973
+
974
+ * :func:`wuttaweb.forms.base.Form.set_label()`
975
+ * :func:`wuttaweb.grids.base.Grid.set_label()`
976
+
977
+ :param obj: Either a :class:`~wuttaweb.grids.base.Grid` or a
978
+ :class:`~wuttaweb.forms.base.Form` instance.
979
+ """
980
+ labels = self.collect_labels()
981
+ for key, label in labels.items():
982
+ obj.set_label(key, label)
983
+
984
+ def collect_labels(self):
985
+ """
986
+ Collect all labels defined by the view class and/or its parents.
987
+
988
+ A master view can declare labels via class-level attribute,
989
+ like so::
990
+
991
+ from wuttaweb.views import MasterView
992
+
993
+ class WidgetView(MasterView):
994
+
995
+ labels = {
996
+ 'id': "Widget ID",
997
+ 'serial_no': "Serial Number",
998
+ }
999
+
1000
+ All such labels, defined by any class from which the master
1001
+ view inherits, will be returned. However if the same label
1002
+ key is defined by multiple classes, the "subclass" always
1003
+ wins.
1004
+
1005
+ Labels defined in this way will apply to both forms and grids.
1006
+ See also :meth:`set_labels()`.
1007
+
1008
+ :returns: Dict of all labels found.
1009
+ """
1010
+ labels = {}
1011
+ hierarchy = self.get_class_hierarchy()
1012
+ for cls in hierarchy:
1013
+ if hasattr(cls, 'labels'):
1014
+ labels.update(cls.labels)
1015
+ return labels
1016
+
952
1017
  def make_model_grid(self, session=None, **kwargs):
953
1018
  """
954
1019
  Create and return a :class:`~wuttaweb.grids.base.Grid`
@@ -1072,6 +1137,8 @@ class MasterView(View):
1072
1137
  if 'uuid' in grid.columns:
1073
1138
  grid.columns.remove('uuid')
1074
1139
 
1140
+ self.set_labels(grid)
1141
+
1075
1142
  for key in self.get_model_key():
1076
1143
  grid.set_link(key)
1077
1144
 
@@ -1311,6 +1378,8 @@ class MasterView(View):
1311
1378
  """
1312
1379
  form.remove('uuid')
1313
1380
 
1381
+ self.set_labels(form)
1382
+
1314
1383
  if self.editing:
1315
1384
  for key in self.get_model_key():
1316
1385
  form.set_readonly(key)
@@ -87,9 +87,10 @@ class PersonView(MasterView):
87
87
 
88
88
  def view_profile(self, session=None):
89
89
  """ """
90
- instance = self.get_instance(session=session)
90
+ person = self.get_instance(session=session)
91
91
  context = {
92
- 'instance': instance,
92
+ 'person': person,
93
+ 'instance': person,
93
94
  }
94
95
  return self.render_to_response('view_profile', context)
95
96
 
@@ -22,9 +22,10 @@ class TestObjectNode(DataTestCase):
22
22
  model = self.app.model
23
23
  person = model.Person(full_name="Betty Boop")
24
24
 
25
- # unsupported type raises error
25
+ # unsupported type is converted to string
26
26
  node = mod.ObjectNode(colander.String())
27
- self.assertRaises(NotImplementedError, node.dictify, person)
27
+ value = node.dictify(person)
28
+ self.assertEqual(value, "Betty Boop")
28
29
 
29
30
  # but supported type can dictify
30
31
  node = mod.ObjectNode(mod.PersonRef(self.request))
@@ -81,6 +81,30 @@ class TestGrid(TestCase):
81
81
  grid.remove('two', 'three')
82
82
  self.assertEqual(grid.columns, ['one', 'four'])
83
83
 
84
+ def test_set_label(self):
85
+ grid = self.make_grid(columns=['foo', 'bar'])
86
+ self.assertEqual(grid.labels, {})
87
+
88
+ # basic
89
+ grid.set_label('foo', "Foo Fighters")
90
+ self.assertEqual(grid.labels['foo'], "Foo Fighters")
91
+
92
+ # can replace label
93
+ grid.set_label('foo', "Different")
94
+ self.assertEqual(grid.labels['foo'], "Different")
95
+ self.assertEqual(grid.get_label('foo'), "Different")
96
+
97
+ def test_get_label(self):
98
+ grid = self.make_grid(columns=['foo', 'bar'])
99
+ self.assertEqual(grid.labels, {})
100
+
101
+ # default derived from key
102
+ self.assertEqual(grid.get_label('foo'), "Foo")
103
+
104
+ # can override
105
+ grid.set_label('foo', "Different")
106
+ self.assertEqual(grid.get_label('foo'), "Different")
107
+
84
108
  def test_set_renderer(self):
85
109
  grid = self.make_grid(columns=['foo', 'bar'])
86
110
  self.assertEqual(grid.renderers, {})
@@ -59,6 +59,11 @@ class TestGetLibVer(TestCase):
59
59
  version = util.get_libver(self.request, 'buefy')
60
60
  self.assertEqual(version, '0.9.29')
61
61
 
62
+ def test_buefy_custom_old_tailbone(self):
63
+ self.config.setdefault('tailbone.libver.buefy', '0.9.28')
64
+ version = util.get_libver(self.request, 'buefy', prefix='tailbone')
65
+ self.assertEqual(version, '0.9.28')
66
+
62
67
  def test_buefy_custom_new(self):
63
68
  self.config.setdefault('wuttaweb.libver.buefy', '0.9.29')
64
69
  version = util.get_libver(self.request, 'buefy')
@@ -221,10 +226,11 @@ class TestGetLibUrl(TestCase):
221
226
  def tearDown(self):
222
227
  testing.tearDown()
223
228
 
224
- def setup_fanstatic(self):
229
+ def setup_fanstatic(self, register=True):
225
230
  self.pyramid_config.include('pyramid_fanstatic')
226
- self.config.setdefault('wuttaweb.static_libcache.module',
227
- 'tests.test_util')
231
+ if register:
232
+ self.config.setdefault('wuttaweb.static_libcache.module',
233
+ 'tests.test_util')
228
234
 
229
235
  needed = MagicMock()
230
236
  needed.library_url = MagicMock(return_value='/fanstatic')
@@ -240,6 +246,11 @@ class TestGetLibUrl(TestCase):
240
246
  url = util.get_liburl(self.request, 'buefy')
241
247
  self.assertEqual(url, '/lib/buefy.js')
242
248
 
249
+ def test_buefy_custom_tailbone(self):
250
+ self.config.setdefault('tailbone.liburl.buefy', '/tailbone/buefy.js')
251
+ url = util.get_liburl(self.request, 'buefy', prefix='tailbone')
252
+ self.assertEqual(url, '/tailbone/buefy.js')
253
+
243
254
  def test_buefy_default_only(self):
244
255
  self.config.setdefault('wuttaweb.liburl.buefy', '/lib/buefy.js')
245
256
  url = util.get_liburl(self.request, 'buefy', default_only=True)
@@ -254,6 +265,12 @@ class TestGetLibUrl(TestCase):
254
265
  url = util.get_liburl(self.request, 'buefy')
255
266
  self.assertEqual(url, '/wutta/fanstatic/buefy.js')
256
267
 
268
+ def test_buefy_fanstatic_tailbone(self):
269
+ self.setup_fanstatic(register=False)
270
+ self.config.setdefault('tailbone.static_libcache.module', 'tests.test_util')
271
+ url = util.get_liburl(self.request, 'buefy', prefix='tailbone')
272
+ self.assertEqual(url, '/wutta/fanstatic/buefy.js')
273
+
257
274
  def test_buefy_css_default(self):
258
275
  url = util.get_liburl(self.request, 'buefy.css')
259
276
  self.assertEqual(url, 'https://unpkg.com/buefy@latest/dist/buefy.min.css')
@@ -10,6 +10,7 @@ from pyramid.httpexceptions import HTTPNotFound
10
10
 
11
11
  from wuttjamaican.conf import WuttaConfig
12
12
  from wuttaweb.views import master
13
+ from wuttaweb.views import View
13
14
  from wuttaweb.subscribers import new_request_set_user
14
15
  from tests.util import WebTestCase
15
16
 
@@ -331,6 +332,14 @@ class TestMasterView(WebTestCase):
331
332
  # support methods
332
333
  ##############################
333
334
 
335
+ def test_get_class_hierarchy(self):
336
+ class MyView(master.MasterView):
337
+ pass
338
+
339
+ view = MyView(self.request)
340
+ classes = view.get_class_hierarchy()
341
+ self.assertEqual(classes, [View, master.MasterView, MyView])
342
+
334
343
  def test_has_perm(self):
335
344
  model = self.app.model
336
345
  auth = self.app.get_auth_handler()
@@ -428,6 +437,36 @@ class TestMasterView(WebTestCase):
428
437
  self.assertEqual(view.get_index_title(), "Wutta Widgets")
429
438
  del master.MasterView.model_title_plural
430
439
 
440
+ def test_collect_labels(self):
441
+
442
+ # no labels by default
443
+ view = self.make_view()
444
+ labels = view.collect_labels()
445
+ self.assertEqual(labels, {})
446
+
447
+ # labels come from all classes; subclass wins
448
+ with patch.object(View, 'labels', new={'foo': "Foo", 'bar': "Bar"}, create=True):
449
+ with patch.object(master.MasterView, 'labels', new={'foo': "FOO FIGHTERS"}, create=True):
450
+ view = self.make_view()
451
+ labels = view.collect_labels()
452
+ self.assertEqual(labels, {'foo': "FOO FIGHTERS", 'bar': "Bar"})
453
+
454
+ def test_set_labels(self):
455
+ model = self.app.model
456
+ with patch.object(master.MasterView, 'model_class', new=model.Setting, create=True):
457
+
458
+ # no labels by default
459
+ view = self.make_view()
460
+ grid = view.make_model_grid(session=self.session)
461
+ view.set_labels(grid)
462
+ self.assertEqual(grid.labels, {})
463
+
464
+ # labels come from all classes; subclass wins
465
+ with patch.object(master.MasterView, 'labels', new={'name': "SETTING NAME"}, create=True):
466
+ view = self.make_view()
467
+ view.set_labels(grid)
468
+ self.assertEqual(grid.labels, {'name': "SETTING NAME"})
469
+
431
470
  def test_make_model_grid(self):
432
471
  model = self.app.model
433
472
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes