c2cgeoportal-admin 2.6.0__py3-none-any.whl → 2.8.1.180__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 (78) hide show
  1. c2cgeoportal_admin/__init__.py +32 -10
  2. c2cgeoportal_admin/lib/lingua_extractor.py +77 -0
  3. c2cgeoportal_admin/lib/ogcserver_synchronizer.py +168 -56
  4. c2cgeoportal_admin/py.typed +0 -0
  5. c2cgeoportal_admin/routes.py +7 -4
  6. c2cgeoportal_admin/schemas/dimensions.py +12 -10
  7. c2cgeoportal_admin/schemas/functionalities.py +62 -21
  8. c2cgeoportal_admin/schemas/interfaces.py +22 -18
  9. c2cgeoportal_admin/schemas/metadata.py +100 -47
  10. c2cgeoportal_admin/schemas/restriction_areas.py +21 -19
  11. c2cgeoportal_admin/schemas/roles.py +7 -5
  12. c2cgeoportal_admin/schemas/treegroup.py +38 -17
  13. c2cgeoportal_admin/schemas/treeitem.py +2 -3
  14. c2cgeoportal_admin/static/layertree.css +3 -4
  15. c2cgeoportal_admin/static/navbar.css +36 -35
  16. c2cgeoportal_admin/static/theme.css +16 -9
  17. c2cgeoportal_admin/subscribers.py +3 -3
  18. c2cgeoportal_admin/templates/404.jinja2 +18 -2
  19. c2cgeoportal_admin/templates/layertree.jinja2 +31 -9
  20. c2cgeoportal_admin/templates/navigation_navbar.jinja2 +33 -0
  21. c2cgeoportal_admin/templates/ogcserver_synchronize.jinja2 +12 -0
  22. c2cgeoportal_admin/templates/widgets/functionality_fields.pt +51 -0
  23. c2cgeoportal_admin/templates/widgets/metadata.pt +7 -1
  24. c2cgeoportal_admin/views/__init__.py +29 -0
  25. c2cgeoportal_admin/views/dimension_layers.py +7 -6
  26. c2cgeoportal_admin/views/functionalities.py +33 -6
  27. c2cgeoportal_admin/views/home.py +5 -5
  28. c2cgeoportal_admin/views/interfaces.py +7 -8
  29. c2cgeoportal_admin/views/layer_groups.py +9 -11
  30. c2cgeoportal_admin/views/layers.py +8 -7
  31. c2cgeoportal_admin/views/layers_vectortiles.py +30 -10
  32. c2cgeoportal_admin/views/layers_wms.py +41 -35
  33. c2cgeoportal_admin/views/layers_wmts.py +41 -35
  34. c2cgeoportal_admin/views/layertree.py +36 -28
  35. c2cgeoportal_admin/views/logged_views.py +80 -0
  36. c2cgeoportal_admin/views/logs.py +90 -0
  37. c2cgeoportal_admin/views/oauth2_clients.py +30 -22
  38. c2cgeoportal_admin/views/ogc_servers.py +119 -37
  39. c2cgeoportal_admin/views/restriction_areas.py +13 -11
  40. c2cgeoportal_admin/views/roles.py +16 -12
  41. c2cgeoportal_admin/views/themes.py +15 -14
  42. c2cgeoportal_admin/views/themes_ordering.py +13 -8
  43. c2cgeoportal_admin/views/treeitems.py +14 -12
  44. c2cgeoportal_admin/views/users.py +12 -7
  45. c2cgeoportal_admin/widgets.py +17 -14
  46. {c2cgeoportal_admin-2.6.0.dist-info → c2cgeoportal_admin-2.8.1.180.dist-info}/METADATA +17 -8
  47. c2cgeoportal_admin-2.8.1.180.dist-info/RECORD +95 -0
  48. {c2cgeoportal_admin-2.6.0.dist-info → c2cgeoportal_admin-2.8.1.180.dist-info}/WHEEL +1 -1
  49. c2cgeoportal_admin-2.8.1.180.dist-info/entry_points.txt +6 -0
  50. tests/__init__.py +11 -12
  51. tests/conftest.py +2 -1
  52. tests/test_edit_url.py +11 -14
  53. tests/test_functionalities.py +52 -14
  54. tests/test_home.py +0 -1
  55. tests/test_interface.py +34 -11
  56. tests/test_layer_groups.py +57 -27
  57. tests/test_layers_vectortiles.py +43 -20
  58. tests/test_layers_wms.py +67 -45
  59. tests/test_layers_wmts.py +47 -26
  60. tests/test_layertree.py +99 -16
  61. tests/test_left_menu.py +0 -1
  62. tests/test_lingua_extractor_config.py +64 -0
  63. tests/test_logs.py +103 -0
  64. tests/test_main.py +3 -1
  65. tests/test_metadatas.py +34 -21
  66. tests/test_oauth2_clients.py +37 -9
  67. tests/test_ogc_servers.py +84 -35
  68. tests/test_restriction_areas.py +38 -15
  69. tests/test_role.py +68 -41
  70. tests/test_themes.py +71 -37
  71. tests/test_themes_ordering.py +1 -2
  72. tests/test_treegroup.py +2 -2
  73. tests/test_user.py +48 -17
  74. tests/themes_ordering.py +1 -2
  75. c2cgeoportal_admin/templates/navigation_vertical.jinja2 +0 -33
  76. c2cgeoportal_admin-2.6.0.dist-info/RECORD +0 -89
  77. c2cgeoportal_admin-2.6.0.dist-info/entry_points.txt +0 -3
  78. {c2cgeoportal_admin-2.6.0.dist-info → c2cgeoportal_admin-2.8.1.180.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,90 @@
1
+ # Copyright (c) 2023-2023, Camptocamp SA
2
+ # All rights reserved.
3
+
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the FreeBSD Project.
27
+
28
+ from functools import partial
29
+
30
+ from c2cgeoform.views.abstract_views import AbstractViews, ItemAction, ListField
31
+ from pyramid.view import view_config, view_defaults
32
+
33
+ from c2cgeoportal_commons.models import _
34
+ from c2cgeoportal_commons.models.main import AbstractLog
35
+
36
+ _list_field = partial(ListField, AbstractLog)
37
+
38
+
39
+ @view_defaults(match_param="table=logs")
40
+ class LogViews(AbstractViews): # type: ignore
41
+ """The theme administration view."""
42
+
43
+ # We pass labels explicitly because actually we are not able to get info
44
+ # from InstrumentedAttribute created from AbstractConcreteBase.
45
+ _list_fields = [
46
+ _list_field("id"),
47
+ _list_field("date", label=_("Date")),
48
+ _list_field("username", label=_("Username")),
49
+ _list_field("action", label=_("Action"), renderer=lambda log: log.action.name),
50
+ _list_field("element_type", label=_("Element type")),
51
+ _list_field("element_id", label=_("Element identifier")),
52
+ _list_field("element_name", label=_("Element name")),
53
+ ]
54
+ _list_ordered_fields = [AbstractLog.date.desc()]
55
+
56
+ _id_field = "id"
57
+ _model = AbstractLog
58
+
59
+ @view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2")
60
+ def index(self):
61
+ return super().index()
62
+
63
+ @view_config(route_name="c2cgeoform_grid", renderer="fast_json")
64
+ def grid(self):
65
+ return super().grid()
66
+
67
+ def _grid_actions(self):
68
+ return []
69
+
70
+ def _grid_item_actions(self, item):
71
+ element_url = self._request.route_url(
72
+ "c2cgeoform_item",
73
+ table=item.element_url_table,
74
+ id=item.element_id,
75
+ )
76
+
77
+ return {
78
+ "dblclick": element_url,
79
+ "dropdown": [
80
+ ItemAction(
81
+ name="edit_element",
82
+ label=_("Edit element"),
83
+ icon="glyphicon glyphicon-pencil",
84
+ url=element_url,
85
+ ).to_dict(self._request)
86
+ ],
87
+ }
88
+
89
+ def _item_actions(self, item, readonly=False):
90
+ return []
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2021, Camptocamp SA
1
+ # Copyright (c) 2021-2023, 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,14 @@
29
27
 
30
28
 
31
29
  from functools import partial
30
+ from typing import Any, Dict
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 ListField
35
34
  from pyramid.view import view_config, view_defaults
36
35
 
37
- from c2cgeoportal_commons.models.static import OAuth2Client
36
+ from c2cgeoportal_admin.views.logged_views import LoggedViews
37
+ from c2cgeoportal_commons.models.static import Log, OAuth2Client
38
38
 
39
39
  _list_field = partial(ListField, OAuth2Client)
40
40
 
@@ -43,7 +43,9 @@ base_schema.add_unique_validator(OAuth2Client.client_id, OAuth2Client.id)
43
43
 
44
44
 
45
45
  @view_defaults(match_param="table=oauth2_clients")
46
- class OAuth2ClientViews(AbstractViews):
46
+ class OAuth2ClientViews(LoggedViews):
47
+ """The oAuth2 client administration view."""
48
+
47
49
  _list_fields = [
48
50
  _list_field("id"),
49
51
  _list_field("client_id"),
@@ -53,32 +55,38 @@ class OAuth2ClientViews(AbstractViews):
53
55
  _id_field = "id"
54
56
  _model = OAuth2Client
55
57
  _base_schema = base_schema
58
+ _log_model = Log
59
+ _name_field = "client_id"
56
60
 
57
61
  def _base_query(self):
58
62
  return self._request.dbsession.query(OAuth2Client)
59
63
 
60
- @view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2")
61
- def index(self):
62
- return super().index()
64
+ @view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2") # type: ignore
65
+ def index(self) -> Dict[str, Any]:
66
+ return super().index() # type: ignore
63
67
 
64
- @view_config(route_name="c2cgeoform_grid", renderer="fast_json")
65
- def grid(self):
66
- return super().grid()
68
+ @view_config(route_name="c2cgeoform_grid", renderer="fast_json") # type: ignore
69
+ def grid(self) -> Dict[str, Any]:
70
+ return super().grid() # type: ignore
67
71
 
68
- @view_config(route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2")
69
- def view(self):
70
- return super().edit()
72
+ @view_config( # type: ignore
73
+ route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2"
74
+ )
75
+ def view(self) -> Dict[str, Any]:
76
+ return super().edit() # type: ignore
71
77
 
72
- @view_config(route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2")
73
- def save(self):
78
+ @view_config( # type: ignore
79
+ route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2"
80
+ )
81
+ def save(self) -> Dict[str, Any]:
74
82
  return super().save()
75
83
 
76
- @view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json")
77
- def delete(self):
84
+ @view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json") # type: ignore
85
+ def delete(self) -> Dict[str, Any]:
78
86
  return super().delete()
79
87
 
80
- @view_config(
88
+ @view_config( # type: ignore
81
89
  route_name="c2cgeoform_item_duplicate", request_method="GET", renderer="../templates/edit.jinja2"
82
90
  )
83
- def duplicate(self):
84
- return super().duplicate()
91
+ def duplicate(self) -> Dict[str, Any]:
92
+ return super().duplicate() # type: ignore
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2017-2021, Camptocamp SA
1
+ # Copyright (c) 2017-2023, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -28,27 +26,38 @@
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
28
 
29
+ import logging
30
+ import threading
31
31
  from functools import partial
32
+ from typing import Any, Dict, List, Union, cast
32
33
 
34
+ import requests
33
35
  from c2cgeoform.schema import GeoFormSchemaNode
34
36
  from c2cgeoform.views.abstract_views import AbstractViews, ItemAction, ListField, UserMessage
35
37
  from deform.widget import FormWidget
36
- from pyramid.httpexceptions import HTTPNotFound
38
+ from pyramid.httpexceptions import HTTPFound
37
39
  from pyramid.view import view_config, view_defaults
38
40
  from sqlalchemy import inspect
39
41
 
40
42
  from c2cgeoportal_admin import _
41
43
  from c2cgeoportal_admin.lib.ogcserver_synchronizer import OGCServerSynchronizer
42
- from c2cgeoportal_commons.models.main import OGCServer
44
+ from c2cgeoportal_admin.views.logged_views import LoggedViews
45
+ from c2cgeoportal_commons.lib.literal import Literal
46
+ from c2cgeoportal_commons.models import cache_invalidate_cb
47
+ from c2cgeoportal_commons.models.main import LogAction, OGCServer
43
48
 
44
49
  _list_field = partial(ListField, OGCServer)
45
50
 
46
51
  base_schema = GeoFormSchemaNode(OGCServer, widget=FormWidget(fields_template="ogcserver_fields"))
47
52
  base_schema.add_unique_validator(OGCServer.name, OGCServer.id)
48
53
 
54
+ LOG = logging.getLogger(__name__)
55
+
49
56
 
50
57
  @view_defaults(match_param="table=ogc_servers")
51
- class OGCServerViews(AbstractViews):
58
+ class OGCServerViews(LoggedViews):
59
+ """The OGC server administration view."""
60
+
52
61
  _list_fields = [
53
62
  _list_field("id"),
54
63
  _list_field("name"),
@@ -73,28 +82,51 @@ class OGCServerViews(AbstractViews):
73
82
  ),
74
83
  }
75
84
 
76
- @view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2")
77
- def index(self):
78
- return super().index()
85
+ @view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2") # type: ignore
86
+ def index(self) -> Dict[str, Any]:
87
+ return super().index() # type: ignore
79
88
 
80
- @view_config(route_name="c2cgeoform_grid", renderer="fast_json")
81
- def grid(self):
82
- return super().grid()
89
+ @view_config(route_name="c2cgeoform_grid", renderer="fast_json") # type: ignore
90
+ def grid(self) -> Dict[str, Any]:
91
+ return super().grid() # type: ignore
83
92
 
84
- def schema(self):
85
- try:
86
- obj = self._get_object()
87
- except HTTPNotFound:
88
- obj = None
93
+ def schema(self) -> GeoFormSchemaNode:
94
+ obj = self._get_object()
89
95
 
90
96
  schema = self._base_schema.clone()
91
- schema["url"].description = obj.url_description(self._request)
92
- schema["url_wfs"].description = obj.url_wfs_description(self._request)
97
+ schema["url"].description = Literal(
98
+ _("{}<br>Current runtime value is: {}").format(
99
+ schema["url"].description,
100
+ obj.url_description(self._request),
101
+ )
102
+ )
103
+ schema["url_wfs"].description = Literal(
104
+ _("{}<br>Current runtime value is: {}").format(
105
+ schema["url_wfs"].description,
106
+ obj.url_wfs_description(self._request),
107
+ )
108
+ )
93
109
  return schema
94
110
 
95
- def _item_actions(self, item, readonly=False):
96
- actions = super()._item_actions(item, readonly)
111
+ def _item_actions(self, item: OGCServer, readonly: bool = False) -> List[Any]:
112
+ actions = cast(List[Any], super()._item_actions(item, readonly))
97
113
  if inspect(item).persistent:
114
+ actions.insert(
115
+ next((i for i, v in enumerate(actions) if v.name() == "delete")),
116
+ ItemAction(
117
+ name="clear-cache",
118
+ label=_("Clear the cache"),
119
+ icon="glyphicon glyphicon-hdd",
120
+ url=self._request.route_url(
121
+ "ogc_server_clear_cache",
122
+ id=getattr(item, self._id_field),
123
+ _query={
124
+ "came_from": self._request.current_route_url(),
125
+ },
126
+ ),
127
+ confirmation=_("The current changes will be lost."),
128
+ ),
129
+ )
98
130
  actions.insert(
99
131
  next((i for i, v in enumerate(actions) if v.name() == "delete")),
100
132
  ItemAction(
@@ -106,16 +138,23 @@ class OGCServerViews(AbstractViews):
106
138
  )
107
139
  return actions
108
140
 
109
- @view_config(route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2")
110
- def view(self):
111
- return super().edit(self.schema())
112
-
113
- @view_config(route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2")
114
- def save(self):
115
- return super().save()
141
+ @view_config( # type: ignore
142
+ route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2"
143
+ )
144
+ def view(self) -> Dict[str, Any]:
145
+ return super().edit(self.schema()) # type: ignore
116
146
 
117
- @view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json")
118
- def delete(self):
147
+ @view_config( # type: ignore
148
+ route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2"
149
+ )
150
+ def save(self) -> Union[HTTPFound, Dict[str, Any]]:
151
+ result: Union[HTTPFound, Dict[str, Any]] = super().save()
152
+ if isinstance(result, HTTPFound):
153
+ self._update_cache(self._obj)
154
+ return result
155
+
156
+ @view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json") # type: ignore
157
+ def delete(self) -> Dict[str, Any]:
119
158
  obj = self._get_object()
120
159
  if len(obj.layers) > 0:
121
160
  return {
@@ -127,16 +166,20 @@ class OGCServerViews(AbstractViews):
127
166
  _query=[("msg_col", "cannot_delete")],
128
167
  ),
129
168
  }
130
- return super().delete()
169
+ result: Dict[str, Any] = super().delete()
170
+ cache_invalidate_cb()
171
+ return result
131
172
 
132
- @view_config(
173
+ @view_config( # type: ignore
133
174
  route_name="c2cgeoform_item_duplicate", request_method="GET", renderer="../templates/edit.jinja2"
134
175
  )
135
- def duplicate(self):
136
- return super().duplicate()
176
+ def duplicate(self) -> Dict[str, Any]:
177
+ return super().duplicate() # type: ignore
137
178
 
138
- @view_config(route_name="ogcserver_synchronize", renderer="../templates/ogcserver_synchronize.jinja2")
139
- def synchronize(self):
179
+ @view_config( # type: ignore
180
+ route_name="ogcserver_synchronize", renderer="../templates/ogcserver_synchronize.jinja2"
181
+ )
182
+ def synchronize(self) -> Dict[str, Any]:
140
183
  obj = self._get_object()
141
184
 
142
185
  if self._request.method == "GET":
@@ -147,17 +190,56 @@ class OGCServerViews(AbstractViews):
147
190
  }
148
191
 
149
192
  if self._request.method == "POST":
150
- synchronizer = OGCServerSynchronizer(self._request, obj)
193
+ force_parents = self._request.POST.get("force-parents", "false") == "on"
194
+ force_ordering = self._request.POST.get("force-ordering", "false") == "on"
195
+ clean = self._request.POST.get("clean", "false") == "on"
196
+
197
+ synchronizer = OGCServerSynchronizer(
198
+ self._request,
199
+ obj,
200
+ force_parents=force_parents,
201
+ force_ordering=force_ordering,
202
+ clean=clean,
203
+ )
204
+
151
205
  if "check" in self._request.params:
152
206
  synchronizer.check_layers()
207
+
153
208
  elif "dry-run" in self._request.params:
154
209
  synchronizer.synchronize(dry_run=True)
210
+
155
211
  elif "synchronize" in self._request.params:
156
212
  synchronizer.synchronize()
213
+
214
+ self._create_log(LogAction.SYNCHRONIZE, obj)
215
+
157
216
  return {
158
217
  "ogcserver": obj,
159
218
  "success": True,
160
219
  "report": synchronizer.report(),
161
220
  }
162
221
 
222
+ self._update_cache(obj)
223
+
163
224
  return {}
225
+
226
+ def _update_cache(self, ogc_server: OGCServer) -> None:
227
+ try:
228
+ ogc_server_id = ogc_server.id
229
+
230
+ def update_cache() -> None:
231
+ response = requests.get(
232
+ self._request.route_url(
233
+ "ogc_server_clear_cache",
234
+ id=ogc_server_id,
235
+ _query={
236
+ "came_from": self._request.current_route_url(),
237
+ },
238
+ )
239
+ )
240
+ if not response.ok:
241
+ LOG.error("Error while cleaning the OGC server cache:\n%s", response.text)
242
+
243
+ threading.Thread(target=update_cache).start()
244
+ except Exception:
245
+ LOG.error("Error on cleaning the OGC server cache", exc_info=True)
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2017-2020, Camptocamp SA
1
+ # Copyright (c) 2017-2023, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -32,32 +30,33 @@ from functools import partial
32
30
 
33
31
  import colander
34
32
  from c2cgeoform.schema import GeoFormManyToManySchemaNode, GeoFormSchemaNode
35
- from c2cgeoform.views.abstract_views import AbstractViews, ListField
33
+ from c2cgeoform.views.abstract_views import ListField
36
34
  from deform.widget import FormWidget
37
35
  from pyramid.view import view_config, view_defaults
38
36
  from sqlalchemy.orm import subqueryload
39
37
 
40
- from c2cgeoportal_admin import _
41
38
  from c2cgeoportal_admin.schemas.roles import roles_schema_node
42
39
  from c2cgeoportal_admin.schemas.treegroup import treeitem_edit_url
40
+ from c2cgeoportal_admin.views.logged_views import LoggedViews
43
41
  from c2cgeoportal_admin.widgets import ChildrenWidget, ChildWidget
44
42
  from c2cgeoportal_commons.models.main import Layer, RestrictionArea
45
43
 
46
44
  _list_field = partial(ListField, RestrictionArea)
47
45
 
48
46
  base_schema = GeoFormSchemaNode(RestrictionArea, widget=FormWidget(fields_template="restriction_area_fields"))
49
- base_schema.add_before("area", roles_schema_node("roles"))
47
+ base_schema.add_before("area", roles_schema_node(RestrictionArea.roles))
50
48
  base_schema.add_unique_validator(RestrictionArea.name, RestrictionArea.id)
51
49
 
52
50
 
53
51
  def layers(node, kw): # pylint: disable=unused-argument
52
+ """Get the layers serializable representation."""
54
53
  dbsession = kw["request"].dbsession
55
54
  query = dbsession.query(Layer).order_by(Layer.name)
56
55
  return [
57
56
  {
58
57
  "id": layer.id,
59
58
  "label": layer.name,
60
- "icon_class": "icon-{}".format(layer.item_type),
59
+ "icon_class": f"icon-{layer.item_type}",
61
60
  "edit_url": treeitem_edit_url(kw["request"], layer),
62
61
  "group": "All",
63
62
  }
@@ -75,12 +74,13 @@ base_schema.add(
75
74
  input_name="id",
76
75
  model=Layer,
77
76
  label_field="name",
78
- icon_class=lambda layer: "icon-{}".format(layer.item_type),
77
+ icon_class=lambda layer: f"icon-{layer.item_type}",
79
78
  edit_url=treeitem_edit_url,
80
79
  ),
81
80
  ),
82
81
  name="layers",
83
- title=_("Layers"),
82
+ title=RestrictionArea.layers.info["colanderalchemy"]["title"],
83
+ description=RestrictionArea.layers.info["colanderalchemy"]["description"],
84
84
  candidates=colander.deferred(layers),
85
85
  widget=ChildrenWidget(child_input_name="id", orderable=False),
86
86
  )
@@ -88,7 +88,9 @@ base_schema.add(
88
88
 
89
89
 
90
90
  @view_defaults(match_param="table=restriction_areas")
91
- class RestrictionAreaViews(AbstractViews):
91
+ class RestrictionAreaViews(LoggedViews):
92
+ """The restriction area administration view."""
93
+
92
94
  _list_fields = [
93
95
  _list_field("id"),
94
96
  _list_field("name"),
@@ -100,7 +102,7 @@ class RestrictionAreaViews(AbstractViews):
100
102
  _list_field(
101
103
  "layers",
102
104
  renderer=lambda restriction_area: ", ".join(
103
- "{}-{}".format(layer.item_type, layer.name) or "" for layer in restriction_area.layers
105
+ f"{layer.item_type}-{layer.name}" or "" for layer in restriction_area.layers
104
106
  ),
105
107
  ),
106
108
  ]
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2017-2020, Camptocamp SA
1
+ # Copyright (c) 2017-2023, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -32,14 +30,14 @@ from functools import partial
32
30
 
33
31
  import colander
34
32
  from c2cgeoform.schema import GeoFormManyToManySchemaNode, GeoFormSchemaNode
35
- from c2cgeoform.views.abstract_views import AbstractViews, ListField
33
+ from c2cgeoform.views.abstract_views import ListField
36
34
  from deform.widget import FormWidget
37
35
  from pyramid.view import view_config, view_defaults
38
36
  from sqlalchemy.orm import subqueryload
39
37
 
40
- from c2cgeoportal_admin import _
41
38
  from c2cgeoportal_admin.schemas.functionalities import functionalities_schema_node
42
39
  from c2cgeoportal_admin.schemas.restriction_areas import restrictionareas_schema_node
40
+ from c2cgeoportal_admin.views.logged_views import LoggedViews
43
41
  from c2cgeoportal_admin.widgets import ChildrenWidget, ChildWidget
44
42
  from c2cgeoportal_commons.models.main import Role
45
43
  from c2cgeoportal_commons.models.static import User
@@ -47,12 +45,13 @@ from c2cgeoportal_commons.models.static import User
47
45
  _list_field = partial(ListField, Role)
48
46
 
49
47
  base_schema = GeoFormSchemaNode(Role, widget=FormWidget(fields_template="role_fields"))
50
- base_schema.add_before("extent", functionalities_schema_node.clone())
51
- base_schema.add_before("extent", restrictionareas_schema_node.clone())
48
+ base_schema.add_before("extent", functionalities_schema_node(Role.functionalities, Role))
49
+ base_schema.add_before("extent", restrictionareas_schema_node(Role.restrictionareas))
52
50
  base_schema.add_unique_validator(Role.name, Role.id)
53
51
 
54
52
 
55
53
  def users(node, kw): # pylint: disable=unused-argument
54
+ """Get the user serializable metadata."""
56
55
  dbsession = kw["request"].dbsession
57
56
  query = dbsession.query(User).order_by(User.username)
58
57
  return [
@@ -75,7 +74,7 @@ base_schema.add(
75
74
  colander.SequenceSchema(
76
75
  GeoFormManyToManySchemaNode(
77
76
  User,
78
- name="layer",
77
+ name="user",
79
78
  includes=["id"],
80
79
  widget=ChildWidget(
81
80
  input_name="id",
@@ -89,23 +88,28 @@ base_schema.add(
89
88
  ),
90
89
  ),
91
90
  ),
92
- name="users",
93
- title=_("Users"),
91
+ name=Role.users.key,
92
+ title=Role.users.info["colanderalchemy"]["title"],
93
+ description=Role.users.info["colanderalchemy"]["description"],
94
94
  candidates=colander.deferred(users),
95
95
  widget=ChildrenWidget(child_input_name="id", orderable=False, category="structural"),
96
96
  )
97
97
  )
98
+ # Not possible to overwrite this in constructor.
99
+ base_schema["users"].children[0].description = ""
98
100
 
99
101
 
100
102
  @view_defaults(match_param="table=roles")
101
- class RoleViews(AbstractViews):
103
+ class RoleViews(LoggedViews):
104
+ """The roles administration view."""
105
+
102
106
  _list_fields = [
103
107
  _list_field("id"),
104
108
  _list_field("name"),
105
109
  _list_field("description"),
106
110
  _list_field(
107
111
  "functionalities",
108
- renderer=lambda role: ", ".join(["{}={}".format(f.name, f.value) for f in role.functionalities]),
112
+ renderer=lambda role: ", ".join([f"{f.name}={f.value}" for f in role.functionalities]),
109
113
  ),
110
114
  _list_field(
111
115
  "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-2023, 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"))