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) 2018-2020, Camptocamp SA
1
+ # Copyright (c) 2018-2021, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -28,28 +26,71 @@
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
28
 
29
+ from typing import Any, Dict, List
30
+
31
+ import colander
31
32
  from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget
32
33
  from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator
33
- import colander
34
- from sqlalchemy import select
34
+ from sqlalchemy import inspect, select
35
+ from sqlalchemy.orm.attributes import InstrumentedAttribute
36
+ from sqlalchemy.orm.decl_api import DeclarativeMeta
35
37
  from sqlalchemy.sql.functions import concat
36
38
 
37
39
  from c2cgeoportal_commons.models.main import Functionality
38
40
 
39
- functionalities_schema_node = colander.SequenceSchema(
40
- GeoFormManyToManySchemaNode(Functionality),
41
- name="functionalities",
42
- widget=RelationCheckBoxListWidget(
43
- select([Functionality.id, concat(Functionality.name, "=", Functionality.value).label("label")]).alias(
44
- "functionality_labels"
45
- ),
46
- "id",
47
- "label",
48
- order_by="label",
49
- edit_url=lambda request, value: request.route_url(
50
- "c2cgeoform_item", table="functionalities", id=value
51
- ),
52
- ),
53
- validator=manytomany_validator,
54
- missing=colander.drop,
55
- )
41
+
42
+ def available_functionalities_for(settings: Dict[str, Any], model: DeclarativeMeta) -> List[Dict[str, Any]]:
43
+ """Return filtered list of functionality definitions."""
44
+ mapper = inspect(model)
45
+ relevant_for = {mapper.local_table.name}
46
+ return [
47
+ f
48
+ for f in settings["admin_interface"]["available_functionalities"]
49
+ if relevant_for & set(f.get("relevant_for", relevant_for))
50
+ ]
51
+
52
+
53
+ def functionalities_widget(model: DeclarativeMeta) -> colander.deferred:
54
+ """Return a colander deferred which itself returns a widget for the functionalities field."""
55
+
56
+ def create_widget(node, kw):
57
+ del node
58
+
59
+ return RelationCheckBoxListWidget(
60
+ select(
61
+ [
62
+ Functionality.id,
63
+ concat(Functionality.name, "=", Functionality.value).label("label"),
64
+ ]
65
+ )
66
+ .where(
67
+ Functionality.name.in_(
68
+ [f["name"] for f in available_functionalities_for(kw["request"].registry.settings, model)]
69
+ )
70
+ )
71
+ .alias("functionality_labels"),
72
+ "id",
73
+ "label",
74
+ order_by="label",
75
+ edit_url=lambda request, value: request.route_url(
76
+ "c2cgeoform_item", table="functionalities", id=value
77
+ ),
78
+ )
79
+
80
+ return colander.deferred(create_widget)
81
+
82
+
83
+ def functionalities_schema_node(
84
+ prop: InstrumentedAttribute, model: DeclarativeMeta
85
+ ) -> colander.SequenceSchema:
86
+ """Get the schema of the functionalities."""
87
+
88
+ return colander.SequenceSchema(
89
+ GeoFormManyToManySchemaNode(Functionality),
90
+ name=prop.key,
91
+ title=prop.info["colanderalchemy"]["title"],
92
+ description=prop.info["colanderalchemy"].get("description"),
93
+ widget=functionalities_widget(model),
94
+ validator=manytomany_validator,
95
+ missing=colander.drop,
96
+ )
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2018-2020, Camptocamp SA
1
+ # Copyright (c) 2018-2021, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -28,24 +26,30 @@
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
28
 
29
+ import colander
31
30
  from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget
32
31
  from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator
33
- import colander
32
+ from sqlalchemy.orm.attributes import InstrumentedAttribute
34
33
 
35
- from c2cgeoportal_admin import _
36
34
  from c2cgeoportal_commons.models.main import Interface
37
35
 
38
- interfaces_schema_node = colander.SequenceSchema(
39
- GeoFormManyToManySchemaNode(Interface),
40
- name="interfaces",
41
- title=_("Interfaces"),
42
- widget=RelationCheckBoxListWidget(
43
- Interface,
44
- "id",
45
- "name",
46
- order_by="name",
47
- edit_url=lambda request, value: request.route_url("c2cgeoform_item", table="interfaces", id=value),
48
- ),
49
- validator=manytomany_validator,
50
- missing=colander.drop,
51
- )
36
+
37
+ def interfaces_schema_node(prop: InstrumentedAttribute) -> colander.SequenceSchema:
38
+ """Get the serializable representation of an interface."""
39
+ return colander.SequenceSchema(
40
+ GeoFormManyToManySchemaNode(Interface),
41
+ name=prop.key,
42
+ title=prop.info["colanderalchemy"]["title"],
43
+ description=prop.info["colanderalchemy"]["description"],
44
+ widget=RelationCheckBoxListWidget(
45
+ Interface,
46
+ "id",
47
+ "name",
48
+ order_by="name",
49
+ edit_url=lambda request, value: request.route_url(
50
+ "c2cgeoform_item", table="interfaces", id=value
51
+ ),
52
+ ),
53
+ validator=manytomany_validator,
54
+ missing=colander.drop,
55
+ )
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2018-2020, Camptocamp SA
1
+ # Copyright (c) 2018-2021, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -27,32 +25,49 @@
27
25
  # of the authors and should not be interpreted as representing official policies,
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
-
31
28
  import json
32
- from typing import Any, Dict, List, Optional, cast
29
+ from typing import Any, Dict, List, Optional, Set, Union, cast
33
30
 
34
- from c2cgeoform.schema import GeoFormSchemaNode
35
31
  import colander
32
+ import pyramid.request
33
+ from c2cgeoform.schema import GeoFormSchemaNode
36
34
  from deform.widget import MappingWidget, SelectWidget, SequenceWidget, TextAreaWidget
35
+ from sqlalchemy import inspect
36
+ from sqlalchemy.orm.attributes import InstrumentedAttribute
37
+ from sqlalchemy.orm.decl_api import DeclarativeMeta
38
+ from sqlalchemy.orm.mapper import Mapper
37
39
 
38
40
  from c2cgeoportal_admin import _
39
41
  from c2cgeoportal_commons.lib.validators import url
40
42
  from c2cgeoportal_commons.models.main import Metadata
41
43
 
42
44
 
43
- @colander.deferred
44
- def metadata_definitions(node, kw):
45
- del node
46
- return {m["name"]: m for m in kw["request"].registry.settings["admin_interface"]["available_metadata"]}
45
+ def get_relevant_for(model: Union[DeclarativeMeta, Mapper]) -> Set[str]:
46
+ """Return list of relevant_for values for passed class."""
47
+ mapper = inspect(model)
48
+ relevant_for = {mapper.local_table.name} # or mapper.polymorphic_identity
49
+ if mapper.inherits:
50
+ relevant_for |= get_relevant_for(mapper.inherits)
51
+ return relevant_for
52
+
47
53
 
54
+ def metadata_definitions(settings: Dict[str, Any], model: DeclarativeMeta) -> List[Dict[str, Any]]:
55
+ """Return filtered list metadata definitions."""
56
+ return [
57
+ m
58
+ for m in settings["admin_interface"]["available_metadata"]
59
+ if get_relevant_for(model) & set(m.get("relevant_for", ["treeitem"]))
60
+ ]
48
61
 
49
- class MetadataSelectWidget(SelectWidget):
50
- """Extends class SelectWidget to support undefined metadatas.
62
+
63
+ class MetadataSelectWidget(SelectWidget): # type: ignore
64
+ """
65
+ Extends class SelectWidget to support undefined metadata.
51
66
 
52
67
  Override serialize to add option in values for current cstruct when needed.
53
68
  """
54
69
 
55
- def serialize(self, field, cstruct, **kw):
70
+ def serialize(self, field: Any, cstruct: Any, **kw: Any) -> Any:
56
71
  values = kw.get("values", self.values)
57
72
  if isinstance(cstruct, str) and (cstruct, cstruct) not in values:
58
73
  values = values.copy()
@@ -61,21 +76,26 @@ class MetadataSelectWidget(SelectWidget):
61
76
  return super().serialize(field, cstruct, **kw)
62
77
 
63
78
 
64
- @colander.deferred
65
- def metadata_name_widget(node, kw):
66
- del node
67
- return MetadataSelectWidget(
68
- values=[
69
- (m["name"], m["name"])
70
- for m in sorted(
71
- kw["request"].registry.settings["admin_interface"]["available_metadata"],
72
- key=lambda m: m["name"],
73
- )
74
- ]
75
- )
79
+ def metadata_name_widget(model: DeclarativeMeta) -> colander.deferred:
80
+ """Return a colander deferred which itself returns a widget for the metadata name field."""
81
+
82
+ def create_widget(node, kw):
83
+ del node
84
+ return MetadataSelectWidget(
85
+ values=[
86
+ (m["name"], m["name"])
87
+ for m in sorted(
88
+ metadata_definitions(kw["request"].registry.settings, model),
89
+ key=lambda m: cast(str, m["name"]),
90
+ )
91
+ ]
92
+ )
93
+
94
+ return colander.deferred(create_widget)
76
95
 
77
96
 
78
97
  def json_validator(node, value):
98
+ """Validate the value to be a valid JSON."""
79
99
  try:
80
100
  json.loads(value)
81
101
  except ValueError as e:
@@ -83,6 +103,7 @@ def json_validator(node, value):
83
103
 
84
104
 
85
105
  def regex_validator(node, value):
106
+ """Validate the value with a regexp."""
86
107
  definition = node.metadata_definitions.get(value["name"], {})
87
108
  if definition.get("type", "string") == "regex":
88
109
  validator = colander.Regex(definition["regex"], msg=_(definition["error_message"]))
@@ -91,21 +112,43 @@ def regex_validator(node, value):
91
112
  except colander.Invalid as e:
92
113
  error = colander.Invalid(node)
93
114
  error.add(e, node.children.index(node["string"]))
94
- raise error
115
+ raise error from e
95
116
 
96
117
 
97
- class MetadataSchemaNode(GeoFormSchemaNode): # pylint: disable=abstract-method
118
+ class BooleanMetadata(colander.Boolean): # type: ignore
119
+ """Boolean metadata values are stored as string in database."""
120
+
121
+ def serialize(self, node, appstruct):
122
+ if appstruct == "true":
123
+ appstruct = True
124
+ elif appstruct == "false":
125
+ appstruct = False
126
+ else:
127
+ appstruct = colander.null
128
+ return super().serialize(node, appstruct)
129
+
130
+ def deserialize(self, node, cstruct):
131
+ appstruct = super().deserialize(node, cstruct)
132
+ if appstruct is True:
133
+ return "true"
134
+ if appstruct is False:
135
+ return "false"
136
+ return None
137
+
138
+
139
+ class MetadataSchemaNode(GeoFormSchemaNode): # type: ignore # pylint: disable=abstract-method
140
+ """The metadata schema."""
98
141
 
99
142
  metadata_definitions: Optional[Dict[str, Any]] = None
100
143
 
101
- def __init__(self, *args, **kw):
144
+ def __init__(self, *args: Any, **kw: Any):
102
145
  super().__init__(*args, **kw)
103
146
 
104
147
  self.available_types: List[str] = []
105
148
 
106
149
  self._add_value_node("string", colander.String())
107
150
  self._add_value_node("liste", colander.String())
108
- self._add_value_node("boolean", colander.Boolean())
151
+ self._add_value_node("boolean", BooleanMetadata())
109
152
  self._add_value_node("int", colander.Int())
110
153
  self._add_value_node("float", colander.Float())
111
154
  self._add_value_node("url", colander.String(), validator=url)
@@ -113,10 +156,17 @@ class MetadataSchemaNode(GeoFormSchemaNode): # pylint: disable=abstract-method
113
156
  "json", colander.String(), widget=TextAreaWidget(rows=10), validator=json_validator
114
157
  )
115
158
 
116
- def _add_value_node(self, type_name, colander_type, **kw):
159
+ def _add_value_node(self, type_name: str, colander_type: colander.SchemaType, **kw: Any) -> None:
117
160
  self.add_before(
118
161
  "description",
119
- colander.SchemaNode(colander_type, name=type_name, title=_("Value"), missing=colander.null, **kw),
162
+ colander.SchemaNode(
163
+ colander_type,
164
+ name=type_name,
165
+ title=Metadata.value.info["colanderalchemy"]["title"],
166
+ description=Metadata.value.info["colanderalchemy"]["description"],
167
+ missing=colander.null,
168
+ **kw,
169
+ ),
120
170
  )
121
171
  self.available_types.append(type_name)
122
172
 
@@ -132,24 +182,48 @@ class MetadataSchemaNode(GeoFormSchemaNode): # pylint: disable=abstract-method
132
182
  dict_[self._ui_type(obj.name)] = value
133
183
  return dict_
134
184
 
135
- def _ui_type(self, metadata_name: str):
185
+ def _ui_type(self, metadata_name: str) -> str:
136
186
  metadata_type = (
137
- cast(Dict[str, Any], self.metadata_definitions).get(metadata_name, {}).get("type", "string")
187
+ cast(Dict[str, Dict[str, str]], self.metadata_definitions)
188
+ .get(metadata_name, {})
189
+ .get("type", "string")
138
190
  )
139
191
  return metadata_type if metadata_type in self.available_types else "string"
140
192
 
141
193
 
142
- metadatas_schema_node = colander.SequenceSchema(
143
- MetadataSchemaNode(
144
- Metadata,
145
- name="metadata",
146
- metadata_definitions=metadata_definitions,
147
- validator=regex_validator,
148
- widget=MappingWidget(template="metadata"),
149
- overrides={"name": {"widget": metadata_name_widget}},
150
- ),
151
- name="metadatas",
152
- title=_("Metadatas"),
153
- metadata_definitions=metadata_definitions,
154
- widget=SequenceWidget(template="metadatas", category="structural"),
155
- )
194
+ def _translate_available_metadata(
195
+ available_metadata: Dict[str, Any], request: pyramid.request.Request
196
+ ) -> Dict[str, Any]:
197
+ result = {}
198
+ result.update(available_metadata)
199
+ result["description"] = request.localizer.translate(_(available_metadata.get("description", "").strip()))
200
+ return result
201
+
202
+
203
+ def metadata_schema_node(prop: InstrumentedAttribute, model: DeclarativeMeta) -> colander.SequenceSchema:
204
+ """Get the schema of a collection of metadata."""
205
+
206
+ # Deferred which returns a dictionary with metadata name as key and metadata definition as value.
207
+ # Needed to get the metadata types on UI side.
208
+ metadata_definitions_dict = colander.deferred(
209
+ lambda node, kw: {
210
+ m["name"]: _translate_available_metadata(m, kw["request"])
211
+ for m in metadata_definitions(kw["request"].registry.settings, model)
212
+ }
213
+ )
214
+
215
+ return colander.SequenceSchema(
216
+ MetadataSchemaNode(
217
+ Metadata,
218
+ name="metadata",
219
+ metadata_definitions=metadata_definitions_dict,
220
+ validator=regex_validator,
221
+ widget=MappingWidget(template="metadata"),
222
+ overrides={"name": {"widget": metadata_name_widget(model)}},
223
+ ),
224
+ name=prop.key,
225
+ title=prop.info["colanderalchemy"]["title"],
226
+ description=prop.info["colanderalchemy"]["description"],
227
+ metadata_definitions=metadata_definitions_dict,
228
+ widget=SequenceWidget(template="metadatas", category="structural"),
229
+ )
@@ -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,26 +26,30 @@
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
28
 
29
+ import colander
31
30
  from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget
32
31
  from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator
33
- import colander
32
+ from sqlalchemy.orm.attributes import InstrumentedAttribute
34
33
 
35
- from c2cgeoportal_admin import _
36
34
  from c2cgeoportal_commons.models.main import RestrictionArea
37
35
 
38
- restrictionareas_schema_node = colander.SequenceSchema(
39
- GeoFormManyToManySchemaNode(RestrictionArea),
40
- name="restrictionareas",
41
- title=_("Restriction areas"),
42
- widget=RelationCheckBoxListWidget(
43
- RestrictionArea,
44
- "id",
45
- "name",
46
- order_by="name",
47
- edit_url=lambda request, value: request.route_url(
48
- "c2cgeoform_item", table="restriction_areas", id=value
36
+
37
+ def restrictionareas_schema_node(prop: InstrumentedAttribute) -> colander.SequenceSchema:
38
+ """Get the schema of a restriction area."""
39
+ return colander.SequenceSchema(
40
+ GeoFormManyToManySchemaNode(RestrictionArea),
41
+ name=prop.key,
42
+ title=prop.info["colanderalchemy"]["title"],
43
+ description=prop.info["colanderalchemy"].get("description"),
44
+ widget=RelationCheckBoxListWidget(
45
+ RestrictionArea,
46
+ "id",
47
+ "name",
48
+ order_by="name",
49
+ edit_url=lambda request, value: request.route_url(
50
+ "c2cgeoform_item", table="restriction_areas", id=value
51
+ ),
49
52
  ),
50
- ),
51
- validator=manytomany_validator,
52
- missing=colander.drop,
53
- )
53
+ validator=manytomany_validator,
54
+ missing=colander.drop,
55
+ )
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2018-2020, Camptocamp SA
1
+ # Copyright (c) 2018-2021, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -28,17 +26,21 @@
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
28
 
29
+ import colander
31
30
  from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget
32
31
  from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator
33
- import colander
32
+ from sqlalchemy.orm.attributes import InstrumentedAttribute
34
33
 
35
34
  from c2cgeoportal_commons.models.main import Role
36
35
 
37
36
 
38
- def roles_schema_node(name):
37
+ def roles_schema_node(prop: InstrumentedAttribute) -> colander.SequenceSchema:
38
+ """Get the schema of all the items."""
39
39
  return colander.SequenceSchema(
40
40
  GeoFormManyToManySchemaNode(Role),
41
- name=name,
41
+ name=prop.key,
42
+ title=prop.info["colanderalchemy"]["title"],
43
+ description=prop.info["colanderalchemy"].get("description"),
42
44
  widget=RelationCheckBoxListWidget(
43
45
  Role,
44
46
  "id",
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2018-2020, Camptocamp SA
1
+ # Copyright (c) 2018-2022, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -28,19 +26,36 @@
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
28
 
29
+ import logging
31
30
  from functools import partial
31
+ from typing import Any, Dict, List, Optional
32
32
 
33
- from c2cgeoform.schema import GeoFormSchemaNode
34
33
  import colander
34
+ import pyramid.request
35
+ from c2cgeoform.schema import GeoFormSchemaNode
35
36
  from sqlalchemy.orm import aliased
36
37
  from sqlalchemy.sql.expression import case, func
37
38
 
38
39
  from c2cgeoportal_admin import _
39
40
  from c2cgeoportal_admin.widgets import ChildrenWidget, ChildWidget
40
- from c2cgeoportal_commons.models.main import LayergroupTreeitem, TreeItem
41
+ from c2cgeoportal_commons.lib.literal import Literal
42
+ from c2cgeoportal_commons.models.main import LayergroupTreeitem, TreeGroup, TreeItem
43
+
44
+ LOG = logging.getLogger(__name__)
45
+
46
+ # Correspondence between TreeItem.item_type and route table segment
47
+ ITEM_TYPE_ROUTE_MAP = {
48
+ "theme": "themes",
49
+ "group": "layer_groups",
50
+ "layer": None,
51
+ "l_wms": "layers_wms",
52
+ "l_wmts": "layers_wmts",
53
+ }
54
+
41
55
 
56
+ class ChildSchemaNode(GeoFormSchemaNode): # type: ignore # pylint: disable=abstract-method
57
+ """Schema of the child nodes."""
42
58
 
43
- class ChildSchemaNode(GeoFormSchemaNode): # pylint: disable=abstract-method
44
59
  def objectify(self, dict_, context=None):
45
60
  if dict_.get("id", None):
46
61
  context = self.dbsession.query(LayergroupTreeitem).get(dict_["id"])
@@ -49,10 +64,14 @@ class ChildSchemaNode(GeoFormSchemaNode): # pylint: disable=abstract-method
49
64
  return context
50
65
 
51
66
 
52
- def treeitems(node, kw, only_groups=False): # pylint: disable=unused-argument
67
+ def treeitems(
68
+ node: TreeGroup, kw: Dict[str, pyramid.request.Request], only_groups: bool = False
69
+ ) -> List[Dict[str, Any]]:
70
+ """Get a serializable representation of the tree items."""
71
+ del node
53
72
  dbsession = kw["request"].dbsession
54
73
 
55
- group = case([(func.count(LayergroupTreeitem.id) == 0, "Unlinked")], else_="Others").label("group")
74
+ group = case([(func.count(LayergroupTreeitem.id) == 0, "Unlinked")], else_="Others")
56
75
 
57
76
  query = (
58
77
  dbsession.query(TreeItem, group)
@@ -85,12 +104,22 @@ def treeitems(node, kw, only_groups=False): # pylint: disable=unused-argument
85
104
  if only_groups:
86
105
  query = query.filter(TreeItem.item_type == "group")
87
106
 
88
- return query
107
+ return [
108
+ {
109
+ "id": item.id,
110
+ "label": item.name,
111
+ "icon_class": f"icon-{item.item_type}",
112
+ "edit_url": treeitem_edit_url(kw["request"], item),
113
+ "group": group,
114
+ }
115
+ for item, group in query
116
+ ]
89
117
 
90
118
 
91
119
  def children_validator(node, cstruct):
120
+ """Get the validator on the children nodes."""
92
121
  for dict_ in cstruct:
93
- if not dict_["treeitem_id"] in [item.id for item, group in node.treeitems]:
122
+ if not dict_["treeitem_id"] in [item["id"] for item in node.candidates]:
94
123
  raise colander.Invalid(
95
124
  node,
96
125
  _("Value {} does not exist in table {} or is not allowed to avoid cycles").format(
@@ -99,22 +128,59 @@ def children_validator(node, cstruct):
99
128
  )
100
129
 
101
130
 
102
- def base_deferred_parent_id_validator(node, kw, model): # pylint: disable=unused-argument
131
+ def base_deferred_parent_id_validator(node, kw, model):
132
+ """Get the validator on the parent node ID."""
133
+ del node
134
+
103
135
  def validator(node, cstruct):
104
136
  if kw["dbsession"].query(model).filter(model.id == cstruct).count() == 0:
105
- raise colander.Invalid(
106
- node, "Value {} does not exist in table {}".format(cstruct, model.__tablename__)
107
- )
137
+ raise colander.Invalid(node, f"Value {cstruct} does not exist in table {model.__tablename__}")
108
138
 
109
139
  return validator
110
140
 
111
141
 
112
- def children_schema_node(only_groups=False):
142
+ def treeitem_edit_url(request: pyramid.request.Request, treeitem: TreeGroup) -> Optional[str]:
143
+ """Get the tree item editing URL."""
144
+ if treeitem.item_type is None:
145
+ return None
146
+ table = ITEM_TYPE_ROUTE_MAP.get(treeitem.item_type, None)
147
+ if table is None:
148
+ LOG.warning("%s not found in ITEM_TYPE_ROUTE_MAP", treeitem.item_type)
149
+ return None
150
+ return request.route_url( # type: ignore
151
+ "c2cgeoform_item",
152
+ table=ITEM_TYPE_ROUTE_MAP[treeitem.item_type],
153
+ id=treeitem.id,
154
+ )
155
+
156
+
157
+ def children_schema_node(only_groups: bool = False) -> colander.SequenceSchema:
158
+ """Geth the sequence to the children nodes."""
113
159
  return colander.SequenceSchema(
114
- ChildSchemaNode(LayergroupTreeitem, name="layergroup_treeitem", widget=ChildWidget()),
160
+ ChildSchemaNode(
161
+ LayergroupTreeitem,
162
+ name="layergroup_treeitem",
163
+ widget=ChildWidget(
164
+ input_name="treeitem_id",
165
+ model=TreeItem,
166
+ label_field="name",
167
+ icon_class=lambda treeitem: f"icon-{treeitem.item_type}",
168
+ edit_url=treeitem_edit_url,
169
+ ),
170
+ ),
115
171
  name="children_relation",
116
172
  title=_("Children"),
117
- treeitems=colander.deferred(partial(treeitems, only_groups=only_groups)),
173
+ description=Literal(
174
+ _(
175
+ """
176
+ <div class="help-block">
177
+ <p>The ordered children elements.</p>
178
+ <hr>
179
+ </div>
180
+ """
181
+ ),
182
+ ),
183
+ candidates=colander.deferred(partial(treeitems, only_groups=only_groups)),
118
184
  validator=children_validator,
119
- widget=ChildrenWidget(category="structural"),
185
+ widget=ChildrenWidget(child_input_name="treeitem_id", add_subitem=True, orderable=True),
120
186
  )
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2018-2020, Camptocamp SA
1
+ # Copyright (c) 2018-2021, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -38,6 +36,7 @@ from c2cgeoportal_admin.schemas.treegroup import base_deferred_parent_id_validat
38
36
 
39
37
  # Used for the creation of a new layer/layergroup from the layertree
40
38
  def parent_id_node(model):
39
+ """Get the scheme to the parent node ID."""
41
40
  return colander.SchemaNode(
42
41
  colander.Integer(),
43
42
  name="parent_id",