c2cgeoportal-geoportal 2.3.5.80__py3-none-any.whl → 2.9rc45__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 (198) hide show
  1. c2cgeoportal_geoportal/__init__.py +960 -0
  2. c2cgeoportal_geoportal/lib/__init__.py +256 -0
  3. c2cgeoportal_geoportal/lib/authentication.py +250 -0
  4. c2cgeoportal_geoportal/lib/bashcolor.py +46 -0
  5. c2cgeoportal_geoportal/lib/cacheversion.py +77 -0
  6. c2cgeoportal_geoportal/lib/caching.py +176 -0
  7. c2cgeoportal_geoportal/lib/check_collector.py +80 -0
  8. c2cgeoportal_geoportal/lib/checker.py +295 -0
  9. c2cgeoportal_geoportal/lib/common_headers.py +172 -0
  10. c2cgeoportal_geoportal/lib/dbreflection.py +266 -0
  11. c2cgeoportal_geoportal/lib/filter_capabilities.py +360 -0
  12. c2cgeoportal_geoportal/lib/fulltextsearch.py +50 -0
  13. c2cgeoportal_geoportal/lib/functionality.py +166 -0
  14. c2cgeoportal_geoportal/lib/headers.py +62 -0
  15. c2cgeoportal_geoportal/lib/i18n.py +38 -0
  16. c2cgeoportal_geoportal/lib/layers.py +132 -0
  17. c2cgeoportal_geoportal/lib/lingva_extractor.py +937 -0
  18. c2cgeoportal_geoportal/lib/loader.py +57 -0
  19. c2cgeoportal_geoportal/lib/metrics.py +117 -0
  20. c2cgeoportal_geoportal/lib/oauth2.py +1186 -0
  21. c2cgeoportal_geoportal/lib/oidc.py +304 -0
  22. c2cgeoportal_geoportal/lib/wmstparsing.py +353 -0
  23. c2cgeoportal_geoportal/lib/xsd.py +166 -0
  24. c2cgeoportal_geoportal/py.typed +0 -0
  25. c2cgeoportal_geoportal/resources.py +49 -0
  26. c2cgeoportal_geoportal/scaffolds/advance_create/ci/config.yaml +26 -0
  27. c2cgeoportal_geoportal/scaffolds/advance_create/cookiecutter.json +18 -0
  28. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.dockerignore +6 -0
  29. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.eslintrc.yaml +19 -0
  30. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml +30 -0
  31. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Dockerfile +75 -0
  32. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Makefile +6 -0
  33. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/alembic.ini +58 -0
  34. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/alembic.yaml +19 -0
  35. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/development.ini +121 -0
  36. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/gunicorn.conf.py +139 -0
  37. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/language_mapping +3 -0
  38. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/lingva-client.cfg +5 -0
  39. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/lingva-server.cfg +6 -0
  40. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/production.ini +38 -0
  41. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/requirements.txt +2 -0
  42. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/setup.py +25 -0
  43. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.api.js +41 -0
  44. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.apps.js +64 -0
  45. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.commons.js +11 -0
  46. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.config.js +22 -0
  47. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/__init__.py +42 -0
  48. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/authentication.py +10 -0
  49. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/dev.py +14 -0
  50. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/models.py +8 -0
  51. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/multi_organization.py +7 -0
  52. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/resources.py +11 -0
  53. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/api/index.js +12 -0
  54. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/js/{{cookiecutter.package}}module.js +25 -0
  55. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/subscribers.py +39 -0
  56. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/views/__init__.py +0 -0
  57. c2cgeoportal_geoportal/scaffolds/advance_update/cookiecutter.json +18 -0
  58. c2cgeoportal_geoportal/scaffolds/advance_update/{{cookiecutter.project}}/geoportal/CONST_Makefile +121 -0
  59. c2cgeoportal_geoportal/scaffolds/create/cookiecutter.json +18 -0
  60. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.dockerignore +14 -0
  61. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.editorconfig +17 -0
  62. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/main.yaml +73 -0
  63. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/rebuild.yaml +50 -0
  64. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/update_l10n.yaml +66 -0
  65. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.gitignore +16 -0
  66. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.pre-commit-config.yaml +35 -0
  67. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierignore +1 -0
  68. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierrc.yaml +2 -0
  69. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Dockerfile +75 -0
  70. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile +70 -0
  71. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/README.rst +29 -0
  72. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build +179 -0
  73. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/config.yaml +22 -0
  74. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/docker-compose-check +25 -0
  75. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/requirements.txt +2 -0
  76. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-db.yaml +24 -0
  77. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml +513 -0
  78. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-qgis.yaml +21 -0
  79. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml +65 -0
  80. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.yaml +121 -0
  81. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.default +102 -0
  82. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.project +69 -0
  83. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/vars.yaml +430 -0
  84. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/locale/en/LC_MESSAGES/{{cookiecutter.package}}_geoportal-client.po +6 -0
  85. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/desktop.css +0 -0
  86. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/iframe_api.css +0 -0
  87. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/mobile.css +0 -0
  88. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/banner_left.png +0 -0
  89. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/banner_right.png +0 -0
  90. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/blank.png +0 -0
  91. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-blue.png +0 -0
  92. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-gold.png +0 -0
  93. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-green.png +0 -0
  94. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker.png +0 -0
  95. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/robot.txt.tmpl +3 -0
  96. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/Readme.txt +69 -0
  97. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/TM_EUROPE_BORDERS-0.3.sql +70 -0
  98. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/demo.map.tmpl +224 -0
  99. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arial.ttf +0 -0
  100. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arialbd.ttf +0 -0
  101. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arialbi.ttf +0 -0
  102. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Ariali.ttf +0 -0
  103. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Bold.ttf +0 -0
  104. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-BoldItalic.ttf +0 -0
  105. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Italic.ttf +0 -0
  106. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Regular.ttf +0 -0
  107. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdana.ttf +0 -0
  108. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanab.ttf +0 -0
  109. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanai.ttf +0 -0
  110. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanaz.ttf +0 -0
  111. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts.conf +12 -0
  112. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.conf +16 -0
  113. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.map.tmpl +87 -0
  114. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/tinyows.xml.tmpl +36 -0
  115. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Landscape.jrxml +207 -0
  116. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Portrait.jrxml +185 -0
  117. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Landscape.jrxml +200 -0
  118. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Portrait.jrxml +170 -0
  119. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/config.yaml.tmpl +175 -0
  120. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/legend.jrxml +109 -0
  121. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation.properties +4 -0
  122. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation_fr.properties +4 -0
  123. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/logo.png +0 -0
  124. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/north.svg +93 -0
  125. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/results.jrxml +25 -0
  126. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/project.yaml +18 -0
  127. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml +7 -0
  128. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/qgisserver/pg_service.conf.tmpl +15 -0
  129. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/run_alembic.sh +11 -0
  130. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-backup +126 -0
  131. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +132 -0
  132. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/setup.cfg +7 -0
  133. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/spell-ignore-words.txt +5 -0
  134. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/__init__.py +0 -0
  135. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/test_app.py +78 -0
  136. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tilegeneration/config.yaml.tmpl +195 -0
  137. c2cgeoportal_geoportal/scaffolds/update/cookiecutter.json +18 -0
  138. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/.upgrade.yaml +67 -0
  139. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_CHANGELOG.txt +304 -0
  140. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_create_template/tests/test_testapp.py +48 -0
  141. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/.CONST_vars.yaml.swp +0 -0
  142. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_config-schema.yaml +927 -0
  143. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_vars.yaml +1503 -0
  144. c2cgeoportal_geoportal/scripts/__init__.py +64 -0
  145. c2cgeoportal_geoportal/scripts/c2cupgrade.py +879 -0
  146. c2cgeoportal_geoportal/scripts/create_demo_theme.py +83 -0
  147. c2cgeoportal_geoportal/scripts/manage_users.py +140 -0
  148. c2cgeoportal_geoportal/scripts/pcreate.py +296 -0
  149. c2cgeoportal_geoportal/scripts/theme2fts.py +347 -0
  150. c2cgeoportal_geoportal/scripts/urllogin.py +81 -0
  151. c2cgeoportal_geoportal/templates/login.html +90 -0
  152. c2cgeoportal_geoportal/templates/notlogin.html +62 -0
  153. c2cgeoportal_geoportal/templates/testi18n.html +12 -0
  154. c2cgeoportal_geoportal/views/__init__.py +59 -0
  155. c2cgeoportal_geoportal/views/dev.py +57 -0
  156. c2cgeoportal_geoportal/views/dynamic.py +209 -0
  157. c2cgeoportal_geoportal/views/entry.py +174 -0
  158. c2cgeoportal_geoportal/views/fulltextsearch.py +189 -0
  159. c2cgeoportal_geoportal/views/geometry_processing.py +75 -0
  160. c2cgeoportal_geoportal/views/i18n.py +129 -0
  161. c2cgeoportal_geoportal/views/layers.py +713 -0
  162. c2cgeoportal_geoportal/views/login.py +684 -0
  163. c2cgeoportal_geoportal/views/mapserverproxy.py +234 -0
  164. c2cgeoportal_geoportal/views/memory.py +90 -0
  165. c2cgeoportal_geoportal/views/ogcproxy.py +120 -0
  166. c2cgeoportal_geoportal/views/pdfreport.py +245 -0
  167. c2cgeoportal_geoportal/views/printproxy.py +143 -0
  168. c2cgeoportal_geoportal/views/profile.py +192 -0
  169. c2cgeoportal_geoportal/views/proxy.py +261 -0
  170. c2cgeoportal_geoportal/views/raster.py +233 -0
  171. c2cgeoportal_geoportal/views/resourceproxy.py +73 -0
  172. c2cgeoportal_geoportal/views/shortener.py +152 -0
  173. c2cgeoportal_geoportal/views/theme.py +1322 -0
  174. c2cgeoportal_geoportal/views/tinyowsproxy.py +189 -0
  175. c2cgeoportal_geoportal/views/vector_tiles.py +83 -0
  176. {c2cgeoportal_geoportal-2.3.5.80.dist-info → c2cgeoportal_geoportal-2.9rc45.dist-info}/METADATA +21 -24
  177. c2cgeoportal_geoportal-2.9rc45.dist-info/RECORD +193 -0
  178. {c2cgeoportal_geoportal-2.3.5.80.dist-info → c2cgeoportal_geoportal-2.9rc45.dist-info}/WHEEL +1 -1
  179. c2cgeoportal_geoportal-2.9rc45.dist-info/entry_points.txt +28 -0
  180. c2cgeoportal_geoportal-2.9rc45.dist-info/top_level.txt +2 -0
  181. tests/__init__.py +100 -0
  182. tests/test_cachebuster.py +71 -0
  183. tests/test_caching.py +275 -0
  184. tests/test_checker.py +85 -0
  185. tests/test_decimaljson.py +47 -0
  186. tests/test_headerstween.py +64 -0
  187. tests/test_i18n.py +31 -0
  188. tests/test_init.py +193 -0
  189. tests/test_locale_negociator.py +69 -0
  190. tests/test_mapserverproxy_route_predicate.py +64 -0
  191. tests/test_raster.py +267 -0
  192. tests/test_wmstparsing.py +238 -0
  193. tests/xmlstr.py +103 -0
  194. c2cgeoportal_geoportal-2.3.5.80.dist-info/DESCRIPTION.rst +0 -8
  195. c2cgeoportal_geoportal-2.3.5.80.dist-info/RECORD +0 -7
  196. c2cgeoportal_geoportal-2.3.5.80.dist-info/entry_points.txt +0 -22
  197. c2cgeoportal_geoportal-2.3.5.80.dist-info/metadata.json +0 -1
  198. c2cgeoportal_geoportal-2.3.5.80.dist-info/top_level.txt +0 -1
@@ -0,0 +1,172 @@
1
+ # Copyright (c) 2012-2025, Camptocamp SA
2
+ # All rights reserved.
3
+
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the FreeBSD Project.
27
+
28
+
29
+ import logging
30
+ from enum import Enum
31
+ from typing import Any, cast
32
+
33
+ import pyramid.request
34
+ import pyramid.response
35
+
36
+ _LOG = logging.getLogger(__name__)
37
+
38
+
39
+ class Cache(Enum):
40
+ """Enumeration for the possible cache values."""
41
+
42
+ # For responses that do not depend on authentication
43
+ PUBLIC = 0
44
+ # For responses that do not depends on authentication
45
+ # We use a small cache (e.g. 10s) instead of no cache to protect the service against too high traffic.
46
+ PUBLIC_NO = 1
47
+ # For responses that can depend on authentication
48
+ PRIVATE = 2
49
+ # See PUBLIC_NO and PRIVATE
50
+ PRIVATE_NO = 3
51
+
52
+
53
+ CORS_METHODS = "HEAD, GET, POST, PUT, DELETE"
54
+
55
+
56
+ def _set_cors_headers(
57
+ request: pyramid.request.Request,
58
+ response: pyramid.response.Response,
59
+ service_name: str,
60
+ service_headers_settings: dict[str, Any],
61
+ credentials: bool,
62
+ ) -> None:
63
+ """Handle CORS requests, as specified in https://www.w3.org/TR/cors/."""
64
+ response.vary = (
65
+ *(response.vary or ()),
66
+ "Origin",
67
+ "Access-Control-Request-Headers",
68
+ *(("Access-Control-Request-Method",) if request.method == "OPTIONS" else ()),
69
+ )
70
+
71
+ if "Origin" not in request.headers:
72
+ return # Not a CORS request if this header is missing
73
+ origin = request.headers["Origin"]
74
+
75
+ if request.method == "OPTIONS" and "Access-Control-Request-Method" not in request.headers:
76
+ _LOG.warning("CORS preflight query missing the Access-Control-Request-Method header")
77
+ return
78
+
79
+ allowed_origins = cast(list[str], service_headers_settings.get("access_control_allow_origin", []))
80
+ if origin not in allowed_origins:
81
+ if "*" in allowed_origins:
82
+ origin = "*"
83
+ credentials = False # Force no credentials
84
+ else:
85
+ _LOG.warning("CORS query not allowed for origin=%s, service=%s", origin, service_name)
86
+ return
87
+
88
+ response.headers.update(
89
+ {"Access-Control-Allow-Origin": origin, "Access-Control-Allow-Methods": CORS_METHODS}
90
+ )
91
+
92
+ max_age = service_headers_settings.get("access_control_max_age", 3600)
93
+ response.headers["Access-Control-Max-Age"] = str(max_age)
94
+
95
+ if credentials:
96
+ response.headers["Access-Control-Allow-Credentials"] = "true"
97
+
98
+ if request.method != "OPTIONS":
99
+ return
100
+
101
+ response.cache_control.max_age = max_age
102
+
103
+ if not service_headers_settings or "access_control_allow_origin" not in service_headers_settings:
104
+ _LOG.warning("CORS query not configured for service=%s", service_name)
105
+ return
106
+
107
+ requested_headers = request.headers.get("Access-Control-Request-Headers", False)
108
+ if requested_headers:
109
+ # For the moment, we allow all requested headers
110
+ response.headers["Access-Control-Allow-Headers"] = requested_headers
111
+
112
+ # If we start using headers in responses, we'll have to add
113
+ # Access-Control-Expose-Headers
114
+
115
+
116
+ def _set_common_headers(
117
+ request: pyramid.request.Request,
118
+ response: pyramid.response.Response,
119
+ service_headers_settings: dict[str, dict[str, str]],
120
+ cache: Cache,
121
+ content_type: str | None,
122
+ ) -> pyramid.response.Response:
123
+ """Set the common headers."""
124
+
125
+ del request # Unused
126
+
127
+ response.headers.update(service_headers_settings.get("headers", {}))
128
+
129
+ if cache in (Cache.PRIVATE, Cache.PRIVATE_NO):
130
+ response.vary = (response.vary or ()) + ("Cookie", "Authorization")
131
+
132
+ maxage = (
133
+ service_headers_settings.get("cache_control_max_age", 3600)
134
+ if cache in (Cache.PUBLIC, Cache.PRIVATE)
135
+ else service_headers_settings.get("cache_control_max_age_nocache", 10)
136
+ )
137
+ response.cache_control.max_age = maxage
138
+ if maxage == 0:
139
+ response.cache_control.no_cache = True
140
+ response.cache_control.no_store = True
141
+ elif cache in (Cache.PUBLIC, Cache.PUBLIC_NO):
142
+ response.cache_control.public = True
143
+ elif cache in (Cache.PRIVATE, Cache.PRIVATE_NO):
144
+ response.cache_control.private = True
145
+ else:
146
+ raise Exception("Invalid cache type") # pylint: disable=broad-exception-raised
147
+
148
+ if content_type is not None:
149
+ response.content_type = content_type
150
+
151
+ return response
152
+
153
+
154
+ def set_common_headers(
155
+ request: pyramid.request.Request,
156
+ service_name: str,
157
+ cache: Cache,
158
+ response: pyramid.response.Response = None,
159
+ credentials: bool = True,
160
+ content_type: str | None = None,
161
+ ) -> pyramid.response.Response:
162
+ """Set the common headers."""
163
+ if response is None:
164
+ response = request.response
165
+
166
+ headers_settings = request.registry.settings.get("headers", {})
167
+ service_headers_settings = headers_settings.get(service_name, {})
168
+
169
+ _set_cors_headers(request, response, service_name, service_headers_settings, credentials)
170
+ if request.method == "OPTIONS":
171
+ return response
172
+ return _set_common_headers(request, response, service_headers_settings, cache, content_type)
@@ -0,0 +1,266 @@
1
+ # Copyright (c) 2011-2024, Camptocamp SA
2
+ # All rights reserved.
3
+
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the FreeBSD Project.
27
+
28
+ import random
29
+ import threading
30
+ import warnings
31
+ from collections.abc import Iterable
32
+ from typing import Any, Union
33
+
34
+ import sqlalchemy.ext.declarative
35
+ from papyrus.geo_interface import GeoInterface
36
+ from sqlalchemy import Column, Integer, MetaData, Table
37
+ from sqlalchemy.exc import SAWarning
38
+ from sqlalchemy.orm import relationship
39
+ from sqlalchemy.orm.session import Session
40
+ from sqlalchemy.orm.util import class_mapper
41
+
42
+ from c2cgeoportal_geoportal.lib.caching import get_region
43
+
44
+ CACHE_REGION_OBJ = get_region("obj")
45
+ SQL_GEOMETRY_COLUMNS = """
46
+ SELECT srid, type
47
+ FROM geometry_columns
48
+ WHERE
49
+ f_table_schema = :table_schema AND
50
+ f_table_name = :table_name AND
51
+ f_geometry_column = :geometry_column
52
+ """
53
+
54
+
55
+ class _AssociationProxy:
56
+ # A specific "association proxy" implementation
57
+
58
+ def __init__(self, target: str, value_attr: str, nullable: bool = True, order_by: str | None = None):
59
+ self.target = target
60
+ self.value_attr = value_attr
61
+ self.nullable = nullable
62
+ self.order_by = order_by
63
+
64
+ def __get__(
65
+ self,
66
+ obj: sqlalchemy.ext.declarative.ConcreteBase,
67
+ type_: sqlalchemy.ext.declarative.DeclarativeMeta | None = None,
68
+ ) -> Union["_AssociationProxy", str] | None:
69
+ if obj is None:
70
+ # For "hybrid" descriptors that work both at the instance
71
+ # and class levels we could return an SQL expression here.
72
+ # The code of hybrid_property in SQLAlchemy illustrates
73
+ # how to do that.
74
+ return self
75
+ target = getattr(obj, self.target)
76
+ return getattr(target, self.value_attr) if target else None
77
+
78
+ def __set__(self, obj: str, val: str) -> None:
79
+ from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
80
+
81
+ assert DBSession is not None
82
+
83
+ o = getattr(obj, self.target)
84
+ # if the obj as no child object or if the child object
85
+ # does not correspond to the new value then we need to
86
+ # read a new child object from the database
87
+ if not o or getattr(o, self.value_attr) != val:
88
+ relationship_property = class_mapper(obj.__class__).get_property(self.target)
89
+ child_cls = relationship_property.argument
90
+ o = DBSession.query(child_cls).filter(getattr(child_cls, self.value_attr) == val).first()
91
+ setattr(obj, self.target, o)
92
+
93
+
94
+ def _get_schema(tablename: str) -> tuple[str, str]:
95
+ if "." in tablename:
96
+ schema, tablename = tablename.split(".", 1)
97
+ else:
98
+ schema = "public"
99
+
100
+ return tablename, schema
101
+
102
+
103
+ _get_table_lock = threading.RLock()
104
+
105
+
106
+ def get_table(
107
+ tablename: str,
108
+ schema: str | None = None,
109
+ session: Session | None = None,
110
+ primary_key: str | None = None,
111
+ ) -> Table:
112
+ """Build an SQLAlchemy table."""
113
+ if schema is None:
114
+ tablename, schema = _get_schema(tablename)
115
+
116
+ if session is not None:
117
+ assert session.bind is not None
118
+ engine = session.bind.engine
119
+ metadata = MetaData()
120
+ else:
121
+ from c2cgeoportal_commons.models import Base # pylint: disable=import-outside-toplevel
122
+ from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
123
+
124
+ assert DBSession is not None
125
+ assert DBSession.bind is not None
126
+
127
+ engine = DBSession.bind.engine
128
+ metadata = Base.metadata
129
+
130
+ # create table and reflect it
131
+ with warnings.catch_warnings():
132
+ warnings.filterwarnings("ignore", "Did not recognize type 'geometry' of column", SAWarning)
133
+ args = []
134
+ if primary_key is not None:
135
+ # Ensure we have a primary key to be able to edit views
136
+ args.append(Column(primary_key, Integer, primary_key=True))
137
+ with _get_table_lock:
138
+ table = Table(
139
+ tablename,
140
+ metadata,
141
+ *args,
142
+ schema=schema,
143
+ autoload_with=engine,
144
+ keep_existing=True,
145
+ )
146
+ return table
147
+
148
+
149
+ @CACHE_REGION_OBJ.cache_on_arguments()
150
+ def get_class(
151
+ tablename: str,
152
+ exclude_properties: list[str] | None = None,
153
+ primary_key: str | None = None,
154
+ attributes_order: list[str] | None = None,
155
+ enumerations_config: dict[str, str] | None = None,
156
+ readonly_attributes: list[str] | None = None,
157
+ ) -> type:
158
+ """
159
+ Get the SQLAlchemy mapped class for "tablename".
160
+
161
+ If no class exists for "tablename" one is created, and added to the cache. "tablename" must reference a
162
+ valid string. If there is no table identified by tablename in the database a NoSuchTableError SQLAlchemy
163
+ exception is raised.
164
+ """
165
+
166
+ tablename, schema = _get_schema(tablename)
167
+
168
+ table = get_table(tablename, schema, None, primary_key=primary_key)
169
+
170
+ # create the mapped class
171
+ cls = _create_class(
172
+ table,
173
+ exclude_properties=exclude_properties,
174
+ attributes_order=attributes_order,
175
+ enumerations_config=enumerations_config,
176
+ readonly_attributes=readonly_attributes,
177
+ )
178
+
179
+ return cls
180
+
181
+
182
+ def _create_class(
183
+ table: Table,
184
+ exclude_properties: Iterable[str] | None = None,
185
+ attributes_order: list[str] | None = None,
186
+ enumerations_config: dict[str, str] | None = None,
187
+ readonly_attributes: list[str] | None = None,
188
+ pk_name: str | None = None,
189
+ ) -> type:
190
+ from c2cgeoportal_commons.models import Base # pylint: disable=import-outside-toplevel
191
+
192
+ exclude_properties = exclude_properties or ()
193
+ attributes = {
194
+ "__table__": table,
195
+ "__mapper_args__": {"exclude_properties": exclude_properties},
196
+ "__attributes_order__": attributes_order,
197
+ "__enumerations_config__": enumerations_config,
198
+ }
199
+ if pk_name is not None:
200
+ attributes[pk_name] = Column(Integer, primary_key=True)
201
+ # The randint is to fix the SAWarning: This declarative base already contains a class with the same
202
+ # class name and module name
203
+ cls = type(
204
+ f"{table.name.capitalize()}_{random.randint(0, 9999999)}", # nosec
205
+ (GeoInterface, Base),
206
+ attributes,
207
+ )
208
+
209
+ for col in table.columns:
210
+ if col.name in (readonly_attributes or []):
211
+ col.info["readonly"] = True
212
+ if col.foreign_keys and col.name not in exclude_properties:
213
+ _add_association_proxy(cls, col)
214
+
215
+ return cls
216
+
217
+
218
+ def _add_association_proxy(cls: type[Any], col: sqlalchemy.sql.schema.Column[Any]) -> None:
219
+ if len(col.foreign_keys) != 1:
220
+ raise NotImplementedError
221
+
222
+ fk = next(iter(col.foreign_keys))
223
+ child_tablename, child_pk = fk.target_fullname.rsplit(".", 1)
224
+ child_cls = get_class(child_tablename)
225
+
226
+ try:
227
+ proxy = col.name[0 : col.name.rindex("_id")]
228
+ except ValueError:
229
+ proxy = col.name + "_"
230
+
231
+ # The following is a workaround for a bug in the geojson lib. The
232
+ # geojson lib indeed treats properties named "type" specifically
233
+ # (this is a GeoJSON keyword), and produces a UnicodeEncodeError
234
+ # if the property includes non-ascii characters.
235
+ if proxy == "type":
236
+ proxy = "type_"
237
+
238
+ rel = proxy + "_"
239
+ primaryjoin = getattr(cls, col.name) == getattr(child_cls, child_pk)
240
+ relationship_ = relationship(child_cls, primaryjoin=primaryjoin, lazy="immediate")
241
+ setattr(cls, rel, relationship_)
242
+
243
+ nullable = True
244
+ cls_column_property = getattr(cls, col.name).property
245
+ for column in cls_column_property.columns:
246
+ nullable = nullable and column.nullable
247
+
248
+ value_attr = "name"
249
+ order_by = value_attr
250
+
251
+ if cls.__enumerations_config__ and col.name in cls.__enumerations_config__:
252
+ enumeration_config = cls.__enumerations_config__[col.name]
253
+ if "value" in enumeration_config:
254
+ value_attr = enumeration_config["value"]
255
+ order_by = value_attr
256
+ if "order_by" in enumeration_config:
257
+ order_by = enumeration_config["order_by"]
258
+
259
+ setattr(cls, proxy, _AssociationProxy(rel, value_attr, nullable=nullable, order_by=order_by))
260
+
261
+ if cls.__add_properties__ is None:
262
+ cls.__add_properties__ = [proxy]
263
+ else:
264
+ cls.__add_properties__.append(proxy)
265
+
266
+ col.info["association_proxy"] = proxy