c2cgeoportal-geoportal 2.7.1.157__py2.py3-none-any.whl → 2.8.1.87__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.
Files changed (101) hide show
  1. c2cgeoportal_geoportal/__init__.py +23 -14
  2. c2cgeoportal_geoportal/lib/__init__.py +3 -5
  3. c2cgeoportal_geoportal/lib/authentication.py +10 -14
  4. c2cgeoportal_geoportal/lib/caching.py +8 -6
  5. c2cgeoportal_geoportal/lib/checker.py +10 -6
  6. c2cgeoportal_geoportal/lib/common_headers.py +2 -2
  7. c2cgeoportal_geoportal/lib/dbreflection.py +8 -8
  8. c2cgeoportal_geoportal/lib/filter_capabilities.py +8 -6
  9. c2cgeoportal_geoportal/lib/lingua_extractor.py +11 -12
  10. c2cgeoportal_geoportal/lib/loader.py +1 -1
  11. c2cgeoportal_geoportal/lib/oauth2.py +217 -100
  12. c2cgeoportal_geoportal/lib/wmstparsing.py +8 -12
  13. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Dockerfile +9 -11
  14. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/development.ini +1 -1
  15. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/gunicorn.conf.py +3 -3
  16. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/requirements.txt +1 -1
  17. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.api.js +6 -4
  18. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.apps.js +1 -3
  19. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.commons.js +1 -0
  20. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/__init__.py +1 -6
  21. c2cgeoportal_geoportal/scaffolds/advance_update/{{cookiecutter.project}}/geoportal/CONST_Makefile +0 -20
  22. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/main.yaml +21 -7
  23. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/rebuild.yaml +1 -1
  24. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/update_l10n.yaml +2 -1
  25. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Dockerfile +22 -22
  26. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile +58 -2
  27. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build +49 -29
  28. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/config.yaml +2 -5
  29. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/docker-compose-check +25 -0
  30. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/requirements.txt +1 -1
  31. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-db.yaml +26 -0
  32. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml +35 -26
  33. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-qgis.yaml +23 -0
  34. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml +0 -2
  35. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.yaml +3 -3
  36. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.default +21 -2
  37. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.project +9 -0
  38. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/vars.yaml +38 -14
  39. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/Readme.txt +2 -2
  40. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.conf +15 -0
  41. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.map.tmpl +2 -3
  42. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Landscape.jrxml +5 -0
  43. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Portrait.jrxml +5 -0
  44. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Landscape.jrxml +5 -0
  45. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Portrait.jrxml +5 -0
  46. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/config.yaml.tmpl +6 -0
  47. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml +4 -0
  48. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/run_alembic.sh +3 -5
  49. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-backup +5 -8
  50. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +5 -8
  51. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/spell-ignore-words.txt +2 -0
  52. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/__init__.py +0 -0
  53. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/test_app.py +38 -0
  54. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/.upgrade.yaml +2 -132
  55. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_CHANGELOG.txt +200 -1105
  56. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_create_template/tests/test_testapp.py +48 -0
  57. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_config-schema.yaml +17 -15
  58. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_vars.yaml +48 -2
  59. c2cgeoportal_geoportal/scripts/__init__.py +3 -5
  60. c2cgeoportal_geoportal/scripts/c2cupgrade.py +1 -2
  61. c2cgeoportal_geoportal/scripts/pcreate.py +8 -10
  62. c2cgeoportal_geoportal/scripts/theme2fts.py +58 -3
  63. c2cgeoportal_geoportal/scripts/urllogin.py +2 -2
  64. c2cgeoportal_geoportal/views/__init__.py +1 -3
  65. c2cgeoportal_geoportal/views/dynamic.py +2 -3
  66. c2cgeoportal_geoportal/views/entry.py +2 -10
  67. c2cgeoportal_geoportal/views/fulltextsearch.py +1 -1
  68. c2cgeoportal_geoportal/views/geometry_processing.py +3 -3
  69. c2cgeoportal_geoportal/views/layers.py +10 -11
  70. c2cgeoportal_geoportal/views/login.py +63 -8
  71. c2cgeoportal_geoportal/views/mapserverproxy.py +3 -4
  72. c2cgeoportal_geoportal/views/ogcproxy.py +6 -2
  73. c2cgeoportal_geoportal/views/pdfreport.py +1 -1
  74. c2cgeoportal_geoportal/views/printproxy.py +6 -8
  75. c2cgeoportal_geoportal/views/profile.py +1 -1
  76. c2cgeoportal_geoportal/views/proxy.py +6 -9
  77. c2cgeoportal_geoportal/views/raster.py +2 -2
  78. c2cgeoportal_geoportal/views/resourceproxy.py +1 -1
  79. c2cgeoportal_geoportal/views/shortener.py +1 -2
  80. c2cgeoportal_geoportal/views/theme.py +97 -61
  81. c2cgeoportal_geoportal/views/tinyowsproxy.py +3 -12
  82. c2cgeoportal_geoportal/views/vector_tiles.py +1 -1
  83. {c2cgeoportal_geoportal-2.7.1.157.dist-info → c2cgeoportal_geoportal-2.8.1.87.dist-info}/METADATA +20 -15
  84. {c2cgeoportal_geoportal-2.7.1.157.dist-info → c2cgeoportal_geoportal-2.8.1.87.dist-info}/RECORD +100 -94
  85. {c2cgeoportal_geoportal-2.7.1.157.dist-info → c2cgeoportal_geoportal-2.8.1.87.dist-info}/entry_points.txt +1 -0
  86. tests/__init__.py +3 -2
  87. tests/test_cachebuster.py +3 -3
  88. tests/test_caching.py +1 -1
  89. tests/test_checker.py +1 -1
  90. tests/test_decimaljson.py +1 -1
  91. tests/test_headerstween.py +1 -1
  92. tests/test_i18n.py +1 -1
  93. tests/test_init.py +14 -15
  94. tests/test_locale_negociator.py +4 -4
  95. tests/test_mapserverproxy_route_predicate.py +1 -2
  96. tests/test_raster.py +15 -15
  97. tests/test_wmstparsing.py +10 -10
  98. tests/xmlstr.py +1 -3
  99. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/tools/extract-messages.js +0 -41
  100. {c2cgeoportal_geoportal-2.7.1.157.dist-info → c2cgeoportal_geoportal-2.8.1.87.dist-info}/WHEEL +0 -0
  101. {c2cgeoportal_geoportal-2.7.1.157.dist-info → c2cgeoportal_geoportal-2.8.1.87.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2011-2022, Camptocamp SA
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(http_options: Dict[str, Any], url: str, headers: Dict[str, str]) -> Tuple[bytes, str]:
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
- @CACHE_REGION.cache_on_arguments() # type: ignore
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 url '%s' in %.1fs.", url, response.elapsed.total_seconds())
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
- return do_get_http_cached(url) # type: ignore
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
- @CACHE_REGION.cache_on_arguments() # type: ignore
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("Url is None")
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
- result = build_web_map_service.get(ogc_server.id)
228
- if result != dogpile.cache.api.NO_VALUE:
229
- return result # type: ignore
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
- # Forward request to target (without Host Header)
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 = await asyncio.get_event_loop().run_in_executor(
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, ogc_server_name: str, preload: bool = False
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("WFS DescribeFeatureType for the URL: %s", wfs_url)
872
+ LOG.debug(
873
+ "Get the WFS DescribeFeatureType of '%s', preload: %s, cache: %s", ogc_server.name, preload, cache
874
+ )
876
875
 
877
- # forward request to target (without Host Header)
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
- headers = restrict_headers(headers, self.headers_whitelist, self.headers_blacklist)
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, _ = await asyncio.get_event_loop().run_in_executor(
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 {ogc_server_name}, "
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 {ogc_server_name}"
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 preload(self, errors: Set[str]) -> None:
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 Landigpage, because the related OGC server
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
- if ogc_server.wfs_support:
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, ogc_server_name: str
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
- @CACHE_REGION.cache_on_arguments() # type: ignore
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
- "Due MapServer strange result the type 'ms:Character' is fallbacked to type '%sType'"
1045
- " for feature '%s', This is a strange comportement of MapServer when we use the "
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
- result = _get_features_attributes_cache.get(url_internal_wfs, ogc_server_name)
1061
- if result != dogpile.cache.api.NO_VALUE:
1062
- return result # type: ignore
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
- feature_type, errors = await self._wfs_get_features_type(url_internal_wfs, ogc_server_name)
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.preload(all_errors)
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 langing page requires an OGC server that can't be used here.
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.name
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-2021, Camptocamp SA
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, Optional, Set, Tuple
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,
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021, Camptocamp SA
1
+ # Copyright (c) 2021-2023, Camptocamp SA
2
2
  # All rights reserved.
3
3
 
4
4
  # Redistribution and use in source and binary forms, with or without
@@ -1,11 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: c2cgeoportal-geoportal
3
- Version: 2.7.1.157
3
+ Version: 2.8.1.87
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: c2c.template (>=2.0.7)
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: Shapely
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: fiona (>=1.10b2)
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
+