c2cgeoportal-geoportal 2.7.1.157__py2.py3-none-any.whl → 2.8.1.89__py2.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_geoportal/__init__.py +23 -14
- c2cgeoportal_geoportal/lib/__init__.py +3 -5
- c2cgeoportal_geoportal/lib/authentication.py +10 -14
- c2cgeoportal_geoportal/lib/caching.py +8 -6
- c2cgeoportal_geoportal/lib/checker.py +10 -6
- c2cgeoportal_geoportal/lib/common_headers.py +2 -2
- c2cgeoportal_geoportal/lib/dbreflection.py +8 -8
- c2cgeoportal_geoportal/lib/filter_capabilities.py +8 -6
- c2cgeoportal_geoportal/lib/lingua_extractor.py +11 -12
- c2cgeoportal_geoportal/lib/loader.py +1 -1
- c2cgeoportal_geoportal/lib/oauth2.py +217 -100
- c2cgeoportal_geoportal/lib/wmstparsing.py +8 -12
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Dockerfile +9 -11
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/development.ini +1 -1
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/gunicorn.conf.py +3 -3
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/requirements.txt +1 -1
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.api.js +6 -4
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.apps.js +1 -3
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.commons.js +1 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/__init__.py +1 -6
- c2cgeoportal_geoportal/scaffolds/advance_update/{{cookiecutter.project}}/geoportal/CONST_Makefile +0 -20
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/main.yaml +21 -7
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/rebuild.yaml +1 -1
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/update_l10n.yaml +2 -1
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Dockerfile +22 -22
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile +58 -2
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build +49 -29
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/config.yaml +2 -5
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/docker-compose-check +25 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/requirements.txt +1 -1
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-db.yaml +26 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml +35 -26
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-qgis.yaml +23 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml +0 -2
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.yaml +3 -3
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.default +21 -2
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.project +9 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/vars.yaml +38 -14
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/Readme.txt +2 -2
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.conf +15 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.map.tmpl +2 -3
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Landscape.jrxml +5 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Portrait.jrxml +5 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Landscape.jrxml +5 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Portrait.jrxml +5 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/config.yaml.tmpl +6 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml +4 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/run_alembic.sh +3 -5
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-backup +5 -8
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +5 -8
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/spell-ignore-words.txt +2 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/__init__.py +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/test_app.py +38 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/.upgrade.yaml +2 -132
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_CHANGELOG.txt +200 -1105
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_create_template/tests/test_testapp.py +48 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_config-schema.yaml +17 -15
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_vars.yaml +48 -2
- c2cgeoportal_geoportal/scripts/__init__.py +3 -5
- c2cgeoportal_geoportal/scripts/c2cupgrade.py +1 -2
- c2cgeoportal_geoportal/scripts/pcreate.py +8 -10
- c2cgeoportal_geoportal/scripts/theme2fts.py +58 -3
- c2cgeoportal_geoportal/scripts/urllogin.py +2 -2
- c2cgeoportal_geoportal/views/__init__.py +1 -3
- c2cgeoportal_geoportal/views/dynamic.py +2 -3
- c2cgeoportal_geoportal/views/entry.py +2 -10
- c2cgeoportal_geoportal/views/fulltextsearch.py +1 -1
- c2cgeoportal_geoportal/views/geometry_processing.py +3 -3
- c2cgeoportal_geoportal/views/layers.py +10 -11
- c2cgeoportal_geoportal/views/login.py +63 -8
- c2cgeoportal_geoportal/views/mapserverproxy.py +3 -4
- c2cgeoportal_geoportal/views/ogcproxy.py +6 -2
- c2cgeoportal_geoportal/views/pdfreport.py +1 -1
- c2cgeoportal_geoportal/views/printproxy.py +6 -8
- c2cgeoportal_geoportal/views/profile.py +1 -1
- c2cgeoportal_geoportal/views/proxy.py +6 -9
- c2cgeoportal_geoportal/views/raster.py +2 -2
- c2cgeoportal_geoportal/views/resourceproxy.py +1 -1
- c2cgeoportal_geoportal/views/shortener.py +1 -2
- c2cgeoportal_geoportal/views/theme.py +97 -61
- c2cgeoportal_geoportal/views/tinyowsproxy.py +3 -12
- c2cgeoportal_geoportal/views/vector_tiles.py +1 -1
- {c2cgeoportal_geoportal-2.7.1.157.dist-info → c2cgeoportal_geoportal-2.8.1.89.dist-info}/METADATA +20 -15
- {c2cgeoportal_geoportal-2.7.1.157.dist-info → c2cgeoportal_geoportal-2.8.1.89.dist-info}/RECORD +100 -94
- {c2cgeoportal_geoportal-2.7.1.157.dist-info → c2cgeoportal_geoportal-2.8.1.89.dist-info}/entry_points.txt +1 -0
- tests/__init__.py +3 -2
- tests/test_cachebuster.py +3 -3
- tests/test_caching.py +1 -1
- tests/test_checker.py +1 -1
- tests/test_decimaljson.py +1 -1
- tests/test_headerstween.py +1 -1
- tests/test_i18n.py +1 -1
- tests/test_init.py +14 -15
- tests/test_locale_negociator.py +4 -4
- tests/test_mapserverproxy_route_predicate.py +1 -2
- tests/test_raster.py +15 -15
- tests/test_wmstparsing.py +10 -10
- tests/xmlstr.py +1 -3
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/tools/extract-messages.js +0 -41
- {c2cgeoportal_geoportal-2.7.1.157.dist-info → c2cgeoportal_geoportal-2.8.1.89.dist-info}/WHEEL +0 -0
- {c2cgeoportal_geoportal-2.7.1.157.dist-info → c2cgeoportal_geoportal-2.8.1.89.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2011-
|
1
|
+
# Copyright (c) 2011-2023, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -38,6 +38,7 @@ from math import sqrt
|
|
38
38
|
from typing import Any, Dict, List, Optional, Set, Tuple, Union, cast
|
39
39
|
|
40
40
|
import dogpile.cache.api
|
41
|
+
import pyramid.httpexceptions
|
41
42
|
import pyramid.request
|
42
43
|
import requests
|
43
44
|
import sqlalchemy
|
@@ -52,7 +53,7 @@ from sqlalchemy.orm.exc import NoResultFound
|
|
52
53
|
|
53
54
|
from c2cgeoportal_commons import models
|
54
55
|
from c2cgeoportal_commons.lib.url import Url, get_url2
|
55
|
-
from c2cgeoportal_commons.models import main
|
56
|
+
from c2cgeoportal_commons.models import cache_invalidate_cb, main
|
56
57
|
from c2cgeoportal_geoportal.lib import get_roles_id, get_typed, get_types_map, is_intranet
|
57
58
|
from c2cgeoportal_geoportal.lib.caching import get_region
|
58
59
|
from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
|
@@ -63,27 +64,31 @@ from c2cgeoportal_geoportal.lib.layers import (
|
|
63
64
|
get_protected_layers_query,
|
64
65
|
)
|
65
66
|
from c2cgeoportal_geoportal.lib.wmstparsing import TimeInformation, parse_extent
|
66
|
-
from c2cgeoportal_geoportal.views import restrict_headers
|
67
67
|
from c2cgeoportal_geoportal.views.layers import get_layer_metadata
|
68
68
|
|
69
69
|
LOG = logging.getLogger(__name__)
|
70
70
|
CACHE_REGION = get_region("std")
|
71
|
+
CACHE_OGC_SERVER_REGION = get_region("ogc-server")
|
71
72
|
TIMEOUT = int(os.environ.get("C2CGEOPORTAL_THEME_TIMEOUT", "300"))
|
72
73
|
|
73
74
|
Metadata = Union[str, int, float, bool, List[Any], Dict[str, Any]]
|
74
75
|
|
75
76
|
|
76
|
-
def get_http_cached(
|
77
|
+
def get_http_cached(
|
78
|
+
http_options: Dict[str, Any], url: str, headers: Dict[str, str], cache: bool = True
|
79
|
+
) -> Tuple[bytes, str]:
|
77
80
|
"""Get the content of the URL with a cash (dogpile)."""
|
78
81
|
|
79
|
-
@
|
82
|
+
@CACHE_OGC_SERVER_REGION.cache_on_arguments() # type: ignore
|
80
83
|
def do_get_http_cached(url: str) -> Tuple[bytes, str]:
|
81
84
|
response = requests.get(url, headers=headers, timeout=TIMEOUT, **http_options)
|
82
85
|
response.raise_for_status()
|
83
|
-
LOG.info("Get
|
86
|
+
LOG.info("Get URL '%s' in %.1fs.", url, response.elapsed.total_seconds())
|
84
87
|
return response.content, response.headers.get("Content-Type", "")
|
85
88
|
|
86
|
-
|
89
|
+
if cache:
|
90
|
+
return do_get_http_cached(url) # type: ignore
|
91
|
+
return do_get_http_cached.refresh(url) # type: ignore
|
87
92
|
|
88
93
|
|
89
94
|
class DimensionInformation:
|
@@ -143,8 +148,6 @@ class Theme:
|
|
143
148
|
self.request = request
|
144
149
|
self.settings = request.registry.settings
|
145
150
|
self.http_options = self.settings.get("http_options", {})
|
146
|
-
self.headers_whitelist = self.settings.get("headers_whitelist", [])
|
147
|
-
self.headers_blacklist = self.settings.get("headers_blacklist", [])
|
148
151
|
self.metadata_type = get_types_map(
|
149
152
|
self.settings.get("admin_interface", {}).get("available_metadata", [])
|
150
153
|
)
|
@@ -179,14 +182,16 @@ class Theme:
|
|
179
182
|
return metadatas
|
180
183
|
|
181
184
|
async def _wms_getcap(
|
182
|
-
self, ogc_server: main.OGCServer, preload: bool = False
|
185
|
+
self, ogc_server: main.OGCServer, preload: bool = False, cache: bool = True
|
183
186
|
) -> Tuple[Optional[Dict[str, Dict[str, Any]]], Set[str]]:
|
184
|
-
|
187
|
+
LOG.debug("Get the WMS Capabilities of '%s', preload: %s, cache: %s", ogc_server.name, preload, cache)
|
188
|
+
|
189
|
+
@CACHE_OGC_SERVER_REGION.cache_on_arguments() # type: ignore
|
185
190
|
def build_web_map_service(ogc_server_id: int) -> Tuple[Optional[Dict[str, Dict[str, Any]]], Set[str]]:
|
186
191
|
del ogc_server_id # Just for cache
|
187
192
|
|
188
193
|
if url is None:
|
189
|
-
raise RuntimeError("
|
194
|
+
raise RuntimeError("URL is None")
|
190
195
|
|
191
196
|
version = url.query.get("VERSION", "1.1.1")
|
192
197
|
layers = {}
|
@@ -220,16 +225,16 @@ class Theme:
|
|
220
225
|
}
|
221
226
|
|
222
227
|
del wms
|
223
|
-
LOG.debug("Run garbage collection: %s", ", ".join([str(gc.collect(n)) for n in range(3)]))
|
224
228
|
|
225
229
|
return {"layers": layers}, set()
|
226
230
|
|
227
|
-
|
228
|
-
|
229
|
-
|
231
|
+
if cache:
|
232
|
+
result = build_web_map_service.get(ogc_server.id)
|
233
|
+
if result != dogpile.cache.api.NO_VALUE:
|
234
|
+
return result # type: ignore
|
230
235
|
|
231
236
|
try:
|
232
|
-
url, content, errors = await self._wms_getcap_cached(ogc_server)
|
237
|
+
url, content, errors = await self._wms_getcap_cached(ogc_server, cache=cache)
|
233
238
|
except requests.exceptions.RequestException as exception:
|
234
239
|
error = (
|
235
240
|
f"Unable to get the WMS Capabilities for OGC server '{ogc_server.name}', "
|
@@ -240,10 +245,10 @@ class Theme:
|
|
240
245
|
if errors or preload:
|
241
246
|
return None, errors
|
242
247
|
|
243
|
-
return build_web_map_service(ogc_server.id) # type: ignore
|
248
|
+
return build_web_map_service.refresh(ogc_server.id) # type: ignore
|
244
249
|
|
245
250
|
async def _wms_getcap_cached(
|
246
|
-
self, ogc_server: main.OGCServer
|
251
|
+
self, ogc_server: main.OGCServer, cache: bool = True
|
247
252
|
) -> Tuple[Optional[Url], Optional[bytes], Set[str]]:
|
248
253
|
errors: Set[str] = set()
|
249
254
|
url = get_url2(f"The OGC server '{ogc_server.name}'", ogc_server.url, self.request, errors)
|
@@ -269,23 +274,15 @@ class Theme:
|
|
269
274
|
|
270
275
|
LOG.debug("Get WMS GetCapabilities for URL: %s", url)
|
271
276
|
|
272
|
-
|
273
|
-
headers = dict(self.request.headers)
|
277
|
+
headers = {}
|
274
278
|
|
275
279
|
# Add headers for Geoserver
|
276
280
|
if ogc_server.auth == main.OGCSERVER_AUTH_GEOSERVER:
|
277
281
|
headers["sec-username"] = "root"
|
278
282
|
headers["sec-roles"] = "root"
|
279
283
|
|
280
|
-
if url.hostname != "localhost" and "Host" in headers:
|
281
|
-
headers.pop("Host")
|
282
|
-
|
283
|
-
headers = restrict_headers(headers, self.headers_whitelist, self.headers_blacklist)
|
284
|
-
|
285
284
|
try:
|
286
|
-
content, content_type =
|
287
|
-
None, get_http_cached, self.http_options, url, headers
|
288
|
-
)
|
285
|
+
content, content_type = get_http_cached(self.http_options, url.url(), headers, cache=cache)
|
289
286
|
except Exception:
|
290
287
|
error = f"Unable to GetCapabilities from URL {url}"
|
291
288
|
errors.add(error)
|
@@ -858,7 +855,7 @@ class Theme:
|
|
858
855
|
return None if self.request.user is None else {role.id for role in self.request.user.roles}
|
859
856
|
|
860
857
|
async def _wfs_get_features_type(
|
861
|
-
self, wfs_url: Url,
|
858
|
+
self, wfs_url: Url, ogc_server: main.OGCServer, preload: bool = False, cache: bool = True
|
862
859
|
) -> Tuple[Optional[etree.Element], Set[str]]:
|
863
860
|
errors = set()
|
864
861
|
|
@@ -872,23 +869,23 @@ class Theme:
|
|
872
869
|
}
|
873
870
|
)
|
874
871
|
|
875
|
-
LOG.debug(
|
872
|
+
LOG.debug(
|
873
|
+
"Get the WFS DescribeFeatureType of '%s', preload: %s, cache: %s", ogc_server.name, preload, cache
|
874
|
+
)
|
876
875
|
|
877
|
-
|
878
|
-
headers = dict(self.request.headers)
|
879
|
-
if wfs_url.hostname != "localhost" and "Host" in headers:
|
880
|
-
headers.pop("Host")
|
876
|
+
headers = {}
|
881
877
|
|
882
|
-
|
878
|
+
# Add headers for Geoserver
|
879
|
+
if ogc_server.auth == main.OGCSERVER_AUTH_GEOSERVER:
|
880
|
+
headers["sec-username"] = "root"
|
881
|
+
headers["sec-roles"] = "root"
|
883
882
|
|
884
883
|
try:
|
885
|
-
content, _ =
|
886
|
-
None, get_http_cached, self.http_options, wfs_url, headers
|
887
|
-
)
|
884
|
+
content, _ = get_http_cached(self.http_options, wfs_url.url(), headers, cache)
|
888
885
|
except requests.exceptions.RequestException as exception:
|
889
886
|
error = (
|
890
887
|
f"Unable to get WFS DescribeFeatureType from the URL '{wfs_url.url()}' for "
|
891
|
-
f"OGC server {
|
888
|
+
f"OGC server {ogc_server.name}, "
|
892
889
|
+ (
|
893
890
|
f"return the error: {exception.response.status_code} {exception.response.reason}"
|
894
891
|
if exception.response is not None
|
@@ -901,7 +898,7 @@ class Theme:
|
|
901
898
|
except Exception:
|
902
899
|
error = (
|
903
900
|
f"Unable to get WFS DescribeFeatureType from the URL {wfs_url} for "
|
904
|
-
f"OGC server {
|
901
|
+
f"OGC server {ogc_server.name}"
|
905
902
|
)
|
906
903
|
errors.add(error)
|
907
904
|
LOG.exception(error)
|
@@ -948,10 +945,10 @@ class Theme:
|
|
948
945
|
url_internal_wfs = url_wfs
|
949
946
|
return url_internal_wfs, url, url_wfs
|
950
947
|
|
951
|
-
async def
|
948
|
+
async def _preload(self, errors: Set[str]) -> None:
|
952
949
|
tasks = set()
|
953
950
|
for ogc_server in models.DBSession.query(main.OGCServer).all():
|
954
|
-
# Don't load unused OGC servers, required for
|
951
|
+
# Don't load unused OGC servers, required for landing page, because the related OGC server
|
955
952
|
# will be on error in those functions.
|
956
953
|
nb_layers = (
|
957
954
|
models.DBSession.query(sqlalchemy.func.count(main.LayerWMS.id))
|
@@ -963,26 +960,30 @@ class Theme:
|
|
963
960
|
LOG.debug("Preload OGC server '%s'", ogc_server.name)
|
964
961
|
url_internal_wfs, _, _ = self.get_url_internal_wfs(ogc_server, errors)
|
965
962
|
if url_internal_wfs is not None:
|
966
|
-
|
967
|
-
tasks.add(self._wfs_get_features_type(url_internal_wfs, ogc_server.name, True))
|
968
|
-
tasks.add(self._wms_getcap(ogc_server, True))
|
963
|
+
tasks.add(self.preload_ogc_server(ogc_server, url_internal_wfs))
|
969
964
|
|
970
965
|
await asyncio.gather(*tasks)
|
971
966
|
|
967
|
+
async def preload_ogc_server(
|
968
|
+
self, ogc_server: main.OGCServer, url_internal_wfs: Url, cache: bool = True
|
969
|
+
) -> None:
|
970
|
+
if ogc_server.wfs_support:
|
971
|
+
await self._get_features_attributes(url_internal_wfs, ogc_server, cache=cache)
|
972
|
+
await self._wms_getcap(ogc_server, False, cache=cache)
|
973
|
+
|
972
974
|
async def _get_features_attributes(
|
973
|
-
self, url_internal_wfs: Url,
|
975
|
+
self, url_internal_wfs: Url, ogc_server: main.OGCServer, cache: bool = True
|
974
976
|
) -> Tuple[Optional[Dict[str, Dict[Any, Dict[str, Any]]]], Optional[str], Set[str]]:
|
975
|
-
@
|
977
|
+
@CACHE_OGC_SERVER_REGION.cache_on_arguments() # type: ignore
|
976
978
|
def _get_features_attributes_cache(
|
977
979
|
url_internal_wfs: Url, ogc_server_name: str
|
978
980
|
) -> Tuple[Optional[Dict[str, Dict[Any, Dict[str, Any]]]], Optional[str], Set[str]]:
|
979
981
|
del url_internal_wfs # Just for cache
|
980
982
|
all_errors: Set[str] = set()
|
981
|
-
LOG.debug("Run garbage collection: %s", ", ".join([str(gc.collect(n)) for n in range(3)]))
|
982
983
|
if errors:
|
983
984
|
all_errors |= errors
|
984
985
|
return None, None, all_errors
|
985
|
-
assert feature_type
|
986
|
+
assert feature_type is not None
|
986
987
|
namespace: str = feature_type.attrib.get("targetNamespace")
|
987
988
|
types: Dict[Any, Dict[str, Any]] = {}
|
988
989
|
elements = {}
|
@@ -1041,9 +1042,8 @@ class Theme:
|
|
1041
1042
|
attributes[name] = types[type_]
|
1042
1043
|
elif (type_ == "Character") and (name + "Type") in types:
|
1043
1044
|
LOG.debug(
|
1044
|
-
|
1045
|
-
"
|
1046
|
-
'METADATA "gml_types" "auto"',
|
1045
|
+
'Due to MapServer weird behavior when using METADATA "gml_types" "auto"'
|
1046
|
+
"the type 'ms:Character' is returned as type '%sType' for feature '%s'.",
|
1047
1047
|
name,
|
1048
1048
|
name,
|
1049
1049
|
)
|
@@ -1057,12 +1057,14 @@ class Theme:
|
|
1057
1057
|
|
1058
1058
|
return attributes, namespace, all_errors
|
1059
1059
|
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1060
|
+
if cache:
|
1061
|
+
result = _get_features_attributes_cache.get(url_internal_wfs, ogc_server.name)
|
1062
|
+
if result != dogpile.cache.api.NO_VALUE:
|
1063
|
+
return result # type: ignore
|
1064
|
+
|
1065
|
+
feature_type, errors = await self._wfs_get_features_type(url_internal_wfs, ogc_server, False, cache)
|
1063
1066
|
|
1064
|
-
|
1065
|
-
return _get_features_attributes_cache(url_internal_wfs, ogc_server_name) # type: ignore
|
1067
|
+
return _get_features_attributes_cache.refresh(url_internal_wfs, ogc_server.name) # type: ignore
|
1066
1068
|
|
1067
1069
|
@view_config(route_name="themes", renderer="json") # type: ignore
|
1068
1070
|
def themes(self) -> Dict[str, Union[Dict[str, Dict[str, Any]], List[str]]]:
|
@@ -1083,11 +1085,12 @@ class Theme:
|
|
1083
1085
|
all_errors: Set[str] = set()
|
1084
1086
|
LOG.debug("Start preload")
|
1085
1087
|
start_time = time.time()
|
1086
|
-
await self.
|
1088
|
+
await self._preload(all_errors)
|
1087
1089
|
LOG.debug("End preload")
|
1088
1090
|
# Don't log if it looks to be already preloaded.
|
1089
1091
|
if (time.time() - start_time) > 1:
|
1090
1092
|
LOG.info("Do preload in %.3fs.", time.time() - start_time)
|
1093
|
+
LOG.debug("Run garbage collection: %s", ", ".join([str(gc.collect(n)) for n in range(3)]))
|
1091
1094
|
result["ogcServers"] = {}
|
1092
1095
|
for ogc_server in models.DBSession.query(main.OGCServer).all():
|
1093
1096
|
nb_layers = (
|
@@ -1096,9 +1099,11 @@ class Theme:
|
|
1096
1099
|
.one()
|
1097
1100
|
)
|
1098
1101
|
if nb_layers[0] == 0:
|
1099
|
-
# QGIS Server
|
1102
|
+
# QGIS Server landing page requires an OGC server that can't be used here.
|
1100
1103
|
continue
|
1101
1104
|
|
1105
|
+
LOG.debug("Process OGC server '%s'", ogc_server.name)
|
1106
|
+
|
1102
1107
|
url_internal_wfs, url, url_wfs = self.get_url_internal_wfs(ogc_server, all_errors)
|
1103
1108
|
|
1104
1109
|
attributes = None
|
@@ -1110,7 +1115,7 @@ class Theme:
|
|
1110
1115
|
)
|
1111
1116
|
if ogc_server.wfs_support and url_internal_wfs:
|
1112
1117
|
attributes, namespace, errors = await self._get_features_attributes(
|
1113
|
-
url_internal_wfs, ogc_server
|
1118
|
+
url_internal_wfs, ogc_server
|
1114
1119
|
)
|
1115
1120
|
# Create a local copy (don't modify the cache)
|
1116
1121
|
if attributes is not None:
|
@@ -1209,3 +1214,34 @@ class Theme:
|
|
1209
1214
|
f"{', '.join([i[0] for i in models.DBSession.query(main.LayerGroup.name).all()])}"
|
1210
1215
|
},
|
1211
1216
|
)
|
1217
|
+
|
1218
|
+
@view_config(route_name="ogc_server_clear_cache", renderer="json") # type: ignore
|
1219
|
+
def ogc_server_clear_cache_view(self) -> Dict[str, Any]:
|
1220
|
+
self._ogc_server_clear_cache(
|
1221
|
+
models.DBSession.query(main.OGCServer).filter_by(id=self.request.matchdict.get("id")).one()
|
1222
|
+
)
|
1223
|
+
came_from = self.request.params.get("came_from")
|
1224
|
+
if came_from:
|
1225
|
+
raise pyramid.httpexceptions.HTTPFound(location=came_from)
|
1226
|
+
return {"success": True}
|
1227
|
+
|
1228
|
+
def _ogc_server_clear_cache(self, ogc_server: main.OGCServer) -> None:
|
1229
|
+
errors: Set[str] = set()
|
1230
|
+
url_internal_wfs, _, _ = self.get_url_internal_wfs(ogc_server, errors)
|
1231
|
+
if errors:
|
1232
|
+
LOG.error(
|
1233
|
+
"Error while getting the URL of the OGC Server %s:\n%s", ogc_server.id, "\n".join(errors)
|
1234
|
+
)
|
1235
|
+
return
|
1236
|
+
if url_internal_wfs is None:
|
1237
|
+
return
|
1238
|
+
|
1239
|
+
asyncio.run(self._async_cache_invalidate_ogc_server_cb(ogc_server, url_internal_wfs))
|
1240
|
+
|
1241
|
+
async def _async_cache_invalidate_ogc_server_cb(
|
1242
|
+
self, ogc_server: main.OGCServer, url_internal_wfs: Url
|
1243
|
+
) -> None:
|
1244
|
+
# Fill the cache
|
1245
|
+
await self.preload_ogc_server(ogc_server, url_internal_wfs, False)
|
1246
|
+
|
1247
|
+
cache_invalidate_cb()
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2015-
|
1
|
+
# Copyright (c) 2015-2023, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -27,7 +27,7 @@
|
|
27
27
|
|
28
28
|
|
29
29
|
import logging
|
30
|
-
from typing import Any, Dict,
|
30
|
+
from typing import Any, Dict, Set, Tuple
|
31
31
|
|
32
32
|
import pyramid.request
|
33
33
|
from defusedxml import ElementTree
|
@@ -68,9 +68,6 @@ class TinyOWSProxy(OGCProxy):
|
|
68
68
|
# params hold the parameters we are going to send to TinyOWS
|
69
69
|
self.lower_params = self._get_lower_params(dict(self.request.params))
|
70
70
|
|
71
|
-
def _get_wfs_url(self, errors: Set[str]) -> Optional[Url]:
|
72
|
-
return Url(self.settings.get("tinyows_url"))
|
73
|
-
|
74
71
|
@view_config(route_name="tinyowsproxy") # type: ignore
|
75
72
|
def proxy(self) -> pyramid.response.Response:
|
76
73
|
if self.user is None:
|
@@ -102,9 +99,7 @@ class TinyOWSProxy(OGCProxy):
|
|
102
99
|
# for DescribeFeatureType we require that exactly one type-name
|
103
100
|
# is given, otherwise we would have to filter the result
|
104
101
|
if len(typenames) != 1:
|
105
|
-
raise HTTPBadRequest(
|
106
|
-
"Exactly one type-name must be given for " "DescribeFeatureType requests"
|
107
|
-
)
|
102
|
+
raise HTTPBadRequest("Exactly one type-name must be given for DescribeFeatureType requests")
|
108
103
|
|
109
104
|
if not self._is_allowed(typenames):
|
110
105
|
raise HTTPForbidden("No access rights for at least one of the given type-names")
|
@@ -113,11 +108,7 @@ class TinyOWSProxy(OGCProxy):
|
|
113
108
|
use_cache = method == "GET" and operation in ("getcapabilities", "describefeaturetype")
|
114
109
|
cache_control = Cache.PRIVATE if use_cache else Cache.PRIVATE_NO
|
115
110
|
|
116
|
-
errors: Set[str] = set()
|
117
111
|
url = Url(self.settings.get("tinyows_url"))
|
118
|
-
if url is None:
|
119
|
-
LOG.error("Error getting the URL:\n%s", "\n".join(errors))
|
120
|
-
raise HTTPInternalServerError()
|
121
112
|
|
122
113
|
response = self._proxy_callback(
|
123
114
|
operation,
|
{c2cgeoportal_geoportal-2.7.1.157.dist-info → c2cgeoportal_geoportal-2.8.1.89.dist-info}/METADATA
RENAMED
@@ -1,11 +1,13 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: c2cgeoportal-geoportal
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.8.1.89
|
4
4
|
Summary: c2cgeoportal geoportal
|
5
5
|
Home-page: https://github.com/camptocamp/c2cgeoportal/
|
6
6
|
Author: Camptocamp
|
7
7
|
Author-email: info@camptocamp.com
|
8
|
+
License: UNKNOWN
|
8
9
|
Keywords: web gis geoportail c2cgeoportal geocommune pyramid
|
10
|
+
Platform: UNKNOWN
|
9
11
|
Classifier: Development Status :: 6 - Mature
|
10
12
|
Classifier: Environment :: Web Environment
|
11
13
|
Classifier: Framework :: Pyramid
|
@@ -17,23 +19,30 @@ Classifier: Programming Language :: Python :: 3
|
|
17
19
|
Classifier: Programming Language :: Python :: 3.8
|
18
20
|
Classifier: Topic :: Scientific/Engineering :: GIS
|
19
21
|
Classifier: Typing :: Typed
|
22
|
+
Requires-Dist: Fiona
|
23
|
+
Requires-Dist: GeoAlchemy2
|
24
|
+
Requires-Dist: Mako
|
25
|
+
Requires-Dist: OWSLib (>=0.6.0)
|
26
|
+
Requires-Dist: PyYAML
|
27
|
+
Requires-Dist: SQLAlchemy
|
28
|
+
Requires-Dist: Shapely
|
20
29
|
Requires-Dist: alembic
|
21
30
|
Requires-Dist: bottle
|
31
|
+
Requires-Dist: c2c.template (>=2.0.7)
|
22
32
|
Requires-Dist: c2cgeoportal-commons[upgrade]
|
23
33
|
Requires-Dist: c2cwsgiutils
|
24
|
-
Requires-Dist:
|
34
|
+
Requires-Dist: certifi (>=2022.12.7)
|
25
35
|
Requires-Dist: defusedxml
|
26
36
|
Requires-Dist: dogpile.cache (>=0.6)
|
27
|
-
Requires-Dist: Fiona
|
28
|
-
Requires-Dist: GeoAlchemy2
|
29
37
|
Requires-Dist: geojson
|
38
|
+
Requires-Dist: idna (>=3.7)
|
30
39
|
Requires-Dist: isodate
|
40
|
+
Requires-Dist: jinja2 (>=3.1.3)
|
31
41
|
Requires-Dist: lingua
|
32
|
-
Requires-Dist: Mako
|
33
|
-
Requires-Dist: OWSLib (>=0.6.0)
|
34
42
|
Requires-Dist: papyrus
|
35
43
|
Requires-Dist: psycopg2
|
36
44
|
Requires-Dist: pycryptodome
|
45
|
+
Requires-Dist: pygments (>=2.15.0)
|
37
46
|
Requires-Dist: pyotp
|
38
47
|
Requires-Dist: pyramid
|
39
48
|
Requires-Dist: pyramid-debugtoolbar
|
@@ -41,18 +50,13 @@ Requires-Dist: pyramid-mako
|
|
41
50
|
Requires-Dist: pyramid-multiauth
|
42
51
|
Requires-Dist: pyramid-tm
|
43
52
|
Requires-Dist: python-dateutil
|
44
|
-
Requires-Dist: PyYAML
|
45
53
|
Requires-Dist: rasterio
|
46
|
-
Requires-Dist: requests
|
47
54
|
Requires-Dist: redis
|
48
|
-
Requires-Dist:
|
49
|
-
Requires-Dist: SQLAlchemy
|
50
|
-
Requires-Dist: transaction
|
51
|
-
Requires-Dist: jinja2 (>=2.11.3)
|
52
|
-
Requires-Dist: pygments (>=2.7.4)
|
53
|
-
Requires-Dist: setuptools (>=65.5.1)
|
55
|
+
Requires-Dist: requests
|
54
56
|
Requires-Dist: requests (>=2.31.0)
|
55
|
-
Requires-Dist:
|
57
|
+
Requires-Dist: setuptools (>=65.5.1)
|
58
|
+
Requires-Dist: transaction
|
59
|
+
Requires-Dist: urllib3 (>=1.26.17)
|
56
60
|
|
57
61
|
c2cgeoportal is the server part of `GeoMapFish <http://geomapfish.org/>`_,
|
58
62
|
the client part is `ngeo <https://github.com/camptocamp/ngeo/>`_.
|
@@ -60,3 +64,4 @@ the client part is `ngeo <https://github.com/camptocamp/ngeo/>`_.
|
|
60
64
|
Read the `Documentation <https://camptocamp.github.io/c2cgeoportal/master/>`_.
|
61
65
|
|
62
66
|
`Sources <https://github.com/camptocamp/c2cgeoportal/>`_
|
67
|
+
|