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
@@ -32,12 +30,13 @@ from c2cgeoform.routes import register_route, register_routes
32
30
 
33
31
 
34
32
  def includeme(config):
33
+ """Initialize the Pyramid routes."""
35
34
  config.add_static_view("c2cgeoportal_admin_node_modules", "c2cgeoportal_admin:node_modules")
36
35
  config.override_asset(
37
36
  to_override="c2cgeoportal_admin:node_modules/", override_with="/opt/c2cgeoportal/admin/node_modules"
38
37
  )
39
38
  # Because c2cgeoform widgets target {root_package}:node_modules/...
40
- asset_spec = "{}:node_modules/".format(config.root_package.__name__)
39
+ asset_spec = f"{config.root_package.__name__}:node_modules/"
41
40
  config.add_static_view("root_package_node_modules", asset_spec)
42
41
  config.override_asset(to_override=asset_spec, override_with="/opt/c2cgeoportal/admin/node_modules")
43
42
 
@@ -51,31 +50,51 @@ def includeme(config):
51
50
  register_route(config, "layertree_delete", "/{application:admin}/layertree/delete/{item_id}")
52
51
  register_route(config, "convert_to_wms", "/{application:admin}/{table:layers_wmts}/{id}/convert_to_wms")
53
52
  register_route(config, "convert_to_wmts", "/{application:admin}/{table:layers_wms}/{id}/convert_to_wmts")
53
+ register_route(
54
+ config, "ogcserver_synchronize", "/{application:admin}/{table:ogc_servers}/{id}/synchronize"
55
+ )
54
56
 
55
57
  from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
56
- Role,
58
+ Functionality,
59
+ Interface,
60
+ LayerCOG,
61
+ LayerGroup,
62
+ LayerVectorTiles,
57
63
  LayerWMS,
58
64
  LayerWMTS,
59
- Theme,
60
- LayerGroup,
61
- Interface,
65
+ Log,
62
66
  OGCServer,
63
- Functionality,
64
67
  RestrictionArea,
68
+ Role,
69
+ Theme,
70
+ )
71
+ from c2cgeoportal_commons.models.static import ( # pylint: disable=import-outside-toplevel
72
+ OAuth2Client,
73
+ User,
65
74
  )
66
- from c2cgeoportal_commons.models.static import User # pylint: disable=import-outside-toplevel
75
+
76
+ authentication_configuration = config.registry.settings.get("authentication", {})
77
+ oidc_configuration = authentication_configuration.get("openid_connect", {})
78
+ oidc_enabled = oidc_configuration.get("enabled", False)
79
+ oidc_provide_roles = oidc_configuration.get("provide_roles", False)
80
+ oauth2_configuration = authentication_configuration.get("oauth2", {})
81
+ oauth2_enabled = oauth2_configuration.get("enabled", not oidc_enabled)
67
82
 
68
83
  visible_routes = [
69
84
  ("themes", Theme),
70
85
  ("layer_groups", LayerGroup),
71
86
  ("layers_wms", LayerWMS),
72
87
  ("layers_wmts", LayerWMTS),
88
+ ("layers_vectortiles", LayerVectorTiles),
89
+ ("layers_cog", LayerCOG),
73
90
  ("ogc_servers", OGCServer),
74
91
  ("restriction_areas", RestrictionArea),
75
- ("users", User),
92
+ *([("users", User)] if not oidc_enabled or not oidc_provide_roles else []),
76
93
  ("roles", Role),
77
94
  ("functionalities", Functionality),
78
95
  ("interfaces", Interface),
96
+ *([("oauth2_clients", OAuth2Client)] if oauth2_enabled else []),
97
+ ("logs", Log),
79
98
  ]
80
99
 
81
100
  admin_interface_config = config.registry.settings["admin_interface"]
@@ -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
@@ -28,16 +26,24 @@
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
28
 
31
- from c2cgeoform.schema import GeoFormSchemaNode
29
+ from typing import Any
30
+
32
31
  import colander
32
+ from c2cgeoform.schema import GeoFormSchemaNode
33
33
  from deform.widget import MappingWidget, SequenceWidget
34
+ from sqlalchemy.orm.attributes import InstrumentedAttribute
34
35
 
35
- from c2cgeoportal_admin import _
36
36
  from c2cgeoportal_commons.models.main import Dimension
37
37
 
38
- dimensions_schema_node = colander.SequenceSchema(
39
- GeoFormSchemaNode(Dimension, name="dimension", widget=MappingWidget(template="dimension")),
40
- name="dimensions",
41
- title=_("Dimensions"),
42
- widget=SequenceWidget(category="structural", template="dimensions"),
43
- )
38
+
39
+ def dimensions_schema_node(
40
+ prop: InstrumentedAttribute[Any], # pylint: disable=unsubscriptable-object
41
+ ) -> colander.SequenceSchema:
42
+ """Get the scheme of the dimensions."""
43
+ return colander.SequenceSchema(
44
+ GeoFormSchemaNode(Dimension, name="dimension", widget=MappingWidget(template="dimension")),
45
+ name=prop.key,
46
+ title=prop.info["colanderalchemy"]["title"],
47
+ description=prop.info["colanderalchemy"]["description"],
48
+ widget=SequenceWidget(category="structural", template="dimensions"),
49
+ )
@@ -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
@@ -28,28 +26,68 @@
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
28
 
29
+ from typing import Any
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
35
36
  from sqlalchemy.sql.functions import concat
36
37
 
37
38
  from c2cgeoportal_commons.models.main import Functionality
38
39
 
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
- )
40
+
41
+ def available_functionalities_for(settings: dict[str, Any], model: type[Any]) -> list[dict[str, Any]]:
42
+ """Return filtered list of functionality definitions."""
43
+ mapper = inspect(model)
44
+ relevant_for = {mapper.local_table.name}
45
+ return [
46
+ f
47
+ for f in settings["admin_interface"]["available_functionalities"]
48
+ if relevant_for & set(f.get("relevant_for", relevant_for))
49
+ ]
50
+
51
+
52
+ def functionalities_widget(model: type[Any]) -> colander.deferred:
53
+ """Return a colander deferred which itself returns a widget for the functionalities field."""
54
+
55
+ def create_widget(node, kw):
56
+ del node
57
+
58
+ return RelationCheckBoxListWidget(
59
+ select( # type: ignore[arg-type]
60
+ Functionality.id,
61
+ concat(Functionality.name, "=", Functionality.value).label("label"),
62
+ )
63
+ .where(
64
+ Functionality.name.in_(
65
+ [f["name"] for f in available_functionalities_for(kw["request"].registry.settings, model)]
66
+ )
67
+ )
68
+ .alias("functionality_labels"),
69
+ "id",
70
+ "label",
71
+ order_by="label",
72
+ edit_url=lambda request, value: request.route_url(
73
+ "c2cgeoform_item", table="functionalities", id=value
74
+ ),
75
+ )
76
+
77
+ return colander.deferred(create_widget)
78
+
79
+
80
+ def functionalities_schema_node(
81
+ prop: InstrumentedAttribute[Any], model: type[Any]
82
+ ) -> colander.SequenceSchema:
83
+ """Get the schema of the functionalities."""
84
+
85
+ return colander.SequenceSchema(
86
+ GeoFormManyToManySchemaNode(Functionality, None),
87
+ name=prop.key,
88
+ title=prop.info["colanderalchemy"]["title"],
89
+ description=prop.info["colanderalchemy"].get("description"),
90
+ widget=functionalities_widget(model),
91
+ validator=manytomany_validator,
92
+ missing=colander.drop,
93
+ )
@@ -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
@@ -28,24 +26,34 @@
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
28
 
29
+ from typing import Any
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.orm.attributes import InstrumentedAttribute
34
35
 
35
- from c2cgeoportal_admin import _
36
36
  from c2cgeoportal_commons.models.main import Interface
37
37
 
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
- )
38
+
39
+ def interfaces_schema_node(
40
+ prop: InstrumentedAttribute[Any], # pylint: disable=unsubscriptable-object
41
+ ) -> colander.SequenceSchema:
42
+ """Get the serializable representation of an interface."""
43
+ return colander.SequenceSchema(
44
+ GeoFormManyToManySchemaNode(Interface, None),
45
+ name=prop.key,
46
+ title=prop.info["colanderalchemy"]["title"],
47
+ description=prop.info["colanderalchemy"]["description"],
48
+ widget=RelationCheckBoxListWidget(
49
+ Interface,
50
+ "id",
51
+ "name",
52
+ order_by="name",
53
+ edit_url=lambda request, value: request.route_url(
54
+ "c2cgeoform_item", table="interfaces", id=value
55
+ ),
56
+ ),
57
+ validator=manytomany_validator,
58
+ missing=colander.drop,
59
+ )
@@ -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
@@ -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, 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.mapper import Mapper
37
38
 
38
39
  from c2cgeoportal_admin import _
39
40
  from c2cgeoportal_commons.lib.validators import url
40
41
  from c2cgeoportal_commons.models.main import Metadata
41
42
 
42
43
 
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"]}
44
+ def get_relevant_for(model: type[Any] | Mapper[Any]) -> set[str]:
45
+ """Return list of relevant_for values for passed class."""
46
+ mapper = inspect(model)
47
+ assert mapper is not None
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: type[Any]) -> 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: type[Any]) -> 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
116
+
117
+
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
95
137
 
96
138
 
97
139
  class MetadataSchemaNode(GeoFormSchemaNode): # pylint: disable=abstract-method
140
+ """The metadata schema."""
98
141
 
99
- metadata_definitions: Optional[Dict[str, Any]] = None
142
+ metadata_definitions: dict[str, Any] | None = 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
- self.available_types: List[str] = []
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[Any], model: type[Any]) -> 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-2024, 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,34 @@
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
28
 
29
+ from typing import Any
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.orm.attributes import InstrumentedAttribute
34
35
 
35
- from c2cgeoportal_admin import _
36
36
  from c2cgeoportal_commons.models.main import RestrictionArea
37
37
 
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
38
+
39
+ def restrictionareas_schema_node(
40
+ prop: InstrumentedAttribute[Any], # pylint: disable=unsubscriptable-object
41
+ ) -> colander.SequenceSchema:
42
+ """Get the schema of a restriction area."""
43
+ return colander.SequenceSchema(
44
+ GeoFormManyToManySchemaNode(RestrictionArea, None),
45
+ name=prop.key,
46
+ title=prop.info["colanderalchemy"]["title"],
47
+ description=prop.info["colanderalchemy"].get("description"),
48
+ widget=RelationCheckBoxListWidget(
49
+ RestrictionArea,
50
+ "id",
51
+ "name",
52
+ order_by="name",
53
+ edit_url=lambda request, value: request.route_url(
54
+ "c2cgeoform_item", table="restriction_areas", id=value
55
+ ),
49
56
  ),
50
- ),
51
- validator=manytomany_validator,
52
- missing=colander.drop,
53
- )
57
+ validator=manytomany_validator,
58
+ missing=colander.drop,
59
+ )
@@ -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
@@ -28,17 +26,25 @@
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
28
 
29
+ from typing import Any
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.orm.attributes import InstrumentedAttribute
34
35
 
35
36
  from c2cgeoportal_commons.models.main import Role
36
37
 
37
38
 
38
- def roles_schema_node(name):
39
+ def roles_schema_node(
40
+ prop: InstrumentedAttribute[Any], # pylint: disable=unsubscriptable-object
41
+ ) -> colander.SequenceSchema:
42
+ """Get the schema of all the items."""
39
43
  return colander.SequenceSchema(
40
- GeoFormManyToManySchemaNode(Role),
41
- name=name,
44
+ GeoFormManyToManySchemaNode(Role, None),
45
+ name=prop.key,
46
+ title=prop.info["colanderalchemy"]["title"],
47
+ description=prop.info["colanderalchemy"].get("description"),
42
48
  widget=RelationCheckBoxListWidget(
43
49
  Role,
44
50
  "id",