WuttaWeb 0.7.0__tar.gz → 0.8.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 (145) hide show
  1. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/CHANGELOG.md +10 -0
  2. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/PKG-INFO +2 -2
  3. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/pyproject.toml +2 -2
  4. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/forms/base.py +2 -4
  5. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/forms/schema.py +6 -3
  6. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/grids/base.py +36 -2
  7. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/views/master.py +69 -0
  8. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/views/people.py +3 -2
  9. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/forms/test_schema.py +3 -2
  10. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/grids/test_base.py +24 -0
  11. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/views/test_master.py +39 -0
  12. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/.gitignore +0 -0
  13. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/COPYING.txt +0 -0
  14. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/README.md +0 -0
  15. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/Makefile +0 -0
  16. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/_static/.keepme +0 -0
  17. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/index.rst +0 -0
  18. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/app.rst +0 -0
  19. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/auth.rst +0 -0
  20. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/db.rst +0 -0
  21. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/forms.base.rst +0 -0
  22. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/forms.rst +0 -0
  23. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/forms.schema.rst +0 -0
  24. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/forms.widgets.rst +0 -0
  25. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/grids.base.rst +0 -0
  26. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/grids.rst +0 -0
  27. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/handler.rst +0 -0
  28. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/helpers.rst +0 -0
  29. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/index.rst +0 -0
  30. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/menus.rst +0 -0
  31. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/static.rst +0 -0
  32. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/subscribers.rst +0 -0
  33. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/util.rst +0 -0
  34. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/views.auth.rst +0 -0
  35. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/views.base.rst +0 -0
  36. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/views.common.rst +0 -0
  37. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/views.essential.rst +0 -0
  38. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/views.master.rst +0 -0
  39. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/views.people.rst +0 -0
  40. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/views.roles.rst +0 -0
  41. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/views.rst +0 -0
  42. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/views.settings.rst +0 -0
  43. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/api/wuttaweb/views.users.rst +0 -0
  44. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/conf.py +0 -0
  45. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/glossary.rst +0 -0
  46. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/index.rst +0 -0
  47. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/make.bat +0 -0
  48. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/docs/narr/index.rst +0 -0
  49. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/__init__.py +0 -0
  50. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/_version.py +0 -0
  51. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/app.py +0 -0
  52. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/auth.py +0 -0
  53. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/db.py +0 -0
  54. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/forms/__init__.py +0 -0
  55. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/forms/widgets.py +0 -0
  56. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/grids/__init__.py +0 -0
  57. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/handler.py +0 -0
  58. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/helpers.py +0 -0
  59. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/menus.py +0 -0
  60. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/static/__init__.py +0 -0
  61. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/static/img/favicon.ico +0 -0
  62. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/static/img/logo.png +0 -0
  63. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/static/img/testing.png +0 -0
  64. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/subscribers.py +0 -0
  65. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
  66. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/appinfo/index.mako +0 -0
  67. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
  68. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/auth/login.mako +0 -0
  69. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/base.mako +0 -0
  70. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/base_meta.mako +0 -0
  71. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/configure.mako +0 -0
  72. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
  73. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
  74. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
  75. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/deform/password.pt +0 -0
  76. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
  77. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
  78. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
  79. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
  80. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/deform/select.pt +0 -0
  81. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
  82. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
  83. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/forbidden.mako +0 -0
  84. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/form.mako +0 -0
  85. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
  86. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
  87. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/home.mako +0 -0
  88. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/master/configure.mako +0 -0
  89. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/master/create.mako +0 -0
  90. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/master/delete.mako +0 -0
  91. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/master/edit.mako +0 -0
  92. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/master/form.mako +0 -0
  93. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/master/index.mako +0 -0
  94. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/master/view.mako +0 -0
  95. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/notfound.mako +0 -0
  96. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/page.mako +0 -0
  97. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
  98. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/setup.mako +0 -0
  99. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/templates/wutta-components.mako +0 -0
  100. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/util.py +0 -0
  101. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/views/__init__.py +0 -0
  102. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/views/auth.py +0 -0
  103. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/views/base.py +0 -0
  104. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/views/common.py +0 -0
  105. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/views/essential.py +0 -0
  106. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/views/roles.py +0 -0
  107. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/views/settings.py +0 -0
  108. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/src/wuttaweb/views/users.py +0 -0
  109. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tasks.py +0 -0
  110. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/__init__.py +0 -0
  111. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/forms/__init__.py +0 -0
  112. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/forms/test_base.py +0 -0
  113. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/forms/test_widgets.py +0 -0
  114. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/grids/__init__.py +0 -0
  115. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
  116. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
  117. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/libcache/bb_oruga.js +0 -0
  118. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/libcache/bb_oruga_bulma.css +0 -0
  119. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/libcache/bb_oruga_bulma.js +0 -0
  120. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/libcache/bb_vue.js +0 -0
  121. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
  122. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/libcache/buefy.css +0 -0
  123. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/libcache/buefy.js +0 -0
  124. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/libcache/fontawesome.js +0 -0
  125. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/libcache/vue.js +0 -0
  126. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/libcache/vue_resource.js +0 -0
  127. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/test_app.py +0 -0
  128. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/test_auth.py +0 -0
  129. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/test_handler.py +0 -0
  130. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/test_helpers.py +0 -0
  131. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/test_menus.py +0 -0
  132. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/test_static.py +0 -0
  133. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/test_subscribers.py +0 -0
  134. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/test_util.py +0 -0
  135. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/util.py +0 -0
  136. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/views/__init__.py +0 -0
  137. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/views/test___init__.py +0 -0
  138. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/views/test_auth.py +0 -0
  139. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/views/test_base.py +0 -0
  140. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/views/test_common.py +0 -0
  141. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/views/test_people.py +0 -0
  142. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/views/test_roles.py +0 -0
  143. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/views/test_settings.py +0 -0
  144. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tests/views/test_users.py +0 -0
  145. {wuttaweb-0.7.0 → wuttaweb-0.8.0}/tox.ini +0 -0
@@ -5,6 +5,16 @@ 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.0 (2024-08-15)
9
+
10
+ ### Feat
11
+
12
+ - add form/grid label auto-overrides for master view
13
+
14
+ ### Fix
15
+
16
+ - add `person` to template context for `PersonView.view_profile()`
17
+
8
18
  ## v0.7.0 (2024-08-15)
9
19
 
10
20
  ### Feat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: WuttaWeb
3
- Version: 0.7.0
3
+ Version: 0.8.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
@@ -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.0"
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
@@ -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, {})
@@ -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
File without changes
File without changes