c2cgeoportal-commons 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.
Potentially problematic release.
This version of c2cgeoportal-commons might be problematic. Click here for more details.
- c2cgeoportal_commons/__init__.py +2 -5
- c2cgeoportal_commons/alembic/env.py +40 -28
- c2cgeoportal_commons/alembic/main/028477929d13_add_technical_roles.py +10 -7
- c2cgeoportal_commons/alembic/main/04f05bfbb05e_remove_the_old_is_expanded_column.py +60 -0
- c2cgeoportal_commons/alembic/main/116b9b79fc4d_internal_and_external_layer_tables_.py +42 -43
- c2cgeoportal_commons/alembic/main/1418cb05921b_merge_1_6_and_master_branches.py +7 -8
- c2cgeoportal_commons/alembic/main/164ac0819a61_add_image_format_to_wmts_layer.py +9 -6
- c2cgeoportal_commons/alembic/main/166ff2dcc48d_create_database.py +21 -24
- c2cgeoportal_commons/alembic/main/16e43f8c0330_remove_old_metadata_column.py +9 -6
- c2cgeoportal_commons/alembic/main/1d5d4abfebd1_add_restricted_theme.py +14 -8
- c2cgeoportal_commons/alembic/main/1de20166b274_remove_v1_artifacts.py +9 -6
- c2cgeoportal_commons/alembic/main/20137477bd02_update_icons_url.py +9 -6
- c2cgeoportal_commons/alembic/main/21f11066f8ec_trigger_on_role_updates_user_in_static.py +21 -20
- c2cgeoportal_commons/alembic/main/22e6dfb556de_add_description_tree_.py +9 -6
- c2cgeoportal_commons/alembic/main/29f2a32859ec_merge_1_6_and_master_branches.py +7 -8
- c2cgeoportal_commons/alembic/main/2b8ed8c1df94_set_layergroup_treeitem_is_as_a_primary_.py +9 -6
- c2cgeoportal_commons/alembic/main/32527659d57b_move_exclude_properties_from_layerv1_to_.py +15 -12
- c2cgeoportal_commons/alembic/main/32b21aa1d0ed_merge_e004f76e951a_and_e004f76e951a_.py +7 -8
- c2cgeoportal_commons/alembic/main/338b57593823_remove_trigger_on_role_name_change.py +25 -24
- c2cgeoportal_commons/alembic/main/415746eb9f6_changes_for_v2.py +45 -43
- c2cgeoportal_commons/alembic/main/44c91d82d419_add_table_log.py +70 -0
- c2cgeoportal_commons/alembic/main/5109242131ce_add_column_time_widget.py +10 -9
- c2cgeoportal_commons/alembic/main/52916d8fde8b_add_sql_fields_to_vector_tiles.py +58 -0
- c2cgeoportal_commons/alembic/main/53ba1a68d5fe_add_theme_to_fulltextsearch.py +9 -6
- c2cgeoportal_commons/alembic/main/54645a535ad6_add_ordering_in_relation.py +17 -14
- c2cgeoportal_commons/alembic/main/56dc90838d90_fix_removing_layerv1.py +10 -8
- c2cgeoportal_commons/alembic/main/596ba21e3833_separate_local_internal.py +13 -14
- c2cgeoportal_commons/alembic/main/678f88c7ad5e_add_vector_tiles_layers_table.py +9 -6
- c2cgeoportal_commons/alembic/main/6a412d9437b1_rename_serverogc_to_ogcserver.py +9 -6
- c2cgeoportal_commons/alembic/main/6d87fdad275a_convert_metadata_to_the_right_case.py +13 -20
- c2cgeoportal_commons/alembic/main/7530011a66a7_trigger_on_role_updates_user_in_static.py +21 -20
- c2cgeoportal_commons/alembic/main/78fd093c8393_add_api_s_intrfaces.py +27 -48
- c2cgeoportal_commons/alembic/main/7d271f4527cd_add_layer_column_in_layerv1_table.py +9 -6
- c2cgeoportal_commons/alembic/main/809650bd04c3_add_dimension_field.py +9 -6
- c2cgeoportal_commons/alembic/main/8117bb9bba16_use_dimension_on_all_the_layers.py +9 -6
- c2cgeoportal_commons/alembic/main/87f8330ed64e_add_missing_delete_cascades.py +9 -6
- c2cgeoportal_commons/alembic/main/9268a1dffac0_add_trigger_to_be_able_to_correctly_.py +11 -8
- c2cgeoportal_commons/alembic/main/94db7e7e5b21_merge_2_2_and_master_branches.py +7 -8
- c2cgeoportal_commons/alembic/main/951ff84bd8ec_be_able_to_delete_a_wms_layer_in_sql.py +9 -6
- c2cgeoportal_commons/alembic/main/a00109812f89_add_field_layer_wms_valid.py +9 -6
- c2cgeoportal_commons/alembic/main/a4f1aac9bda_merge_1_6_and_master_branches.py +7 -8
- c2cgeoportal_commons/alembic/main/b60f2a505f42_remame_uimetadata_to_metadata.py +9 -6
- c2cgeoportal_commons/alembic/main/c75124553bf3_remove_deprecated_columns.py +9 -6
- c2cgeoportal_commons/alembic/main/d48a63b348f1_change_mapserver_url_for_docker.py +13 -14
- c2cgeoportal_commons/alembic/main/d8ef99bc227e_be_able_to_delete_a_linked_functionality.py +15 -12
- c2cgeoportal_commons/alembic/main/daf738d5bae4_merge_2_0_and_master_branches.py +7 -8
- c2cgeoportal_commons/alembic/main/dba87f2647f9_merge_2_2_on_2_3.py +7 -8
- c2cgeoportal_commons/alembic/main/e004f76e951a_add_missing_not_null.py +15 -32
- c2cgeoportal_commons/alembic/main/e7e03dedade3_put_the_default_wms_server_in_the_.py +13 -14
- c2cgeoportal_commons/alembic/main/e85afd327ab3_cascade_deletes_to_tsearch.py +9 -6
- c2cgeoportal_commons/alembic/main/ec82a8906649_add_missing_on_delete_cascade_on_layer_.py +13 -10
- c2cgeoportal_commons/alembic/main/ee25d267bf46_main_interface_desktop.py +11 -14
- c2cgeoportal_commons/alembic/main/eeb345672454_merge_2_4_and_master_branches.py +7 -8
- c2cgeoportal_commons/alembic/static/0c640a58a09a_add_opt_key_column.py +9 -6
- c2cgeoportal_commons/alembic/static/107b81f5b9fe_add_missing_delete_cascades.py +9 -6
- c2cgeoportal_commons/alembic/static/1857owc78a07_add_last_login_and_expiration_date.py +7 -6
- c2cgeoportal_commons/alembic/static/1da396a88908_move_user_table_to_static_schema.py +18 -19
- c2cgeoportal_commons/alembic/static/3f89a7d71a5e_alter_column_url_to_remove_limitation.py +8 -7
- c2cgeoportal_commons/alembic/static/44c91d82d419_add_table_log.py +70 -0
- c2cgeoportal_commons/alembic/static/53d671b17b20_add_timezone_on_datetime_fields.py +9 -6
- c2cgeoportal_commons/alembic/static/5472fbc19f39_add_temp_password_column.py +9 -6
- c2cgeoportal_commons/alembic/static/76d72fb3fcb9_add_oauth2_pkce.py +68 -0
- c2cgeoportal_commons/alembic/static/7ef947f30f20_add_oauth2_tables.py +7 -6
- c2cgeoportal_commons/alembic/static/ae5e88f35669_add_table_user_role.py +15 -18
- c2cgeoportal_commons/alembic/static/bd029dbfc11a_fill_tech_data_column.py +10 -8
- c2cgeoportal_commons/lib/email_.py +11 -13
- c2cgeoportal_commons/lib/literal.py +44 -0
- c2cgeoportal_commons/lib/url.py +164 -57
- c2cgeoportal_commons/lib/validators.py +2 -4
- c2cgeoportal_commons/models/__init__.py +7 -10
- c2cgeoportal_commons/models/main.py +968 -127
- c2cgeoportal_commons/models/sqlalchemy.py +13 -18
- c2cgeoportal_commons/models/static.py +212 -34
- c2cgeoportal_commons/py.typed +0 -0
- c2cgeoportal_commons/testing/__init__.py +11 -10
- c2cgeoportal_commons/testing/initializedb.py +12 -12
- {c2cgeoportal_commons-2.6.0.dist-info → c2cgeoportal_commons-2.8.1.180.dist-info}/METADATA +16 -11
- c2cgeoportal_commons-2.8.1.180.dist-info/RECORD +83 -0
- {c2cgeoportal_commons-2.6.0.dist-info → c2cgeoportal_commons-2.8.1.180.dist-info}/WHEEL +1 -1
- tests/conftest.py +0 -2
- c2cgeoportal_commons-2.6.0.dist-info/RECORD +0 -76
- {c2cgeoportal_commons-2.6.0.dist-info → c2cgeoportal_commons-2.8.1.180.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
# Copyright (c) 2011-2021, Camptocamp SA
|
|
1
|
+
# Copyright (c) 2011-2023, Camptocamp SA
|
|
4
2
|
# All rights reserved.
|
|
5
3
|
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
|
@@ -27,23 +25,26 @@
|
|
|
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
|
-
# pylint: disable=no-member
|
|
31
|
-
|
|
32
28
|
|
|
29
|
+
import enum
|
|
33
30
|
import logging
|
|
31
|
+
import os
|
|
34
32
|
import re
|
|
35
|
-
from typing import Any, Dict, List, Optional, Set, Tuple, Union, cast
|
|
33
|
+
from typing import Any, Dict, List, Optional, Set, Tuple, Union, cast
|
|
36
34
|
|
|
35
|
+
import pyramid.request
|
|
36
|
+
import sqlalchemy.orm.base
|
|
37
37
|
from c2c.template.config import config
|
|
38
38
|
from geoalchemy2 import Geometry
|
|
39
39
|
from geoalchemy2.shape import to_shape
|
|
40
40
|
from papyrus.geo_interface import GeoInterface
|
|
41
|
-
from pyramid.request import Request
|
|
42
41
|
from sqlalchemy import Column, ForeignKey, Table, UniqueConstraint, event
|
|
42
|
+
from sqlalchemy.ext.declarative import AbstractConcreteBase
|
|
43
43
|
from sqlalchemy.orm import Session, backref, relationship
|
|
44
44
|
from sqlalchemy.schema import Index
|
|
45
|
-
from sqlalchemy.types import Boolean, Enum, Integer, String, Unicode
|
|
45
|
+
from sqlalchemy.types import Boolean, DateTime, Enum, Integer, String, Unicode
|
|
46
46
|
|
|
47
|
+
from c2cgeoportal_commons.lib.literal import Literal
|
|
47
48
|
from c2cgeoportal_commons.lib.url import get_url2
|
|
48
49
|
from c2cgeoportal_commons.models import Base, _, cache_invalidate_cb
|
|
49
50
|
from c2cgeoportal_commons.models.sqlalchemy import JSONEncodedDict, TsVector
|
|
@@ -59,6 +60,8 @@ except ModuleNotFoundError:
|
|
|
59
60
|
default_map_settings = {"srid": 3857, "view": {"projection": "EPSG:3857"}}
|
|
60
61
|
|
|
61
62
|
class GenericClass:
|
|
63
|
+
"""Fallback class implementation."""
|
|
64
|
+
|
|
62
65
|
def __init__(self, *args: Any, **kwargs: Any):
|
|
63
66
|
pass
|
|
64
67
|
|
|
@@ -72,20 +75,33 @@ except ModuleNotFoundError:
|
|
|
72
75
|
TextInputWidget = GenericClass
|
|
73
76
|
|
|
74
77
|
|
|
78
|
+
if os.environ.get("DEVELOPMENT", "0") == "1":
|
|
79
|
+
|
|
80
|
+
def state_str(state: Any) -> str:
|
|
81
|
+
"""Return a string describing an instance via its InstanceState."""
|
|
82
|
+
|
|
83
|
+
return "None" if state is None else f"<{state.class_.__name__} {state.obj()}>"
|
|
84
|
+
|
|
85
|
+
# In the original function sqlalchemy use the id of the object that don't allow us to give some useful
|
|
86
|
+
# information
|
|
87
|
+
sqlalchemy.orm.base.state_str = state_str
|
|
88
|
+
|
|
75
89
|
LOG = logging.getLogger(__name__)
|
|
76
90
|
|
|
77
91
|
_schema: str = config["schema"] or "main"
|
|
78
92
|
_srid: int = cast(int, config["srid"]) or 3857
|
|
79
93
|
|
|
80
94
|
# Set some default values for the admin interface
|
|
81
|
-
_admin_config: Dict = config.get_config().get("admin_interface", {})
|
|
82
|
-
_map_config: Dict = {**default_map_settings, **_admin_config.get("map", {})}
|
|
95
|
+
_admin_config: Dict[str, Any] = config.get_config().get("admin_interface", {})
|
|
96
|
+
_map_config: Dict[str, Any] = {**default_map_settings, **_admin_config.get("map", {})}
|
|
83
97
|
view_srid_match = re.match(r"EPSG:(\d+)", _map_config["view"]["projection"])
|
|
84
98
|
if "map_srid" not in _admin_config and view_srid_match is not None:
|
|
85
99
|
_admin_config["map_srid"] = view_srid_match.group(1)
|
|
86
100
|
|
|
87
101
|
|
|
88
|
-
class FullTextSearch(GeoInterface, Base):
|
|
102
|
+
class FullTextSearch(GeoInterface, Base): # type: ignore
|
|
103
|
+
"""The tsearch table representation."""
|
|
104
|
+
|
|
89
105
|
__tablename__ = "tsearch"
|
|
90
106
|
__table_args__ = (Index("tsearch_ts_idx", "ts", postgresql_using="gin"), {"schema": _schema})
|
|
91
107
|
|
|
@@ -104,8 +120,13 @@ class FullTextSearch(GeoInterface, Base):
|
|
|
104
120
|
actions = Column(JSONEncodedDict, nullable=True)
|
|
105
121
|
from_theme = Column(Boolean, server_default="false")
|
|
106
122
|
|
|
123
|
+
def __str__(self) -> str:
|
|
124
|
+
return f"{self.label}[{self.id}]"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class Functionality(Base): # type: ignore
|
|
128
|
+
"""The functionality table representation."""
|
|
107
129
|
|
|
108
|
-
class Functionality(Base):
|
|
109
130
|
__tablename__ = "functionality"
|
|
110
131
|
__table_args__ = {"schema": _schema}
|
|
111
132
|
__colanderalchemy_config__ = {"title": _("Functionality"), "plural": _("Functionalities")}
|
|
@@ -113,9 +134,45 @@ class Functionality(Base):
|
|
|
113
134
|
__c2cgeoform_config__ = {"duplicate": True}
|
|
114
135
|
|
|
115
136
|
id = Column(Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}})
|
|
116
|
-
name = Column(
|
|
117
|
-
|
|
118
|
-
|
|
137
|
+
name = Column(
|
|
138
|
+
Unicode,
|
|
139
|
+
nullable=False,
|
|
140
|
+
info={
|
|
141
|
+
"colanderalchemy": {
|
|
142
|
+
"title": _("Name"),
|
|
143
|
+
"description": _("Name of the functionality."),
|
|
144
|
+
"widget": SelectWidget(
|
|
145
|
+
values=[("", _("- Select -"))]
|
|
146
|
+
+ [
|
|
147
|
+
(f["name"], f["name"])
|
|
148
|
+
for f in sorted(
|
|
149
|
+
_admin_config.get("available_functionalities", []),
|
|
150
|
+
key=lambda f: cast(str, f["name"]),
|
|
151
|
+
)
|
|
152
|
+
],
|
|
153
|
+
),
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
)
|
|
157
|
+
description = Column(
|
|
158
|
+
Unicode,
|
|
159
|
+
info={
|
|
160
|
+
"colanderalchemy": {
|
|
161
|
+
"title": _("Description"),
|
|
162
|
+
"description": _("An optional description."),
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
)
|
|
166
|
+
value = Column(
|
|
167
|
+
Unicode,
|
|
168
|
+
nullable=False,
|
|
169
|
+
info={
|
|
170
|
+
"colanderalchemy": {
|
|
171
|
+
"title": _("Value"),
|
|
172
|
+
"description": _("A value for the functionality."),
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
)
|
|
119
176
|
|
|
120
177
|
def __init__(self, name: str = "", value: str = "", description: str = "") -> None:
|
|
121
178
|
self.name = name
|
|
@@ -123,7 +180,7 @@ class Functionality(Base):
|
|
|
123
180
|
self.description = description
|
|
124
181
|
|
|
125
182
|
def __str__(self) -> str:
|
|
126
|
-
return "{}
|
|
183
|
+
return f"{self.name}={self.value}[{self.id}]"
|
|
127
184
|
|
|
128
185
|
|
|
129
186
|
event.listen(Functionality, "after_update", cache_invalidate_cb)
|
|
@@ -159,19 +216,41 @@ theme_functionality = Table(
|
|
|
159
216
|
)
|
|
160
217
|
|
|
161
218
|
|
|
162
|
-
class Role(Base):
|
|
219
|
+
class Role(Base): # type: ignore
|
|
220
|
+
"""The role table representation."""
|
|
221
|
+
|
|
163
222
|
__tablename__ = "role"
|
|
164
223
|
__table_args__ = {"schema": _schema}
|
|
165
224
|
__colanderalchemy_config__ = {"title": _("Role"), "plural": _("Roles")}
|
|
166
225
|
__c2cgeoform_config__ = {"duplicate": True}
|
|
167
226
|
|
|
168
227
|
id = Column(Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}})
|
|
169
|
-
name = Column(
|
|
170
|
-
|
|
228
|
+
name = Column(
|
|
229
|
+
Unicode,
|
|
230
|
+
unique=True,
|
|
231
|
+
nullable=False,
|
|
232
|
+
info={
|
|
233
|
+
"colanderalchemy": {
|
|
234
|
+
"title": _("Name"),
|
|
235
|
+
"description": _("A name for this role."),
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
)
|
|
239
|
+
description = Column(
|
|
240
|
+
Unicode,
|
|
241
|
+
info={
|
|
242
|
+
"colanderalchemy": {
|
|
243
|
+
"title": _("Description"),
|
|
244
|
+
"description": _("An optional description."),
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
)
|
|
171
248
|
extent = Column(
|
|
172
249
|
Geometry("POLYGON", srid=_srid),
|
|
173
250
|
info={
|
|
174
251
|
"colanderalchemy": {
|
|
252
|
+
"title": _("Extent"),
|
|
253
|
+
"description": _("Initial extent for this role."),
|
|
175
254
|
"typ": ColanderGeometry("POLYGON", srid=_srid, map_srid=_admin_config["map_srid"]),
|
|
176
255
|
"widget": MapWidget(map_options=_map_config),
|
|
177
256
|
}
|
|
@@ -183,14 +262,20 @@ class Role(Base):
|
|
|
183
262
|
"Functionality",
|
|
184
263
|
secondary=role_functionality,
|
|
185
264
|
cascade="save-update,merge,refresh-expire",
|
|
186
|
-
info={
|
|
265
|
+
info={
|
|
266
|
+
"colanderalchemy": {
|
|
267
|
+
"title": _("Functionalities"),
|
|
268
|
+
"description": _("Functionality values for this role."),
|
|
269
|
+
"exclude": True,
|
|
270
|
+
}
|
|
271
|
+
},
|
|
187
272
|
)
|
|
188
273
|
|
|
189
274
|
def __init__(
|
|
190
275
|
self,
|
|
191
276
|
name: str = "",
|
|
192
277
|
description: str = "",
|
|
193
|
-
functionalities: List[Functionality] = None,
|
|
278
|
+
functionalities: Optional[List[Functionality]] = None,
|
|
194
279
|
extent: Geometry = None,
|
|
195
280
|
) -> None:
|
|
196
281
|
if functionalities is None:
|
|
@@ -201,13 +286,13 @@ class Role(Base):
|
|
|
201
286
|
self.description = description
|
|
202
287
|
|
|
203
288
|
def __str__(self) -> str:
|
|
204
|
-
return self.name
|
|
289
|
+
return f"{self.name}[{self.id}]>"
|
|
205
290
|
|
|
206
291
|
@property
|
|
207
|
-
def bounds(self) ->
|
|
292
|
+
def bounds(self) -> Optional[Tuple[float, float, float, float]]: # TODO
|
|
208
293
|
if self.extent is None:
|
|
209
294
|
return None
|
|
210
|
-
return to_shape(self.extent).bounds
|
|
295
|
+
return cast(Tuple[float, float, float, float], to_shape(self.extent).bounds)
|
|
211
296
|
|
|
212
297
|
|
|
213
298
|
event.listen(Role.functionalities, "set", cache_invalidate_cb)
|
|
@@ -215,9 +300,11 @@ event.listen(Role.functionalities, "append", cache_invalidate_cb)
|
|
|
215
300
|
event.listen(Role.functionalities, "remove", cache_invalidate_cb)
|
|
216
301
|
|
|
217
302
|
|
|
218
|
-
class TreeItem(Base):
|
|
303
|
+
class TreeItem(Base): # type: ignore
|
|
304
|
+
"""The treeitem table representation."""
|
|
305
|
+
|
|
219
306
|
__tablename__ = "treeitem"
|
|
220
|
-
__table_args__: Union[Tuple, Dict[str, Any]] = (
|
|
307
|
+
__table_args__: Union[Tuple[Any, ...], Dict[str, Any]] = (
|
|
221
308
|
UniqueConstraint("type", "name"),
|
|
222
309
|
{"schema": _schema},
|
|
223
310
|
)
|
|
@@ -225,16 +312,38 @@ class TreeItem(Base):
|
|
|
225
312
|
__mapper_args__ = {"polymorphic_on": item_type}
|
|
226
313
|
|
|
227
314
|
id = Column(Integer, primary_key=True)
|
|
228
|
-
name = Column(
|
|
229
|
-
|
|
315
|
+
name = Column(
|
|
316
|
+
Unicode,
|
|
317
|
+
nullable=False,
|
|
318
|
+
info={
|
|
319
|
+
"colanderalchemy": {
|
|
320
|
+
"title": _("Name"),
|
|
321
|
+
"description": _(
|
|
322
|
+
"""
|
|
323
|
+
The name of the node, it is used through the i18n tools to display the name on the layers
|
|
324
|
+
tree.
|
|
325
|
+
"""
|
|
326
|
+
),
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
)
|
|
330
|
+
description = Column(
|
|
331
|
+
Unicode,
|
|
332
|
+
info={
|
|
333
|
+
"colanderalchemy": {
|
|
334
|
+
"title": _("Description"),
|
|
335
|
+
"description": _("An optional description."),
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
)
|
|
230
339
|
|
|
231
340
|
@property
|
|
232
|
-
# Better: def parents(self) -> List[TreeGroup]:
|
|
233
|
-
def parents(self) -> List["TreeItem"]:
|
|
341
|
+
# Better: def parents(self) -> List[TreeGroup]:
|
|
342
|
+
def parents(self) -> List["TreeItem"]:
|
|
234
343
|
return [c.treegroup for c in self.parents_relation]
|
|
235
344
|
|
|
236
345
|
def is_in_interface(self, name: str) -> bool:
|
|
237
|
-
if not hasattr(self, "interfaces"):
|
|
346
|
+
if not hasattr(self, "interfaces"):
|
|
238
347
|
return False
|
|
239
348
|
|
|
240
349
|
for interface in self.interfaces:
|
|
@@ -243,12 +352,15 @@ class TreeItem(Base):
|
|
|
243
352
|
|
|
244
353
|
return False
|
|
245
354
|
|
|
246
|
-
def
|
|
355
|
+
def get_metadata(self, name: str) -> List["Metadata"]:
|
|
247
356
|
return [metadata for metadata in self.metadatas if metadata.name == name]
|
|
248
357
|
|
|
249
358
|
def __init__(self, name: str = "") -> None:
|
|
250
359
|
self.name = name
|
|
251
360
|
|
|
361
|
+
def __str__(self) -> str:
|
|
362
|
+
return f"{self.name}[{self.id}]>"
|
|
363
|
+
|
|
252
364
|
|
|
253
365
|
event.listen(TreeItem, "after_insert", cache_invalidate_cb, propagate=True)
|
|
254
366
|
event.listen(TreeItem, "after_update", cache_invalidate_cb, propagate=True)
|
|
@@ -256,7 +368,9 @@ event.listen(TreeItem, "after_delete", cache_invalidate_cb, propagate=True)
|
|
|
256
368
|
|
|
257
369
|
|
|
258
370
|
# association table TreeGroup <> TreeItem
|
|
259
|
-
class LayergroupTreeitem(Base):
|
|
371
|
+
class LayergroupTreeitem(Base): # type: ignore
|
|
372
|
+
"""The layergroup_treeitem table representation."""
|
|
373
|
+
|
|
260
374
|
__tablename__ = "layergroup_treeitem"
|
|
261
375
|
__table_args__ = {"schema": _schema}
|
|
262
376
|
|
|
@@ -301,11 +415,16 @@ class LayergroupTreeitem(Base):
|
|
|
301
415
|
)
|
|
302
416
|
ordering = Column(Integer, info={"colanderalchemy": {"widget": HiddenWidget()}})
|
|
303
417
|
|
|
304
|
-
def __init__(
|
|
418
|
+
def __init__(
|
|
419
|
+
self, group: Optional["TreeGroup"] = None, item: Optional[TreeItem] = None, ordering: int = 0
|
|
420
|
+
) -> None:
|
|
305
421
|
self.treegroup = group
|
|
306
422
|
self.treeitem = item
|
|
307
423
|
self.ordering = ordering
|
|
308
424
|
|
|
425
|
+
def __str__(self) -> str:
|
|
426
|
+
return f"{self.id}"
|
|
427
|
+
|
|
309
428
|
|
|
310
429
|
event.listen(LayergroupTreeitem, "after_insert", cache_invalidate_cb, propagate=True)
|
|
311
430
|
event.listen(LayergroupTreeitem, "after_update", cache_invalidate_cb, propagate=True)
|
|
@@ -313,6 +432,8 @@ event.listen(LayergroupTreeitem, "after_delete", cache_invalidate_cb, propagate=
|
|
|
313
432
|
|
|
314
433
|
|
|
315
434
|
class TreeGroup(TreeItem):
|
|
435
|
+
"""The treegroup table representation."""
|
|
436
|
+
|
|
316
437
|
__tablename__ = "treegroup"
|
|
317
438
|
__table_args__ = {"schema": _schema}
|
|
318
439
|
__mapper_args__ = {"polymorphic_identity": "treegroup"} # needed for _identity_class
|
|
@@ -323,6 +444,17 @@ class TreeGroup(TreeItem):
|
|
|
323
444
|
return [c.treeitem for c in self.children_relation]
|
|
324
445
|
|
|
325
446
|
def _set_children(self, children: List[TreeItem], order: bool = False) -> None:
|
|
447
|
+
"""
|
|
448
|
+
Set the current TreeGroup children TreeItem instances.
|
|
449
|
+
|
|
450
|
+
By managing related LayergroupTreeitem instances.
|
|
451
|
+
|
|
452
|
+
If order is False:
|
|
453
|
+
Append new children at end of current ones.
|
|
454
|
+
|
|
455
|
+
If order is True:
|
|
456
|
+
Force order of children.
|
|
457
|
+
"""
|
|
326
458
|
for child in self.children_relation:
|
|
327
459
|
if child.treeitem not in children:
|
|
328
460
|
child.treeitem = None
|
|
@@ -336,9 +468,9 @@ class TreeGroup(TreeItem):
|
|
|
336
468
|
LayergroupTreeitem(self, child, index * 10)
|
|
337
469
|
self.children_relation.sort(key=lambda child: child.ordering)
|
|
338
470
|
else:
|
|
339
|
-
|
|
471
|
+
current_children = [child.treeitem for child in self.children_relation]
|
|
340
472
|
for index, item in enumerate(children):
|
|
341
|
-
if item not in
|
|
473
|
+
if item not in current_children:
|
|
342
474
|
LayergroupTreeitem(self, item, 1000000 + index)
|
|
343
475
|
for index, child in enumerate(self.children_relation):
|
|
344
476
|
child.ordering = index * 10
|
|
@@ -346,13 +478,30 @@ class TreeGroup(TreeItem):
|
|
|
346
478
|
children = property(_get_children, _set_children)
|
|
347
479
|
|
|
348
480
|
def __init__(self, name: str = "") -> None:
|
|
349
|
-
|
|
481
|
+
super().__init__(name=name)
|
|
350
482
|
|
|
351
483
|
|
|
352
484
|
class LayerGroup(TreeGroup):
|
|
485
|
+
"""The layergroup table representation."""
|
|
486
|
+
|
|
353
487
|
__tablename__ = "layergroup"
|
|
354
488
|
__table_args__ = {"schema": _schema}
|
|
355
|
-
__colanderalchemy_config__ = {
|
|
489
|
+
__colanderalchemy_config__ = {
|
|
490
|
+
"title": _("Layers group"),
|
|
491
|
+
"plural": _("Layers groups"),
|
|
492
|
+
"description": Literal(
|
|
493
|
+
_(
|
|
494
|
+
"""
|
|
495
|
+
<div class="help-block">
|
|
496
|
+
<h4>Background layers</h4>
|
|
497
|
+
<p>The background layers are configured in the database, with the layer group named
|
|
498
|
+
<b>background</b> (by default).</p>
|
|
499
|
+
<hr>
|
|
500
|
+
</div>
|
|
501
|
+
"""
|
|
502
|
+
)
|
|
503
|
+
),
|
|
504
|
+
}
|
|
356
505
|
__mapper_args__ = {"polymorphic_identity": "group"}
|
|
357
506
|
__c2cgeoform_config__ = {"duplicate": True}
|
|
358
507
|
|
|
@@ -362,13 +511,9 @@ class LayerGroup(TreeGroup):
|
|
|
362
511
|
primary_key=True,
|
|
363
512
|
info={"colanderalchemy": {"missing": drop, "widget": HiddenWidget()}},
|
|
364
513
|
)
|
|
365
|
-
is_expanded = Column(
|
|
366
|
-
Boolean, info={"colanderalchemy": {"title": _("Expanded"), "column": 2}}
|
|
367
|
-
) # shouldn't be used in V3
|
|
368
514
|
|
|
369
|
-
def __init__(self, name: str = ""
|
|
370
|
-
|
|
371
|
-
self.is_expanded = is_expanded
|
|
515
|
+
def __init__(self, name: str = "") -> None:
|
|
516
|
+
super().__init__(name=name)
|
|
372
517
|
|
|
373
518
|
|
|
374
519
|
# role theme link for restricted theme
|
|
@@ -382,6 +527,8 @@ restricted_role_theme = Table(
|
|
|
382
527
|
|
|
383
528
|
|
|
384
529
|
class Theme(TreeGroup):
|
|
530
|
+
"""The theme table representation."""
|
|
531
|
+
|
|
385
532
|
__tablename__ = "theme"
|
|
386
533
|
__table_args__ = {"schema": _schema}
|
|
387
534
|
__colanderalchemy_config__ = {"title": _("Theme"), "plural": _("Themes")}
|
|
@@ -397,15 +544,39 @@ class Theme(TreeGroup):
|
|
|
397
544
|
ordering = Column(
|
|
398
545
|
Integer, nullable=False, info={"colanderalchemy": {"title": _("Order"), "widget": HiddenWidget()}}
|
|
399
546
|
)
|
|
400
|
-
public = Column(
|
|
401
|
-
|
|
547
|
+
public = Column(
|
|
548
|
+
Boolean,
|
|
549
|
+
default=True,
|
|
550
|
+
nullable=False,
|
|
551
|
+
info={
|
|
552
|
+
"colanderalchemy": {
|
|
553
|
+
"title": _("Public"),
|
|
554
|
+
"description": _("Makes the theme public."),
|
|
555
|
+
}
|
|
556
|
+
},
|
|
557
|
+
)
|
|
558
|
+
icon = Column(
|
|
559
|
+
Unicode,
|
|
560
|
+
info={
|
|
561
|
+
"colanderalchemy": {
|
|
562
|
+
"title": _("Icon"),
|
|
563
|
+
"description": _("The icon URL."),
|
|
564
|
+
}
|
|
565
|
+
},
|
|
566
|
+
)
|
|
402
567
|
|
|
403
568
|
# functionality
|
|
404
569
|
functionalities = relationship(
|
|
405
570
|
"Functionality",
|
|
406
571
|
secondary=theme_functionality,
|
|
407
572
|
cascade="save-update,merge,refresh-expire",
|
|
408
|
-
info={
|
|
573
|
+
info={
|
|
574
|
+
"colanderalchemy": {
|
|
575
|
+
"title": _("Functionalities"),
|
|
576
|
+
"description": _("The linked functionalities."),
|
|
577
|
+
"exclude": True,
|
|
578
|
+
}
|
|
579
|
+
},
|
|
409
580
|
)
|
|
410
581
|
|
|
411
582
|
# restricted to role
|
|
@@ -413,11 +584,17 @@ class Theme(TreeGroup):
|
|
|
413
584
|
"Role",
|
|
414
585
|
secondary=restricted_role_theme,
|
|
415
586
|
cascade="save-update,merge,refresh-expire",
|
|
416
|
-
info={
|
|
587
|
+
info={
|
|
588
|
+
"colanderalchemy": {
|
|
589
|
+
"title": _("Roles"),
|
|
590
|
+
"description": _("Users with checked roles will get access to this theme."),
|
|
591
|
+
"exclude": True,
|
|
592
|
+
}
|
|
593
|
+
},
|
|
417
594
|
)
|
|
418
595
|
|
|
419
596
|
def __init__(self, name: str = "", ordering: int = 100, icon: str = "") -> None:
|
|
420
|
-
|
|
597
|
+
super().__init__(name=name)
|
|
421
598
|
self.ordering = ordering
|
|
422
599
|
self.icon = icon
|
|
423
600
|
|
|
@@ -428,6 +605,8 @@ event.listen(Theme.functionalities, "remove", cache_invalidate_cb)
|
|
|
428
605
|
|
|
429
606
|
|
|
430
607
|
class Layer(TreeItem):
|
|
608
|
+
"""The layer table representation."""
|
|
609
|
+
|
|
431
610
|
__tablename__ = "layer"
|
|
432
611
|
__table_args__ = {"schema": _schema}
|
|
433
612
|
__mapper_args__ = {"polymorphic_identity": "layer"} # needed for _identity_class
|
|
@@ -438,16 +617,49 @@ class Layer(TreeItem):
|
|
|
438
617
|
primary_key=True,
|
|
439
618
|
info={"colanderalchemy": {"widget": HiddenWidget()}},
|
|
440
619
|
)
|
|
441
|
-
public = Column(
|
|
442
|
-
|
|
443
|
-
|
|
620
|
+
public = Column(
|
|
621
|
+
Boolean,
|
|
622
|
+
default=True,
|
|
623
|
+
info={
|
|
624
|
+
"colanderalchemy": {
|
|
625
|
+
"title": _("Public"),
|
|
626
|
+
"description": _("Makes the layer public."),
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
)
|
|
630
|
+
geo_table = Column(
|
|
631
|
+
Unicode,
|
|
632
|
+
info={
|
|
633
|
+
"colanderalchemy": {
|
|
634
|
+
"title": _("Geo table"),
|
|
635
|
+
"description": _("The related database table, used by the editing module."),
|
|
636
|
+
}
|
|
637
|
+
},
|
|
638
|
+
)
|
|
639
|
+
exclude_properties = Column(
|
|
640
|
+
Unicode,
|
|
641
|
+
info={
|
|
642
|
+
"colanderalchemy": {
|
|
643
|
+
"title": _("Exclude properties"),
|
|
644
|
+
"description": _(
|
|
645
|
+
"""
|
|
646
|
+
The list of attributes (database columns) that should not appear in
|
|
647
|
+
the editing form so that they cannot be modified by the end user.
|
|
648
|
+
For enumerable attributes (foreign key), the column name should end with '_id'.
|
|
649
|
+
"""
|
|
650
|
+
),
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
)
|
|
444
654
|
|
|
445
655
|
def __init__(self, name: str = "", public: bool = True) -> None:
|
|
446
|
-
|
|
656
|
+
super().__init__(name=name)
|
|
447
657
|
self.public = public
|
|
448
658
|
|
|
449
659
|
|
|
450
660
|
class DimensionLayer(Layer):
|
|
661
|
+
"""The intermediate class for the leyser with dimension."""
|
|
662
|
+
|
|
451
663
|
__mapper_args__ = {"polymorphic_identity": "dimensionlayer"} # needed for _identity_class
|
|
452
664
|
|
|
453
665
|
|
|
@@ -463,16 +675,69 @@ OGCSERVER_AUTH_GEOSERVER = "Geoserver auth"
|
|
|
463
675
|
OGCSERVER_AUTH_PROXY = "Proxy"
|
|
464
676
|
|
|
465
677
|
|
|
466
|
-
class OGCServer(Base):
|
|
678
|
+
class OGCServer(Base): # type: ignore
|
|
679
|
+
"""The ogc_server table representation."""
|
|
680
|
+
|
|
467
681
|
__tablename__ = "ogc_server"
|
|
468
682
|
__table_args__ = {"schema": _schema}
|
|
469
|
-
__colanderalchemy_config__ = {
|
|
683
|
+
__colanderalchemy_config__ = {
|
|
684
|
+
"title": _("OGC Server"),
|
|
685
|
+
"plural": _("OGC Servers"),
|
|
686
|
+
"description": Literal(
|
|
687
|
+
_(
|
|
688
|
+
"""
|
|
689
|
+
<div class="help-block">
|
|
690
|
+
<p>This is the description of an OGC server (WMS/WFS).\n
|
|
691
|
+
For one server we try to create only one request when it is possible.</p>
|
|
692
|
+
<p>If you want to query the same server to get PNG and JPEG images,\n
|
|
693
|
+
you should define two <code>OGC servers</code>.</p>
|
|
694
|
+
<hr>
|
|
695
|
+
</div>
|
|
696
|
+
"""
|
|
697
|
+
)
|
|
698
|
+
),
|
|
699
|
+
}
|
|
470
700
|
__c2cgeoform_config__ = {"duplicate": True}
|
|
471
701
|
id = Column(Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}})
|
|
472
|
-
name = Column(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
702
|
+
name = Column(
|
|
703
|
+
Unicode,
|
|
704
|
+
nullable=False,
|
|
705
|
+
unique=True,
|
|
706
|
+
info={
|
|
707
|
+
"colanderalchemy": {
|
|
708
|
+
"title": _("Name"),
|
|
709
|
+
"description": _("The name of the OGC Server"),
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
)
|
|
713
|
+
description = Column(
|
|
714
|
+
Unicode,
|
|
715
|
+
info={
|
|
716
|
+
"colanderalchemy": {
|
|
717
|
+
"title": _("Description"),
|
|
718
|
+
"description": _("A description"),
|
|
719
|
+
}
|
|
720
|
+
},
|
|
721
|
+
)
|
|
722
|
+
url = Column(
|
|
723
|
+
Unicode,
|
|
724
|
+
nullable=False,
|
|
725
|
+
info={
|
|
726
|
+
"colanderalchemy": {
|
|
727
|
+
"title": _("Basic URL"),
|
|
728
|
+
"description": _("The server URL"),
|
|
729
|
+
}
|
|
730
|
+
},
|
|
731
|
+
)
|
|
732
|
+
url_wfs = Column(
|
|
733
|
+
Unicode,
|
|
734
|
+
info={
|
|
735
|
+
"colanderalchemy": {
|
|
736
|
+
"title": _("WFS URL"),
|
|
737
|
+
"description": _("The WFS server URL. If empty, the ``Basic URL`` is used."),
|
|
738
|
+
}
|
|
739
|
+
},
|
|
740
|
+
)
|
|
476
741
|
type = Column(
|
|
477
742
|
Enum(
|
|
478
743
|
OGCSERVER_TYPE_MAPSERVER,
|
|
@@ -486,6 +751,9 @@ class OGCServer(Base):
|
|
|
486
751
|
info={
|
|
487
752
|
"colanderalchemy": {
|
|
488
753
|
"title": _("Server type"),
|
|
754
|
+
"description": _(
|
|
755
|
+
"The server type which is used to know which custom attribute will be used."
|
|
756
|
+
),
|
|
489
757
|
"widget": SelectWidget(
|
|
490
758
|
values=(
|
|
491
759
|
(OGCSERVER_TYPE_MAPSERVER, OGCSERVER_TYPE_MAPSERVER),
|
|
@@ -504,6 +772,7 @@ class OGCServer(Base):
|
|
|
504
772
|
info={
|
|
505
773
|
"colanderalchemy": {
|
|
506
774
|
"title": _("Image type"),
|
|
775
|
+
"description": _("The MIME type of the images (e.g.: ``image/png``)."),
|
|
507
776
|
"widget": SelectWidget(values=(("image/jpeg", "image/jpeg"), ("image/png", "image/png"))),
|
|
508
777
|
"column": 2,
|
|
509
778
|
}
|
|
@@ -521,6 +790,7 @@ class OGCServer(Base):
|
|
|
521
790
|
info={
|
|
522
791
|
"colanderalchemy": {
|
|
523
792
|
"title": _("Authentication type"),
|
|
793
|
+
"description": "The kind of authentication to use.",
|
|
524
794
|
"widget": SelectWidget(
|
|
525
795
|
values=(
|
|
526
796
|
(OGCSERVER_AUTH_NOAUTH, OGCSERVER_AUTH_NOAUTH),
|
|
@@ -533,15 +803,33 @@ class OGCServer(Base):
|
|
|
533
803
|
}
|
|
534
804
|
},
|
|
535
805
|
)
|
|
536
|
-
wfs_support = Column(
|
|
537
|
-
|
|
806
|
+
wfs_support = Column(
|
|
807
|
+
Boolean,
|
|
808
|
+
info={
|
|
809
|
+
"colanderalchemy": {
|
|
810
|
+
"title": _("WFS support"),
|
|
811
|
+
"description": _("Whether WFS is supported by the server."),
|
|
812
|
+
"column": 2,
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
)
|
|
816
|
+
is_single_tile = Column(
|
|
817
|
+
Boolean,
|
|
818
|
+
info={
|
|
819
|
+
"colanderalchemy": {
|
|
820
|
+
"title": _("Single tile"),
|
|
821
|
+
"description": _("Whether to use the single tile mode (For future use)."),
|
|
822
|
+
"column": 2,
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
)
|
|
538
826
|
|
|
539
827
|
def __init__(
|
|
540
828
|
self,
|
|
541
829
|
name: str = "",
|
|
542
830
|
description: Optional[str] = None,
|
|
543
831
|
url: str = "https://wms.example.com",
|
|
544
|
-
url_wfs: str = None,
|
|
832
|
+
url_wfs: Optional[str] = None,
|
|
545
833
|
type_: str = "mapserver",
|
|
546
834
|
image_type: str = "image/png",
|
|
547
835
|
auth: str = "Standard auth",
|
|
@@ -559,30 +847,42 @@ class OGCServer(Base):
|
|
|
559
847
|
self.is_single_tile = is_single_tile
|
|
560
848
|
|
|
561
849
|
def __str__(self) -> str:
|
|
562
|
-
return self.name or ""
|
|
850
|
+
return self.name or ""
|
|
563
851
|
|
|
564
|
-
def url_description(self, request: Request) -> str:
|
|
852
|
+
def url_description(self, request: pyramid.request.Request) -> str:
|
|
565
853
|
errors: Set[str] = set()
|
|
566
854
|
url = get_url2(self.name, self.url, request, errors)
|
|
567
|
-
return url
|
|
855
|
+
return url.url() if url else "\n".join(errors)
|
|
568
856
|
|
|
569
|
-
def url_wfs_description(self, request: Request) -> Optional[str]:
|
|
857
|
+
def url_wfs_description(self, request: pyramid.request.Request) -> Optional[str]:
|
|
570
858
|
if not self.url_wfs:
|
|
571
859
|
return self.url_description(request)
|
|
572
860
|
errors: Set[str] = set()
|
|
573
861
|
url = get_url2(self.name, self.url_wfs, request, errors)
|
|
574
|
-
return url
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
event.listen(OGCServer, "after_insert", cache_invalidate_cb, propagate=True)
|
|
578
|
-
event.listen(OGCServer, "after_update", cache_invalidate_cb, propagate=True)
|
|
579
|
-
event.listen(OGCServer, "after_delete", cache_invalidate_cb, propagate=True)
|
|
862
|
+
return url.url() if url else "\n".join(errors)
|
|
580
863
|
|
|
581
864
|
|
|
582
865
|
class LayerWMS(DimensionLayer):
|
|
866
|
+
"""The layer_wms table representation."""
|
|
867
|
+
|
|
583
868
|
__tablename__ = "layer_wms"
|
|
584
869
|
__table_args__ = {"schema": _schema}
|
|
585
|
-
__colanderalchemy_config__ = {
|
|
870
|
+
__colanderalchemy_config__ = {
|
|
871
|
+
"title": _("WMS Layer"),
|
|
872
|
+
"plural": _("WMS Layers"),
|
|
873
|
+
"description": Literal(
|
|
874
|
+
_(
|
|
875
|
+
"""
|
|
876
|
+
<div class="help-block">
|
|
877
|
+
<p>Definition of a <code>WMS Layer</code>.</p>
|
|
878
|
+
<p>Note: The layers named <code>wms-defaults</code> contains the values
|
|
879
|
+
used when we create a new <code>WMS layer</code>.</p>
|
|
880
|
+
<hr>
|
|
881
|
+
</div>
|
|
882
|
+
"""
|
|
883
|
+
)
|
|
884
|
+
),
|
|
885
|
+
}
|
|
586
886
|
|
|
587
887
|
__c2cgeoform_config__ = {"duplicate": True}
|
|
588
888
|
|
|
@@ -609,18 +909,50 @@ class LayerWMS(DimensionLayer):
|
|
|
609
909
|
},
|
|
610
910
|
)
|
|
611
911
|
layer = Column(
|
|
612
|
-
Unicode,
|
|
912
|
+
Unicode,
|
|
913
|
+
nullable=False,
|
|
914
|
+
info={
|
|
915
|
+
"colanderalchemy": {
|
|
916
|
+
"title": _("WMS layer name"),
|
|
917
|
+
"description": _(
|
|
918
|
+
"""
|
|
919
|
+
The WMS layers. Can be one layer, one group, or a comma separated list of layers.
|
|
920
|
+
In the case of a comma separated list of layers, you will get the legend rule for the
|
|
921
|
+
layer icon on the first layer, and to support the legend you should define a legend
|
|
922
|
+
metadata.
|
|
923
|
+
"""
|
|
924
|
+
),
|
|
925
|
+
"column": 2,
|
|
926
|
+
}
|
|
927
|
+
},
|
|
928
|
+
)
|
|
929
|
+
style = Column(
|
|
930
|
+
Unicode,
|
|
931
|
+
info={
|
|
932
|
+
"colanderalchemy": {
|
|
933
|
+
"title": _("Style"),
|
|
934
|
+
"description": _("The style to use for this layer, can be empty."),
|
|
935
|
+
"column": 2,
|
|
936
|
+
}
|
|
937
|
+
},
|
|
613
938
|
)
|
|
614
|
-
style = Column(Unicode, info={"colanderalchemy": {"title": _("Style"), "column": 2}})
|
|
615
939
|
valid = Column(
|
|
616
940
|
Boolean,
|
|
617
|
-
info={
|
|
941
|
+
info={
|
|
942
|
+
"colanderalchemy": {
|
|
943
|
+
"title": _("Valid"),
|
|
944
|
+
"description": _("The status reported by latest synchronization (readonly)."),
|
|
945
|
+
"column": 2,
|
|
946
|
+
"widget": CheckboxWidget(readonly=True),
|
|
947
|
+
}
|
|
948
|
+
},
|
|
618
949
|
)
|
|
619
950
|
invalid_reason = Column(
|
|
620
951
|
Unicode,
|
|
621
952
|
info={
|
|
622
953
|
"colanderalchemy": {
|
|
623
954
|
"title": _("Reason why I am not valid"),
|
|
955
|
+
"description": _("The reason for status reported by latest synchronization (readonly)."),
|
|
624
956
|
"column": 2,
|
|
625
957
|
"widget": TextInputWidget(readonly=True),
|
|
626
958
|
}
|
|
@@ -633,6 +965,7 @@ class LayerWMS(DimensionLayer):
|
|
|
633
965
|
info={
|
|
634
966
|
"colanderalchemy": {
|
|
635
967
|
"title": _("Time mode"),
|
|
968
|
+
"description": _("Used for the WMS time component."),
|
|
636
969
|
"column": 2,
|
|
637
970
|
"widget": SelectWidget(
|
|
638
971
|
values=(("disabled", _("Disabled")), ("value", _("Value")), ("range", _("Range")))
|
|
@@ -647,6 +980,7 @@ class LayerWMS(DimensionLayer):
|
|
|
647
980
|
info={
|
|
648
981
|
"colanderalchemy": {
|
|
649
982
|
"title": _("Time widget"),
|
|
983
|
+
"description": _("The component type used for the WMS time."),
|
|
650
984
|
"column": 2,
|
|
651
985
|
"widget": SelectWidget(values=(("slider", _("Slider")), ("datepicker", _("Datepicker")))),
|
|
652
986
|
}
|
|
@@ -657,7 +991,13 @@ class LayerWMS(DimensionLayer):
|
|
|
657
991
|
ogc_server = relationship(
|
|
658
992
|
"OGCServer",
|
|
659
993
|
backref=backref("layers", info={"colanderalchemy": {"exclude": True, "title": _("WMS Layers")}}),
|
|
660
|
-
info={
|
|
994
|
+
info={
|
|
995
|
+
"colanderalchemy": {
|
|
996
|
+
"title": _("OGC server"),
|
|
997
|
+
"description": _("The OGC server to use for this layer."),
|
|
998
|
+
"exclude": True,
|
|
999
|
+
}
|
|
1000
|
+
},
|
|
661
1001
|
)
|
|
662
1002
|
|
|
663
1003
|
def __init__(
|
|
@@ -668,20 +1008,74 @@ class LayerWMS(DimensionLayer):
|
|
|
668
1008
|
time_mode: str = "disabled",
|
|
669
1009
|
time_widget: str = "slider",
|
|
670
1010
|
) -> None:
|
|
671
|
-
|
|
1011
|
+
super().__init__(name=name, public=public)
|
|
672
1012
|
self.layer = layer
|
|
673
1013
|
self.time_mode = time_mode
|
|
674
1014
|
self.time_widget = time_widget
|
|
675
1015
|
|
|
676
1016
|
@staticmethod
|
|
677
|
-
def get_default(dbsession: Session) -> DimensionLayer:
|
|
678
|
-
return
|
|
1017
|
+
def get_default(dbsession: Session) -> Optional[DimensionLayer]:
|
|
1018
|
+
return cast(
|
|
1019
|
+
Optional[DimensionLayer],
|
|
1020
|
+
dbsession.query(LayerWMS).filter(LayerWMS.name == "wms-defaults").one_or_none(),
|
|
1021
|
+
)
|
|
679
1022
|
|
|
680
1023
|
|
|
681
1024
|
class LayerWMTS(DimensionLayer):
|
|
1025
|
+
"""The layer_wmts table representation."""
|
|
1026
|
+
|
|
682
1027
|
__tablename__ = "layer_wmts"
|
|
683
1028
|
__table_args__ = {"schema": _schema}
|
|
684
|
-
__colanderalchemy_config__ = {
|
|
1029
|
+
__colanderalchemy_config__ = {
|
|
1030
|
+
"title": _("WMTS Layer"),
|
|
1031
|
+
"plural": _("WMTS Layers"),
|
|
1032
|
+
"description": Literal(
|
|
1033
|
+
_(
|
|
1034
|
+
"""
|
|
1035
|
+
<div class="help-block">
|
|
1036
|
+
<p>Definition of a <code>WMTS Layer</code>.</p>
|
|
1037
|
+
<p>Note: The layers named <code>wmts-defaults</code> contains the values used when
|
|
1038
|
+
we create a new <code>WMTS layer</code>.</p>
|
|
1039
|
+
|
|
1040
|
+
<h4>Self generated WMTS tiles</h4>
|
|
1041
|
+
<p>When using self generated WMTS tiles, you should use URL
|
|
1042
|
+
<code>config://local/tiles/1.0.0/WMTSCapabilities.xml</code> where:<p>
|
|
1043
|
+
<ul>
|
|
1044
|
+
<li><code>config://local</code> is a dynamic path based on the project
|
|
1045
|
+
configuration.</li>
|
|
1046
|
+
<li><code>/tiles</code> is a proxy in the tilecloudchain container.</li>
|
|
1047
|
+
</ul>
|
|
1048
|
+
|
|
1049
|
+
<h4>Queryable WMTS</h4>
|
|
1050
|
+
<p>To make the WMTS queryable, you should add the following <code>Metadata</code>:
|
|
1051
|
+
<ul>
|
|
1052
|
+
<li><code>ogcServer</code> with the name of the used <code>OGC server</code>,
|
|
1053
|
+
<li><code>wmsLayers</code> or <code>queryLayers</code> with the layers to query
|
|
1054
|
+
(comma separated list. Groups are not supported).
|
|
1055
|
+
</ul>
|
|
1056
|
+
<p>By default the scale limits for the queryable layers are the
|
|
1057
|
+
<code>minResolution</code> and/or the <code>maxResolution</code>a metadata
|
|
1058
|
+
value(s) of the WMTS layer. These values correspond to the WMTS layer
|
|
1059
|
+
resolution(s) which should be the zoom limit.
|
|
1060
|
+
You can also set a <code>minQueryResolution</code> and/or a
|
|
1061
|
+
<code>maxQueryResolution</code> to set a query zoom limits independent of the
|
|
1062
|
+
WMTS layer.</p>
|
|
1063
|
+
|
|
1064
|
+
<h4>Print WMTS in high quality</h4>
|
|
1065
|
+
<p>To print the layers in high quality, you can define that the image shall be
|
|
1066
|
+
retrieved with a <code>GetMap</code> on the original WMS server.
|
|
1067
|
+
<p>To activate this, you should add the following <code>Metadata</code>:</p>
|
|
1068
|
+
<ul>
|
|
1069
|
+
<li><code>ogcServer</code> with the name of the used <code>OGC server</code>,</li>
|
|
1070
|
+
<li><code>wmsLayers</code> or <code>printLayers</code> with the layers to print
|
|
1071
|
+
(comma separated list).</li>
|
|
1072
|
+
</ul>
|
|
1073
|
+
<hr>
|
|
1074
|
+
</div>
|
|
1075
|
+
"""
|
|
1076
|
+
)
|
|
1077
|
+
),
|
|
1078
|
+
}
|
|
685
1079
|
__c2cgeoform_config__ = {"duplicate": True}
|
|
686
1080
|
__mapper_args__ = {"polymorphic_identity": "l_wmts"}
|
|
687
1081
|
|
|
@@ -692,19 +1086,63 @@ class LayerWMTS(DimensionLayer):
|
|
|
692
1086
|
info={"colanderalchemy": {"missing": None, "widget": HiddenWidget()}},
|
|
693
1087
|
)
|
|
694
1088
|
url = Column(
|
|
695
|
-
Unicode,
|
|
1089
|
+
Unicode,
|
|
1090
|
+
nullable=False,
|
|
1091
|
+
info={
|
|
1092
|
+
"colanderalchemy": {
|
|
1093
|
+
"title": _("GetCapabilities URL"),
|
|
1094
|
+
"description": _("The URL to the WMTS capabilities."),
|
|
1095
|
+
"column": 2,
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
696
1098
|
)
|
|
697
1099
|
layer = Column(
|
|
698
|
-
Unicode,
|
|
1100
|
+
Unicode,
|
|
1101
|
+
nullable=False,
|
|
1102
|
+
info={
|
|
1103
|
+
"colanderalchemy": {
|
|
1104
|
+
"title": _("WMTS layer name"),
|
|
1105
|
+
"description": _("The name of the WMTS layer to use"),
|
|
1106
|
+
"column": 2,
|
|
1107
|
+
}
|
|
1108
|
+
},
|
|
1109
|
+
)
|
|
1110
|
+
style = Column(
|
|
1111
|
+
Unicode,
|
|
1112
|
+
info={
|
|
1113
|
+
"colanderalchemy": {
|
|
1114
|
+
"title": _("Style"),
|
|
1115
|
+
"description": _("The style to use; if not present, the default style is used."),
|
|
1116
|
+
"column": 2,
|
|
1117
|
+
}
|
|
1118
|
+
},
|
|
1119
|
+
)
|
|
1120
|
+
matrix_set = Column(
|
|
1121
|
+
Unicode,
|
|
1122
|
+
info={
|
|
1123
|
+
"colanderalchemy": {
|
|
1124
|
+
"title": _("Matrix set"),
|
|
1125
|
+
"description": _(
|
|
1126
|
+
"The matrix set to use; if there is only one matrix set in the capabilities, it can be"
|
|
1127
|
+
"left empty."
|
|
1128
|
+
),
|
|
1129
|
+
"column": 2,
|
|
1130
|
+
}
|
|
1131
|
+
},
|
|
699
1132
|
)
|
|
700
|
-
style = Column(Unicode, info={"colanderalchemy": {"title": _("Style"), "column": 2}})
|
|
701
|
-
matrix_set = Column(Unicode, info={"colanderalchemy": {"title": _("Matrix set"), "column": 2}})
|
|
702
1133
|
image_type = Column(
|
|
703
1134
|
Enum("image/jpeg", "image/png", native_enum=False),
|
|
704
1135
|
nullable=False,
|
|
705
1136
|
info={
|
|
706
1137
|
"colanderalchemy": {
|
|
707
1138
|
"title": _("Image type"),
|
|
1139
|
+
"description": Literal(
|
|
1140
|
+
_(
|
|
1141
|
+
"""
|
|
1142
|
+
The MIME type of the images (e.g.: <code>image/png</code>).
|
|
1143
|
+
"""
|
|
1144
|
+
)
|
|
1145
|
+
),
|
|
708
1146
|
"column": 2,
|
|
709
1147
|
"widget": SelectWidget(values=(("image/jpeg", "image/jpeg"), ("image/png", "image/png"))),
|
|
710
1148
|
}
|
|
@@ -712,12 +1150,15 @@ class LayerWMTS(DimensionLayer):
|
|
|
712
1150
|
)
|
|
713
1151
|
|
|
714
1152
|
def __init__(self, name: str = "", public: bool = True, image_type: str = "image/png") -> None:
|
|
715
|
-
|
|
1153
|
+
super().__init__(name=name, public=public)
|
|
716
1154
|
self.image_type = image_type
|
|
717
1155
|
|
|
718
1156
|
@staticmethod
|
|
719
|
-
def get_default(dbsession: Session) -> DimensionLayer:
|
|
720
|
-
return
|
|
1157
|
+
def get_default(dbsession: Session) -> Optional[DimensionLayer]:
|
|
1158
|
+
return cast(
|
|
1159
|
+
Optional[DimensionLayer],
|
|
1160
|
+
dbsession.query(LayerWMTS).filter(LayerWMTS.name == "wmts-defaults").one_or_none(),
|
|
1161
|
+
)
|
|
721
1162
|
|
|
722
1163
|
|
|
723
1164
|
# association table role <> restriction area
|
|
@@ -750,9 +1191,44 @@ layer_ra = Table(
|
|
|
750
1191
|
|
|
751
1192
|
|
|
752
1193
|
class LayerVectorTiles(DimensionLayer):
|
|
1194
|
+
"""The layer_vectortiles table representation."""
|
|
1195
|
+
|
|
753
1196
|
__tablename__ = "layer_vectortiles"
|
|
754
1197
|
__table_args__ = {"schema": _schema}
|
|
755
|
-
__colanderalchemy_config__ = {
|
|
1198
|
+
__colanderalchemy_config__ = {
|
|
1199
|
+
"title": _("Vector Tiles Layer"),
|
|
1200
|
+
"plural": _("Vector Tiles Layers"),
|
|
1201
|
+
"description": Literal(
|
|
1202
|
+
_(
|
|
1203
|
+
"""
|
|
1204
|
+
<div class="help-block">
|
|
1205
|
+
<p>Definition of a <code>Vector Tiles Layer</code>.</p>
|
|
1206
|
+
<p>Note: The layers named <code>vector-tiles-defaults</code> contains the values used when
|
|
1207
|
+
we create a new <code>Vector Tiles layer</code>.</p>
|
|
1208
|
+
|
|
1209
|
+
<h4>Queryable Vector Tiles</h4>
|
|
1210
|
+
<p>To make the Vector Tiles queryable, you should add the following <code>Metadata</code>:
|
|
1211
|
+
<ul>
|
|
1212
|
+
<li><code>ogcServer</code> with the name of the used <code>OGC server</code>,
|
|
1213
|
+
<li><code>wmsLayers</code> or <code>queryLayers</code> with the layers to query
|
|
1214
|
+
(comma separated list. Groups are not supported).
|
|
1215
|
+
</ul>
|
|
1216
|
+
|
|
1217
|
+
<h4>Print Vector Tiles in high quality</h4>
|
|
1218
|
+
<p>To print the layers in high quality, you can define that the image shall be
|
|
1219
|
+
retrieved with a <code>GetMap</code> on the original WMS server.
|
|
1220
|
+
<p>To activate this, you should add the following <code>Metadata</code>:</p>
|
|
1221
|
+
<ul>
|
|
1222
|
+
<li><code>ogcServer</code> with the name of the used <code>OGC server</code>,</li>
|
|
1223
|
+
<li><code>wmsLayers</code> or <code>printLayers</code> with the layers to print
|
|
1224
|
+
(comma separated list).</li>
|
|
1225
|
+
</ul>
|
|
1226
|
+
<hr>
|
|
1227
|
+
</div>
|
|
1228
|
+
"""
|
|
1229
|
+
)
|
|
1230
|
+
),
|
|
1231
|
+
}
|
|
756
1232
|
|
|
757
1233
|
__c2cgeoform_config__ = {"duplicate": True}
|
|
758
1234
|
|
|
@@ -771,8 +1247,29 @@ class LayerVectorTiles(DimensionLayer):
|
|
|
771
1247
|
info={
|
|
772
1248
|
"colanderalchemy": {
|
|
773
1249
|
"title": _("Style"),
|
|
774
|
-
"description":
|
|
1250
|
+
"description": _(
|
|
1251
|
+
"""
|
|
1252
|
+
The path to a Mapbox Style file (version 8 or higher). Example: https://url/style.json
|
|
1253
|
+
"""
|
|
1254
|
+
),
|
|
1255
|
+
"column": 2,
|
|
1256
|
+
}
|
|
1257
|
+
},
|
|
1258
|
+
)
|
|
1259
|
+
|
|
1260
|
+
sql = Column(
|
|
1261
|
+
Unicode,
|
|
1262
|
+
nullable=True,
|
|
1263
|
+
info={
|
|
1264
|
+
"colanderalchemy": {
|
|
1265
|
+
"title": _("SQL query"),
|
|
1266
|
+
"description": _(
|
|
1267
|
+
"""
|
|
1268
|
+
A SQL query to get the vector tiles data.
|
|
1269
|
+
"""
|
|
1270
|
+
),
|
|
775
1271
|
"column": 2,
|
|
1272
|
+
"widget": TextAreaWidget(rows=15),
|
|
776
1273
|
}
|
|
777
1274
|
},
|
|
778
1275
|
)
|
|
@@ -783,27 +1280,80 @@ class LayerVectorTiles(DimensionLayer):
|
|
|
783
1280
|
info={
|
|
784
1281
|
"colanderalchemy": {
|
|
785
1282
|
"title": _("Raster URL"),
|
|
786
|
-
"description":
|
|
1283
|
+
"description": _(
|
|
1284
|
+
"""
|
|
1285
|
+
The raster url. Example: https://url/{z}/{x}/{y}.png. Alternative to print the
|
|
1286
|
+
layer with a service which rasterises the vector tiles.
|
|
1287
|
+
"""
|
|
1288
|
+
),
|
|
787
1289
|
"column": 2,
|
|
788
1290
|
}
|
|
789
1291
|
},
|
|
790
1292
|
)
|
|
791
1293
|
|
|
1294
|
+
def __init__(self, name: str = "", public: bool = True, style: str = "", sql: str = "") -> None:
|
|
1295
|
+
super().__init__(name=name, public=public)
|
|
1296
|
+
self.style = style
|
|
1297
|
+
self.sql = sql
|
|
1298
|
+
|
|
1299
|
+
@staticmethod
|
|
1300
|
+
def get_default(dbsession: Session) -> Optional[DimensionLayer]:
|
|
1301
|
+
return cast(
|
|
1302
|
+
Optional[DimensionLayer],
|
|
1303
|
+
dbsession.query(LayerVectorTiles)
|
|
1304
|
+
.filter(LayerVectorTiles.name == "vector-tiles-defaults")
|
|
1305
|
+
.one_or_none(),
|
|
1306
|
+
)
|
|
1307
|
+
|
|
1308
|
+
def style_description(self, request: pyramid.request.Request) -> str:
|
|
1309
|
+
errors: Set[str] = set()
|
|
1310
|
+
url = get_url2(self.name, self.style, request, errors)
|
|
1311
|
+
return url.url() if url else "\n".join(errors)
|
|
1312
|
+
|
|
1313
|
+
|
|
1314
|
+
class RestrictionArea(Base): # type: ignore
|
|
1315
|
+
"""The restrictionarea table representation."""
|
|
792
1316
|
|
|
793
|
-
class RestrictionArea(Base):
|
|
794
1317
|
__tablename__ = "restrictionarea"
|
|
795
1318
|
__table_args__ = {"schema": _schema}
|
|
796
1319
|
__colanderalchemy_config__ = {"title": _("Restriction area"), "plural": _("Restriction areas")}
|
|
797
1320
|
__c2cgeoform_config__ = {"duplicate": True}
|
|
798
1321
|
id = Column(Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}})
|
|
799
1322
|
|
|
800
|
-
name = Column(
|
|
801
|
-
|
|
802
|
-
|
|
1323
|
+
name = Column(
|
|
1324
|
+
Unicode,
|
|
1325
|
+
info={
|
|
1326
|
+
"colanderalchemy": {
|
|
1327
|
+
"title": _("Name"),
|
|
1328
|
+
"description": _("A name."),
|
|
1329
|
+
}
|
|
1330
|
+
},
|
|
1331
|
+
)
|
|
1332
|
+
description = Column(
|
|
1333
|
+
Unicode,
|
|
1334
|
+
info={
|
|
1335
|
+
"colanderalchemy": {
|
|
1336
|
+
"title": _("Description"),
|
|
1337
|
+
"description": _("An optional description"),
|
|
1338
|
+
}
|
|
1339
|
+
},
|
|
1340
|
+
)
|
|
1341
|
+
readwrite = Column(
|
|
1342
|
+
Boolean,
|
|
1343
|
+
default=False,
|
|
1344
|
+
info={
|
|
1345
|
+
"colanderalchemy": {
|
|
1346
|
+
"title": _("Read/write"),
|
|
1347
|
+
"description": _("Allows the linked users to change the objects."),
|
|
1348
|
+
}
|
|
1349
|
+
},
|
|
1350
|
+
)
|
|
803
1351
|
area = Column(
|
|
804
1352
|
Geometry("POLYGON", srid=_srid),
|
|
805
1353
|
info={
|
|
806
1354
|
"colanderalchemy": {
|
|
1355
|
+
"title": _("Area"),
|
|
1356
|
+
"description": _("Active in the following area, if not defined, it is active everywhere."),
|
|
807
1357
|
"typ": ColanderGeometry("POLYGON", srid=_srid, map_srid=_map_config["srid"]),
|
|
808
1358
|
"widget": MapWidget(map_options=_map_config),
|
|
809
1359
|
}
|
|
@@ -814,20 +1364,57 @@ class RestrictionArea(Base):
|
|
|
814
1364
|
roles = relationship(
|
|
815
1365
|
"Role",
|
|
816
1366
|
secondary=role_ra,
|
|
817
|
-
info={
|
|
1367
|
+
info={
|
|
1368
|
+
"colanderalchemy": {
|
|
1369
|
+
"title": _("Roles"),
|
|
1370
|
+
"description": _("Checked roles will grant access to this restriction area."),
|
|
1371
|
+
"exclude": True,
|
|
1372
|
+
}
|
|
1373
|
+
},
|
|
818
1374
|
cascade="save-update,merge,refresh-expire",
|
|
819
1375
|
backref=backref(
|
|
820
|
-
"restrictionareas",
|
|
1376
|
+
"restrictionareas",
|
|
1377
|
+
info={
|
|
1378
|
+
"colanderalchemy": {
|
|
1379
|
+
"title": _("Restriction areas"),
|
|
1380
|
+
"description": _(
|
|
1381
|
+
"Users with this role will be granted with access to those restriction areas."
|
|
1382
|
+
),
|
|
1383
|
+
"exclude": True,
|
|
1384
|
+
}
|
|
1385
|
+
},
|
|
821
1386
|
),
|
|
822
1387
|
)
|
|
823
1388
|
layers = relationship(
|
|
824
1389
|
"Layer",
|
|
825
1390
|
secondary=layer_ra,
|
|
826
1391
|
order_by=Layer.name,
|
|
827
|
-
info={
|
|
1392
|
+
info={
|
|
1393
|
+
"colanderalchemy": {
|
|
1394
|
+
"title": _("Layers"),
|
|
1395
|
+
"exclude": True,
|
|
1396
|
+
"description": Literal(
|
|
1397
|
+
_(
|
|
1398
|
+
"""
|
|
1399
|
+
<div class="help-block">
|
|
1400
|
+
<p>This restriction area will grant access to the checked layers.</p>
|
|
1401
|
+
<hr>
|
|
1402
|
+
</div>
|
|
1403
|
+
"""
|
|
1404
|
+
)
|
|
1405
|
+
),
|
|
1406
|
+
}
|
|
1407
|
+
},
|
|
828
1408
|
cascade="save-update,merge,refresh-expire",
|
|
829
1409
|
backref=backref(
|
|
830
|
-
"restrictionareas",
|
|
1410
|
+
"restrictionareas",
|
|
1411
|
+
info={
|
|
1412
|
+
"colanderalchemy": {
|
|
1413
|
+
"title": _("Restriction areas"),
|
|
1414
|
+
"exclude": True,
|
|
1415
|
+
"description": _("The areas through which the user can see the layer."),
|
|
1416
|
+
}
|
|
1417
|
+
},
|
|
831
1418
|
),
|
|
832
1419
|
)
|
|
833
1420
|
|
|
@@ -835,8 +1422,8 @@ class RestrictionArea(Base):
|
|
|
835
1422
|
self,
|
|
836
1423
|
name: str = "",
|
|
837
1424
|
description: str = "",
|
|
838
|
-
layers: List[Layer] = None,
|
|
839
|
-
roles: List[Role] = None,
|
|
1425
|
+
layers: Optional[List[Layer]] = None,
|
|
1426
|
+
roles: Optional[List[Role]] = None,
|
|
840
1427
|
area: Geometry = None,
|
|
841
1428
|
readwrite: bool = False,
|
|
842
1429
|
) -> None:
|
|
@@ -851,8 +1438,8 @@ class RestrictionArea(Base):
|
|
|
851
1438
|
self.area = area
|
|
852
1439
|
self.readwrite = readwrite
|
|
853
1440
|
|
|
854
|
-
def __str__(self) -> str:
|
|
855
|
-
return self.name
|
|
1441
|
+
def __str__(self) -> str:
|
|
1442
|
+
return f"{self.name}[{self.id}]"
|
|
856
1443
|
|
|
857
1444
|
|
|
858
1445
|
event.listen(RestrictionArea, "after_insert", cache_invalidate_cb)
|
|
@@ -883,15 +1470,33 @@ interface_theme = Table(
|
|
|
883
1470
|
)
|
|
884
1471
|
|
|
885
1472
|
|
|
886
|
-
class Interface(Base):
|
|
1473
|
+
class Interface(Base): # type: ignore
|
|
1474
|
+
"""The interface table representation."""
|
|
1475
|
+
|
|
887
1476
|
__tablename__ = "interface"
|
|
888
1477
|
__table_args__ = {"schema": _schema}
|
|
889
1478
|
__c2cgeoform_config__ = {"duplicate": True}
|
|
890
1479
|
__colanderalchemy_config__ = {"title": _("Interface"), "plural": _("Interfaces")}
|
|
891
1480
|
|
|
892
1481
|
id = Column(Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}})
|
|
893
|
-
name = Column(
|
|
894
|
-
|
|
1482
|
+
name = Column(
|
|
1483
|
+
Unicode,
|
|
1484
|
+
info={
|
|
1485
|
+
"colanderalchemy": {
|
|
1486
|
+
"title": _("Name"),
|
|
1487
|
+
"description": _("The name of the interface, as used in request URL."),
|
|
1488
|
+
}
|
|
1489
|
+
},
|
|
1490
|
+
)
|
|
1491
|
+
description = Column(
|
|
1492
|
+
Unicode,
|
|
1493
|
+
info={
|
|
1494
|
+
"colanderalchemy": {
|
|
1495
|
+
"title": _("Description"),
|
|
1496
|
+
"description": _("An optional description."),
|
|
1497
|
+
}
|
|
1498
|
+
},
|
|
1499
|
+
)
|
|
895
1500
|
|
|
896
1501
|
# relationship with Layer and Theme
|
|
897
1502
|
layers = relationship(
|
|
@@ -899,33 +1504,81 @@ class Interface(Base):
|
|
|
899
1504
|
secondary=interface_layer,
|
|
900
1505
|
cascade="save-update,merge,refresh-expire",
|
|
901
1506
|
info={"colanderalchemy": {"title": _("Layers"), "exclude": True}, "c2cgeoform": {"duplicate": False}},
|
|
902
|
-
backref=backref(
|
|
1507
|
+
backref=backref(
|
|
1508
|
+
"interfaces",
|
|
1509
|
+
info={
|
|
1510
|
+
"colanderalchemy": {
|
|
1511
|
+
"title": _("Interfaces"),
|
|
1512
|
+
"exclude": True,
|
|
1513
|
+
"description": _("Make it visible in the checked interfaces."),
|
|
1514
|
+
}
|
|
1515
|
+
},
|
|
1516
|
+
),
|
|
903
1517
|
)
|
|
904
1518
|
theme = relationship(
|
|
905
1519
|
"Theme",
|
|
906
1520
|
secondary=interface_theme,
|
|
907
1521
|
cascade="save-update,merge,refresh-expire",
|
|
908
1522
|
info={"colanderalchemy": {"title": _("Themes"), "exclude": True}, "c2cgeoform": {"duplicate": False}},
|
|
909
|
-
backref=backref(
|
|
1523
|
+
backref=backref(
|
|
1524
|
+
"interfaces",
|
|
1525
|
+
info={
|
|
1526
|
+
"colanderalchemy": {
|
|
1527
|
+
"title": _("Interfaces"),
|
|
1528
|
+
"description": _("Make it visible in the checked interfaces."),
|
|
1529
|
+
"exclude": True,
|
|
1530
|
+
}
|
|
1531
|
+
},
|
|
1532
|
+
),
|
|
910
1533
|
)
|
|
911
1534
|
|
|
912
1535
|
def __init__(self, name: str = "", description: str = "") -> None:
|
|
913
1536
|
self.name = name
|
|
914
1537
|
self.description = description
|
|
915
1538
|
|
|
916
|
-
def __str__(self) -> str:
|
|
917
|
-
return self.name
|
|
1539
|
+
def __str__(self) -> str:
|
|
1540
|
+
return f"{self.name}[{self.id}]"
|
|
1541
|
+
|
|
918
1542
|
|
|
1543
|
+
class Metadata(Base): # type: ignore
|
|
1544
|
+
"""The metadata table representation."""
|
|
919
1545
|
|
|
920
|
-
class Metadata(Base):
|
|
921
1546
|
__tablename__ = "metadata"
|
|
922
1547
|
__table_args__ = {"schema": _schema}
|
|
1548
|
+
__colanderalchemy_config__ = {
|
|
1549
|
+
"title": _("Metadata"),
|
|
1550
|
+
"plural": _("Metadatas"),
|
|
1551
|
+
}
|
|
923
1552
|
|
|
924
1553
|
id = Column(Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}})
|
|
925
|
-
name = Column(
|
|
926
|
-
|
|
1554
|
+
name = Column(
|
|
1555
|
+
Unicode,
|
|
1556
|
+
info={
|
|
1557
|
+
"colanderalchemy": {
|
|
1558
|
+
"title": _("Name"),
|
|
1559
|
+
"description": Literal(_("The type of <code>Metadata</code> we want to set.")),
|
|
1560
|
+
}
|
|
1561
|
+
},
|
|
1562
|
+
)
|
|
1563
|
+
value = Column(
|
|
1564
|
+
Unicode,
|
|
1565
|
+
info={
|
|
1566
|
+
"colanderalchemy": {
|
|
1567
|
+
"title": _("Value"),
|
|
1568
|
+
"exclude": True,
|
|
1569
|
+
"description": _("The value of the metadata entry."),
|
|
1570
|
+
}
|
|
1571
|
+
},
|
|
1572
|
+
)
|
|
927
1573
|
description = Column(
|
|
928
|
-
Unicode,
|
|
1574
|
+
Unicode,
|
|
1575
|
+
info={
|
|
1576
|
+
"colanderalchemy": {
|
|
1577
|
+
"title": _("Description"),
|
|
1578
|
+
"widget": TextAreaWidget(),
|
|
1579
|
+
"description": _("An optional description."),
|
|
1580
|
+
}
|
|
1581
|
+
},
|
|
929
1582
|
)
|
|
930
1583
|
|
|
931
1584
|
item_id = Column(
|
|
@@ -942,17 +1595,49 @@ class Metadata(Base):
|
|
|
942
1595
|
"metadatas",
|
|
943
1596
|
cascade="save-update,merge,delete,delete-orphan,expunge",
|
|
944
1597
|
order_by="Metadata.name",
|
|
945
|
-
info={
|
|
1598
|
+
info={
|
|
1599
|
+
"colanderalchemy": {
|
|
1600
|
+
"title": _("Metadatas"),
|
|
1601
|
+
"description": Literal(
|
|
1602
|
+
_(
|
|
1603
|
+
"""
|
|
1604
|
+
<div class="help-block">
|
|
1605
|
+
<p>You can associate metadata to all theme elements (tree items).
|
|
1606
|
+
The purpose of this metadata is to trigger specific features, mainly UI features.
|
|
1607
|
+
Each metadata entry has the following attributes:</p>
|
|
1608
|
+
<p>The available names are configured in the <code>vars.yaml</code>
|
|
1609
|
+
files in <code>admin_interface/available_metadata</code>.</p>
|
|
1610
|
+
<p>To set a metadata entry, create or edit an entry in the Metadata view of the
|
|
1611
|
+
administration UI.
|
|
1612
|
+
Regarding effect on the referenced tree item on the client side,
|
|
1613
|
+
you will find an official description for each sort of metadata in the
|
|
1614
|
+
<code>GmfMetaData</code> definition in <code>themes.js</code>
|
|
1615
|
+
<a target="_blank" href="${url}">see ngeo documentation</a>.</p>
|
|
1616
|
+
<hr>
|
|
1617
|
+
</div>
|
|
1618
|
+
""",
|
|
1619
|
+
mapping={
|
|
1620
|
+
"url": (
|
|
1621
|
+
"https://camptocamp.github.io/ngeo/"
|
|
1622
|
+
f"{os.environ.get('MAJOR_VERSION', 'master')}"
|
|
1623
|
+
"/apidoc/interfaces/contribs_gmf_src_themes.GmfMetaData.html"
|
|
1624
|
+
)
|
|
1625
|
+
},
|
|
1626
|
+
)
|
|
1627
|
+
),
|
|
1628
|
+
"exclude": True,
|
|
1629
|
+
}
|
|
1630
|
+
},
|
|
946
1631
|
),
|
|
947
1632
|
)
|
|
948
1633
|
|
|
949
|
-
def __init__(self, name: str = "", value: str = "", description: str = None) -> None:
|
|
1634
|
+
def __init__(self, name: str = "", value: str = "", description: Optional[str] = None) -> None:
|
|
950
1635
|
self.name = name
|
|
951
1636
|
self.value = value
|
|
952
1637
|
self.description = description
|
|
953
1638
|
|
|
954
|
-
def __str__(self) -> str:
|
|
955
|
-
return "{}
|
|
1639
|
+
def __str__(self) -> str:
|
|
1640
|
+
return f"{self.name}={self.value}[{self.id}]"
|
|
956
1641
|
|
|
957
1642
|
|
|
958
1643
|
event.listen(Metadata, "after_insert", cache_invalidate_cb, propagate=True)
|
|
@@ -960,16 +1645,55 @@ event.listen(Metadata, "after_update", cache_invalidate_cb, propagate=True)
|
|
|
960
1645
|
event.listen(Metadata, "after_delete", cache_invalidate_cb, propagate=True)
|
|
961
1646
|
|
|
962
1647
|
|
|
963
|
-
class Dimension(Base):
|
|
1648
|
+
class Dimension(Base): # type: ignore
|
|
1649
|
+
"""The dimension table representation."""
|
|
1650
|
+
|
|
964
1651
|
__tablename__ = "dimension"
|
|
965
1652
|
__table_args__ = {"schema": _schema}
|
|
1653
|
+
__colanderalchemy_config__ = {
|
|
1654
|
+
"title": _("Dimension"),
|
|
1655
|
+
"plural": _("Dimensions"),
|
|
1656
|
+
}
|
|
966
1657
|
|
|
967
1658
|
id = Column(Integer, primary_key=True, info={"colanderalchemy": {"widget": HiddenWidget()}})
|
|
968
|
-
name = Column(
|
|
969
|
-
|
|
970
|
-
|
|
1659
|
+
name = Column(
|
|
1660
|
+
Unicode,
|
|
1661
|
+
info={
|
|
1662
|
+
"colanderalchemy": {
|
|
1663
|
+
"title": _("Name"),
|
|
1664
|
+
"description": _("The name of the dimension as it will be sent in requests."),
|
|
1665
|
+
}
|
|
1666
|
+
},
|
|
1667
|
+
)
|
|
1668
|
+
value = Column(
|
|
1669
|
+
Unicode,
|
|
1670
|
+
info={
|
|
1671
|
+
"colanderalchemy": {
|
|
1672
|
+
"title": _("Value"),
|
|
1673
|
+
"description": _("The default value for this dimension."),
|
|
1674
|
+
}
|
|
1675
|
+
},
|
|
1676
|
+
)
|
|
1677
|
+
field = Column(
|
|
1678
|
+
Unicode,
|
|
1679
|
+
info={
|
|
1680
|
+
"colanderalchemy": {
|
|
1681
|
+
"title": _("Field"),
|
|
1682
|
+
"description": _(
|
|
1683
|
+
"The name of the field to use for filtering (leave empty when not using OGC filters)."
|
|
1684
|
+
),
|
|
1685
|
+
}
|
|
1686
|
+
},
|
|
1687
|
+
)
|
|
971
1688
|
description = Column(
|
|
972
|
-
Unicode,
|
|
1689
|
+
Unicode,
|
|
1690
|
+
info={
|
|
1691
|
+
"colanderalchemy": {
|
|
1692
|
+
"title": _("Description"),
|
|
1693
|
+
"description": _("An optional description."),
|
|
1694
|
+
"widget": TextAreaWidget(),
|
|
1695
|
+
}
|
|
1696
|
+
},
|
|
973
1697
|
)
|
|
974
1698
|
|
|
975
1699
|
layer_id = Column(
|
|
@@ -985,12 +1709,32 @@ class Dimension(Base):
|
|
|
985
1709
|
backref=backref(
|
|
986
1710
|
"dimensions",
|
|
987
1711
|
cascade="save-update,merge,delete,delete-orphan,expunge",
|
|
988
|
-
info={
|
|
1712
|
+
info={
|
|
1713
|
+
"colanderalchemy": {
|
|
1714
|
+
"title": _("Dimensions"),
|
|
1715
|
+
"exclude": True,
|
|
1716
|
+
"description": Literal(
|
|
1717
|
+
_(
|
|
1718
|
+
"""
|
|
1719
|
+
<div class="help-block">
|
|
1720
|
+
<p>The dimensions, if not provided default values are used.</p>
|
|
1721
|
+
<hr>
|
|
1722
|
+
</div>
|
|
1723
|
+
"""
|
|
1724
|
+
)
|
|
1725
|
+
),
|
|
1726
|
+
}
|
|
1727
|
+
},
|
|
989
1728
|
),
|
|
990
1729
|
)
|
|
991
1730
|
|
|
992
1731
|
def __init__(
|
|
993
|
-
self,
|
|
1732
|
+
self,
|
|
1733
|
+
name: str = "",
|
|
1734
|
+
value: str = "",
|
|
1735
|
+
layer: Optional[str] = None,
|
|
1736
|
+
field: Optional[str] = None,
|
|
1737
|
+
description: Optional[str] = None,
|
|
994
1738
|
) -> None:
|
|
995
1739
|
self.name = name
|
|
996
1740
|
self.value = value
|
|
@@ -999,5 +1743,102 @@ class Dimension(Base):
|
|
|
999
1743
|
self.layer = layer
|
|
1000
1744
|
self.description = description
|
|
1001
1745
|
|
|
1002
|
-
def __str__(self) -> str:
|
|
1003
|
-
return self.name
|
|
1746
|
+
def __str__(self) -> str:
|
|
1747
|
+
return f"{self.name}={self.value}[{self.id}]"
|
|
1748
|
+
|
|
1749
|
+
|
|
1750
|
+
class LogAction(enum.Enum):
|
|
1751
|
+
"""The log action enumeration."""
|
|
1752
|
+
|
|
1753
|
+
INSERT = enum.auto()
|
|
1754
|
+
UPDATE = enum.auto()
|
|
1755
|
+
DELETE = enum.auto()
|
|
1756
|
+
SYNCHRONIZE = enum.auto()
|
|
1757
|
+
CONVERT_TO_WMTS = enum.auto()
|
|
1758
|
+
CONVERT_TO_WMS = enum.auto()
|
|
1759
|
+
|
|
1760
|
+
|
|
1761
|
+
class AbstractLog(AbstractConcreteBase, Base): # type: ignore
|
|
1762
|
+
"""The abstract log table representation."""
|
|
1763
|
+
|
|
1764
|
+
strict_attrs = True
|
|
1765
|
+
__colanderalchemy_config__ = {
|
|
1766
|
+
"title": _("Log"),
|
|
1767
|
+
"plural": _("Logs"),
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
id = Column(Integer, primary_key=True, info={"colanderalchemy": {}})
|
|
1771
|
+
date = Column(
|
|
1772
|
+
DateTime(timezone=True),
|
|
1773
|
+
nullable=False,
|
|
1774
|
+
info={
|
|
1775
|
+
"colanderalchemy": {
|
|
1776
|
+
"title": _("Date"),
|
|
1777
|
+
}
|
|
1778
|
+
},
|
|
1779
|
+
)
|
|
1780
|
+
action = Column(
|
|
1781
|
+
Enum(LogAction, native_enum=False),
|
|
1782
|
+
nullable=False,
|
|
1783
|
+
info={
|
|
1784
|
+
"colanderalchemy": {
|
|
1785
|
+
"title": _("Action"),
|
|
1786
|
+
}
|
|
1787
|
+
},
|
|
1788
|
+
)
|
|
1789
|
+
element_type = Column(
|
|
1790
|
+
String(50),
|
|
1791
|
+
nullable=False,
|
|
1792
|
+
info={
|
|
1793
|
+
"colanderalchemy": {
|
|
1794
|
+
"title": _("Element type"),
|
|
1795
|
+
}
|
|
1796
|
+
},
|
|
1797
|
+
)
|
|
1798
|
+
element_id = Column(
|
|
1799
|
+
Integer,
|
|
1800
|
+
nullable=False,
|
|
1801
|
+
info={
|
|
1802
|
+
"colanderalchemy": {
|
|
1803
|
+
"title": _("Element identifier"),
|
|
1804
|
+
}
|
|
1805
|
+
},
|
|
1806
|
+
)
|
|
1807
|
+
element_name = Column(
|
|
1808
|
+
Unicode,
|
|
1809
|
+
nullable=False,
|
|
1810
|
+
info={
|
|
1811
|
+
"colanderalchemy": {
|
|
1812
|
+
"title": _("Element name"),
|
|
1813
|
+
}
|
|
1814
|
+
},
|
|
1815
|
+
)
|
|
1816
|
+
element_url_table = Column(
|
|
1817
|
+
Unicode,
|
|
1818
|
+
nullable=False,
|
|
1819
|
+
info={
|
|
1820
|
+
"colanderalchemy": {
|
|
1821
|
+
"title": _("Table segment of the element URL"),
|
|
1822
|
+
}
|
|
1823
|
+
},
|
|
1824
|
+
)
|
|
1825
|
+
username = Column(
|
|
1826
|
+
Unicode,
|
|
1827
|
+
nullable=False,
|
|
1828
|
+
info={
|
|
1829
|
+
"colanderalchemy": {
|
|
1830
|
+
"title": _("Username"),
|
|
1831
|
+
}
|
|
1832
|
+
},
|
|
1833
|
+
)
|
|
1834
|
+
|
|
1835
|
+
|
|
1836
|
+
class Log(AbstractLog):
|
|
1837
|
+
"""The main log table representation."""
|
|
1838
|
+
|
|
1839
|
+
__tablename__ = "log"
|
|
1840
|
+
__table_args__ = {"schema": _schema}
|
|
1841
|
+
__mapper_args__ = {
|
|
1842
|
+
"polymorphic_identity": "main",
|
|
1843
|
+
"concrete": True,
|
|
1844
|
+
}
|