c2cgeoportal-admin 2.8.1.181__py3-none-any.whl → 2.9rc1__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 +14 -6
- c2cgeoportal_admin/lib/{lingua_extractor.py → lingva_extractor.py} +6 -6
- c2cgeoportal_admin/lib/ogcserver_synchronizer.py +6 -5
- c2cgeoportal_admin/routes.py +12 -3
- c2cgeoportal_admin/schemas/dimensions.py +4 -2
- c2cgeoportal_admin/schemas/functionalities.py +9 -12
- c2cgeoportal_admin/schemas/interfaces.py +7 -3
- c2cgeoportal_admin/schemas/metadata.py +13 -13
- c2cgeoportal_admin/schemas/restriction_areas.py +5 -3
- c2cgeoportal_admin/schemas/roles.py +7 -3
- c2cgeoportal_admin/schemas/treegroup.py +14 -10
- c2cgeoportal_admin/schemas/treeitem.py +2 -2
- c2cgeoportal_admin/static/theme.css +3 -0
- c2cgeoportal_admin/views/dimension_layers.py +11 -7
- c2cgeoportal_admin/views/functionalities.py +25 -18
- c2cgeoportal_admin/views/interfaces.py +22 -15
- c2cgeoportal_admin/views/layer_groups.py +33 -20
- c2cgeoportal_admin/views/layers.py +12 -9
- c2cgeoportal_admin/views/layers_cog.py +135 -0
- c2cgeoportal_admin/views/layers_vectortiles.py +42 -27
- c2cgeoportal_admin/views/layers_wms.py +47 -32
- c2cgeoportal_admin/views/layers_wmts.py +46 -32
- c2cgeoportal_admin/views/layertree.py +9 -8
- c2cgeoportal_admin/views/logged_views.py +15 -12
- c2cgeoportal_admin/views/logs.py +9 -8
- c2cgeoportal_admin/views/oauth2_clients.py +26 -22
- c2cgeoportal_admin/views/ogc_servers.py +48 -34
- c2cgeoportal_admin/views/restriction_areas.py +29 -19
- c2cgeoportal_admin/views/roles.py +29 -19
- c2cgeoportal_admin/views/themes.py +35 -24
- c2cgeoportal_admin/views/themes_ordering.py +11 -11
- c2cgeoportal_admin/views/treeitems.py +13 -11
- c2cgeoportal_admin/views/users.py +37 -22
- c2cgeoportal_admin/widgets.py +3 -3
- {c2cgeoportal_admin-2.8.1.181.dist-info → c2cgeoportal_admin-2.9rc1.dist-info}/METADATA +3 -12
- {c2cgeoportal_admin-2.8.1.181.dist-info → c2cgeoportal_admin-2.9rc1.dist-info}/RECORD +48 -46
- {c2cgeoportal_admin-2.8.1.181.dist-info → c2cgeoportal_admin-2.9rc1.dist-info}/WHEEL +1 -1
- c2cgeoportal_admin-2.9rc1.dist-info/entry_points.txt +5 -0
- tests/__init__.py +13 -8
- tests/conftest.py +20 -10
- tests/test_layers_cog.py +243 -0
- tests/test_layers_vectortiles.py +2 -7
- tests/{test_lingua_extractor_config.py → test_lingva_extractor_config.py} +3 -3
- tests/test_logs.py +3 -4
- tests/test_oauth2_clients.py +3 -2
- tests/test_role.py +3 -2
- tests/test_user.py +8 -2
- c2cgeoportal_admin-2.8.1.181.dist-info/entry_points.txt +0 -6
- {c2cgeoportal_admin-2.8.1.181.dist-info → c2cgeoportal_admin-2.9rc1.dist-info}/top_level.txt +0 -0
c2cgeoportal_admin/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2017-
|
1
|
+
# Copyright (c) 2017-2024, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -30,7 +30,7 @@ from typing import Any
|
|
30
30
|
|
31
31
|
import c2cgeoform
|
32
32
|
import c2cwsgiutils.pretty_json
|
33
|
-
import sqlalchemy
|
33
|
+
import sqlalchemy.orm.session
|
34
34
|
import zope.sqlalchemy
|
35
35
|
from c2c.template.config import config as configuration
|
36
36
|
from pkg_resources import resource_filename
|
@@ -52,8 +52,12 @@ _ = TranslationStringFactory("c2cgeoportal_admin")
|
|
52
52
|
|
53
53
|
def main(_, **settings):
|
54
54
|
"""Return a Pyramid WSGI application."""
|
55
|
-
|
56
|
-
|
55
|
+
app_cfg = settings.get("app.cfg")
|
56
|
+
assert app_cfg is not None
|
57
|
+
configuration.init(app_cfg)
|
58
|
+
conf = configuration.get_config()
|
59
|
+
assert conf is not None
|
60
|
+
settings.update(conf)
|
57
61
|
|
58
62
|
config = Configurator(settings=settings)
|
59
63
|
|
@@ -70,9 +74,13 @@ def main(_, **settings):
|
|
70
74
|
session_factory.configure(bind=engine)
|
71
75
|
|
72
76
|
def get_tm_session(
|
73
|
-
session_factory: sessionmaker
|
77
|
+
session_factory: sessionmaker[ # pylint: disable=unsubscriptable-object
|
78
|
+
sqlalchemy.orm.session.Session
|
79
|
+
],
|
80
|
+
transaction_manager: TransactionManager,
|
74
81
|
) -> sqlalchemy.orm.session.Session:
|
75
82
|
dbsession = session_factory()
|
83
|
+
assert isinstance(dbsession, sqlalchemy.orm.session.Session)
|
76
84
|
zope.sqlalchemy.register(dbsession, transaction_manager=transaction_manager)
|
77
85
|
return dbsession
|
78
86
|
|
@@ -95,7 +103,7 @@ def main(_, **settings):
|
|
95
103
|
property=True,
|
96
104
|
)
|
97
105
|
|
98
|
-
config.add_route("ogc_server_clear_cache", "/ogc_server_clear_cache/{id}")
|
106
|
+
config.add_route("ogc_server_clear_cache", "/admin/ogc_server_clear_cache/{id}")
|
99
107
|
|
100
108
|
config.add_subscriber(add_renderer_globals, BeforeRender)
|
101
109
|
config.add_subscriber(add_localizer, NewRequest)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2011-
|
1
|
+
# Copyright (c) 2011-2024, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -27,10 +27,10 @@
|
|
27
27
|
|
28
28
|
|
29
29
|
import os
|
30
|
-
from typing import Any
|
30
|
+
from typing import Any
|
31
31
|
|
32
32
|
import yaml
|
33
|
-
from
|
33
|
+
from lingva.extractors import Extractor, Message
|
34
34
|
|
35
35
|
|
36
36
|
class GeomapfishConfigExtractor(Extractor): # type: ignore
|
@@ -41,10 +41,10 @@ class GeomapfishConfigExtractor(Extractor): # type: ignore
|
|
41
41
|
def __call__(
|
42
42
|
self,
|
43
43
|
filename: str,
|
44
|
-
options:
|
45
|
-
fileobj:
|
44
|
+
options: dict[str, Any],
|
45
|
+
fileobj: dict[str, Any] | None = None,
|
46
46
|
lineno: int = 0,
|
47
|
-
) ->
|
47
|
+
) -> list[Message]:
|
48
48
|
del options, fileobj, lineno
|
49
49
|
|
50
50
|
print(f"Running {self.__class__.__name__} on {filename}")
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2020-
|
1
|
+
# Copyright (c) 2020-2024, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -29,7 +29,7 @@
|
|
29
29
|
import functools
|
30
30
|
import logging
|
31
31
|
from io import StringIO
|
32
|
-
from typing import Any, Optional,
|
32
|
+
from typing import Any, Optional, cast
|
33
33
|
from xml.etree.ElementTree import Element # nosec
|
34
34
|
|
35
35
|
import pyramid.request
|
@@ -202,7 +202,7 @@ class OGCServerSynchronizer:
|
|
202
202
|
self._logger.info("%s layers added", self._layers_added)
|
203
203
|
self._logger.info("%s layers removed", self._layers_removed)
|
204
204
|
|
205
|
-
def synchronize_layer(self, el: Element, parent:
|
205
|
+
def synchronize_layer(self, el: Element, parent: main.TreeGroup | None = None) -> main.TreeItem:
|
206
206
|
if el.find("Layer") is None:
|
207
207
|
tree_item = self.get_layer_wms(el, parent)
|
208
208
|
elif parent is None:
|
@@ -287,10 +287,11 @@ class OGCServerSynchronizer:
|
|
287
287
|
|
288
288
|
return group
|
289
289
|
|
290
|
-
def get_layer_wms(self, el: Element, parent:
|
290
|
+
def get_layer_wms(self, el: Element, parent: main.TreeGroup | None = None) -> main.LayerWMS:
|
291
291
|
name_el = el.find("Name")
|
292
292
|
assert name_el is not None
|
293
293
|
name = name_el.text
|
294
|
+
assert name is not None
|
294
295
|
|
295
296
|
layer = cast(
|
296
297
|
Optional[main.LayerWMS],
|
@@ -359,7 +360,7 @@ class OGCServerSynchronizer:
|
|
359
360
|
|
360
361
|
@functools.lru_cache(maxsize=10)
|
361
362
|
def wms_capabilities(self) -> bytes:
|
362
|
-
errors:
|
363
|
+
errors: set[str] = set()
|
363
364
|
url = get_url2(
|
364
365
|
f"The OGC server '{self._ogc_server.name}'",
|
365
366
|
self._ogc_server.url,
|
c2cgeoportal_admin/routes.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2017-
|
1
|
+
# Copyright (c) 2017-2024, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -57,6 +57,7 @@ def includeme(config):
|
|
57
57
|
from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
|
58
58
|
Functionality,
|
59
59
|
Interface,
|
60
|
+
LayerCOG,
|
60
61
|
LayerGroup,
|
61
62
|
LayerVectorTiles,
|
62
63
|
LayerWMS,
|
@@ -72,19 +73,27 @@ def includeme(config):
|
|
72
73
|
User,
|
73
74
|
)
|
74
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)
|
82
|
+
|
75
83
|
visible_routes = [
|
76
84
|
("themes", Theme),
|
77
85
|
("layer_groups", LayerGroup),
|
78
86
|
("layers_wms", LayerWMS),
|
79
87
|
("layers_wmts", LayerWMTS),
|
80
88
|
("layers_vectortiles", LayerVectorTiles),
|
89
|
+
("layers_cog", LayerCOG),
|
81
90
|
("ogc_servers", OGCServer),
|
82
91
|
("restriction_areas", RestrictionArea),
|
83
|
-
("users", User),
|
92
|
+
*([("users", User)] if not oidc_enabled or not oidc_provide_roles else []),
|
84
93
|
("roles", Role),
|
85
94
|
("functionalities", Functionality),
|
86
95
|
("interfaces", Interface),
|
87
|
-
("oauth2_clients", OAuth2Client),
|
96
|
+
*([("oauth2_clients", OAuth2Client)] if oauth2_enabled else []),
|
88
97
|
("logs", Log),
|
89
98
|
]
|
90
99
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2018-
|
1
|
+
# Copyright (c) 2018-2024, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -26,6 +26,8 @@
|
|
26
26
|
# either expressed or implied, of the FreeBSD Project.
|
27
27
|
|
28
28
|
|
29
|
+
from typing import Any
|
30
|
+
|
29
31
|
import colander
|
30
32
|
from c2cgeoform.schema import GeoFormSchemaNode
|
31
33
|
from deform.widget import MappingWidget, SequenceWidget
|
@@ -34,7 +36,7 @@ from sqlalchemy.orm.attributes import InstrumentedAttribute
|
|
34
36
|
from c2cgeoportal_commons.models.main import Dimension
|
35
37
|
|
36
38
|
|
37
|
-
def dimensions_schema_node(prop: InstrumentedAttribute) -> colander.SequenceSchema:
|
39
|
+
def dimensions_schema_node(prop: InstrumentedAttribute[Any]) -> colander.SequenceSchema:
|
38
40
|
"""Get the scheme of the dimensions."""
|
39
41
|
return colander.SequenceSchema(
|
40
42
|
GeoFormSchemaNode(Dimension, name="dimension", widget=MappingWidget(template="dimension")),
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2018-
|
1
|
+
# Copyright (c) 2018-2024, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -26,20 +26,19 @@
|
|
26
26
|
# either expressed or implied, of the FreeBSD Project.
|
27
27
|
|
28
28
|
|
29
|
-
from typing import Any
|
29
|
+
from typing import Any
|
30
30
|
|
31
31
|
import colander
|
32
32
|
from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget
|
33
33
|
from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator
|
34
34
|
from sqlalchemy import inspect, select
|
35
35
|
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
36
|
-
from sqlalchemy.orm.decl_api import DeclarativeMeta
|
37
36
|
from sqlalchemy.sql.functions import concat
|
38
37
|
|
39
38
|
from c2cgeoportal_commons.models.main import Functionality
|
40
39
|
|
41
40
|
|
42
|
-
def available_functionalities_for(settings:
|
41
|
+
def available_functionalities_for(settings: dict[str, Any], model: type[Any]) -> list[dict[str, Any]]:
|
43
42
|
"""Return filtered list of functionality definitions."""
|
44
43
|
mapper = inspect(model)
|
45
44
|
relevant_for = {mapper.local_table.name}
|
@@ -50,18 +49,16 @@ def available_functionalities_for(settings: Dict[str, Any], model: DeclarativeMe
|
|
50
49
|
]
|
51
50
|
|
52
51
|
|
53
|
-
def functionalities_widget(model:
|
52
|
+
def functionalities_widget(model: type[Any]) -> colander.deferred:
|
54
53
|
"""Return a colander deferred which itself returns a widget for the functionalities field."""
|
55
54
|
|
56
55
|
def create_widget(node, kw):
|
57
56
|
del node
|
58
57
|
|
59
58
|
return RelationCheckBoxListWidget(
|
60
|
-
select(
|
61
|
-
|
62
|
-
|
63
|
-
concat(Functionality.name, "=", Functionality.value).label("label"),
|
64
|
-
]
|
59
|
+
select( # type: ignore[arg-type]
|
60
|
+
Functionality.id,
|
61
|
+
concat(Functionality.name, "=", Functionality.value).label("label"),
|
65
62
|
)
|
66
63
|
.where(
|
67
64
|
Functionality.name.in_(
|
@@ -81,12 +78,12 @@ def functionalities_widget(model: DeclarativeMeta) -> colander.deferred:
|
|
81
78
|
|
82
79
|
|
83
80
|
def functionalities_schema_node(
|
84
|
-
prop: InstrumentedAttribute, model:
|
81
|
+
prop: InstrumentedAttribute[Any], model: type[Any]
|
85
82
|
) -> colander.SequenceSchema:
|
86
83
|
"""Get the schema of the functionalities."""
|
87
84
|
|
88
85
|
return colander.SequenceSchema(
|
89
|
-
GeoFormManyToManySchemaNode(Functionality),
|
86
|
+
GeoFormManyToManySchemaNode(Functionality, None),
|
90
87
|
name=prop.key,
|
91
88
|
title=prop.info["colanderalchemy"]["title"],
|
92
89
|
description=prop.info["colanderalchemy"].get("description"),
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2018-
|
1
|
+
# Copyright (c) 2018-2024, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -26,6 +26,8 @@
|
|
26
26
|
# either expressed or implied, of the FreeBSD Project.
|
27
27
|
|
28
28
|
|
29
|
+
from typing import Any
|
30
|
+
|
29
31
|
import colander
|
30
32
|
from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget
|
31
33
|
from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator
|
@@ -34,10 +36,12 @@ from sqlalchemy.orm.attributes import InstrumentedAttribute
|
|
34
36
|
from c2cgeoportal_commons.models.main import Interface
|
35
37
|
|
36
38
|
|
37
|
-
def interfaces_schema_node(
|
39
|
+
def interfaces_schema_node(
|
40
|
+
prop: InstrumentedAttribute[Any], # pylint: disable=unsubscriptable-object
|
41
|
+
) -> colander.SequenceSchema:
|
38
42
|
"""Get the serializable representation of an interface."""
|
39
43
|
return colander.SequenceSchema(
|
40
|
-
GeoFormManyToManySchemaNode(Interface),
|
44
|
+
GeoFormManyToManySchemaNode(Interface, None),
|
41
45
|
name=prop.key,
|
42
46
|
title=prop.info["colanderalchemy"]["title"],
|
43
47
|
description=prop.info["colanderalchemy"]["description"],
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2018-
|
1
|
+
# Copyright (c) 2018-2024, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -26,7 +26,7 @@
|
|
26
26
|
# either expressed or implied, of the FreeBSD Project.
|
27
27
|
|
28
28
|
import json
|
29
|
-
from typing import Any,
|
29
|
+
from typing import Any, cast
|
30
30
|
|
31
31
|
import colander
|
32
32
|
import pyramid.request
|
@@ -34,7 +34,6 @@ from c2cgeoform.schema import GeoFormSchemaNode
|
|
34
34
|
from deform.widget import MappingWidget, SelectWidget, SequenceWidget, TextAreaWidget
|
35
35
|
from sqlalchemy import inspect
|
36
36
|
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
37
|
-
from sqlalchemy.orm.decl_api import DeclarativeMeta
|
38
37
|
from sqlalchemy.orm.mapper import Mapper
|
39
38
|
|
40
39
|
from c2cgeoportal_admin import _
|
@@ -42,16 +41,17 @@ from c2cgeoportal_commons.lib.validators import url
|
|
42
41
|
from c2cgeoportal_commons.models.main import Metadata
|
43
42
|
|
44
43
|
|
45
|
-
def get_relevant_for(model:
|
44
|
+
def get_relevant_for(model: type[Any] | Mapper[Any]) -> set[str]:
|
46
45
|
"""Return list of relevant_for values for passed class."""
|
47
46
|
mapper = inspect(model)
|
47
|
+
assert mapper is not None
|
48
48
|
relevant_for = {mapper.local_table.name} # or mapper.polymorphic_identity
|
49
49
|
if mapper.inherits:
|
50
50
|
relevant_for |= get_relevant_for(mapper.inherits)
|
51
51
|
return relevant_for
|
52
52
|
|
53
53
|
|
54
|
-
def metadata_definitions(settings:
|
54
|
+
def metadata_definitions(settings: dict[str, Any], model: type[Any]) -> list[dict[str, Any]]:
|
55
55
|
"""Return filtered list metadata definitions."""
|
56
56
|
return [
|
57
57
|
m
|
@@ -76,7 +76,7 @@ class MetadataSelectWidget(SelectWidget): # type: ignore
|
|
76
76
|
return super().serialize(field, cstruct, **kw)
|
77
77
|
|
78
78
|
|
79
|
-
def metadata_name_widget(model:
|
79
|
+
def metadata_name_widget(model: type[Any]) -> colander.deferred:
|
80
80
|
"""Return a colander deferred which itself returns a widget for the metadata name field."""
|
81
81
|
|
82
82
|
def create_widget(node, kw):
|
@@ -136,15 +136,15 @@ class BooleanMetadata(colander.Boolean): # type: ignore
|
|
136
136
|
return None
|
137
137
|
|
138
138
|
|
139
|
-
class MetadataSchemaNode(GeoFormSchemaNode): #
|
139
|
+
class MetadataSchemaNode(GeoFormSchemaNode): # pylint: disable=abstract-method
|
140
140
|
"""The metadata schema."""
|
141
141
|
|
142
|
-
metadata_definitions:
|
142
|
+
metadata_definitions: dict[str, Any] | None = None
|
143
143
|
|
144
144
|
def __init__(self, *args: Any, **kw: Any):
|
145
145
|
super().__init__(*args, **kw)
|
146
146
|
|
147
|
-
self.available_types:
|
147
|
+
self.available_types: list[str] = []
|
148
148
|
|
149
149
|
self._add_value_node("string", colander.String())
|
150
150
|
self._add_value_node("liste", colander.String())
|
@@ -184,7 +184,7 @@ class MetadataSchemaNode(GeoFormSchemaNode): # type: ignore # pylint: disable=a
|
|
184
184
|
|
185
185
|
def _ui_type(self, metadata_name: str) -> str:
|
186
186
|
metadata_type = (
|
187
|
-
cast(
|
187
|
+
cast(dict[str, dict[str, str]], self.metadata_definitions)
|
188
188
|
.get(metadata_name, {})
|
189
189
|
.get("type", "string")
|
190
190
|
)
|
@@ -192,15 +192,15 @@ class MetadataSchemaNode(GeoFormSchemaNode): # type: ignore # pylint: disable=a
|
|
192
192
|
|
193
193
|
|
194
194
|
def _translate_available_metadata(
|
195
|
-
available_metadata:
|
196
|
-
) ->
|
195
|
+
available_metadata: dict[str, Any], request: pyramid.request.Request
|
196
|
+
) -> dict[str, Any]:
|
197
197
|
result = {}
|
198
198
|
result.update(available_metadata)
|
199
199
|
result["description"] = request.localizer.translate(_(available_metadata.get("description", "").strip()))
|
200
200
|
return result
|
201
201
|
|
202
202
|
|
203
|
-
def metadata_schema_node(prop: InstrumentedAttribute, model:
|
203
|
+
def metadata_schema_node(prop: InstrumentedAttribute[Any], model: type[Any]) -> colander.SequenceSchema:
|
204
204
|
"""Get the schema of a collection of metadata."""
|
205
205
|
|
206
206
|
# Deferred which returns a dictionary with metadata name as key and metadata definition as value.
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2017-
|
1
|
+
# Copyright (c) 2017-2024, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -26,6 +26,8 @@
|
|
26
26
|
# either expressed or implied, of the FreeBSD Project.
|
27
27
|
|
28
28
|
|
29
|
+
from typing import Any
|
30
|
+
|
29
31
|
import colander
|
30
32
|
from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget
|
31
33
|
from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator
|
@@ -34,10 +36,10 @@ from sqlalchemy.orm.attributes import InstrumentedAttribute
|
|
34
36
|
from c2cgeoportal_commons.models.main import RestrictionArea
|
35
37
|
|
36
38
|
|
37
|
-
def restrictionareas_schema_node(prop: InstrumentedAttribute) -> colander.SequenceSchema:
|
39
|
+
def restrictionareas_schema_node(prop: InstrumentedAttribute[Any]) -> colander.SequenceSchema:
|
38
40
|
"""Get the schema of a restriction area."""
|
39
41
|
return colander.SequenceSchema(
|
40
|
-
GeoFormManyToManySchemaNode(RestrictionArea),
|
42
|
+
GeoFormManyToManySchemaNode(RestrictionArea, None),
|
41
43
|
name=prop.key,
|
42
44
|
title=prop.info["colanderalchemy"]["title"],
|
43
45
|
description=prop.info["colanderalchemy"].get("description"),
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2018-
|
1
|
+
# Copyright (c) 2018-2024, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -26,6 +26,8 @@
|
|
26
26
|
# either expressed or implied, of the FreeBSD Project.
|
27
27
|
|
28
28
|
|
29
|
+
from typing import Any
|
30
|
+
|
29
31
|
import colander
|
30
32
|
from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget
|
31
33
|
from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator
|
@@ -34,10 +36,12 @@ from sqlalchemy.orm.attributes import InstrumentedAttribute
|
|
34
36
|
from c2cgeoportal_commons.models.main import Role
|
35
37
|
|
36
38
|
|
37
|
-
def roles_schema_node(
|
39
|
+
def roles_schema_node(
|
40
|
+
prop: InstrumentedAttribute[Any], # pylint: disable=unsubscriptable-object
|
41
|
+
) -> colander.SequenceSchema:
|
38
42
|
"""Get the schema of all the items."""
|
39
43
|
return colander.SequenceSchema(
|
40
|
-
GeoFormManyToManySchemaNode(Role),
|
44
|
+
GeoFormManyToManySchemaNode(Role, None),
|
41
45
|
name=prop.key,
|
42
46
|
title=prop.info["colanderalchemy"]["title"],
|
43
47
|
description=prop.info["colanderalchemy"].get("description"),
|
@@ -28,10 +28,11 @@
|
|
28
28
|
|
29
29
|
import logging
|
30
30
|
from functools import partial
|
31
|
-
from typing import Any
|
31
|
+
from typing import Any
|
32
32
|
|
33
33
|
import colander
|
34
34
|
import pyramid.request
|
35
|
+
import sqlalchemy
|
35
36
|
from c2cgeoform.schema import GeoFormSchemaNode
|
36
37
|
from sqlalchemy.orm import aliased
|
37
38
|
from sqlalchemy.sql.expression import case, func
|
@@ -41,7 +42,7 @@ from c2cgeoportal_admin.widgets import ChildrenWidget, ChildWidget
|
|
41
42
|
from c2cgeoportal_commons.lib.literal import Literal
|
42
43
|
from c2cgeoportal_commons.models.main import LayergroupTreeitem, TreeGroup, TreeItem
|
43
44
|
|
44
|
-
|
45
|
+
_LOG = logging.getLogger(__name__)
|
45
46
|
|
46
47
|
# Correspondence between TreeItem.item_type and route table segment
|
47
48
|
ITEM_TYPE_ROUTE_MAP = {
|
@@ -53,7 +54,7 @@ ITEM_TYPE_ROUTE_MAP = {
|
|
53
54
|
}
|
54
55
|
|
55
56
|
|
56
|
-
class ChildSchemaNode(GeoFormSchemaNode): #
|
57
|
+
class ChildSchemaNode(GeoFormSchemaNode): # pylint: disable=abstract-method
|
57
58
|
"""Schema of the child nodes."""
|
58
59
|
|
59
60
|
def objectify(self, dict_, context=None):
|
@@ -65,18 +66,21 @@ class ChildSchemaNode(GeoFormSchemaNode): # type: ignore # pylint: disable=abst
|
|
65
66
|
|
66
67
|
|
67
68
|
def treeitems(
|
68
|
-
node: TreeGroup, kw:
|
69
|
-
) ->
|
69
|
+
node: TreeGroup, kw: dict[str, pyramid.request.Request], only_groups: bool = False
|
70
|
+
) -> list[dict[str, Any]]:
|
70
71
|
"""Get a serializable representation of the tree items."""
|
71
72
|
del node
|
72
73
|
dbsession = kw["request"].dbsession
|
74
|
+
assert isinstance(dbsession, sqlalchemy.orm.Session)
|
73
75
|
|
74
|
-
group = case(
|
76
|
+
group = case(
|
77
|
+
(func.count(LayergroupTreeitem.id) == 0, "Unlinked"), else_="Others" # pylint: disable=not-callable
|
78
|
+
)
|
75
79
|
|
76
80
|
query = (
|
77
81
|
dbsession.query(TreeItem, group)
|
78
82
|
.distinct()
|
79
|
-
.outerjoin(
|
83
|
+
.outerjoin(TreeItem.parents_relation)
|
80
84
|
.filter(TreeItem.item_type != "theme")
|
81
85
|
.group_by(TreeItem)
|
82
86
|
.order_by(group.desc(), TreeItem.name)
|
@@ -93,7 +97,7 @@ def treeitems(
|
|
93
97
|
search_alias = aliased(search_ancestors, name="search_ancestors")
|
94
98
|
relation_alias = aliased(LayergroupTreeitem, name="relation")
|
95
99
|
search_ancestors = search_ancestors.union_all(
|
96
|
-
dbsession.query(relation_alias.treegroup_id).filter(
|
100
|
+
dbsession.query(relation_alias.treegroup_id).filter( # type: ignore[arg-type]
|
97
101
|
relation_alias.treeitem_id == search_alias.c.treegroup_id
|
98
102
|
)
|
99
103
|
)
|
@@ -139,13 +143,13 @@ def base_deferred_parent_id_validator(node, kw, model):
|
|
139
143
|
return validator
|
140
144
|
|
141
145
|
|
142
|
-
def treeitem_edit_url(request: pyramid.request.Request, treeitem: TreeGroup) ->
|
146
|
+
def treeitem_edit_url(request: pyramid.request.Request, treeitem: TreeGroup) -> str | None:
|
143
147
|
"""Get the tree item editing URL."""
|
144
148
|
if treeitem.item_type is None:
|
145
149
|
return None
|
146
150
|
table = ITEM_TYPE_ROUTE_MAP.get(treeitem.item_type, None)
|
147
151
|
if table is None:
|
148
|
-
|
152
|
+
_LOG.warning("%s not found in ITEM_TYPE_ROUTE_MAP", treeitem.item_type)
|
149
153
|
return None
|
150
154
|
return request.route_url( # type: ignore
|
151
155
|
"c2cgeoform_item",
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2018-
|
1
|
+
# Copyright (c) 2018-2024, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -35,7 +35,7 @@ from c2cgeoportal_admin.schemas.treegroup import base_deferred_parent_id_validat
|
|
35
35
|
|
36
36
|
|
37
37
|
# Used for the creation of a new layer/layergroup from the layertree
|
38
|
-
def parent_id_node(model):
|
38
|
+
def parent_id_node(model: type) -> colander.SchemaNode:
|
39
39
|
"""Get the scheme to the parent node ID."""
|
40
40
|
return colander.SchemaNode(
|
41
41
|
colander.Integer(),
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2017-
|
1
|
+
# Copyright (c) 2017-2024, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -28,9 +28,9 @@
|
|
28
28
|
|
29
29
|
from functools import partial
|
30
30
|
from itertools import groupby
|
31
|
-
from typing import cast
|
31
|
+
from typing import Generic, TypeVar, cast
|
32
32
|
|
33
|
-
import sqlalchemy
|
33
|
+
import sqlalchemy.orm.query
|
34
34
|
from c2cgeoform.views.abstract_views import ListField
|
35
35
|
from sqlalchemy.orm import subqueryload
|
36
36
|
|
@@ -39,8 +39,10 @@ from c2cgeoportal_commons.models.main import DimensionLayer
|
|
39
39
|
|
40
40
|
_list_field = partial(ListField, DimensionLayer)
|
41
41
|
|
42
|
+
_T = TypeVar("_T", bound=DimensionLayer)
|
42
43
|
|
43
|
-
|
44
|
+
|
45
|
+
class DimensionLayerViews(LayerViews[_T], Generic[_T]):
|
44
46
|
"""The layer with dimensions administration view."""
|
45
47
|
|
46
48
|
_extra_list_fields = [
|
@@ -53,7 +55,9 @@ class DimensionLayerViews(LayerViews):
|
|
53
55
|
]
|
54
56
|
),
|
55
57
|
)
|
56
|
-
] + LayerViews._extra_list_fields
|
58
|
+
] + LayerViews._extra_list_fields # pylint: disable=protected-access
|
57
59
|
|
58
|
-
def
|
59
|
-
|
60
|
+
def _sub_query(
|
61
|
+
self, query: sqlalchemy.orm.query.Query[DimensionLayer]
|
62
|
+
) -> sqlalchemy.orm.query.Query[DimensionLayer]:
|
63
|
+
return super()._sub_query(query.options(subqueryload(DimensionLayer.dimensions)))
|