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.
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/CHANGELOG.md +16 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/PKG-INFO +2 -2
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/pyproject.toml +2 -2
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/forms/base.py +2 -4
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/forms/schema.py +6 -3
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/grids/base.py +36 -2
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/util.py +40 -3
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/master.py +69 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/people.py +3 -2
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/forms/test_schema.py +3 -2
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/grids/test_base.py +24 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_util.py +20 -3
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_master.py +39 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/.gitignore +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/COPYING.txt +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/README.md +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/Makefile +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/_static/.keepme +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/index.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/app.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/auth.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/db.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/forms.base.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/forms.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/forms.schema.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/forms.widgets.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/grids.base.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/grids.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/handler.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/helpers.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/index.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/menus.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/static.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/subscribers.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/util.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.auth.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.base.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.common.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.essential.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.master.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.people.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.roles.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.settings.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/api/wuttaweb/views.users.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/conf.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/glossary.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/index.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/make.bat +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/docs/narr/index.rst +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/__init__.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/_version.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/app.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/auth.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/db.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/forms/__init__.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/forms/widgets.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/grids/__init__.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/handler.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/helpers.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/menus.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/static/__init__.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/static/img/favicon.ico +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/static/img/logo.png +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/static/img/testing.png +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/subscribers.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/appinfo/index.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/auth/change_password.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/auth/login.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/base.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/base_meta.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/configure.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/password.pt +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/permissions.pt +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/select.pt +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/textarea.pt +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/deform/textinput.pt +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/forbidden.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/form.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/home.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/master/configure.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/master/create.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/master/delete.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/master/edit.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/master/form.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/master/index.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/master/view.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/notfound.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/page.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/people/view_profile.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/setup.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/templates/wutta-components.mako +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/__init__.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/auth.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/base.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/common.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/essential.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/roles.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/settings.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/src/wuttaweb/views/users.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tasks.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/__init__.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/forms/__init__.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/forms/test_base.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/forms/test_widgets.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/grids/__init__.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/bb_oruga.js +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/bb_oruga_bulma.css +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/bb_oruga_bulma.js +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/bb_vue.js +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/bb_vue_fontawesome.js +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/buefy.css +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/buefy.js +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/fontawesome.js +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/vue.js +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/libcache/vue_resource.js +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_app.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_auth.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_handler.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_helpers.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_menus.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_static.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/test_subscribers.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/util.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/__init__.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test___init__.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_auth.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_base.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_common.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_people.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_roles.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_settings.py +0 -0
- {wuttaweb-0.7.0 → wuttaweb-0.8.1}/tests/views/test_users.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
68
|
-
|
|
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.
|
|
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
|
|
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:
|
|
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'
|
|
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
|
-
|
|
90
|
+
person = self.get_instance(session=session)
|
|
91
91
|
context = {
|
|
92
|
-
'
|
|
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
|
|
25
|
+
# unsupported type is converted to string
|
|
26
26
|
node = mod.ObjectNode(colander.String())
|
|
27
|
-
|
|
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
|
-
|
|
227
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|