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.
- c2cgeoportal_admin/__init__.py +44 -14
- c2cgeoportal_admin/lib/__init__.py +0 -0
- c2cgeoportal_admin/lib/lingva_extractor.py +77 -0
- c2cgeoportal_admin/lib/ogcserver_synchronizer.py +410 -0
- c2cgeoportal_admin/py.typed +0 -0
- c2cgeoportal_admin/routes.py +30 -11
- c2cgeoportal_admin/schemas/dimensions.py +17 -11
- c2cgeoportal_admin/schemas/functionalities.py +60 -22
- c2cgeoportal_admin/schemas/interfaces.py +27 -19
- c2cgeoportal_admin/schemas/metadata.py +122 -48
- c2cgeoportal_admin/schemas/restriction_areas.py +26 -20
- c2cgeoportal_admin/schemas/roles.py +13 -7
- c2cgeoportal_admin/schemas/treegroup.py +90 -20
- c2cgeoportal_admin/schemas/treeitem.py +3 -4
- c2cgeoportal_admin/static/layertree.css +26 -4
- c2cgeoportal_admin/static/navbar.css +59 -36
- c2cgeoportal_admin/static/theme.css +51 -11
- c2cgeoportal_admin/subscribers.py +3 -3
- c2cgeoportal_admin/templates/404.jinja2 +41 -2
- c2cgeoportal_admin/templates/edit.jinja2 +23 -0
- c2cgeoportal_admin/templates/home.jinja2 +23 -0
- c2cgeoportal_admin/templates/index.jinja2 +23 -0
- c2cgeoportal_admin/templates/layertree.jinja2 +55 -11
- c2cgeoportal_admin/templates/layout.jinja2 +23 -0
- c2cgeoportal_admin/templates/navigation_navbar.jinja2 +56 -0
- c2cgeoportal_admin/templates/ogcserver_synchronize.jinja2 +90 -0
- c2cgeoportal_admin/templates/widgets/child.pt +35 -3
- c2cgeoportal_admin/templates/widgets/children.pt +121 -92
- c2cgeoportal_admin/templates/widgets/dimension.pt +23 -0
- c2cgeoportal_admin/templates/widgets/dimensions.pt +23 -0
- c2cgeoportal_admin/templates/widgets/functionality_fields.pt +51 -0
- c2cgeoportal_admin/templates/widgets/layer_fields.pt +23 -0
- c2cgeoportal_admin/templates/widgets/layer_group_fields.pt +23 -0
- c2cgeoportal_admin/templates/widgets/layer_v1_fields.pt +23 -0
- c2cgeoportal_admin/templates/widgets/metadata.pt +30 -1
- c2cgeoportal_admin/templates/widgets/metadatas.pt +23 -0
- c2cgeoportal_admin/templates/widgets/ogcserver_fields.pt +23 -0
- c2cgeoportal_admin/templates/widgets/restriction_area_fields.pt +25 -9
- c2cgeoportal_admin/templates/widgets/role_fields.pt +52 -25
- c2cgeoportal_admin/templates/widgets/theme_fields.pt +23 -0
- c2cgeoportal_admin/templates/widgets/user_fields.pt +23 -0
- c2cgeoportal_admin/views/__init__.py +29 -0
- c2cgeoportal_admin/views/dimension_layers.py +14 -9
- c2cgeoportal_admin/views/functionalities.py +52 -18
- c2cgeoportal_admin/views/home.py +5 -5
- c2cgeoportal_admin/views/interfaces.py +29 -21
- c2cgeoportal_admin/views/layer_groups.py +36 -25
- c2cgeoportal_admin/views/layers.py +17 -13
- c2cgeoportal_admin/views/layers_cog.py +135 -0
- c2cgeoportal_admin/views/layers_vectortiles.py +62 -27
- c2cgeoportal_admin/views/layers_wms.py +61 -36
- c2cgeoportal_admin/views/layers_wmts.py +54 -32
- c2cgeoportal_admin/views/layertree.py +37 -28
- c2cgeoportal_admin/views/logged_views.py +83 -0
- c2cgeoportal_admin/views/logs.py +91 -0
- c2cgeoportal_admin/views/oauth2_clients.py +96 -0
- c2cgeoportal_admin/views/ogc_servers.py +192 -21
- c2cgeoportal_admin/views/restriction_areas.py +78 -25
- c2cgeoportal_admin/views/roles.py +88 -25
- c2cgeoportal_admin/views/themes.py +47 -35
- c2cgeoportal_admin/views/themes_ordering.py +44 -24
- c2cgeoportal_admin/views/treeitems.py +21 -17
- c2cgeoportal_admin/views/users.py +46 -26
- c2cgeoportal_admin/widgets.py +79 -28
- {c2cgeoportal_admin-2.5.0.100.dist-info → c2cgeoportal_admin-2.9rc44.dist-info}/METADATA +15 -13
- c2cgeoportal_admin-2.9rc44.dist-info/RECORD +97 -0
- {c2cgeoportal_admin-2.5.0.100.dist-info → c2cgeoportal_admin-2.9rc44.dist-info}/WHEEL +1 -1
- c2cgeoportal_admin-2.9rc44.dist-info/entry_points.txt +5 -0
- tests/__init__.py +36 -27
- tests/conftest.py +23 -24
- tests/test_edit_url.py +16 -19
- tests/test_functionalities.py +52 -14
- tests/test_home.py +0 -1
- tests/test_interface.py +35 -12
- tests/test_layer_groups.py +58 -32
- tests/test_layers_cog.py +243 -0
- tests/test_layers_vectortiles.py +46 -30
- tests/test_layers_wms.py +77 -82
- tests/test_layers_wmts.py +51 -30
- tests/test_layertree.py +107 -101
- tests/test_learn.py +1 -1
- tests/test_left_menu.py +0 -1
- tests/test_lingva_extractor_config.py +64 -0
- tests/test_logs.py +102 -0
- tests/test_main.py +4 -2
- tests/test_metadatas.py +79 -71
- tests/test_oauth2_clients.py +186 -0
- tests/test_ogc_servers.py +110 -28
- tests/test_restriction_areas.py +109 -20
- tests/test_role.py +142 -82
- tests/test_themes.py +75 -41
- tests/test_themes_ordering.py +1 -2
- tests/test_treegroup.py +2 -2
- tests/test_user.py +72 -70
- tests/themes_ordering.py +1 -2
- c2cgeoportal_admin/templates/navigation_vertical.jinja2 +0 -10
- c2cgeoportal_admin-2.5.0.100.dist-info/RECORD +0 -84
- c2cgeoportal_admin-2.5.0.100.dist-info/entry_points.txt +0 -3
- {c2cgeoportal_admin-2.5.0.100.dist-info → c2cgeoportal_admin-2.9rc44.dist-info}/top_level.txt +0 -0
c2cgeoportal_admin/routes.py
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
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/"
|
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
|
-
|
58
|
+
Functionality,
|
59
|
+
Interface,
|
60
|
+
LayerCOG,
|
61
|
+
LayerGroup,
|
62
|
+
LayerVectorTiles,
|
57
63
|
LayerWMS,
|
58
64
|
LayerWMTS,
|
59
|
-
|
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
|
-
|
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
|
-
#
|
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
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
#
|
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
|
34
|
-
from sqlalchemy import
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
"
|
47
|
-
"
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
#
|
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
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
#
|
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,
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
50
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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:
|
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:
|
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",
|
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(
|
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(
|
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
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|
-
#
|
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
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
52
|
-
|
53
|
-
)
|
57
|
+
validator=manytomany_validator,
|
58
|
+
missing=colander.drop,
|
59
|
+
)
|
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
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
|
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(
|
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=
|
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",
|