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,234 @@
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
+
29
+ import logging
30
+ from typing import Any
31
+
32
+ from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPInternalServerError, HTTPUnauthorized
33
+ from pyramid.request import Request
34
+ from pyramid.response import Response
35
+ from pyramid.view import view_config
36
+
37
+ from c2cgeoportal_commons.lib.url import Url
38
+ from c2cgeoportal_commons.models import main
39
+ from c2cgeoportal_geoportal.lib import get_roles_id, get_roles_name
40
+ from c2cgeoportal_geoportal.lib.caching import get_region
41
+ from c2cgeoportal_geoportal.lib.common_headers import Cache
42
+ from c2cgeoportal_geoportal.lib.filter_capabilities import filter_capabilities
43
+ from c2cgeoportal_geoportal.lib.functionality import get_mapserver_substitution_params
44
+ from c2cgeoportal_geoportal.views.ogcproxy import OGCProxy
45
+
46
+ _CACHE_REGION = get_region("std")
47
+ _LOG = logging.getLogger(__name__)
48
+
49
+
50
+ class MapservProxy(OGCProxy):
51
+ """Proxy for OGC (WMS/WFS) servers."""
52
+
53
+ params: dict[str, str] = {}
54
+
55
+ def __init__(self, request: Request) -> None:
56
+ OGCProxy.__init__(self, request)
57
+ self.user = self.request.user
58
+
59
+ @view_config(route_name="mapserverproxy") # type: ignore[misc]
60
+ @view_config(route_name="mapserverproxy_post") # type: ignore[misc]
61
+ def proxy(self) -> Response:
62
+ if self.user is None and "authentication_required" in self.request.params:
63
+ _LOG.debug("proxy() detected authentication_required")
64
+ if self.request.registry.settings.get("basicauth", "False").lower() == "true":
65
+ raise HTTPUnauthorized(
66
+ headers={"WWW-Authenticate": 'Basic realm="Access to restricted layers"'}
67
+ )
68
+ raise HTTPForbidden("Basic auth is not enabled")
69
+
70
+ # We have a user logged in. We need to set group_id and possible layer_name in the params. We set
71
+ # layer_name when either QUERY_PARAMS or LAYERS is set in the WMS params, i.e. for GetMap and
72
+ # GetFeatureInfo requests. For GetLegendGraphic requests we do not send layer_name, but MapServer
73
+ # should not use the DATA string for GetLegendGraphic.
74
+
75
+ self._setup_auth()
76
+
77
+ # Get method
78
+ method = self.request.method
79
+
80
+ # we want the browser to cache GetLegendGraphic and
81
+ # DescribeFeatureType requests
82
+ use_cache = False
83
+
84
+ errors: set[str] = set()
85
+ if method == "GET":
86
+ # For GET requests, params are added only if the self.request
87
+ # parameter is actually provided.
88
+ if "request" not in self.lower_params:
89
+ self.params = {}
90
+ else:
91
+ if self.ogc_server.type != main.OGCSERVER_TYPE_QGISSERVER or "user_id" not in self.params:
92
+ use_cache = self.lower_params["request"] in ("getlegendgraphic",)
93
+
94
+ # no user_id and role_id or cached queries
95
+ if use_cache and "user_id" in self.params:
96
+ del self.params["user_id"]
97
+ if use_cache and "role_ids" in self.params:
98
+ del self.params["role_ids"]
99
+
100
+ if "service" in self.lower_params and self.lower_params["service"] == "wfs":
101
+ _url = self._get_wfs_url(errors)
102
+ else:
103
+ _url = self._get_wms_url(errors)
104
+ else:
105
+ # POST means WFS
106
+ _url = self._get_wfs_url(errors)
107
+
108
+ if _url is None:
109
+ _LOG.error("Error getting the URL:\n%s", "\n".join(errors))
110
+ raise HTTPInternalServerError()
111
+
112
+ cache_control = (
113
+ Cache.PRIVATE
114
+ if method == "GET"
115
+ and self.lower_params.get("request")
116
+ in (
117
+ "getcapabilities",
118
+ "getlegendgraphic",
119
+ "describefeaturetype",
120
+ "describelayer",
121
+ )
122
+ else Cache.PRIVATE_NO
123
+ )
124
+
125
+ headers = self.get_headers()
126
+ # Add headers for Geoserver
127
+ if self.ogc_server.auth == main.OGCSERVER_AUTH_GEOSERVER and self.user is not None:
128
+ headers["sec-username"] = self.user.username
129
+ headers["sec-roles"] = ";".join(get_roles_name(self.request))
130
+
131
+ response = self._proxy_callback(
132
+ cache_control,
133
+ url=_url,
134
+ params=self.params,
135
+ cache=use_cache,
136
+ headers=headers,
137
+ body=self.request.body,
138
+ )
139
+
140
+ if (
141
+ self.lower_params.get("request") == "getmap"
142
+ and not response.content_type.startswith("image/")
143
+ and response.status_code < 400
144
+ ):
145
+ response.status_code = 400
146
+
147
+ return response
148
+
149
+ def _setup_auth(self) -> None:
150
+ if self.ogc_server.auth == main.OGCSERVER_AUTH_STANDARD:
151
+ self.params["role_ids"] = ",".join([str(e) for e in get_roles_id(self.request)])
152
+
153
+ # In some application we want to display the features owned by a user than we need his id.
154
+ self.params["user_id"] = self.user.id if self.user is not None else "-1"
155
+
156
+ # Do not allows direct variable substitution
157
+ for k in list(self.params.keys()):
158
+ if len(k) > 1 and k[:2].capitalize() == "S_":
159
+ _LOG.warning("Direct substitution not allowed (%s=%s).", k, self.params[k])
160
+ del self.params[k]
161
+
162
+ if (
163
+ self.ogc_server.auth == main.OGCSERVER_AUTH_STANDARD
164
+ and self.ogc_server.type == main.OGCSERVER_TYPE_MAPSERVER
165
+ ):
166
+ # Add functionalities params
167
+ self.params.update(get_mapserver_substitution_params(self.request))
168
+
169
+ @view_config(route_name="mapserverproxy_ogcapi_mapserver") # type: ignore[misc]
170
+ def proxy_ogcapi_mapserver(self) -> Response:
171
+ return self.proxy_ogcapi("ogcapi")
172
+
173
+ @view_config(route_name="mapserverproxy_ogcapi_qgisserver") # type: ignore[misc]
174
+ def proxy_ogcapi_qgisserver(self) -> Response:
175
+ return self.proxy_ogcapi("wfs3")
176
+
177
+ def proxy_ogcapi(self, subpath: str) -> Response:
178
+ self._setup_auth()
179
+
180
+ use_cache = False
181
+
182
+ errors: set[str] = set()
183
+
184
+ _url = self._get_wfs_url(errors)
185
+ if _url is not None:
186
+ _url.path = "/".join([_url.path.rstrip("/"), subpath, *self.request.matchdict["path"]])
187
+
188
+ if _url is None:
189
+ _LOG.error("Error getting the URL:\n%s", "\n".join(errors))
190
+ raise HTTPInternalServerError()
191
+
192
+ cache_control = Cache.PRIVATE_NO
193
+
194
+ headers = self.get_headers()
195
+ # Add headers for Geoserver
196
+ if self.ogc_server.auth == main.OGCSERVER_AUTH_GEOSERVER and self.user is not None:
197
+ headers["sec-username"] = self.user.username
198
+ headers["sec-roles"] = ";".join(get_roles_name(self.request))
199
+
200
+ response = self._proxy_callback(
201
+ cache_control,
202
+ url=_url,
203
+ params=self.params,
204
+ cache=use_cache,
205
+ headers=headers,
206
+ body=self.request.body,
207
+ )
208
+
209
+ return response
210
+
211
+ def _proxy_callback(
212
+ self, cache_control: Cache, url: Url, params: dict[str, str], **kwargs: Any
213
+ ) -> Response:
214
+ if self.request.matched_route.name.endswith("_path"):
215
+ if self.request.matchdict["path"] == ("favicon.ico",):
216
+ return HTTPFound("/favicon.ico")
217
+ url = url.clone()
218
+ url.path = self.request.path
219
+
220
+ response = self._proxy(url=url, params=params, **kwargs)
221
+
222
+ content = response.content
223
+ if self.lower_params.get("request") == "getcapabilities":
224
+ content = filter_capabilities(
225
+ self.request,
226
+ response.text,
227
+ self.lower_params.get("service") == "wms",
228
+ url,
229
+ self.request.headers,
230
+ ).encode("utf-8")
231
+
232
+ content_type = response.headers["Content-Type"]
233
+
234
+ return self._build_response(response, content, cache_control, "mapserver", content_type=content_type)
@@ -0,0 +1,90 @@
1
+ # Copyright (c) 2018-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
+
29
+ import logging
30
+ import os
31
+ import time
32
+ from typing import Any, cast
33
+
34
+ import pyramid.request
35
+ from c2cwsgiutils import broadcast
36
+ from c2cwsgiutils.auth import auth_view
37
+ from c2cwsgiutils.debug import get_size
38
+ from pyramid.view import view_config
39
+
40
+ from c2cgeoportal_geoportal.lib.caching import MEMORY_CACHE_DICT
41
+ from c2cgeoportal_geoportal.views import raster
42
+
43
+ _LOG = logging.getLogger(__name__)
44
+
45
+
46
+ @view_config(route_name="memory", renderer="fast_json") # type: ignore[misc]
47
+ def memory(request: pyramid.request.Request) -> dict[str, Any]:
48
+ """Offer an authenticated view throw c2cwsgiutils to provide some memory information."""
49
+ auth_view(request)
50
+ return cast(dict[str, Any], _memory())
51
+
52
+
53
+ def _nice_type_name(obj: Any, dogpile_cache: bool = False) -> str:
54
+ # See: https://dogpilecache.sqlalchemy.org/en/latest/api.html#dogpile.cache.api.CachedValue
55
+ if dogpile_cache:
56
+ obj, _ = obj
57
+ type_ = type(obj)
58
+ return f"{type_.__module__}.{type_.__name__}"
59
+
60
+
61
+ def _process_dict(dict_: dict[str, Any], dogpile_cache: bool = False) -> dict[str, Any]:
62
+ # Timeout after one minute, must be set to a bit less that the timeout of the broadcast
63
+ timeout = time.monotonic() + 20
64
+
65
+ return {
66
+ "elements": sorted(
67
+ (
68
+ {
69
+ "key": key,
70
+ "type": _nice_type_name(value, dogpile_cache),
71
+ "repr": repr(value),
72
+ "id": id(value),
73
+ "size_kb": get_size(value) / 1024 if time.monotonic() < timeout else -1,
74
+ }
75
+ for key, value in dict_.items()
76
+ ),
77
+ key=lambda i: cast(float, -i["size_kb"]),
78
+ ),
79
+ "id": id(dict_),
80
+ "size_kb": get_size(dict_) / 1024 if time.monotonic() < timeout else -1,
81
+ "timeout": time.monotonic() > timeout,
82
+ }
83
+
84
+
85
+ @broadcast.decorator(expect_answers=True, timeout=110)
86
+ def _memory() -> dict[str, Any]:
87
+ result = {"raster_data": _process_dict(raster.Raster.data)}
88
+ if os.environ.get("GEOMAPFISH_DEBUG_MEMORY_CACHE", "false").lower() in ("true", "1", "yes", "on"):
89
+ result["memory_cache"] = _process_dict(MEMORY_CACHE_DICT, True)
90
+ return result
@@ -0,0 +1,120 @@
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 logging
29
+
30
+ import pyramid.request
31
+ from pyramid.httpexceptions import HTTPBadRequest
32
+ from sqlalchemy.orm.exc import NoResultFound # type: ignore[attr-defined]
33
+
34
+ from c2cgeoportal_commons.lib.url import Url, get_url2
35
+ from c2cgeoportal_commons.models import DBSession, main
36
+ from c2cgeoportal_geoportal.lib.caching import get_region
37
+ from c2cgeoportal_geoportal.views.proxy import Proxy
38
+
39
+ _CACHE_REGION = get_region("std")
40
+ _LOG = logging.getLogger(__name__)
41
+
42
+
43
+ class OGCProxy(Proxy):
44
+ """
45
+ Proxy implementation that manly manage the ogcserver parameter.
46
+
47
+ Then load the corresponding OGCServer.
48
+ """
49
+
50
+ def __init__(self, request: pyramid.request.Request, has_default_ogc_server: bool = False):
51
+ Proxy.__init__(self, request)
52
+
53
+ # params hold the parameters we"re going to send to backend
54
+ self.params = dict(self.request.params)
55
+
56
+ # reset possible value of role_id and user_id
57
+ if "role_id" in self.params:
58
+ del self.params["role_id"]
59
+ if "user_id" in self.params:
60
+ del self.params["user_id"]
61
+
62
+ main_ogc_server = self.request.registry.settings.get("main_ogc_server")
63
+
64
+ self.lower_params = self._get_lower_params(self.params)
65
+
66
+ # We need original case for OGCSERVER parameter value
67
+ self.lower_key_params = {k.lower(): v for k, v in self.params.items()}
68
+
69
+ if "ogcserver" in request.matchdict:
70
+ self.ogc_server = self._get_ogcserver_byname(request.matchdict["ogcserver"])
71
+ elif "ogcserver" in self.lower_key_params:
72
+ self.ogc_server = self._get_ogcserver_byname(self.lower_key_params["ogcserver"])
73
+ elif main_ogc_server is not None:
74
+ self.ogc_server = self._get_ogcserver_byname(main_ogc_server)
75
+ elif not has_default_ogc_server:
76
+ raise HTTPBadRequest("The querystring argument 'ogcserver' is required")
77
+
78
+ @_CACHE_REGION.cache_on_arguments()
79
+ def _get_ogcserver_byname(self, name: str) -> main.OGCServer:
80
+ assert DBSession is not None
81
+
82
+ try:
83
+ result = DBSession.query(main.OGCServer).filter(main.OGCServer.name == name).one()
84
+ DBSession.expunge(result)
85
+ return result
86
+ except NoResultFound:
87
+ raise HTTPBadRequest( # pylint: disable=raise-missing-from
88
+ f"The OGC Server '{name}' does not exist (existing: "
89
+ f"{','.join([t[0] for t in DBSession.query(main.OGCServer.name).all()])})."
90
+ )
91
+
92
+ def _get_wms_url(self, errors: set[str]) -> Url | None:
93
+ ogc_server = self.ogc_server
94
+ url = get_url2(f"The OGC server '{ogc_server.name}'", ogc_server.url, self.request, errors)
95
+ if errors:
96
+ _LOG.error("\n".join(errors))
97
+ return url
98
+
99
+ def _get_wfs_url(self, errors: set[str]) -> Url | None:
100
+ ogc_server = self.ogc_server
101
+ url = get_url2(
102
+ f"The OGC server (WFS) '{ogc_server.name}'",
103
+ ogc_server.url_wfs or ogc_server.url,
104
+ self.request,
105
+ errors,
106
+ )
107
+ if errors:
108
+ _LOG.error("\n".join(errors))
109
+ return url
110
+
111
+ def get_headers(self) -> dict[str, str]:
112
+ headers: dict[str, str] = super().get_headers()
113
+ if self.ogc_server.type == main.OGCSERVER_TYPE_QGISSERVER:
114
+ if self.request.matched_route.name.endswith("_path"):
115
+ headers["X-Qgis-Service-Url"] = self.request.current_route_url(path=[], _query={})
116
+ else:
117
+ headers["X-Qgis-Service-Url"] = self.request.current_route_url(
118
+ _query={"ogcserver": self.ogc_server.name}
119
+ )
120
+ return headers
@@ -0,0 +1,245 @@
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 logging
29
+ from json import dumps, loads
30
+ from typing import Any
31
+
32
+ import pyramid.request
33
+ import pyramid.response
34
+ from pyramid.httpexceptions import HTTPBadRequest, HTTPForbidden
35
+ from pyramid.view import view_config
36
+
37
+ from c2cgeoportal_commons import models
38
+ from c2cgeoportal_commons.lib.url import Url
39
+ from c2cgeoportal_commons.models import main
40
+ from c2cgeoportal_geoportal.lib.common_headers import Cache
41
+ from c2cgeoportal_geoportal.lib.layers import get_private_layers, get_protected_layers
42
+ from c2cgeoportal_geoportal.views.ogcproxy import OGCProxy
43
+
44
+ _LOG = logging.getLogger(__name__)
45
+
46
+
47
+ class PdfReport(OGCProxy):
48
+ """All the views concerned the PDF report."""
49
+
50
+ layername = None
51
+
52
+ def __init__(self, request: pyramid.request.Request):
53
+ OGCProxy.__init__(self, request)
54
+ self.config = self.request.registry.settings.get("pdfreport", {})
55
+
56
+ def _do_print(self, spec: dict[str, Any]) -> pyramid.response.Response:
57
+ """Create and get report PDF."""
58
+ headers = dict(self.request.headers)
59
+ headers["Content-Type"] = "application/json"
60
+ response = self._proxy(
61
+ Url(f"{self.config['print_url']}/buildreport.{spec['outputFormat']}"),
62
+ method="POST",
63
+ body=dumps(spec).encode("utf-8"),
64
+ headers=headers,
65
+ )
66
+
67
+ return self._build_response(response, response.content, Cache.PRIVATE_NO, "pdfreport")
68
+
69
+ @staticmethod
70
+ def _build_map(
71
+ mapserv_url: str, vector_request_url: str, srs: str, map_config: dict[str, Any]
72
+ ) -> dict[str, Any]:
73
+ backgroundlayers = map_config["backgroundlayers"]
74
+ imageformat = map_config["imageformat"]
75
+ return {
76
+ "projection": srs,
77
+ "dpi": 254,
78
+ "rotation": 0,
79
+ "bbox": [0, 0, 1000000, 1000000],
80
+ "zoomToFeatures": {
81
+ "zoomType": map_config["zoomType"],
82
+ "layer": "vector",
83
+ "minScale": map_config["minScale"],
84
+ },
85
+ "layers": [
86
+ {
87
+ "type": "gml",
88
+ "name": "vector",
89
+ "style": {"version": "2", "[1 > 0]": map_config["style"]},
90
+ "opacity": 1,
91
+ "url": vector_request_url,
92
+ },
93
+ {
94
+ "baseURL": mapserv_url,
95
+ "opacity": 1,
96
+ "type": "WMS",
97
+ "serverType": "mapserver",
98
+ "layers": backgroundlayers,
99
+ "imageFormat": imageformat,
100
+ },
101
+ ],
102
+ }
103
+
104
+ @view_config(route_name="pdfreport", renderer="json") # type: ignore[misc]
105
+ def get_report(self) -> pyramid.response.Response:
106
+ assert models.DBSession is not None
107
+
108
+ self.layername = self.request.matchdict["layername"]
109
+ layer_config = self.config["layers"].get(self.layername)
110
+
111
+ if layer_config is None:
112
+ raise HTTPBadRequest("Layer not found: " + self.layername)
113
+
114
+ multiple = layer_config.get("multiple", False)
115
+ ids = self.request.matchdict["ids"]
116
+ if multiple:
117
+ ids = ids.split(",")
118
+
119
+ features_ids = (
120
+ [self.layername + "." + id_ for id_ in ids] if multiple else [self.layername + "." + ids]
121
+ )
122
+
123
+ if layer_config["check_credentials"]:
124
+ # FIXME: support of mapserver groups
125
+ ogc_server = (
126
+ models.DBSession.query(main.OGCServer)
127
+ .filter(main.OGCServer.name == layer_config["ogc_server"])
128
+ .one()
129
+ )
130
+ ogc_server_ids = [ogc_server]
131
+
132
+ private_layers_object = get_private_layers(ogc_server_ids)
133
+ private_layers_names = [private_layers_object[oid].name for oid in private_layers_object]
134
+
135
+ protected_layers_object = get_protected_layers(self.request.user, ogc_server_ids)
136
+ protected_layers_names = [protected_layers_object[oid].name for oid in protected_layers_object]
137
+
138
+ if self.layername in private_layers_names and self.layername not in protected_layers_names:
139
+ raise HTTPForbidden
140
+
141
+ srs = layer_config["srs"]
142
+
143
+ mapserv_url = self.request.route_url(
144
+ "mapserverproxy", _query={"ogcserver": layer_config["ogc_server"]}
145
+ )
146
+ url = Url(mapserv_url)
147
+ url.add_query(
148
+ {
149
+ "service": "WFS",
150
+ "version": "1.1.0",
151
+ "outputformat": "gml3",
152
+ "request": "GetFeature",
153
+ "typeName": self.layername,
154
+ "featureid": ",".join(features_ids),
155
+ "srsName": srs,
156
+ }
157
+ )
158
+ vector_request_url = url.url()
159
+
160
+ spec = layer_config["spec"]
161
+ if spec is None:
162
+ spec = {
163
+ "layout": self.layername,
164
+ "outputFormat": "pdf",
165
+ "attributes": {"ids": [{"id": id_} for id_ in ids]} if multiple else {"id": id},
166
+ }
167
+ map_config = layer_config.get("map")
168
+ if map_config is not None:
169
+ spec["attributes"]["map"] = self._build_map(mapserv_url, vector_request_url, srs, map_config)
170
+
171
+ maps_config = layer_config.get("maps")
172
+ if maps_config is not None:
173
+ spec["attributes"]["maps"] = []
174
+ for map_config in maps_config:
175
+ spec["attributes"]["maps"].append(
176
+ self._build_map(mapserv_url, vector_request_url, srs, map_config)
177
+ )
178
+ else:
179
+ datasource = layer_config.get("datasource", True)
180
+ if multiple and datasource:
181
+ data = dumps(layer_config["data"])
182
+ data_list = [
183
+ loads(
184
+ data
185
+ % {
186
+ "layername": self.layername,
187
+ "id": id_,
188
+ "srs": srs,
189
+ "mapserv_url": mapserv_url,
190
+ "vector_request_url": vector_request_url,
191
+ }
192
+ )
193
+ for id_ in ids
194
+ ]
195
+ self.walker(spec, "%(datasource)s", data_list)
196
+ spec = loads(
197
+ dumps(spec)
198
+ % {
199
+ "layername": self.layername,
200
+ "srs": srs,
201
+ "mapserv_url": mapserv_url,
202
+ "vector_request_url": vector_request_url,
203
+ }
204
+ )
205
+ elif multiple:
206
+ spec = loads(
207
+ dumps(spec)
208
+ % {
209
+ "layername": self.layername,
210
+ "ids": ",".join(ids),
211
+ "srs": srs,
212
+ "mapserv_url": mapserv_url,
213
+ "vector_request_url": vector_request_url,
214
+ }
215
+ )
216
+ else:
217
+ spec = loads(
218
+ dumps(spec)
219
+ % {
220
+ "layername": self.layername,
221
+ "id": ids,
222
+ "srs": srs,
223
+ "mapserv_url": mapserv_url,
224
+ "vector_request_url": vector_request_url,
225
+ }
226
+ )
227
+
228
+ return self._do_print(spec)
229
+
230
+ def walker(self, spec: dict[str, Any] | list[dict[str, Any]], name: str, value: Any) -> None:
231
+ if isinstance(spec, dict):
232
+ for k, v in spec.items():
233
+ if isinstance(v, str):
234
+ if v == name:
235
+ spec[k] = value
236
+ else:
237
+ self.walker(v, name, value)
238
+
239
+ if isinstance(spec, list):
240
+ for k2, v2 in enumerate(spec):
241
+ if isinstance(v2, str):
242
+ if v2 == name:
243
+ spec[k2] = value
244
+ else:
245
+ self.walker(v2, name, value)