c2cgeoportal-admin 2.5.0.100__py3-none-any.whl → 2.9rc44__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 (99) hide show
  1. c2cgeoportal_admin/__init__.py +44 -14
  2. c2cgeoportal_admin/lib/__init__.py +0 -0
  3. c2cgeoportal_admin/lib/lingva_extractor.py +77 -0
  4. c2cgeoportal_admin/lib/ogcserver_synchronizer.py +410 -0
  5. c2cgeoportal_admin/py.typed +0 -0
  6. c2cgeoportal_admin/routes.py +30 -11
  7. c2cgeoportal_admin/schemas/dimensions.py +17 -11
  8. c2cgeoportal_admin/schemas/functionalities.py +60 -22
  9. c2cgeoportal_admin/schemas/interfaces.py +27 -19
  10. c2cgeoportal_admin/schemas/metadata.py +122 -48
  11. c2cgeoportal_admin/schemas/restriction_areas.py +26 -20
  12. c2cgeoportal_admin/schemas/roles.py +13 -7
  13. c2cgeoportal_admin/schemas/treegroup.py +90 -20
  14. c2cgeoportal_admin/schemas/treeitem.py +3 -4
  15. c2cgeoportal_admin/static/layertree.css +26 -4
  16. c2cgeoportal_admin/static/navbar.css +59 -36
  17. c2cgeoportal_admin/static/theme.css +51 -11
  18. c2cgeoportal_admin/subscribers.py +3 -3
  19. c2cgeoportal_admin/templates/404.jinja2 +41 -2
  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/__init__.py +29 -0
  43. c2cgeoportal_admin/views/dimension_layers.py +14 -9
  44. c2cgeoportal_admin/views/functionalities.py +52 -18
  45. c2cgeoportal_admin/views/home.py +5 -5
  46. c2cgeoportal_admin/views/interfaces.py +29 -21
  47. c2cgeoportal_admin/views/layer_groups.py +36 -25
  48. c2cgeoportal_admin/views/layers.py +17 -13
  49. c2cgeoportal_admin/views/layers_cog.py +135 -0
  50. c2cgeoportal_admin/views/layers_vectortiles.py +62 -27
  51. c2cgeoportal_admin/views/layers_wms.py +61 -36
  52. c2cgeoportal_admin/views/layers_wmts.py +54 -32
  53. c2cgeoportal_admin/views/layertree.py +37 -28
  54. c2cgeoportal_admin/views/logged_views.py +83 -0
  55. c2cgeoportal_admin/views/logs.py +91 -0
  56. c2cgeoportal_admin/views/oauth2_clients.py +96 -0
  57. c2cgeoportal_admin/views/ogc_servers.py +192 -21
  58. c2cgeoportal_admin/views/restriction_areas.py +78 -25
  59. c2cgeoportal_admin/views/roles.py +88 -25
  60. c2cgeoportal_admin/views/themes.py +47 -35
  61. c2cgeoportal_admin/views/themes_ordering.py +44 -24
  62. c2cgeoportal_admin/views/treeitems.py +21 -17
  63. c2cgeoportal_admin/views/users.py +46 -26
  64. c2cgeoportal_admin/widgets.py +79 -28
  65. {c2cgeoportal_admin-2.5.0.100.dist-info → c2cgeoportal_admin-2.9rc44.dist-info}/METADATA +15 -13
  66. c2cgeoportal_admin-2.9rc44.dist-info/RECORD +97 -0
  67. {c2cgeoportal_admin-2.5.0.100.dist-info → c2cgeoportal_admin-2.9rc44.dist-info}/WHEEL +1 -1
  68. c2cgeoportal_admin-2.9rc44.dist-info/entry_points.txt +5 -0
  69. tests/__init__.py +36 -27
  70. tests/conftest.py +23 -24
  71. tests/test_edit_url.py +16 -19
  72. tests/test_functionalities.py +52 -14
  73. tests/test_home.py +0 -1
  74. tests/test_interface.py +35 -12
  75. tests/test_layer_groups.py +58 -32
  76. tests/test_layers_cog.py +243 -0
  77. tests/test_layers_vectortiles.py +46 -30
  78. tests/test_layers_wms.py +77 -82
  79. tests/test_layers_wmts.py +51 -30
  80. tests/test_layertree.py +107 -101
  81. tests/test_learn.py +1 -1
  82. tests/test_left_menu.py +0 -1
  83. tests/test_lingva_extractor_config.py +64 -0
  84. tests/test_logs.py +102 -0
  85. tests/test_main.py +4 -2
  86. tests/test_metadatas.py +79 -71
  87. tests/test_oauth2_clients.py +186 -0
  88. tests/test_ogc_servers.py +110 -28
  89. tests/test_restriction_areas.py +109 -20
  90. tests/test_role.py +142 -82
  91. tests/test_themes.py +75 -41
  92. tests/test_themes_ordering.py +1 -2
  93. tests/test_treegroup.py +2 -2
  94. tests/test_user.py +72 -70
  95. tests/themes_ordering.py +1 -2
  96. c2cgeoportal_admin/templates/navigation_vertical.jinja2 +0 -10
  97. c2cgeoportal_admin-2.5.0.100.dist-info/RECORD +0 -84
  98. c2cgeoportal_admin-2.5.0.100.dist-info/entry_points.txt +0 -3
  99. {c2cgeoportal_admin-2.5.0.100.dist-info → c2cgeoportal_admin-2.9rc44.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-2024, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -30,8 +28,18 @@
30
28
 
31
29
  from functools import partial
32
30
 
31
+ import sqlalchemy
32
+ from c2cgeoform import JSONDict
33
33
  from c2cgeoform.schema import GeoFormSchemaNode
34
- from c2cgeoform.views.abstract_views import ItemAction, ListField
34
+ from c2cgeoform.views.abstract_views import (
35
+ DeleteResponse,
36
+ GridResponse,
37
+ IndexResponse,
38
+ ItemAction,
39
+ ListField,
40
+ ObjectResponse,
41
+ SaveResponse,
42
+ )
35
43
  from deform.widget import FormWidget
36
44
  from pyramid.view import view_config, view_defaults
37
45
  from sqlalchemy import delete, insert, inspect, update
@@ -40,60 +48,71 @@ from zope.sqlalchemy import mark_changed
40
48
  from c2cgeoportal_admin import _
41
49
  from c2cgeoportal_admin.schemas.dimensions import dimensions_schema_node
42
50
  from c2cgeoportal_admin.schemas.interfaces import interfaces_schema_node
43
- from c2cgeoportal_admin.schemas.metadata import metadatas_schema_node
51
+ from c2cgeoportal_admin.schemas.metadata import metadata_schema_node
44
52
  from c2cgeoportal_admin.schemas.restriction_areas import restrictionareas_schema_node
45
53
  from c2cgeoportal_admin.schemas.treeitem import parent_id_node
46
54
  from c2cgeoportal_admin.views.dimension_layers import DimensionLayerViews
47
- from c2cgeoportal_commons.models.main import LayerGroup, LayerWMS, LayerWMTS, OGCServer, TreeItem
55
+ from c2cgeoportal_commons.models.main import LayerGroup, LayerWMS, LayerWMTS, LogAction, OGCServer, TreeItem
48
56
 
49
57
  _list_field = partial(ListField, LayerWMS)
50
58
 
51
59
  base_schema = GeoFormSchemaNode(LayerWMS, widget=FormWidget(fields_template="layer_fields"))
52
- base_schema.add(dimensions_schema_node.clone())
53
- base_schema.add(metadatas_schema_node.clone())
54
- base_schema.add(interfaces_schema_node.clone())
55
- base_schema.add(restrictionareas_schema_node.clone())
60
+ base_schema.add(dimensions_schema_node(LayerWMS.dimensions))
61
+ base_schema.add(metadata_schema_node(LayerWMS.metadatas, LayerWMS))
62
+ base_schema.add(interfaces_schema_node(LayerWMS.interfaces))
63
+ base_schema.add(restrictionareas_schema_node(LayerWMS.restrictionareas))
56
64
  base_schema.add_unique_validator(LayerWMS.name, LayerWMS.id)
57
65
  base_schema.add(parent_id_node(LayerGroup))
58
66
 
59
67
 
60
68
  @view_defaults(match_param="table=layers_wms")
61
- class LayerWmsViews(DimensionLayerViews):
69
+ class LayerWmsViews(DimensionLayerViews[LayerWMS]):
70
+ """The WMS layer administration view."""
71
+
62
72
  _list_fields = (
63
- DimensionLayerViews._list_fields
73
+ DimensionLayerViews._list_fields # pylint: disable=protected-access
64
74
  + [
65
- _list_field("layer"),
66
- _list_field("style"),
67
- _list_field("time_mode"),
68
- _list_field("time_widget"),
69
75
  _list_field(
70
76
  "ogc_server",
71
77
  renderer=lambda layer_wms: layer_wms.ogc_server.name,
72
78
  sort_column=OGCServer.name,
73
79
  filter_column=OGCServer.name,
74
80
  ),
81
+ _list_field("layer"),
82
+ _list_field("style"),
83
+ _list_field("valid", label="Valid"),
84
+ _list_field("invalid_reason", visible=False),
85
+ _list_field("time_mode"),
86
+ _list_field("time_widget"),
75
87
  ]
76
- + DimensionLayerViews._extra_list_fields
88
+ + DimensionLayerViews._extra_list_fields # pylint: disable=protected-access
77
89
  )
78
90
  _id_field = "id"
79
91
  _model = LayerWMS
80
92
  _base_schema = base_schema
81
93
 
82
- def _base_query(self, query=None):
94
+ def _base_query(self) -> sqlalchemy.orm.query.Query[LayerWMS]:
95
+ return super()._sub_query(
96
+ self._request.dbsession.query(LayerWMS, OGCServer.name).distinct().outerjoin(LayerWMS.ogc_server)
97
+ )
98
+
99
+ def _sub_query(
100
+ self, query: sqlalchemy.orm.query.Query[LayerWMS] | None
101
+ ) -> sqlalchemy.orm.query.Query[LayerWMS]:
83
102
  del query
84
- return super()._base_query(self._request.dbsession.query(LayerWMS).distinct().outerjoin("ogc_server"))
103
+ return self._base_query()
85
104
 
86
- @view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2")
87
- def index(self):
105
+ @view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2") # type: ignore[misc]
106
+ def index(self) -> IndexResponse:
88
107
  return super().index()
89
108
 
90
- @view_config(route_name="c2cgeoform_grid", renderer="fast_json")
91
- def grid(self):
109
+ @view_config(route_name="c2cgeoform_grid", renderer="fast_json") # type: ignore[misc]
110
+ def grid(self) -> GridResponse:
92
111
  return super().grid()
93
112
 
94
- def _item_actions(self, item, readonly=False):
95
- actions = super()._item_actions(item, readonly)
96
- if inspect(item).persistent:
113
+ def _item_actions(self, item: LayerWMS, readonly: bool = False) -> list[ItemAction]:
114
+ actions: list[ItemAction] = super()._item_actions(item, readonly)
115
+ if inspect(item).persistent: # type: ignore[attr-defined]
97
116
  actions.insert(
98
117
  next((i for i, v in enumerate(actions) if v.name() == "delete")),
99
118
  ItemAction(
@@ -107,8 +126,10 @@ class LayerWmsViews(DimensionLayerViews):
107
126
  )
108
127
  return actions
109
128
 
110
- @view_config(route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2")
111
- def view(self):
129
+ @view_config( # type: ignore[misc]
130
+ route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2"
131
+ )
132
+ def view(self) -> ObjectResponse:
112
133
  if self._is_new():
113
134
  dbsession = self._request.dbsession
114
135
  default_wms = LayerWMS.get_default(dbsession)
@@ -116,22 +137,24 @@ class LayerWmsViews(DimensionLayerViews):
116
137
  return self.copy(default_wms, excludes=["name", "layer"])
117
138
  return super().edit()
118
139
 
119
- @view_config(route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2")
120
- def save(self):
140
+ @view_config( # type: ignore[misc]
141
+ route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2"
142
+ )
143
+ def save(self) -> SaveResponse:
121
144
  return super().save()
122
145
 
123
- @view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json")
124
- def delete(self):
146
+ @view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json") # type: ignore[misc]
147
+ def delete(self) -> DeleteResponse:
125
148
  return super().delete()
126
149
 
127
- @view_config(
150
+ @view_config( # type: ignore[misc]
128
151
  route_name="c2cgeoform_item_duplicate", request_method="GET", renderer="../templates/edit.jinja2"
129
152
  )
130
- def duplicate(self):
153
+ def duplicate(self) -> ObjectResponse:
131
154
  return super().duplicate()
132
155
 
133
- @view_config(route_name="convert_to_wmts", request_method="POST", renderer="fast_json")
134
- def convert_to_wmts(self):
156
+ @view_config(route_name="convert_to_wmts", request_method="POST", renderer="fast_json") # type: ignore[misc]
157
+ def convert_to_wmts(self) -> JSONDict:
135
158
  src = self._get_object()
136
159
  dbsession = self._request.dbsession
137
160
  default_wmts = LayerWMTS.get_default(dbsession)
@@ -164,6 +187,8 @@ class LayerWmsViews(DimensionLayerViews):
164
187
  dbsession.flush()
165
188
  mark_changed(dbsession)
166
189
 
190
+ self._create_log(LogAction.CONVERT_TO_WMTS, src, element_url_table="layers_wmts")
191
+
167
192
  return {
168
193
  "success": True,
169
194
  "redirect": self._request.route_url(
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2017-2020, Camptocamp SA
1
+ # Copyright (c) 2017-2024, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -30,8 +28,18 @@
30
28
 
31
29
  from functools import partial
32
30
 
31
+ import sqlalchemy
32
+ from c2cgeoform import JSONDict
33
33
  from c2cgeoform.schema import GeoFormSchemaNode
34
- from c2cgeoform.views.abstract_views import ItemAction, ListField
34
+ from c2cgeoform.views.abstract_views import (
35
+ DeleteResponse,
36
+ GridResponse,
37
+ IndexResponse,
38
+ ItemAction,
39
+ ListField,
40
+ ObjectResponse,
41
+ SaveResponse,
42
+ )
35
43
  from deform.widget import FormWidget
36
44
  from pyramid.view import view_config, view_defaults
37
45
  from sqlalchemy import delete, insert, inspect, update
@@ -40,27 +48,29 @@ from zope.sqlalchemy import mark_changed
40
48
  from c2cgeoportal_admin import _
41
49
  from c2cgeoportal_admin.schemas.dimensions import dimensions_schema_node
42
50
  from c2cgeoportal_admin.schemas.interfaces import interfaces_schema_node
43
- from c2cgeoportal_admin.schemas.metadata import metadatas_schema_node
51
+ from c2cgeoportal_admin.schemas.metadata import metadata_schema_node
44
52
  from c2cgeoportal_admin.schemas.restriction_areas import restrictionareas_schema_node
45
53
  from c2cgeoportal_admin.schemas.treeitem import parent_id_node
46
54
  from c2cgeoportal_admin.views.dimension_layers import DimensionLayerViews
47
- from c2cgeoportal_commons.models.main import LayerGroup, LayerWMS, LayerWMTS, OGCServer, TreeItem
55
+ from c2cgeoportal_commons.models.main import LayerGroup, LayerWMS, LayerWMTS, LogAction, OGCServer, TreeItem
48
56
 
49
57
  _list_field = partial(ListField, LayerWMTS)
50
58
 
51
59
  base_schema = GeoFormSchemaNode(LayerWMTS, widget=FormWidget(fields_template="layer_fields"))
52
- base_schema.add(dimensions_schema_node.clone())
53
- base_schema.add(metadatas_schema_node.clone())
54
- base_schema.add(interfaces_schema_node.clone())
55
- base_schema.add(restrictionareas_schema_node.clone())
60
+ base_schema.add(dimensions_schema_node(LayerWMTS.dimensions))
61
+ base_schema.add(metadata_schema_node(LayerWMTS.metadatas, LayerWMTS))
62
+ base_schema.add(interfaces_schema_node(LayerWMTS.interfaces))
63
+ base_schema.add(restrictionareas_schema_node(LayerWMTS.restrictionareas))
56
64
  base_schema.add_unique_validator(LayerWMTS.name, LayerWMTS.id)
57
65
  base_schema.add(parent_id_node(LayerGroup))
58
66
 
59
67
 
60
68
  @view_defaults(match_param="table=layers_wmts")
61
- class LayerWmtsViews(DimensionLayerViews):
69
+ class LayerWmtsViews(DimensionLayerViews[LayerWMTS]):
70
+ """The WMTS layer administration view."""
71
+
62
72
  _list_fields = (
63
- DimensionLayerViews._list_fields
73
+ DimensionLayerViews._list_fields # pylint: disable=protected-access
64
74
  + [
65
75
  _list_field("url"),
66
76
  _list_field("layer"),
@@ -68,26 +78,32 @@ class LayerWmtsViews(DimensionLayerViews):
68
78
  _list_field("matrix_set"),
69
79
  _list_field("image_type"),
70
80
  ]
71
- + DimensionLayerViews._extra_list_fields
81
+ + DimensionLayerViews._extra_list_fields # pylint: disable=protected-access
72
82
  )
73
83
  _id_field = "id"
74
84
  _model = LayerWMTS
75
85
  _base_schema = base_schema
76
86
 
77
- def _base_query(self, query=None):
78
- return super()._base_query(self._request.dbsession.query(LayerWMTS).distinct())
87
+ def _base_query(self) -> sqlalchemy.orm.query.Query[LayerWMTS]:
88
+ return super()._sub_query(self._request.dbsession.query(LayerWMTS).distinct())
89
+
90
+ def _sub_query(
91
+ self, query: sqlalchemy.orm.query.Query[LayerWMTS]
92
+ ) -> sqlalchemy.orm.query.Query[LayerWMTS]:
93
+ del query
94
+ return self._base_query()
79
95
 
80
- @view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2")
81
- def index(self):
96
+ @view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2") # type: ignore[misc]
97
+ def index(self) -> IndexResponse:
82
98
  return super().index()
83
99
 
84
- @view_config(route_name="c2cgeoform_grid", renderer="fast_json")
85
- def grid(self):
100
+ @view_config(route_name="c2cgeoform_grid", renderer="fast_json") # type: ignore[misc]
101
+ def grid(self) -> GridResponse:
86
102
  return super().grid()
87
103
 
88
- def _item_actions(self, item, readonly=False):
89
- actions = super()._item_actions(item, readonly)
90
- if inspect(item).persistent:
104
+ def _item_actions(self, item: LayerWMTS, readonly: bool = False) -> list[ItemAction]:
105
+ actions: list[ItemAction] = super()._item_actions(item, readonly)
106
+ if inspect(item).persistent: # type: ignore[attr-defined]
91
107
  actions.insert(
92
108
  next((i for i, v in enumerate(actions) if v.name() == "delete")),
93
109
  ItemAction(
@@ -101,8 +117,10 @@ class LayerWmtsViews(DimensionLayerViews):
101
117
  )
102
118
  return actions
103
119
 
104
- @view_config(route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2")
105
- def view(self):
120
+ @view_config( # type: ignore[misc]
121
+ route_name="c2cgeoform_item", request_method="GET", renderer="../templates/edit.jinja2"
122
+ )
123
+ def view(self) -> ObjectResponse:
106
124
  if self._is_new():
107
125
  dbsession = self._request.dbsession
108
126
  default_wmts = LayerWMTS.get_default(dbsession)
@@ -110,22 +128,24 @@ class LayerWmtsViews(DimensionLayerViews):
110
128
  return self.copy(default_wmts, excludes=["name", "layer"])
111
129
  return super().edit()
112
130
 
113
- @view_config(route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2")
114
- def save(self):
131
+ @view_config( # type: ignore[misc]
132
+ route_name="c2cgeoform_item", request_method="POST", renderer="../templates/edit.jinja2"
133
+ )
134
+ def save(self) -> SaveResponse:
115
135
  return super().save()
116
136
 
117
- @view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json")
118
- def delete(self):
137
+ @view_config(route_name="c2cgeoform_item", request_method="DELETE", renderer="fast_json") # type: ignore[misc]
138
+ def delete(self) -> DeleteResponse:
119
139
  return super().delete()
120
140
 
121
- @view_config(
141
+ @view_config( # type: ignore[misc]
122
142
  route_name="c2cgeoform_item_duplicate", request_method="GET", renderer="../templates/edit.jinja2"
123
143
  )
124
- def duplicate(self):
144
+ def duplicate(self) -> ObjectResponse:
125
145
  return super().duplicate()
126
146
 
127
- @view_config(route_name="convert_to_wms", request_method="POST", renderer="fast_json")
128
- def convert_to_wms(self):
147
+ @view_config(route_name="convert_to_wms", request_method="POST", renderer="fast_json") # type: ignore[misc]
148
+ def convert_to_wms(self) -> JSONDict:
129
149
  src = self._get_object()
130
150
  dbsession = self._request.dbsession
131
151
  default_wms = LayerWMS.get_default(dbsession)
@@ -159,6 +179,8 @@ class LayerWmtsViews(DimensionLayerViews):
159
179
  dbsession.flush()
160
180
  mark_changed(dbsession)
161
181
 
182
+ self._create_log(LogAction.CONVERT_TO_WMS, src, element_url_table="layers_wms")
183
+
162
184
  return {
163
185
  "success": True,
164
186
  "redirect": self._request.route_url(
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2017-2020, Camptocamp SA
1
+ # Copyright (c) 2017-2024, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -28,48 +26,59 @@
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
28
 
31
- from c2cgeoform.views.abstract_views import ItemAction
29
+ from typing import Any
30
+
31
+ import pyramid.request
32
+ from c2cgeoform.views.abstract_views import DeleteResponse, ItemAction
32
33
  from pyramid.httpexceptions import HTTPNotFound
33
34
  from pyramid.view import view_config, view_defaults
34
35
  from translationstring import TranslationStringFactory
35
36
 
36
37
  from c2cgeoportal_admin import _
37
- from c2cgeoportal_commons.models.main import LayergroupTreeitem, Theme, TreeItem
38
+ from c2cgeoportal_commons.models.main import Interface, Layer, LayergroupTreeitem, Theme, TreeItem
38
39
 
39
40
  itemtypes_tables = {
40
41
  "theme": "themes",
41
42
  "group": "layer_groups",
42
43
  "l_wms": "layers_wms",
43
44
  "l_wmts": "layers_wmts",
45
+ "l_cog": "layers_cog",
44
46
  }
45
47
 
46
48
 
47
- @view_defaults(match_param=("application=admin"))
49
+ @view_defaults(match_param="application=admin")
48
50
  class LayerTreeViews:
49
- def __init__(self, request):
51
+ """The layer tree administration view."""
52
+
53
+ def __init__(self, request: pyramid.request.Request):
50
54
  self._request = request
51
55
  self._dbsession = request.dbsession
52
56
 
53
- @view_config(route_name="layertree", renderer="../templates/layertree.jinja2")
54
- def index(self):
57
+ @view_config(route_name="layertree", renderer="../templates/layertree.jinja2") # type: ignore[misc]
58
+ def index(self) -> dict[str, int]:
55
59
  node_limit = self._request.registry.settings["admin_interface"].get("layer_tree_max_nodes")
56
60
  limit_exceeded = self._dbsession.query(LayergroupTreeitem).count() < node_limit
57
- return {"limit_exceeded": limit_exceeded}
61
+ return {"limit_exceeded": limit_exceeded, "interfaces": self._dbsession.query(Interface).all()}
58
62
 
59
- @view_config(route_name="layertree_children", renderer="fast_json")
60
- def children(self):
63
+ @view_config(route_name="layertree_children", renderer="fast_json") # type: ignore[misc]
64
+ def children(self) -> list[dict[str, Any]]:
65
+ interface = self._request.params.get("interface", None)
61
66
  group_id = self._request.params.get("group_id", None)
62
67
  path = self._request.params.get("path", "")
63
68
 
64
- client_tsf = TranslationStringFactory("{}-client".format(self._request.registry.package_name))
69
+ client_tsf = TranslationStringFactory(f"{self._request.registry.package_name}-client")
65
70
 
66
71
  if group_id is None:
67
72
  items = self._dbsession.query(Theme).order_by(Theme.ordering)
73
+ if interface is not None:
74
+ items = items.join(Theme.interfaces).filter(Interface.name == interface)
75
+
68
76
  else:
69
77
  items = (
70
78
  self._dbsession.query(TreeItem)
71
79
  .join(TreeItem.parents_relation)
72
80
  .filter(LayergroupTreeitem.treegroup_id == group_id)
81
+ .order_by(LayergroupTreeitem.ordering)
73
82
  )
74
83
 
75
84
  return [
@@ -79,14 +88,17 @@ class LayerTreeViews:
79
88
  "name": item.name,
80
89
  "translated_name": self._request.localizer.translate(client_tsf(item.name)),
81
90
  "description": item.description,
82
- "path": "{}_{}".format(path, item.id),
91
+ "path": f"{path}_{item.id}",
83
92
  "parent_path": path,
84
93
  "actions": [action.to_dict(self._request) for action in self._item_actions(item, group_id)],
85
94
  }
86
95
  for item in items
96
+ if interface is None
97
+ or not isinstance(item, Layer)
98
+ or interface in [interface.name for interface in item.interfaces]
87
99
  ]
88
100
 
89
- def _item_actions(self, item, parent_id=None):
101
+ def _item_actions(self, item: TreeItem, parent_id: int | None = None) -> list[ItemAction]:
90
102
  actions = []
91
103
  actions.append(
92
104
  ItemAction(
@@ -105,9 +117,8 @@ class LayerTreeViews:
105
117
  name="new_layer_group",
106
118
  label=_("New layer group"),
107
119
  icon="glyphicon glyphicon-plus",
108
- url="{}?parent_id={}".format(
109
- self._request.route_url("c2cgeoform_item", table="layer_groups", id="new"), item.id
110
- ),
120
+ url=f"{self._request.route_url('c2cgeoform_item', table='layer_groups', id='new')}?"
121
+ f"parent_id={item.id}",
111
122
  )
112
123
  )
113
124
 
@@ -117,9 +128,8 @@ class LayerTreeViews:
117
128
  name="new_layer_wms",
118
129
  label=_("New WMS layer"),
119
130
  icon="glyphicon glyphicon-plus",
120
- url="{}?parent_id={}".format(
121
- self._request.route_url("c2cgeoform_item", table="layers_wms", id="new"), item.id
122
- ),
131
+ url=f"{self._request.route_url('c2cgeoform_item', table='layers_wms', id='new')}?"
132
+ f"parent_id={item.id}",
123
133
  )
124
134
  )
125
135
 
@@ -128,9 +138,8 @@ class LayerTreeViews:
128
138
  name="new_layer_wmts",
129
139
  label=_("New WMTS layer"),
130
140
  icon="glyphicon glyphicon-plus",
131
- url="{}?parent_id={}".format(
132
- self._request.route_url("c2cgeoform_item", table="layers_wmts", id="new"), item.id
133
- ),
141
+ url=f"{self._request.route_url('c2cgeoform_item', table='layers_wmts', id='new')}?"
142
+ f"parent_id={item.id}",
134
143
  )
135
144
  )
136
145
 
@@ -170,8 +179,8 @@ class LayerTreeViews:
170
179
 
171
180
  return actions
172
181
 
173
- @view_config(route_name="layertree_unlink", request_method="DELETE", renderer="fast_json")
174
- def unlink(self):
182
+ @view_config(route_name="layertree_unlink", request_method="DELETE", renderer="fast_json") # type: ignore[misc]
183
+ def unlink(self) -> dict[str, Any]:
175
184
  group_id = self._request.matchdict.get("group_id")
176
185
  item_id = self._request.matchdict.get("item_id")
177
186
  link = (
@@ -186,8 +195,8 @@ class LayerTreeViews:
186
195
  self._request.dbsession.flush()
187
196
  return {"success": True, "redirect": self._request.route_url("layertree")}
188
197
 
189
- @view_config(route_name="layertree_delete", request_method="DELETE", renderer="fast_json")
190
- def delete(self):
198
+ @view_config(route_name="layertree_delete", request_method="DELETE", renderer="fast_json") # type: ignore[misc]
199
+ def delete(self) -> DeleteResponse:
191
200
  item_id = self._request.matchdict.get("item_id")
192
201
  item = self._request.dbsession.query(TreeItem).get(item_id)
193
202
  if item is None:
@@ -0,0 +1,83 @@
1
+ # Copyright (c) 2023-2024, 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
+ import datetime
29
+ from typing import Generic, TypeVar
30
+
31
+ from c2cgeoform.views.abstract_views import AbstractViews, DeleteResponse, SaveResponse
32
+ from pyramid.httpexceptions import HTTPFound
33
+
34
+ from c2cgeoportal_commons.models import Base
35
+ from c2cgeoportal_commons.models.main import Log, LogAction
36
+
37
+ _T = TypeVar("_T", bound=Log)
38
+
39
+
40
+ class LoggedViews(AbstractViews[_T], Generic[_T]):
41
+ """Extension of AbstractViews which log actions in a table."""
42
+
43
+ _log_model = Log # main.Log or static.Log
44
+ _name_field = "name"
45
+
46
+ def save(self) -> SaveResponse:
47
+ response = super().save()
48
+ if isinstance(response, HTTPFound):
49
+ self._create_log(
50
+ action=LogAction.INSERT if self._is_new() else LogAction.UPDATE,
51
+ obj=self._obj,
52
+ )
53
+
54
+ return response
55
+
56
+ def delete(self) -> DeleteResponse:
57
+ obj = self._get_object()
58
+
59
+ response = super().delete()
60
+
61
+ self._create_log(LogAction.DELETE, obj)
62
+
63
+ return response
64
+
65
+ def _create_log(
66
+ self,
67
+ action: LogAction,
68
+ obj: Base, # type: ignore[valid-type]
69
+ element_url_table: str | None = None,
70
+ ) -> None:
71
+ assert self._model is not None
72
+ assert self._name_field is not None
73
+ assert self._id_field is not None
74
+ log = self._log_model(
75
+ date=datetime.datetime.now(datetime.timezone.utc),
76
+ action=action,
77
+ element_type=self._model.__tablename__,
78
+ element_id=getattr(obj, self._id_field),
79
+ element_name=getattr(obj, self._name_field),
80
+ element_url_table=element_url_table or self._request.matchdict.get("table", None),
81
+ username=self._request.user.username,
82
+ )
83
+ self._request.dbsession.add(log)
@@ -0,0 +1,91 @@
1
+ # Copyright (c) 2023-2024, 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 import JSONDict
31
+ from c2cgeoform.views.abstract_views import AbstractViews, GridResponse, IndexResponse, ItemAction, ListField
32
+ from pyramid.view import view_config, view_defaults
33
+
34
+ from c2cgeoportal_commons.models import _
35
+ from c2cgeoportal_commons.models.main import AbstractLog
36
+
37
+ _list_field = partial(ListField, AbstractLog)
38
+
39
+
40
+ @view_defaults(match_param="table=logs")
41
+ class LogViews(AbstractViews[AbstractLog]):
42
+ """The theme administration view."""
43
+
44
+ # We pass labels explicitly because actually we are not able to get info
45
+ # from InstrumentedAttribute created from AbstractConcreteBase.
46
+ _list_fields = [
47
+ _list_field("id"),
48
+ _list_field("date", label=_("Date")),
49
+ _list_field("username", label=_("Username")),
50
+ _list_field("action", label=_("Action"), renderer=lambda log: log.action.name),
51
+ _list_field("element_type", label=_("Element type")),
52
+ _list_field("element_id", label=_("Element identifier")),
53
+ _list_field("element_name", label=_("Element name")),
54
+ ]
55
+ _list_ordered_fields = [AbstractLog.date.desc()]
56
+
57
+ _id_field = "id"
58
+ _model = AbstractLog
59
+
60
+ @view_config(route_name="c2cgeoform_index", renderer="../templates/index.jinja2") # type: ignore[misc]
61
+ def index(self) -> IndexResponse:
62
+ return super().index()
63
+
64
+ @view_config(route_name="c2cgeoform_grid", renderer="fast_json") # type: ignore[misc]
65
+ def grid(self) -> GridResponse:
66
+ return super().grid()
67
+
68
+ def _grid_actions(self):
69
+ return []
70
+
71
+ def _grid_item_actions(self, item: AbstractLog) -> JSONDict:
72
+ element_url = self._request.route_url(
73
+ "c2cgeoform_item",
74
+ table=item.element_url_table,
75
+ id=item.element_id,
76
+ )
77
+
78
+ return {
79
+ "dblclick": element_url,
80
+ "dropdown": [
81
+ ItemAction(
82
+ name="edit_element",
83
+ label=_("Edit element"),
84
+ icon="glyphicon glyphicon-pencil",
85
+ url=element_url,
86
+ ).to_dict(self._request)
87
+ ],
88
+ }
89
+
90
+ def _item_actions(self, item, readonly=False):
91
+ return []