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
@@ -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
31
  import colander
32
32
  from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget
33
33
  from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator
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
@@ -31,21 +29,27 @@
31
29
  import colander
32
30
  from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget
33
31
  from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator
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-2021, Camptocamp SA
1
+ # Copyright (c) 2018-2023, 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
31
  import colander
32
+ import pyramid.request
35
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
+
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
+ ]
47
61
 
48
62
 
49
- class MetadataSelectWidget(SelectWidget):
50
- """Extends class SelectWidget to support undefined metadatas.
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,11 +112,11 @@ 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 BooleanMetadata(colander.Boolean):
98
- """Boolean metadata values are stored as string in database"""
118
+ class BooleanMetadata(colander.Boolean): # type: ignore
119
+ """Boolean metadata values are stored as string in database."""
99
120
 
100
121
  def serialize(self, node, appstruct):
101
122
  if appstruct == "true":
@@ -115,11 +136,12 @@ class BooleanMetadata(colander.Boolean):
115
136
  return None
116
137
 
117
138
 
118
- class MetadataSchemaNode(GeoFormSchemaNode): # pylint: disable=abstract-method
139
+ class MetadataSchemaNode(GeoFormSchemaNode): # type: ignore # pylint: disable=abstract-method
140
+ """The metadata schema."""
119
141
 
120
142
  metadata_definitions: Optional[Dict[str, Any]] = None
121
143
 
122
- def __init__(self, *args, **kw):
144
+ def __init__(self, *args: Any, **kw: Any):
123
145
  super().__init__(*args, **kw)
124
146
 
125
147
  self.available_types: List[str] = []
@@ -134,10 +156,17 @@ class MetadataSchemaNode(GeoFormSchemaNode): # pylint: disable=abstract-method
134
156
  "json", colander.String(), widget=TextAreaWidget(rows=10), validator=json_validator
135
157
  )
136
158
 
137
- 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:
138
160
  self.add_before(
139
161
  "description",
140
- 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
+ ),
141
170
  )
142
171
  self.available_types.append(type_name)
143
172
 
@@ -153,24 +182,48 @@ class MetadataSchemaNode(GeoFormSchemaNode): # pylint: disable=abstract-method
153
182
  dict_[self._ui_type(obj.name)] = value
154
183
  return dict_
155
184
 
156
- def _ui_type(self, metadata_name: str):
185
+ def _ui_type(self, metadata_name: str) -> str:
157
186
  metadata_type = (
158
- 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")
159
190
  )
160
191
  return metadata_type if metadata_type in self.available_types else "string"
161
192
 
162
193
 
163
- metadatas_schema_node = colander.SequenceSchema(
164
- MetadataSchemaNode(
165
- Metadata,
166
- name="metadata",
167
- metadata_definitions=metadata_definitions,
168
- validator=regex_validator,
169
- widget=MappingWidget(template="metadata"),
170
- overrides={"name": {"widget": metadata_name_widget}},
171
- ),
172
- name="metadatas",
173
- title=_("Metadatas"),
174
- metadata_definitions=metadata_definitions,
175
- widget=SequenceWidget(template="metadatas", category="structural"),
176
- )
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
@@ -31,23 +29,27 @@
31
29
  import colander
32
30
  from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget
33
31
  from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator
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
@@ -31,14 +29,18 @@
31
29
  import colander
32
30
  from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget
33
31
  from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator
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-2024, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -30,15 +28,18 @@
30
28
 
31
29
  import logging
32
30
  from functools import partial
31
+ from typing import Any, Dict, List, Optional
33
32
 
34
33
  import colander
34
+ import pyramid.request
35
35
  from c2cgeoform.schema import GeoFormSchemaNode
36
36
  from sqlalchemy.orm import aliased
37
37
  from sqlalchemy.sql.expression import case, func
38
38
 
39
39
  from c2cgeoportal_admin import _
40
40
  from c2cgeoportal_admin.widgets import ChildrenWidget, ChildWidget
41
- 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
42
43
 
43
44
  LOG = logging.getLogger(__name__)
44
45
 
@@ -48,11 +49,13 @@ ITEM_TYPE_ROUTE_MAP = {
48
49
  "group": "layer_groups",
49
50
  "layer": None,
50
51
  "l_wms": "layers_wms",
51
- "l_wmts": "layers_wms",
52
+ "l_wmts": "layers_wmts",
52
53
  }
53
54
 
54
55
 
55
- class ChildSchemaNode(GeoFormSchemaNode): # pylint: disable=abstract-method
56
+ class ChildSchemaNode(GeoFormSchemaNode): # type: ignore # pylint: disable=abstract-method
57
+ """Schema of the child nodes."""
58
+
56
59
  def objectify(self, dict_, context=None):
57
60
  if dict_.get("id", None):
58
61
  context = self.dbsession.query(LayergroupTreeitem).get(dict_["id"])
@@ -61,7 +64,11 @@ class ChildSchemaNode(GeoFormSchemaNode): # pylint: disable=abstract-method
61
64
  return context
62
65
 
63
66
 
64
- 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
65
72
  dbsession = kw["request"].dbsession
66
73
 
67
74
  group = case([(func.count(LayergroupTreeitem.id) == 0, "Unlinked")], else_="Others")
@@ -71,7 +78,7 @@ def treeitems(node, kw, only_groups=False): # pylint: disable=unused-argument
71
78
  .distinct()
72
79
  .outerjoin("parents_relation")
73
80
  .filter(TreeItem.item_type != "theme")
74
- .group_by(TreeItem.id)
81
+ .group_by(TreeItem)
75
82
  .order_by(group.desc(), TreeItem.name)
76
83
  )
77
84
 
@@ -101,7 +108,7 @@ def treeitems(node, kw, only_groups=False): # pylint: disable=unused-argument
101
108
  {
102
109
  "id": item.id,
103
110
  "label": item.name,
104
- "icon_class": "icon-{}".format(item.item_type),
111
+ "icon_class": f"icon-{item.item_type}",
105
112
  "edit_url": treeitem_edit_url(kw["request"], item),
106
113
  "group": group,
107
114
  }
@@ -110,6 +117,7 @@ def treeitems(node, kw, only_groups=False): # pylint: disable=unused-argument
110
117
 
111
118
 
112
119
  def children_validator(node, cstruct):
120
+ """Get the validator on the children nodes."""
113
121
  for dict_ in cstruct:
114
122
  if not dict_["treeitem_id"] in [item["id"] for item in node.candidates]:
115
123
  raise colander.Invalid(
@@ -120,31 +128,34 @@ def children_validator(node, cstruct):
120
128
  )
121
129
 
122
130
 
123
- 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
+
124
135
  def validator(node, cstruct):
125
136
  if kw["dbsession"].query(model).filter(model.id == cstruct).count() == 0:
126
- raise colander.Invalid(
127
- node, "Value {} does not exist in table {}".format(cstruct, model.__tablename__)
128
- )
137
+ raise colander.Invalid(node, f"Value {cstruct} does not exist in table {model.__tablename__}")
129
138
 
130
139
  return validator
131
140
 
132
141
 
133
- def treeitem_edit_url(request, treeitem):
142
+ def treeitem_edit_url(request: pyramid.request.Request, treeitem: TreeGroup) -> Optional[str]:
143
+ """Get the tree item editing URL."""
134
144
  if treeitem.item_type is None:
135
145
  return None
136
146
  table = ITEM_TYPE_ROUTE_MAP.get(treeitem.item_type, None)
137
147
  if table is None:
138
148
  LOG.warning("%s not found in ITEM_TYPE_ROUTE_MAP", treeitem.item_type)
139
149
  return None
140
- return request.route_url(
150
+ return request.route_url( # type: ignore
141
151
  "c2cgeoform_item",
142
152
  table=ITEM_TYPE_ROUTE_MAP[treeitem.item_type],
143
153
  id=treeitem.id,
144
154
  )
145
155
 
146
156
 
147
- def children_schema_node(only_groups=False):
157
+ def children_schema_node(only_groups: bool = False) -> colander.SequenceSchema:
158
+ """Geth the sequence to the children nodes."""
148
159
  return colander.SequenceSchema(
149
160
  ChildSchemaNode(
150
161
  LayergroupTreeitem,
@@ -153,12 +164,22 @@ def children_schema_node(only_groups=False):
153
164
  input_name="treeitem_id",
154
165
  model=TreeItem,
155
166
  label_field="name",
156
- icon_class=lambda treeitem: "icon-{}".format(treeitem.item_type),
167
+ icon_class=lambda treeitem: f"icon-{treeitem.item_type}",
157
168
  edit_url=treeitem_edit_url,
158
169
  ),
159
170
  ),
160
171
  name="children_relation",
161
172
  title=_("Children"),
173
+ description=Literal(
174
+ _(
175
+ """
176
+ <div class="help-block">
177
+ <p>The ordered children elements.</p>
178
+ <hr>
179
+ </div>
180
+ """
181
+ ),
182
+ ),
162
183
  candidates=colander.deferred(partial(treeitems, only_groups=only_groups)),
163
184
  validator=children_validator,
164
185
  widget=ChildrenWidget(child_input_name="treeitem_id", add_subitem=True, orderable=True),
@@ -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",
@@ -64,7 +64,6 @@
64
64
  padding-left: 0px;
65
65
  }
66
66
 
67
-
68
67
  .jstree-grid-wrapper .jstree-node,
69
68
  .jstree-grid-wrapper .jstree-grid-cell-regular {
70
69
  border-top: 1px solid #ddd;
@@ -115,11 +114,11 @@
115
114
  }
116
115
 
117
116
  .jstree-open .jstree-icon.jstree-ocl:before {
118
- content: "\e252";
117
+ content: '\e252';
119
118
  }
120
119
  .jstree-closed .jstree-icon.jstree-ocl:before {
121
- content: "\e250";
120
+ content: '\e250';
122
121
  }
123
122
  .jstree-leaf .jstree-icon.jstree-ocl:before {
124
- content: "";
123
+ content: '';
125
124
  }