c2cgeoportal-admin 2.5.0.100__py3-none-any.whl → 2.7.1.156__py3-none-any.whl

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 (91) hide show
  1. c2cgeoportal_admin/__init__.py +19 -12
  2. c2cgeoportal_admin/lib/__init__.py +0 -0
  3. c2cgeoportal_admin/lib/lingua_extractor.py +77 -0
  4. c2cgeoportal_admin/lib/ogcserver_synchronizer.py +409 -0
  5. c2cgeoportal_admin/py.typed +0 -0
  6. c2cgeoportal_admin/routes.py +18 -10
  7. c2cgeoportal_admin/schemas/dimensions.py +13 -11
  8. c2cgeoportal_admin/schemas/functionalities.py +63 -22
  9. c2cgeoportal_admin/schemas/interfaces.py +23 -19
  10. c2cgeoportal_admin/schemas/metadata.py +121 -47
  11. c2cgeoportal_admin/schemas/restriction_areas.py +22 -20
  12. c2cgeoportal_admin/schemas/roles.py +8 -6
  13. c2cgeoportal_admin/schemas/treegroup.py +84 -18
  14. c2cgeoportal_admin/schemas/treeitem.py +2 -3
  15. c2cgeoportal_admin/static/layertree.css +26 -4
  16. c2cgeoportal_admin/static/navbar.css +59 -36
  17. c2cgeoportal_admin/static/theme.css +48 -11
  18. c2cgeoportal_admin/subscribers.py +3 -3
  19. c2cgeoportal_admin/templates/404.jinja2 +23 -0
  20. c2cgeoportal_admin/templates/edit.jinja2 +23 -0
  21. c2cgeoportal_admin/templates/home.jinja2 +23 -0
  22. c2cgeoportal_admin/templates/index.jinja2 +23 -0
  23. c2cgeoportal_admin/templates/layertree.jinja2 +55 -11
  24. c2cgeoportal_admin/templates/layout.jinja2 +23 -0
  25. c2cgeoportal_admin/templates/navigation_navbar.jinja2 +56 -0
  26. c2cgeoportal_admin/templates/ogcserver_synchronize.jinja2 +90 -0
  27. c2cgeoportal_admin/templates/widgets/child.pt +35 -3
  28. c2cgeoportal_admin/templates/widgets/children.pt +121 -92
  29. c2cgeoportal_admin/templates/widgets/dimension.pt +23 -0
  30. c2cgeoportal_admin/templates/widgets/dimensions.pt +23 -0
  31. c2cgeoportal_admin/templates/widgets/functionality_fields.pt +51 -0
  32. c2cgeoportal_admin/templates/widgets/layer_fields.pt +23 -0
  33. c2cgeoportal_admin/templates/widgets/layer_group_fields.pt +23 -0
  34. c2cgeoportal_admin/templates/widgets/layer_v1_fields.pt +23 -0
  35. c2cgeoportal_admin/templates/widgets/metadata.pt +30 -1
  36. c2cgeoportal_admin/templates/widgets/metadatas.pt +23 -0
  37. c2cgeoportal_admin/templates/widgets/ogcserver_fields.pt +23 -0
  38. c2cgeoportal_admin/templates/widgets/restriction_area_fields.pt +25 -9
  39. c2cgeoportal_admin/templates/widgets/role_fields.pt +52 -25
  40. c2cgeoportal_admin/templates/widgets/theme_fields.pt +23 -0
  41. c2cgeoportal_admin/templates/widgets/user_fields.pt +23 -0
  42. c2cgeoportal_admin/views/dimension_layers.py +7 -6
  43. c2cgeoportal_admin/views/functionalities.py +31 -5
  44. c2cgeoportal_admin/views/home.py +5 -5
  45. c2cgeoportal_admin/views/interfaces.py +8 -8
  46. c2cgeoportal_admin/views/layer_groups.py +9 -11
  47. c2cgeoportal_admin/views/layers.py +8 -7
  48. c2cgeoportal_admin/views/layers_vectortiles.py +30 -10
  49. c2cgeoportal_admin/views/layers_wms.py +45 -37
  50. c2cgeoportal_admin/views/layers_wmts.py +39 -33
  51. c2cgeoportal_admin/views/layertree.py +34 -26
  52. c2cgeoportal_admin/views/oauth2_clients.py +89 -0
  53. c2cgeoportal_admin/views/ogc_servers.py +130 -27
  54. c2cgeoportal_admin/views/restriction_areas.py +50 -8
  55. c2cgeoportal_admin/views/roles.py +60 -8
  56. c2cgeoportal_admin/views/themes.py +15 -14
  57. c2cgeoportal_admin/views/themes_ordering.py +38 -18
  58. c2cgeoportal_admin/views/treeitems.py +12 -11
  59. c2cgeoportal_admin/views/users.py +7 -5
  60. c2cgeoportal_admin/widgets.py +79 -28
  61. {c2cgeoportal_admin-2.5.0.100.dist-info → c2cgeoportal_admin-2.7.1.156.dist-info}/METADATA +16 -11
  62. c2cgeoportal_admin-2.7.1.156.dist-info/RECORD +92 -0
  63. {c2cgeoportal_admin-2.5.0.100.dist-info → c2cgeoportal_admin-2.7.1.156.dist-info}/WHEEL +1 -1
  64. c2cgeoportal_admin-2.7.1.156.dist-info/entry_points.txt +5 -0
  65. tests/__init__.py +23 -18
  66. tests/conftest.py +4 -15
  67. tests/test_edit_url.py +16 -18
  68. tests/test_functionalities.py +23 -10
  69. tests/test_interface.py +8 -8
  70. tests/test_layer_groups.py +15 -23
  71. tests/test_layers_vectortiles.py +16 -20
  72. tests/test_layers_wms.py +37 -75
  73. tests/test_layers_wmts.py +20 -24
  74. tests/test_layertree.py +107 -100
  75. tests/test_learn.py +1 -1
  76. tests/test_lingua_extractor_config.py +66 -0
  77. tests/test_main.py +4 -2
  78. tests/test_metadatas.py +79 -70
  79. tests/test_oauth2_clients.py +157 -0
  80. tests/test_ogc_servers.py +51 -7
  81. tests/test_restriction_areas.py +81 -17
  82. tests/test_role.py +110 -76
  83. tests/test_themes.py +44 -37
  84. tests/test_themes_ordering.py +1 -1
  85. tests/test_treegroup.py +2 -2
  86. tests/test_user.py +31 -64
  87. tests/themes_ordering.py +1 -1
  88. c2cgeoportal_admin/templates/navigation_vertical.jinja2 +0 -10
  89. c2cgeoportal_admin-2.5.0.100.dist-info/RECORD +0 -84
  90. c2cgeoportal_admin-2.5.0.100.dist-info/entry_points.txt +0 -3
  91. {c2cgeoportal_admin-2.5.0.100.dist-info → c2cgeoportal_admin-2.7.1.156.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2017-2020, Camptocamp SA
1
+ # Copyright (c) 2017-2021, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -29,12 +27,18 @@
29
27
 
30
28
 
31
29
  from functools import partial
30
+ from typing import Any, Dict, List, cast
32
31
 
33
32
  from c2cgeoform.schema import GeoFormSchemaNode
34
- from c2cgeoform.views.abstract_views import AbstractViews, ListField
33
+ from c2cgeoform.views.abstract_views import AbstractViews, ItemAction, ListField, UserMessage
35
34
  from deform.widget import FormWidget
35
+ from pyramid.httpexceptions import HTTPNotFound
36
36
  from pyramid.view import view_config, view_defaults
37
+ from sqlalchemy import inspect
37
38
 
39
+ from c2cgeoportal_admin import _
40
+ from c2cgeoportal_admin.lib.ogcserver_synchronizer import OGCServerSynchronizer
41
+ from c2cgeoportal_commons.lib.literal import Literal
38
42
  from c2cgeoportal_commons.models.main import OGCServer
39
43
 
40
44
  _list_field = partial(ListField, OGCServer)
@@ -44,7 +48,9 @@ base_schema.add_unique_validator(OGCServer.name, OGCServer.id)
44
48
 
45
49
 
46
50
  @view_defaults(match_param="table=ogc_servers")
47
- class OGCServerViews(AbstractViews):
51
+ class OGCServerViews(AbstractViews): # type: ignore
52
+ """The OGC server administration view."""
53
+
48
54
  _list_fields = [
49
55
  _list_field("id"),
50
56
  _list_field("name"),
@@ -61,28 +67,125 @@ class OGCServerViews(AbstractViews):
61
67
  _model = OGCServer
62
68
  _base_schema = base_schema
63
69
 
64
- @view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2")
65
- def index(self):
66
- return super().index()
67
-
68
- @view_config(route_name="c2cgeoform_grid", renderer="fast_json")
69
- def grid(self):
70
- return super().grid()
71
-
72
- @view_config(route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2")
73
- def view(self):
74
- return super().edit()
75
-
76
- @view_config(route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2")
77
- def save(self):
78
- return super().save()
79
-
80
- @view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json")
81
- def delete(self):
82
- return super().delete()
70
+ MSG_COL = {
71
+ **AbstractViews.MSG_COL,
72
+ "cannot_delete": UserMessage(
73
+ _("Impossible to delete this server while it contains WMS layers."),
74
+ "alert-danger",
75
+ ),
76
+ }
77
+
78
+ @view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2") # type: ignore
79
+ def index(self) -> Dict[str, Any]:
80
+ return super().index() # type: ignore
81
+
82
+ @view_config(route_name="c2cgeoform_grid", renderer="fast_json") # type: ignore
83
+ def grid(self) -> Dict[str, Any]:
84
+ return super().grid() # type: ignore
85
+
86
+ def schema(self) -> GeoFormSchemaNode:
87
+ try:
88
+ obj = self._get_object()
89
+ except HTTPNotFound:
90
+ obj = None
91
+
92
+ schema = self._base_schema.clone()
93
+ schema["url"].description = Literal(
94
+ _("{}<br>Current runtime value is: {}").format(
95
+ schema["url"].description,
96
+ obj.url_description(self._request),
97
+ )
98
+ )
99
+ schema["url_wfs"].description = Literal(
100
+ _("{}<br>Current runtime value is: {}").format(
101
+ schema["url_wfs"].description,
102
+ obj.url_wfs_description(self._request),
103
+ )
104
+ )
105
+ return schema
106
+
107
+ def _item_actions(self, item: OGCServer, readonly: bool = False) -> List[Any]:
108
+ actions = cast(List[Any], super()._item_actions(item, readonly))
109
+ if inspect(item).persistent:
110
+ actions.insert(
111
+ next((i for i, v in enumerate(actions) if v.name() == "delete")),
112
+ ItemAction(
113
+ name="synchronize",
114
+ label=_("Synchronize"),
115
+ icon="glyphicon glyphicon-import",
116
+ url=self._request.route_url("ogcserver_synchronize", id=getattr(item, self._id_field)),
117
+ ),
118
+ )
119
+ return actions
120
+
121
+ @view_config( # type: ignore
122
+ route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2"
123
+ )
124
+ def view(self) -> Dict[str, Any]:
125
+ return super().edit(self.schema()) # type: ignore
83
126
 
84
- @view_config(
127
+ @view_config( # type: ignore
128
+ route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2"
129
+ )
130
+ def save(self) -> Dict[str, Any]:
131
+ return super().save() # type: ignore
132
+
133
+ @view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json") # type: ignore
134
+ def delete(self) -> Dict[str, Any]:
135
+ obj = self._get_object()
136
+ if len(obj.layers) > 0:
137
+ return {
138
+ "success": True,
139
+ "redirect": self._request.route_url(
140
+ "c2cgeoform_item",
141
+ action="edit",
142
+ id=obj.id,
143
+ _query=[("msg_col", "cannot_delete")],
144
+ ),
145
+ }
146
+ return super().delete() # type: ignore
147
+
148
+ @view_config( # type: ignore
85
149
  route_name="c2cgeoform_item_duplicate", request_method="GET", renderer="../templates/edit.jinja2"
86
150
  )
87
- def duplicate(self):
88
- return super().duplicate()
151
+ def duplicate(self) -> Dict[str, Any]:
152
+ return super().duplicate() # type: ignore
153
+
154
+ @view_config( # type: ignore
155
+ route_name="ogcserver_synchronize", renderer="../templates/ogcserver_synchronize.jinja2"
156
+ )
157
+ def synchronize(self) -> Dict[str, Any]:
158
+ obj = self._get_object()
159
+
160
+ if self._request.method == "GET":
161
+ return {
162
+ "ogcserver": obj,
163
+ "success": None,
164
+ "report": None,
165
+ }
166
+
167
+ if self._request.method == "POST":
168
+ force_parents = self._request.POST.get("force-parents", "false") == "on"
169
+ force_ordering = self._request.POST.get("force-ordering", "false") == "on"
170
+ clean = self._request.POST.get("clean", "false") == "on"
171
+
172
+ synchronizer = OGCServerSynchronizer(
173
+ self._request,
174
+ obj,
175
+ force_parents=force_parents,
176
+ force_ordering=force_ordering,
177
+ clean=clean,
178
+ )
179
+ if "check" in self._request.params:
180
+ synchronizer.check_layers()
181
+ elif "dry-run" in self._request.params:
182
+ synchronizer.synchronize(dry_run=True)
183
+ elif "synchronize" in self._request.params:
184
+ synchronizer.synchronize()
185
+ return {
186
+ "ogcserver": obj,
187
+ "success": True,
188
+ "report": synchronizer.report(),
189
+ }
190
+
191
+ return {}
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2017-2020, Camptocamp SA
1
+ # Copyright (c) 2017-2021, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -30,24 +28,68 @@
30
28
 
31
29
  from functools import partial
32
30
 
33
- from c2cgeoform.schema import GeoFormSchemaNode
31
+ import colander
32
+ from c2cgeoform.schema import GeoFormManyToManySchemaNode, GeoFormSchemaNode
34
33
  from c2cgeoform.views.abstract_views import AbstractViews, ListField
35
34
  from deform.widget import FormWidget
36
35
  from pyramid.view import view_config, view_defaults
37
36
  from sqlalchemy.orm import subqueryload
38
37
 
39
38
  from c2cgeoportal_admin.schemas.roles import roles_schema_node
40
- from c2cgeoportal_commons.models.main import RestrictionArea
39
+ from c2cgeoportal_admin.schemas.treegroup import treeitem_edit_url
40
+ from c2cgeoportal_admin.widgets import ChildrenWidget, ChildWidget
41
+ from c2cgeoportal_commons.models.main import Layer, RestrictionArea
41
42
 
42
43
  _list_field = partial(ListField, RestrictionArea)
43
44
 
44
45
  base_schema = GeoFormSchemaNode(RestrictionArea, widget=FormWidget(fields_template="restriction_area_fields"))
45
- base_schema.add_before("area", roles_schema_node("roles"))
46
+ base_schema.add_before("area", roles_schema_node(RestrictionArea.roles))
46
47
  base_schema.add_unique_validator(RestrictionArea.name, RestrictionArea.id)
47
48
 
48
49
 
50
+ def layers(node, kw): # pylint: disable=unused-argument
51
+ """Get the layers serializable representation."""
52
+ dbsession = kw["request"].dbsession
53
+ query = dbsession.query(Layer).order_by(Layer.name)
54
+ return [
55
+ {
56
+ "id": layer.id,
57
+ "label": layer.name,
58
+ "icon_class": f"icon-{layer.item_type}",
59
+ "edit_url": treeitem_edit_url(kw["request"], layer),
60
+ "group": "All",
61
+ }
62
+ for layer in query
63
+ ]
64
+
65
+
66
+ base_schema.add(
67
+ colander.SequenceSchema(
68
+ GeoFormManyToManySchemaNode(
69
+ Layer,
70
+ name="layer",
71
+ includes=["id"],
72
+ widget=ChildWidget(
73
+ input_name="id",
74
+ model=Layer,
75
+ label_field="name",
76
+ icon_class=lambda layer: f"icon-{layer.item_type}",
77
+ edit_url=treeitem_edit_url,
78
+ ),
79
+ ),
80
+ name="layers",
81
+ title=RestrictionArea.layers.info["colanderalchemy"]["title"],
82
+ description=RestrictionArea.layers.info["colanderalchemy"]["description"],
83
+ candidates=colander.deferred(layers),
84
+ widget=ChildrenWidget(child_input_name="id", orderable=False),
85
+ )
86
+ )
87
+
88
+
49
89
  @view_defaults(match_param="table=restriction_areas")
50
- class RestrictionAreaViews(AbstractViews):
90
+ class RestrictionAreaViews(AbstractViews): # type: ignore
91
+ """The restriction area administration view."""
92
+
51
93
  _list_fields = [
52
94
  _list_field("id"),
53
95
  _list_field("name"),
@@ -59,7 +101,7 @@ class RestrictionAreaViews(AbstractViews):
59
101
  _list_field(
60
102
  "layers",
61
103
  renderer=lambda restriction_area: ", ".join(
62
- "{}-{}".format(l.item_type, l.name) or "" for l in restriction_area.layers
104
+ f"{layer.item_type}-{layer.name}" or "" for layer in restriction_area.layers
63
105
  ),
64
106
  ),
65
107
  ]
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2017-2020, Camptocamp SA
1
+ # Copyright (c) 2017-2021, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -30,7 +28,8 @@
30
28
 
31
29
  from functools import partial
32
30
 
33
- from c2cgeoform.schema import GeoFormSchemaNode
31
+ import colander
32
+ from c2cgeoform.schema import GeoFormManyToManySchemaNode, GeoFormSchemaNode
34
33
  from c2cgeoform.views.abstract_views import AbstractViews, ListField
35
34
  from deform.widget import FormWidget
36
35
  from pyramid.view import view_config, view_defaults
@@ -38,25 +37,78 @@ from sqlalchemy.orm import subqueryload
38
37
 
39
38
  from c2cgeoportal_admin.schemas.functionalities import functionalities_schema_node
40
39
  from c2cgeoportal_admin.schemas.restriction_areas import restrictionareas_schema_node
40
+ from c2cgeoportal_admin.widgets import ChildrenWidget, ChildWidget
41
41
  from c2cgeoportal_commons.models.main import Role
42
+ from c2cgeoportal_commons.models.static import User
42
43
 
43
44
  _list_field = partial(ListField, Role)
44
45
 
45
46
  base_schema = GeoFormSchemaNode(Role, widget=FormWidget(fields_template="role_fields"))
46
- base_schema.add_before("extent", functionalities_schema_node.clone())
47
- base_schema.add_before("extent", restrictionareas_schema_node.clone())
47
+ base_schema.add_before("extent", functionalities_schema_node(Role.functionalities, Role))
48
+ base_schema.add_before("extent", restrictionareas_schema_node(Role.restrictionareas))
48
49
  base_schema.add_unique_validator(Role.name, Role.id)
49
50
 
50
51
 
52
+ def users(node, kw): # pylint: disable=unused-argument
53
+ """Get the user serializable metadata."""
54
+ dbsession = kw["request"].dbsession
55
+ query = dbsession.query(User).order_by(User.username)
56
+ return [
57
+ {
58
+ "id": user.id,
59
+ "label": user.username,
60
+ "icon_class": "icon-user",
61
+ "edit_url": kw["request"].route_url(
62
+ "c2cgeoform_item",
63
+ table="users",
64
+ id=user.id,
65
+ ),
66
+ "group": "All",
67
+ }
68
+ for user in query
69
+ ]
70
+
71
+
72
+ base_schema.add(
73
+ colander.SequenceSchema(
74
+ GeoFormManyToManySchemaNode(
75
+ User,
76
+ name="user",
77
+ includes=["id"],
78
+ widget=ChildWidget(
79
+ input_name="id",
80
+ model=User,
81
+ label_field="username",
82
+ icon_class=lambda user: "icon-user",
83
+ edit_url=lambda request, user: request.route_url(
84
+ "c2cgeoform_item",
85
+ table="users",
86
+ id=user.id,
87
+ ),
88
+ ),
89
+ ),
90
+ name=Role.users.key,
91
+ title=Role.users.info["colanderalchemy"]["title"],
92
+ description=Role.users.info["colanderalchemy"]["description"],
93
+ candidates=colander.deferred(users),
94
+ widget=ChildrenWidget(child_input_name="id", orderable=False, category="structural"),
95
+ )
96
+ )
97
+ # Not possible to overwrite this in constructor.
98
+ base_schema["users"].children[0].description = ""
99
+
100
+
51
101
  @view_defaults(match_param="table=roles")
52
- class RoleViews(AbstractViews):
102
+ class RoleViews(AbstractViews): # type: ignore
103
+ """The roles administration view."""
104
+
53
105
  _list_fields = [
54
106
  _list_field("id"),
55
107
  _list_field("name"),
56
108
  _list_field("description"),
57
109
  _list_field(
58
110
  "functionalities",
59
- renderer=lambda role: ", ".join(["{}={}".format(f.name, f.value) for f in role.functionalities]),
111
+ renderer=lambda role: ", ".join([f"{f.name}={f.value}" for f in role.functionalities]),
60
112
  ),
61
113
  _list_field(
62
114
  "restrictionareas", renderer=lambda role: ", ".join([r.name or "" for r in role.restrictionareas])
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2017-2020, Camptocamp SA
1
+ # Copyright (c) 2017-2021, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -29,7 +27,9 @@
29
27
 
30
28
 
31
29
  from functools import partial
30
+ from typing import Optional, cast
32
31
 
32
+ import sqlalchemy
33
33
  from c2cgeoform.schema import GeoFormSchemaNode
34
34
  from c2cgeoform.views.abstract_views import ListField
35
35
  from deform.widget import FormWidget
@@ -39,7 +39,7 @@ from sqlalchemy.sql.functions import concat
39
39
 
40
40
  from c2cgeoportal_admin.schemas.functionalities import functionalities_schema_node
41
41
  from c2cgeoportal_admin.schemas.interfaces import interfaces_schema_node
42
- from c2cgeoportal_admin.schemas.metadata import metadatas_schema_node
42
+ from c2cgeoportal_admin.schemas.metadata import metadata_schema_node
43
43
  from c2cgeoportal_admin.schemas.roles import roles_schema_node
44
44
  from c2cgeoportal_admin.schemas.treegroup import children_schema_node
45
45
  from c2cgeoportal_admin.views.treeitems import TreeItemViews
@@ -49,15 +49,16 @@ _list_field = partial(ListField, Theme)
49
49
 
50
50
  base_schema = GeoFormSchemaNode(Theme, widget=FormWidget(fields_template="theme_fields"))
51
51
  base_schema.add(children_schema_node(only_groups=True))
52
- base_schema.add(functionalities_schema_node.clone())
53
- base_schema.add(roles_schema_node("restricted_roles"))
54
- base_schema.add(interfaces_schema_node.clone())
55
- base_schema.add(metadatas_schema_node.clone())
52
+ base_schema.add(functionalities_schema_node(Theme.functionalities, Theme))
53
+ base_schema.add(roles_schema_node(Theme.restricted_roles))
54
+ base_schema.add(interfaces_schema_node(Theme.interfaces))
55
+ base_schema.add(metadata_schema_node(Theme.metadatas, Theme))
56
56
  base_schema.add_unique_validator(Theme.name, Theme.id)
57
57
 
58
58
 
59
59
  @view_defaults(match_param="table=themes")
60
60
  class ThemeViews(TreeItemViews):
61
+ """The theme administration view."""
61
62
 
62
63
  _list_fields = (
63
64
  TreeItemViews._list_fields
@@ -69,8 +70,8 @@ class ThemeViews(TreeItemViews):
69
70
  "functionalities",
70
71
  renderer=lambda themes: ", ".join(
71
72
  [
72
- "{}={}".format(f.name, f.value)
73
- for f in sorted(themes.functionalities, key=lambda f: f.name)
73
+ f"{f.name}={f.value}"
74
+ for f in sorted(themes.functionalities, key=lambda f: cast(str, f.name))
74
75
  ]
75
76
  ),
76
77
  filter_column=concat(Functionality.name, "=", Functionality.value),
@@ -83,7 +84,7 @@ class ThemeViews(TreeItemViews):
83
84
  _list_field(
84
85
  "interfaces",
85
86
  renderer=lambda themes: ", ".join(
86
- [i.name or "" for i in sorted(themes.interfaces, key=lambda i: i.name)]
87
+ [i.name or "" for i in sorted(themes.interfaces, key=lambda i: cast(str, i.name))]
87
88
  ),
88
89
  filter_column=Interface.name,
89
90
  ),
@@ -95,13 +96,13 @@ class ThemeViews(TreeItemViews):
95
96
  _model = Theme
96
97
  _base_schema = base_schema
97
98
 
98
- def _base_query(self, query=None):
99
+ def _base_query(self, query: Optional[sqlalchemy.orm.query.Query] = None) -> sqlalchemy.orm.query.Query:
99
100
  return super()._base_query(
100
101
  self._request.dbsession.query(Theme)
101
102
  .distinct()
102
103
  .outerjoin("interfaces")
103
- .outerjoin("restricted_roles")
104
- .outerjoin("functionalities")
104
+ .outerjoin(Theme.restricted_roles)
105
+ .outerjoin(Theme.functionalities)
105
106
  .options(subqueryload("functionalities"))
106
107
  .options(subqueryload("restricted_roles"))
107
108
  .options(subqueryload("interfaces"))
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2017-2020, Camptocamp SA
1
+ # Copyright (c) 2017-2021, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -28,20 +26,22 @@
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
28
 
29
+ import colander
31
30
  from c2cgeoform.schema import GeoFormSchemaNode
32
31
  from c2cgeoform.views.abstract_views import AbstractViews
33
- import colander
34
32
  from deform import ValidationFailure
35
33
  from pyramid.httpexceptions import HTTPFound
36
34
  from pyramid.view import view_config
37
- from sqlalchemy.sql.expression import literal_column
38
35
 
39
36
  from c2cgeoportal_admin import _
40
- from c2cgeoportal_admin.widgets import ChildrenWidget, ThemeOrderWidget
41
- from c2cgeoportal_commons.models.main import Theme
37
+ from c2cgeoportal_admin.schemas.treegroup import treeitem_edit_url
38
+ from c2cgeoportal_admin.widgets import ChildrenWidget, ChildWidget
39
+ from c2cgeoportal_commons.models.main import Theme, TreeItem
42
40
 
43
41
 
44
- class ThemeOrderSchema(GeoFormSchemaNode): # pylint: disable=abstract-method
42
+ class ThemeOrderSchema(GeoFormSchemaNode): # type: ignore # pylint: disable=abstract-method
43
+ """The theme order schema."""
44
+
45
45
  def objectify(self, dict_, context=None):
46
46
  context = self.dbsession.query(Theme).get(dict_["id"])
47
47
  context = super().objectify(dict_, context)
@@ -49,30 +49,50 @@ class ThemeOrderSchema(GeoFormSchemaNode): # pylint: disable=abstract-method
49
49
 
50
50
 
51
51
  @colander.deferred
52
- def treeitems(node, kw): # pylint: disable=unused-argument
53
- return kw["dbsession"].query(Theme, literal_column("0")).order_by(Theme.ordering, Theme.name)
52
+ def themes(node, kw): # pylint: disable=unused-argument
53
+ """Get some theme metadata."""
54
+ query = kw["dbsession"].query(Theme).order_by(Theme.ordering, Theme.name)
55
+ return [
56
+ {"id": item.id, "label": item.name, "icon_class": f"icon-{item.item_type}", "group": "All"}
57
+ for item in query
58
+ ]
54
59
 
55
60
 
56
61
  def themes_validator(node, cstruct):
62
+ """Validate the theme."""
57
63
  for dict_ in cstruct:
58
- if not dict_["id"] in [item.id for item, dummy in node.treeitems]:
64
+ if not dict_["id"] in [item["id"] for item in node.candidates]:
59
65
  raise colander.Invalid(
60
66
  node,
61
- _("Value {} does not exist in table {}").format(dict_["treeitem_id"], Theme.__tablename__),
67
+ _("Value {} does not exist in table {}").format(dict_["id"], Theme.__tablename__),
62
68
  )
63
69
 
64
70
 
65
- class ThemesOrderingSchema(colander.MappingSchema):
71
+ class ThemesOrderingSchema(colander.MappingSchema): # type: ignore
72
+ """The theme ordering schema."""
73
+
66
74
  themes = colander.SequenceSchema(
67
- ThemeOrderSchema(Theme, includes=["id", "ordering"], name="theme", widget=ThemeOrderWidget()),
75
+ ThemeOrderSchema(
76
+ Theme,
77
+ includes=["id", "ordering"],
78
+ name="theme",
79
+ widget=ChildWidget(
80
+ input_name="id",
81
+ model=TreeItem,
82
+ label_field="name",
83
+ icon_class=lambda item: f"icon-{item.item_type}",
84
+ edit_url=treeitem_edit_url,
85
+ ),
86
+ ),
68
87
  name="themes",
69
- treeitems=treeitems,
88
+ candidates=themes,
70
89
  validator=themes_validator,
71
- widget=ChildrenWidget(add_subitem=False, category="structural"),
90
+ widget=ChildrenWidget(child_input_name="id", add_subitem=False, orderable=True),
72
91
  )
73
92
 
74
93
 
75
- class ThemesOrdering(AbstractViews):
94
+ class ThemesOrdering(AbstractViews): # type: ignore
95
+ """The theme ordering admin view."""
76
96
 
77
97
  _base_schema = ThemesOrderingSchema()
78
98
 
@@ -82,7 +102,7 @@ class ThemesOrdering(AbstractViews):
82
102
  dict_ = {
83
103
  "themes": [
84
104
  form.schema["themes"].children[0].dictify(theme)
85
- for theme, dummy in form.schema["themes"].treeitems
105
+ for theme in self._request.dbsession.query(Theme).order_by(Theme.ordering)
86
106
  ]
87
107
  }
88
108
  return {
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2017-2020, Camptocamp SA
1
+ # Copyright (c) 2017-2021, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -30,6 +28,7 @@
30
28
 
31
29
  from functools import partial
32
30
 
31
+ import sqlalchemy
33
32
  from c2cgeoform.views.abstract_views import AbstractViews, ListField
34
33
  from pyramid.view import view_config
35
34
  from sqlalchemy.orm import subqueryload
@@ -40,7 +39,9 @@ from c2cgeoportal_commons.models.main import LayergroupTreeitem, Metadata, TreeG
40
39
  _list_field = partial(ListField, TreeItem)
41
40
 
42
41
 
43
- class TreeItemViews(AbstractViews):
42
+ class TreeItemViews(AbstractViews): # type: ignore
43
+ """The admin tree item view."""
44
+
44
45
  _list_fields = [
45
46
  _list_field("id"),
46
47
  _list_field("name"),
@@ -50,9 +51,7 @@ class TreeItemViews(AbstractViews):
50
51
  _extra_list_fields_no_parents = [
51
52
  _list_field(
52
53
  "metadatas",
53
- renderer=lambda layers_group: ", ".join(
54
- ["{}: {}".format(m.name, m.value) or "" for m in layers_group.metadatas]
55
- ),
54
+ renderer=lambda treeitem: ", ".join([f"{m.name}: {m.value}" or "" for m in treeitem.metadatas]),
56
55
  filter_column=concat(Metadata.name, ": ", Metadata.value).label("metadata"),
57
56
  )
58
57
  ]
@@ -72,16 +71,18 @@ class TreeItemViews(AbstractViews):
72
71
  def save(self):
73
72
  response = super().save()
74
73
  # correctly handles the validation error as if there is a validation error, cstruct is empty
75
- has_to_be_registred_in_parent = (
74
+ has_to_be_registered_in_parent = (
76
75
  hasattr(self, "_appstruct") and self._appstruct is not None and self._appstruct.get("parent_id")
77
76
  )
78
- if has_to_be_registred_in_parent:
79
- parent = self._request.dbsession.query(TreeGroup).get(has_to_be_registred_in_parent)
77
+ if has_to_be_registered_in_parent:
78
+ parent = self._request.dbsession.query(TreeGroup).get(has_to_be_registered_in_parent)
80
79
  rel = LayergroupTreeitem(parent, self._obj, 100)
81
80
  self._request.dbsession.add(rel)
82
81
  return response
83
82
 
84
- def _base_query(self, query): # pylint: disable=arguments-differ
83
+ def _base_query( # pylint: disable=arguments-differ
84
+ self, query: sqlalchemy.orm.query.Query
85
+ ) -> sqlalchemy.orm.query.Query:
85
86
  return (
86
87
  query.outerjoin("metadatas")
87
88
  .options(subqueryload("parents_relation").joinedload("treegroup"))
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2017-2020, Camptocamp SA
1
+ # Copyright (c) 2017-2022, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -46,14 +44,16 @@ from c2cgeoportal_commons.models.static import User
46
44
  _list_field = partial(ListField, User)
47
45
 
48
46
  base_schema = GeoFormSchemaNode(User, widget=FormWidget(fields_template="user_fields"))
49
- base_schema.add(roles_schema_node("roles"))
47
+ base_schema.add(roles_schema_node(User.roles))
50
48
  base_schema.add_unique_validator(User.username, User.id)
51
49
 
52
50
  settings_role = aliased(Role)
53
51
 
54
52
 
55
53
  @view_defaults(match_param="table=users")
56
- class UserViews(AbstractViews):
54
+ class UserViews(AbstractViews): # type: ignore
55
+ """The admin user view."""
56
+
57
57
  _list_fields = [
58
58
  _list_field("id"),
59
59
  _list_field("username"),
@@ -119,6 +119,8 @@ class UserViews(AbstractViews):
119
119
  email=user.email,
120
120
  user=user.username,
121
121
  password=password,
122
+ application_url=self._request.route_url("base"),
123
+ current_url=self._request.current_route_url(),
122
124
  )
123
125
 
124
126
  return response