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.
- c2cgeoportal_admin/__init__.py +19 -12
- c2cgeoportal_admin/lib/__init__.py +0 -0
- c2cgeoportal_admin/lib/lingua_extractor.py +77 -0
- c2cgeoportal_admin/lib/ogcserver_synchronizer.py +409 -0
- c2cgeoportal_admin/py.typed +0 -0
- c2cgeoportal_admin/routes.py +18 -10
- c2cgeoportal_admin/schemas/dimensions.py +13 -11
- c2cgeoportal_admin/schemas/functionalities.py +63 -22
- c2cgeoportal_admin/schemas/interfaces.py +23 -19
- c2cgeoportal_admin/schemas/metadata.py +121 -47
- c2cgeoportal_admin/schemas/restriction_areas.py +22 -20
- c2cgeoportal_admin/schemas/roles.py +8 -6
- c2cgeoportal_admin/schemas/treegroup.py +84 -18
- c2cgeoportal_admin/schemas/treeitem.py +2 -3
- c2cgeoportal_admin/static/layertree.css +26 -4
- c2cgeoportal_admin/static/navbar.css +59 -36
- c2cgeoportal_admin/static/theme.css +48 -11
- c2cgeoportal_admin/subscribers.py +3 -3
- c2cgeoportal_admin/templates/404.jinja2 +23 -0
- 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/dimension_layers.py +7 -6
- c2cgeoportal_admin/views/functionalities.py +31 -5
- c2cgeoportal_admin/views/home.py +5 -5
- c2cgeoportal_admin/views/interfaces.py +8 -8
- c2cgeoportal_admin/views/layer_groups.py +9 -11
- c2cgeoportal_admin/views/layers.py +8 -7
- c2cgeoportal_admin/views/layers_vectortiles.py +30 -10
- c2cgeoportal_admin/views/layers_wms.py +45 -37
- c2cgeoportal_admin/views/layers_wmts.py +39 -33
- c2cgeoportal_admin/views/layertree.py +34 -26
- c2cgeoportal_admin/views/oauth2_clients.py +89 -0
- c2cgeoportal_admin/views/ogc_servers.py +130 -27
- c2cgeoportal_admin/views/restriction_areas.py +50 -8
- c2cgeoportal_admin/views/roles.py +60 -8
- c2cgeoportal_admin/views/themes.py +15 -14
- c2cgeoportal_admin/views/themes_ordering.py +38 -18
- c2cgeoportal_admin/views/treeitems.py +12 -11
- c2cgeoportal_admin/views/users.py +7 -5
- c2cgeoportal_admin/widgets.py +79 -28
- {c2cgeoportal_admin-2.5.0.100.dist-info → c2cgeoportal_admin-2.7.1.156.dist-info}/METADATA +16 -11
- c2cgeoportal_admin-2.7.1.156.dist-info/RECORD +92 -0
- {c2cgeoportal_admin-2.5.0.100.dist-info → c2cgeoportal_admin-2.7.1.156.dist-info}/WHEEL +1 -1
- c2cgeoportal_admin-2.7.1.156.dist-info/entry_points.txt +5 -0
- tests/__init__.py +23 -18
- tests/conftest.py +4 -15
- tests/test_edit_url.py +16 -18
- tests/test_functionalities.py +23 -10
- tests/test_interface.py +8 -8
- tests/test_layer_groups.py +15 -23
- tests/test_layers_vectortiles.py +16 -20
- tests/test_layers_wms.py +37 -75
- tests/test_layers_wmts.py +20 -24
- tests/test_layertree.py +107 -100
- tests/test_learn.py +1 -1
- tests/test_lingua_extractor_config.py +66 -0
- tests/test_main.py +4 -2
- tests/test_metadatas.py +79 -70
- tests/test_oauth2_clients.py +157 -0
- tests/test_ogc_servers.py +51 -7
- tests/test_restriction_areas.py +81 -17
- tests/test_role.py +110 -76
- tests/test_themes.py +44 -37
- tests/test_themes_ordering.py +1 -1
- tests/test_treegroup.py +2 -2
- tests/test_user.py +31 -64
- tests/themes_ordering.py +1 -1
- 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.7.1.156.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
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
|
34
|
-
from sqlalchemy import
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
"
|
47
|
-
"
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
#
|
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
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
"
|
45
|
-
"
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
#
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
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: 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
|
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",
|
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(Dict[str,
|
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, 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
|
-
#
|
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
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
"
|
45
|
-
"
|
46
|
-
|
47
|
-
|
48
|
-
"
|
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
|
-
|
52
|
-
|
53
|
-
)
|
53
|
+
validator=manytomany_validator,
|
54
|
+
missing=colander.drop,
|
55
|
+
)
|
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
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
|
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(
|
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=
|
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
|
-
#
|
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.
|
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(
|
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")
|
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
|
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
|
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):
|
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
|
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(
|
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
|
-
|
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(
|
185
|
+
widget=ChildrenWidget(child_input_name="treeitem_id", add_subitem=True, orderable=True),
|
120
186
|
)
|
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
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",
|