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.
- c2cgeoportal_admin/__init__.py +32 -10
- c2cgeoportal_admin/lib/lingua_extractor.py +77 -0
- c2cgeoportal_admin/lib/ogcserver_synchronizer.py +168 -56
- c2cgeoportal_admin/py.typed +0 -0
- c2cgeoportal_admin/routes.py +7 -4
- c2cgeoportal_admin/schemas/dimensions.py +12 -10
- c2cgeoportal_admin/schemas/functionalities.py +62 -21
- c2cgeoportal_admin/schemas/interfaces.py +22 -18
- c2cgeoportal_admin/schemas/metadata.py +100 -47
- c2cgeoportal_admin/schemas/restriction_areas.py +21 -19
- c2cgeoportal_admin/schemas/roles.py +7 -5
- c2cgeoportal_admin/schemas/treegroup.py +38 -17
- c2cgeoportal_admin/schemas/treeitem.py +2 -3
- c2cgeoportal_admin/static/layertree.css +3 -4
- c2cgeoportal_admin/static/navbar.css +36 -35
- c2cgeoportal_admin/static/theme.css +16 -9
- c2cgeoportal_admin/subscribers.py +3 -3
- c2cgeoportal_admin/templates/404.jinja2 +18 -2
- c2cgeoportal_admin/templates/layertree.jinja2 +31 -9
- c2cgeoportal_admin/templates/navigation_navbar.jinja2 +33 -0
- c2cgeoportal_admin/templates/ogcserver_synchronize.jinja2 +12 -0
- c2cgeoportal_admin/templates/widgets/functionality_fields.pt +51 -0
- c2cgeoportal_admin/templates/widgets/metadata.pt +7 -1
- c2cgeoportal_admin/views/__init__.py +29 -0
- c2cgeoportal_admin/views/dimension_layers.py +7 -6
- c2cgeoportal_admin/views/functionalities.py +33 -6
- c2cgeoportal_admin/views/home.py +5 -5
- c2cgeoportal_admin/views/interfaces.py +7 -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 +41 -35
- c2cgeoportal_admin/views/layers_wmts.py +41 -35
- c2cgeoportal_admin/views/layertree.py +36 -28
- c2cgeoportal_admin/views/logged_views.py +80 -0
- c2cgeoportal_admin/views/logs.py +90 -0
- c2cgeoportal_admin/views/oauth2_clients.py +30 -22
- c2cgeoportal_admin/views/ogc_servers.py +119 -37
- c2cgeoportal_admin/views/restriction_areas.py +13 -11
- c2cgeoportal_admin/views/roles.py +16 -12
- c2cgeoportal_admin/views/themes.py +15 -14
- c2cgeoportal_admin/views/themes_ordering.py +13 -8
- c2cgeoportal_admin/views/treeitems.py +14 -12
- c2cgeoportal_admin/views/users.py +12 -7
- c2cgeoportal_admin/widgets.py +17 -14
- {c2cgeoportal_admin-2.6.0.dist-info → c2cgeoportal_admin-2.8.1.180.dist-info}/METADATA +17 -8
- c2cgeoportal_admin-2.8.1.180.dist-info/RECORD +95 -0
- {c2cgeoportal_admin-2.6.0.dist-info → c2cgeoportal_admin-2.8.1.180.dist-info}/WHEEL +1 -1
- c2cgeoportal_admin-2.8.1.180.dist-info/entry_points.txt +6 -0
- tests/__init__.py +11 -12
- tests/conftest.py +2 -1
- tests/test_edit_url.py +11 -14
- tests/test_functionalities.py +52 -14
- tests/test_home.py +0 -1
- tests/test_interface.py +34 -11
- tests/test_layer_groups.py +57 -27
- tests/test_layers_vectortiles.py +43 -20
- tests/test_layers_wms.py +67 -45
- tests/test_layers_wmts.py +47 -26
- tests/test_layertree.py +99 -16
- tests/test_left_menu.py +0 -1
- tests/test_lingua_extractor_config.py +64 -0
- tests/test_logs.py +103 -0
- tests/test_main.py +3 -1
- tests/test_metadatas.py +34 -21
- tests/test_oauth2_clients.py +37 -9
- tests/test_ogc_servers.py +84 -35
- tests/test_restriction_areas.py +38 -15
- tests/test_role.py +68 -41
- tests/test_themes.py +71 -37
- tests/test_themes_ordering.py +1 -2
- tests/test_treegroup.py +2 -2
- tests/test_user.py +48 -17
- tests/themes_ordering.py +1 -2
- c2cgeoportal_admin/templates/navigation_vertical.jinja2 +0 -33
- c2cgeoportal_admin-2.6.0.dist-info/RECORD +0 -89
- c2cgeoportal_admin-2.6.0.dist-info/entry_points.txt +0 -3
- {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
|
-
#
|
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
|
-
|
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
|
@@ -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
|
-
|
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-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
|
-
|
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
|
+
|
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
|
-
"""
|
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,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(
|
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,
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
#
|
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
|
-
|
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
|
@@ -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(
|
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-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.
|
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": "
|
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(
|
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
|
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-{
|
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):
|
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-{
|
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
|
-
#
|
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:
|
117
|
+
content: '\e252';
|
119
118
|
}
|
120
119
|
.jstree-closed .jstree-icon.jstree-ocl:before {
|
121
|
-
content:
|
120
|
+
content: '\e250';
|
122
121
|
}
|
123
122
|
.jstree-leaf .jstree-icon.jstree-ocl:before {
|
124
|
-
content:
|
123
|
+
content: '';
|
125
124
|
}
|