c2cgeoportal-geoportal 2.5.0.100__py2.py3-none-any.whl → 2.7.1.83__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 (224) hide show
  1. c2cgeoportal_geoportal/__init__.py +261 -130
  2. c2cgeoportal_geoportal/lib/__init__.py +72 -120
  3. c2cgeoportal_geoportal/lib/authentication.py +170 -21
  4. c2cgeoportal_geoportal/lib/bashcolor.py +17 -13
  5. c2cgeoportal_geoportal/lib/cacheversion.py +19 -11
  6. c2cgeoportal_geoportal/lib/caching.py +66 -160
  7. c2cgeoportal_geoportal/lib/check_collector.py +17 -10
  8. c2cgeoportal_geoportal/lib/checker.py +62 -64
  9. c2cgeoportal_geoportal/lib/common_headers.py +170 -0
  10. c2cgeoportal_geoportal/lib/dbreflection.py +70 -31
  11. c2cgeoportal_geoportal/lib/filter_capabilities.py +124 -96
  12. c2cgeoportal_geoportal/lib/fulltextsearch.py +50 -0
  13. c2cgeoportal_geoportal/lib/functionality.py +36 -21
  14. c2cgeoportal_geoportal/lib/headers.py +14 -5
  15. c2cgeoportal_geoportal/lib/i18n.py +39 -0
  16. c2cgeoportal_geoportal/lib/layers.py +29 -10
  17. c2cgeoportal_geoportal/lib/lingua_extractor.py +408 -211
  18. c2cgeoportal_geoportal/lib/loader.py +18 -16
  19. c2cgeoportal_geoportal/lib/metrics.py +29 -18
  20. c2cgeoportal_geoportal/lib/oauth2.py +1036 -0
  21. c2cgeoportal_geoportal/lib/wmstparsing.py +115 -90
  22. c2cgeoportal_geoportal/lib/xsd.py +29 -19
  23. c2cgeoportal_geoportal/resources.py +15 -9
  24. c2cgeoportal_geoportal/scaffolds/advance_create/ci/config.yaml +26 -0
  25. c2cgeoportal_geoportal/scaffolds/advance_create/cookiecutter.json +18 -0
  26. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.dockerignore +6 -0
  27. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.eslintrc.yaml +19 -0
  28. c2cgeoportal_geoportal/scaffolds/{create/+dot+prospector.yaml → advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml} +8 -4
  29. c2cgeoportal_geoportal/scaffolds/{create/geoportal/Dockerfile_tmpl → advance_create/{{cookiecutter.project}}/geoportal/Dockerfile} +24 -15
  30. c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/alembic.ini +1 -0
  31. c2cgeoportal_geoportal/scaffolds/{create/geoportal/alembic.yaml_tmpl → advance_create/{{cookiecutter.project}}/geoportal/alembic.yaml} +1 -1
  32. c2cgeoportal_geoportal/scaffolds/{create/geoportal/development.ini_tmpl → advance_create/{{cookiecutter.project}}/geoportal/development.ini} +34 -15
  33. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/gunicorn.conf.py +104 -0
  34. c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/lingua-client.cfg +1 -0
  35. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/production.ini +38 -0
  36. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/requirements.txt +2 -0
  37. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/setup.py +25 -0
  38. c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/tools/extract-messages.js +8 -6
  39. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/tsconfig.json +8 -0
  40. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.api.js +75 -0
  41. c2cgeoportal_geoportal/scaffolds/{create/geoportal/webpack.apps.js_tmpl → advance_create/{{cookiecutter.project}}/geoportal/webpack.apps.js} +31 -28
  42. c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/webpack.commons.js +3 -7
  43. c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/webpack.config.js +4 -4
  44. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/__init__.py +47 -0
  45. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/authentication.py +10 -0
  46. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/dev.py +14 -0
  47. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/models.py +8 -0
  48. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/multi_organization.py +7 -0
  49. c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/resources.py +4 -3
  50. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/api/index.js +12 -0
  51. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/js/{{cookiecutter.package}}module.js +25 -0
  52. c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal/subscribers.py_tmpl → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/subscribers.py} +3 -6
  53. c2cgeoportal_geoportal/scaffolds/advance_update/cookiecutter.json +18 -0
  54. c2cgeoportal_geoportal/scaffolds/{update/geoportal/CONST_Makefile_tmpl → advance_update/{{cookiecutter.project}}/geoportal/CONST_Makefile} +32 -20
  55. c2cgeoportal_geoportal/scaffolds/create/cookiecutter.json +18 -0
  56. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.dockerignore +14 -0
  57. c2cgeoportal_geoportal/scaffolds/create/{+dot+editorconfig → {{cookiecutter.project}}/.editorconfig} +4 -8
  58. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/main.yaml +43 -0
  59. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/rebuild.yaml +46 -0
  60. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/update_l10n.yaml +65 -0
  61. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.gitignore +16 -0
  62. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierignore +1 -0
  63. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierrc.yaml +2 -0
  64. c2cgeoportal_geoportal/scaffolds/create/{Dockerfile_tmpl → {{cookiecutter.project}}/Dockerfile} +34 -24
  65. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile +14 -0
  66. c2cgeoportal_geoportal/scaffolds/create/{README.rst_tmpl → {{cookiecutter.project}}/README.rst} +4 -4
  67. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build +158 -0
  68. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/config.yaml +25 -0
  69. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/requirements.txt +1 -0
  70. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml +474 -0
  71. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml +66 -0
  72. c2cgeoportal_geoportal/scaffolds/create/{docker-compose.yaml → {{cookiecutter.project}}/docker-compose.yaml} +43 -18
  73. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.default +82 -0
  74. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.project +60 -0
  75. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/vars.yaml +396 -0
  76. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/mobile.css +0 -0
  77. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-blue.png +0 -0
  78. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-gold.png +0 -0
  79. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-green.png +0 -0
  80. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker.png +0 -0
  81. c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/data/Readme.txt +1 -1
  82. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/demo.map.tmpl +224 -0
  83. c2cgeoportal_geoportal/scaffolds/create/{mapserver/mapserver.map.tmpl_tmpl → {{cookiecutter.project}}/mapserver/mapserver.map.tmpl} +7 -15
  84. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A3_Landscape.jrxml +17 -9
  85. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A3_Portrait.jrxml +17 -9
  86. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A4_Landscape.jrxml +17 -9
  87. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A4_Portrait.jrxml +17 -9
  88. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/config.yaml.tmpl +30 -27
  89. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/legend.jrxml +109 -0
  90. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation.properties +4 -0
  91. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation_fr.properties +4 -0
  92. c2cgeoportal_geoportal/scaffolds/create/{project.yaml_tmpl → {{cookiecutter.project}}/project.yaml} +6 -6
  93. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml +3 -0
  94. c2cgeoportal_geoportal/scaffolds/create/{qgisserver/pg_service.conf.tmpl_tmpl → {{cookiecutter.project}}/qgisserver/pg_service.conf.tmpl} +2 -2
  95. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-backup +107 -0
  96. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +111 -0
  97. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/setup.cfg +7 -0
  98. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/spell-ignore-words.txt +3 -0
  99. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tilegeneration/config.yaml.tmpl +195 -0
  100. c2cgeoportal_geoportal/scaffolds/update/cookiecutter.json +18 -0
  101. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/.upgrade.yaml +191 -0
  102. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_CHANGELOG.txt +1153 -0
  103. c2cgeoportal_geoportal/scaffolds/update/{geoportal → {{cookiecutter.project}}/geoportal}/CONST_config-schema.yaml +99 -47
  104. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_vars.yaml +1412 -0
  105. c2cgeoportal_geoportal/scripts/__init__.py +17 -33
  106. c2cgeoportal_geoportal/scripts/c2cupgrade.py +295 -200
  107. c2cgeoportal_geoportal/scripts/create_demo_theme.py +5 -6
  108. c2cgeoportal_geoportal/scripts/manage_users.py +34 -37
  109. c2cgeoportal_geoportal/scripts/pcreate.py +312 -0
  110. c2cgeoportal_geoportal/scripts/theme2fts.py +92 -25
  111. c2cgeoportal_geoportal/scripts/urllogin.py +23 -17
  112. c2cgeoportal_geoportal/templates/login.html +88 -84
  113. c2cgeoportal_geoportal/templates/notlogin.html +62 -0
  114. c2cgeoportal_geoportal/templates/testi18n.html +6 -8
  115. c2cgeoportal_geoportal/views/__init__.py +23 -4
  116. c2cgeoportal_geoportal/views/dev.py +9 -7
  117. c2cgeoportal_geoportal/views/dynamic.py +70 -40
  118. c2cgeoportal_geoportal/views/entry.py +93 -24
  119. c2cgeoportal_geoportal/views/fulltextsearch.py +36 -29
  120. c2cgeoportal_geoportal/views/geometry_processing.py +15 -7
  121. c2cgeoportal_geoportal/views/i18n.py +91 -9
  122. c2cgeoportal_geoportal/views/layers.py +173 -134
  123. c2cgeoportal_geoportal/views/login.py +206 -87
  124. c2cgeoportal_geoportal/views/mapserverproxy.py +59 -35
  125. c2cgeoportal_geoportal/views/memory.py +13 -13
  126. c2cgeoportal_geoportal/views/ogcproxy.py +48 -30
  127. c2cgeoportal_geoportal/views/pdfreport.py +31 -27
  128. c2cgeoportal_geoportal/views/printproxy.py +67 -53
  129. c2cgeoportal_geoportal/views/profile.py +25 -24
  130. c2cgeoportal_geoportal/views/proxy.py +97 -68
  131. c2cgeoportal_geoportal/views/raster.py +47 -29
  132. c2cgeoportal_geoportal/views/resourceproxy.py +13 -11
  133. c2cgeoportal_geoportal/views/shortener.py +31 -24
  134. c2cgeoportal_geoportal/views/theme.py +475 -365
  135. c2cgeoportal_geoportal/views/tinyowsproxy.py +46 -39
  136. c2cgeoportal_geoportal/views/vector_tiles.py +80 -0
  137. {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.83.dist-info}/METADATA +16 -11
  138. c2cgeoportal_geoportal-2.7.1.83.dist-info/RECORD +185 -0
  139. {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.83.dist-info}/WHEEL +1 -1
  140. {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.83.dist-info}/entry_points.txt +3 -1
  141. tests/__init__.py +24 -3
  142. tests/test_cachebuster.py +1 -3
  143. tests/test_caching.py +19 -26
  144. tests/test_checker.py +2 -3
  145. tests/test_decimaljson.py +4 -4
  146. tests/test_headerstween.py +0 -3
  147. tests/test_i18n.py +31 -0
  148. tests/test_init.py +12 -27
  149. tests/test_locale_negociator.py +6 -6
  150. tests/test_mapserverproxy_route_predicate.py +0 -2
  151. tests/test_raster.py +18 -5
  152. tests/test_wmstparsing.py +7 -8
  153. c2cgeoportal_geoportal/scaffolds/__init__.py +0 -226
  154. c2cgeoportal_geoportal/scaffolds/create/+dot+dockerignore_tmpl +0 -11
  155. c2cgeoportal_geoportal/scaffolds/create/+dot+github/workflows/ci.yaml_tmpl +0 -56
  156. c2cgeoportal_geoportal/scaffolds/create/+dot+gitignore_tmpl +0 -12
  157. c2cgeoportal_geoportal/scaffolds/create/build_tmpl +0 -144
  158. c2cgeoportal_geoportal/scaffolds/create/docker-compose-lib.yaml_tmpl +0 -302
  159. c2cgeoportal_geoportal/scaffolds/create/docker-compose.override.sample.yaml_tmpl +0 -54
  160. c2cgeoportal_geoportal/scaffolds/create/env.default_tmpl +0 -49
  161. c2cgeoportal_geoportal/scaffolds/create/env.project_tmpl +0 -39
  162. c2cgeoportal_geoportal/scaffolds/create/geoportal/+dot+dockerignore_tmpl +0 -5
  163. c2cgeoportal_geoportal/scaffolds/create/geoportal/+dot+eslintrc_tmpl +0 -19
  164. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/__init__.py_tmpl +0 -48
  165. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/models.py_tmpl +0 -10
  166. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/static/images/favicon.ico +0 -0
  167. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/static/robot.txt +0 -3
  168. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/static-ngeo/api/index.js_tmpl +0 -37
  169. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/static-ngeo/js/+package+module.js_tmpl +0 -22
  170. c2cgeoportal_geoportal/scaffolds/create/geoportal/production.ini_tmpl +0 -106
  171. c2cgeoportal_geoportal/scaffolds/create/geoportal/requirements.txt +0 -2
  172. c2cgeoportal_geoportal/scaffolds/create/geoportal/setup.py_tmpl +0 -18
  173. c2cgeoportal_geoportal/scaffolds/create/geoportal/tsconfig.json_tmpl +0 -9
  174. c2cgeoportal_geoportal/scaffolds/create/geoportal/vars.yaml_tmpl +0 -224
  175. c2cgeoportal_geoportal/scaffolds/create/geoportal/webpack.api.js_tmpl +0 -71
  176. c2cgeoportal_geoportal/scaffolds/create/mapserver/demo.map.tmpl_tmpl +0 -262
  177. c2cgeoportal_geoportal/scaffolds/create/mapserver/tinyows.xml +0 -36
  178. c2cgeoportal_geoportal/scaffolds/create/print/print-apps/+package+/config.yaml +0 -166
  179. c2cgeoportal_geoportal/scaffolds/create/print/print-apps/+package+/legend.jrxml +0 -27
  180. c2cgeoportal_geoportal/scaffolds/create/qgisserver/geomapfish.yaml.tmpl_tmpl +0 -29
  181. c2cgeoportal_geoportal/scaffolds/create/scripts/publish-docker +0 -124
  182. c2cgeoportal_geoportal/scaffolds/create/setup.cfg_tmpl +0 -3
  183. c2cgeoportal_geoportal/scaffolds/create/spell-ignore-words.txt +0 -1
  184. c2cgeoportal_geoportal/scaffolds/create/tilegeneration/config.yaml.tmpl_tmpl +0 -169
  185. c2cgeoportal_geoportal/scaffolds/create/yamllint.yaml +0 -11
  186. c2cgeoportal_geoportal/scaffolds/update/+dot+upgrade.yaml_tmpl +0 -171
  187. c2cgeoportal_geoportal/scaffolds/update/CONST_CHANGELOG.txt_tmpl +0 -64
  188. c2cgeoportal_geoportal/scaffolds/update/geoportal/CONST_vars.yaml_tmpl +0 -783
  189. c2cgeoportal_geoportal/templates/dynamic.js +0 -21
  190. c2cgeoportal_geoportal-2.5.0.100.dist-info/RECORD +0 -162
  191. tests/test_get_url.py +0 -96
  192. tests/test_lib.py +0 -77
  193. /c2cgeoportal_geoportal/{scaffolds/create/geoportal/+package+_geoportal/static/css/desktop.css → py.typed} +0 -0
  194. /c2cgeoportal_geoportal/scaffolds/{create/geoportal/Makefile_tmpl → advance_create/{{cookiecutter.project}}/geoportal/Makefile} +0 -0
  195. /c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/language_mapping +0 -0
  196. /c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/lingua-server.cfg +0 -0
  197. /c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/views/__init__.py +0 -0
  198. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal/locale/en/LC_MESSAGES/+package+_geoportal-client.po → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/locale/en/LC_MESSAGES/{{cookiecutter.package}}_geoportal-client.po} +0 -0
  199. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal/static/css/iframe_api.css → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/desktop.css} +0 -0
  200. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal/static/css/mobile.css → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/iframe_api.css} +0 -0
  201. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/banner_left.png +0 -0
  202. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/banner_right.png +0 -0
  203. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/blank.png +0 -0
  204. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/robot.txt.tmpl +0 -0
  205. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/data/TM_EUROPE_BORDERS-0.3.sql +0 -0
  206. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arial.ttf +0 -0
  207. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arialbd.ttf +0 -0
  208. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arialbi.ttf +0 -0
  209. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Ariali.ttf +0 -0
  210. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Bold.ttf +0 -0
  211. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-BoldItalic.ttf +0 -0
  212. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Italic.ttf +0 -0
  213. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Regular.ttf +0 -0
  214. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdana.ttf +0 -0
  215. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanab.ttf +0 -0
  216. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanai.ttf +0 -0
  217. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanaz.ttf +0 -0
  218. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts.conf +0 -0
  219. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/tinyows.xml.tmpl +0 -0
  220. /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/logo.png +0 -0
  221. /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/north.svg +0 -0
  222. /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/results.jrxml +0 -0
  223. /c2cgeoportal_geoportal/scaffolds/create/{run_alembic.sh → {{cookiecutter.project}}/run_alembic.sh} +0 -0
  224. {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.83.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2011-2020, Camptocamp SA
1
+ # Copyright (c) 2011-2023, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -33,11 +31,13 @@ import os
33
31
  import re
34
32
  import subprocess
35
33
  import traceback
36
- from typing import Dict, List, Optional, Set, cast
37
- from urllib.parse import urlsplit
34
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple, Type, cast
38
35
  from xml.dom import Node
39
36
  from xml.parsers.expat import ExpatError
40
37
 
38
+ import pyramid.threadlocal
39
+ import requests
40
+ import yaml
41
41
  from bottle import MakoTemplate, template
42
42
  from c2c.template.config import config
43
43
  from defusedxml.minidom import parseString
@@ -46,66 +46,84 @@ from lingua.extractors import Extractor, Message
46
46
  from mako.lookup import TemplateLookup
47
47
  from mako.template import Template
48
48
  from owslib.wms import WebMapService
49
- import requests
50
- import sqlalchemy
51
49
  from sqlalchemy.exc import NoSuchTableError, OperationalError, ProgrammingError
52
50
  from sqlalchemy.orm.exc import NoResultFound
53
51
  from sqlalchemy.orm.properties import ColumnProperty
52
+ from sqlalchemy.orm.session import Session
54
53
  from sqlalchemy.orm.util import class_mapper
55
- import yaml
56
54
 
57
- import c2cgeoportal_commons.models
58
55
  import c2cgeoportal_geoportal
59
- from c2cgeoportal_geoportal.lib import add_url_params, get_url2
60
- from c2cgeoportal_geoportal.lib.bashcolor import RED, colorize
56
+ from c2cgeoportal_commons.lib.url import Url, get_url2
57
+ from c2cgeoportal_geoportal.lib.bashcolor import Color, colorize
61
58
  from c2cgeoportal_geoportal.lib.caching import init_region
62
59
  from c2cgeoportal_geoportal.views.layers import Layers, get_layer_class
63
60
 
61
+ if TYPE_CHECKING:
62
+ from c2cgeoportal_commons.models import main # pylint: disable=ungrouped-imports,useless-suppression
63
+
64
+
65
+ def _get_config(key: str, default: Optional[str] = None) -> Optional[str]:
66
+ """
67
+ Return the config value for passed key.
68
+
69
+ Passed throw environment variable for the command line,
70
+ or throw the query string on HTTP request.
71
+ """
72
+ request = pyramid.threadlocal.get_current_request()
73
+ if request is not None:
74
+ return cast(Optional[str], request.params.get(key.lower(), default))
75
+
76
+ return os.environ.get(key, default)
77
+
78
+
79
+ def _get_config_str(key: str, default: str) -> str:
80
+ result = _get_config(key, default)
81
+ assert result is not None
82
+ return result
83
+
64
84
 
65
- class _Registry: # pragma: no cover
85
+ class _Registry:
66
86
  settings = None
67
87
 
68
- def __init__(self, settings):
88
+ def __init__(self, settings: Optional[Dict[str, Any]]) -> None:
69
89
  self.settings = settings
70
90
 
71
91
 
72
- class _Request: # pragma: no cover
92
+ class _Request:
73
93
  params: Dict[str, str] = {}
74
94
  matchdict: Dict[str, str] = {}
75
95
  GET: Dict[str, str] = {}
76
96
 
77
- def __init__(self, settings=None):
97
+ def __init__(self, settings: Optional[Dict[str, Any]] = None) -> None:
78
98
  self.registry: _Registry = _Registry(settings)
79
99
 
80
100
  @staticmethod
81
- def static_url(*args, **kwargs):
101
+ def static_url(*args: Any, **kwargs: Any) -> str:
82
102
  del args
83
103
  del kwargs
84
104
  return ""
85
105
 
86
106
  @staticmethod
87
- def static_path(*args, **kwargs):
107
+ def static_path(*args: Any, **kwargs: Any) -> str:
88
108
  del args
89
109
  del kwargs
90
110
  return ""
91
111
 
92
112
  @staticmethod
93
- def route_url(*args, **kwargs):
113
+ def route_url(*args: Any, **kwargs: Any) -> str:
94
114
  del args
95
115
  del kwargs
96
116
  return ""
97
117
 
98
118
  @staticmethod
99
- def current_route_url(*args, **kwargs):
119
+ def current_route_url(*args: Any, **kwargs: Any) -> str:
100
120
  del args
101
121
  del kwargs
102
122
  return ""
103
123
 
104
124
 
105
- class GeomapfishAngularExtractor(Extractor): # pragma: no cover
106
- """
107
- GeoMapFish angular extractor
108
- """
125
+ class GeomapfishAngularExtractor(Extractor): # type: ignore
126
+ """GeoMapFish angular extractor."""
109
127
 
110
128
  extensions = [".js", ".html"]
111
129
 
@@ -118,27 +136,49 @@ class GeomapfishAngularExtractor(Extractor): # pragma: no cover
118
136
  self.config = None
119
137
  self.tpl = None
120
138
 
121
- def __call__(self, filename, options, fileobj=None, lineno=0):
139
+ @staticmethod
140
+ def get_message_cleaner(filename: str) -> Callable[[str], str]:
141
+ """Return a function for cleaning messages according to input file format."""
142
+ ext = os.path.splitext(filename)[1]
143
+
144
+ if ext in [".html", ".ejs"]:
145
+ # Remove \n in HTML multi-line strings
146
+ pattern = re.compile("\n *")
147
+ return lambda s: re.sub(pattern, " ", s)
148
+
149
+ return lambda s: s
150
+
151
+ def __call__(
152
+ self,
153
+ filename: str,
154
+ options: Dict[str, Any],
155
+ fileobj: Optional[Dict[str, Any]] = None,
156
+ lineno: int = 0,
157
+ ) -> List[Message]:
122
158
  del fileobj, lineno
123
159
 
160
+ print(f"Running {self.__class__.__name__} on {filename}")
161
+
162
+ cleaner = self.get_message_cleaner(filename)
163
+
124
164
  init_region({"backend": "dogpile.cache.memory"}, "std")
125
165
  init_region({"backend": "dogpile.cache.memory"}, "obj")
126
166
 
127
167
  int_filename = filename
128
- if re.match("^" + re.escape("./{}/templates".format(self.config["package"])), filename):
168
+ if re.match("^" + re.escape(f"./{self.config['package']}/templates"), filename):
129
169
  try:
130
170
  empty_template = Template("") # nosec
131
171
 
132
- class Lookup(TemplateLookup):
172
+ class Lookup(TemplateLookup): # type: ignore
133
173
  @staticmethod
134
- def get_template(uri):
174
+ def get_template(uri: str) -> Template:
135
175
  del uri # unused
136
176
  return empty_template
137
177
 
138
- class MyTemplate(MakoTemplate):
178
+ class MyTemplate(MakoTemplate): # type: ignore
139
179
  tpl = None
140
180
 
141
- def prepare(self, **kwargs):
181
+ def prepare(self, **kwargs: Any) -> None:
142
182
  options.update({"input_encoding": self.encoding})
143
183
  lookup = Lookup(**kwargs)
144
184
  if self.source:
@@ -149,10 +189,12 @@ class GeomapfishAngularExtractor(Extractor): # pragma: no cover
149
189
  )
150
190
 
151
191
  try:
192
+ request = pyramid.threadlocal.get_current_request()
193
+ request = _Request() if request is None else request
152
194
  processed = template(
153
195
  filename,
154
196
  {
155
- "request": _Request(self.config),
197
+ "request": request,
156
198
  "lang": "fr",
157
199
  "debug": False,
158
200
  "extra_params": {},
@@ -167,11 +209,9 @@ class GeomapfishAngularExtractor(Extractor): # pragma: no cover
167
209
  with open(int_filename, "wb") as file_open:
168
210
  file_open.write(processed.encode("utf-8"))
169
211
  except Exception:
170
- print(
171
- colorize("ERROR! Occurred during the '{}' template generation".format(filename), RED)
172
- )
173
- print(colorize(traceback.format_exc(), RED))
174
- if os.environ.get("IGNORE_I18N_ERRORS", "FALSE") == "TRUE":
212
+ print(colorize(f"ERROR! Occurred during the '{filename}' template generation", Color.RED))
213
+ print(colorize(traceback.format_exc(), Color.RED))
214
+ if _get_config_str("IGNORE_I18N_ERRORS", "FALSE") == "TRUE":
175
215
  # Continue with the original one
176
216
  int_filename = filename
177
217
  else:
@@ -179,37 +219,85 @@ class GeomapfishAngularExtractor(Extractor): # pragma: no cover
179
219
  except Exception:
180
220
  print(traceback.format_exc())
181
221
 
182
- message_str = subprocess.check_output(
183
- ["node", "geoportal/tools/extract-messages.js", int_filename]
184
- ).decode("utf-8")
222
+ # Path in geomapfish-tools
223
+ script_path = "geoportal/tools/extract-messages.js"
224
+ if not os.path.isfile(script_path):
225
+ # Path in geomapfish runner
226
+ script_path = "/app/tools/extract-messages.js"
227
+ message_str = subprocess.check_output(["node", script_path, int_filename]).decode("utf-8")
185
228
  if int_filename != filename:
186
229
  os.unlink(int_filename)
187
230
  try:
188
231
  messages = []
189
232
  for contexts, message in json.loads(message_str):
233
+ assert message is not None
234
+ message = cleaner(message)
190
235
  for context in contexts.split(", "):
191
236
  messages.append(Message(None, message, None, [], "", "", context.split(":")))
192
237
  return messages
193
238
  except Exception:
194
- print(colorize("An error occurred", RED))
195
- print(colorize(message_str, RED))
239
+ print(colorize("An error occurred", Color.RED))
240
+ print(colorize(message_str, Color.RED))
196
241
  print("------")
197
242
  raise
198
243
 
199
244
 
200
- class GeomapfishConfigExtractor(Extractor): # pragma: no cover
245
+ def init_db(settings: Dict[str, Any]) -> None:
201
246
  """
202
- GeoMapFish config extractor (raster layers, and print templates)
247
+ Initialize the SQLAlchemy Session.
248
+
249
+ First test the connection, on wen environment it should be OK, with the command line we should get
250
+ an exception ind initialise the connection.
203
251
  """
204
252
 
253
+ try:
254
+ from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
255
+ from c2cgeoportal_commons.models.main import Theme # pylint: disable=import-outside-toplevel
256
+
257
+ session = DBSession()
258
+ session.query(Theme).count()
259
+ except: # pylint: disable=bare-except
260
+
261
+ # Init db sessions
262
+
263
+ class R:
264
+ settings: Dict[str, Any] = {}
265
+
266
+ class C:
267
+ registry = R()
268
+
269
+ def get_settings(self) -> Dict[str, Any]:
270
+ return self.registry.settings
271
+
272
+ def add_tween(self, *args: Any, **kwargs: Any) -> None:
273
+ pass
274
+
275
+ config_ = C()
276
+ config_.registry.settings = settings
277
+
278
+ c2cgeoportal_geoportal.init_db_sessions(settings, config_)
279
+
280
+
281
+ class GeomapfishConfigExtractor(Extractor): # type: ignore
282
+ """GeoMapFish config extractor (raster layers, and print templates)."""
283
+
205
284
  extensions = [".yaml", ".tmpl"]
206
285
 
207
- def __call__(self, filename, options, fileobj=None, lineno=0):
208
- del fileobj, lineno
286
+ def __call__(
287
+ self,
288
+ filename: str,
289
+ options: Dict[str, Any],
290
+ fileobj: Optional[Dict[str, Any]] = None,
291
+ lineno: int = 0,
292
+ ) -> List[Message]:
293
+ del options, fileobj, lineno
294
+
295
+ print(f"Running {self.__class__.__name__} on {filename}")
296
+
209
297
  init_region({"backend": "dogpile.cache.memory"}, "std")
210
298
  init_region({"backend": "dogpile.cache.memory"}, "obj")
211
299
 
212
- with open(filename) as config_file:
300
+ with open(filename, encoding="utf8") as config_file:
213
301
  gmf_config = yaml.load(config_file, Loader=yaml.BaseLoader) # nosec
214
302
  # For application config (config.yaml)
215
303
  if "vars" in gmf_config:
@@ -219,37 +307,26 @@ class GeomapfishConfigExtractor(Extractor): # pragma: no cover
219
307
  return self._collect_print_config(gmf_config, filename)
220
308
  raise Exception("Not a known config file")
221
309
 
222
- def _collect_app_config(self, filename):
310
+ def _collect_app_config(self, filename: str) -> List[Message]:
223
311
  config.init(filename)
224
312
  settings = config.get_config()
313
+ assert not [
314
+ raster_layer for raster_layer in list(settings.get("raster", {}).keys()) if raster_layer is None
315
+ ]
225
316
  # Collect raster layers names
226
317
  raster = [
227
- Message(None, raster_layer, None, [], "", "", (filename, "raster/{}".format(raster_layer)))
318
+ Message(None, raster_layer, None, [], "", "", (filename, f"raster/{raster_layer}"))
228
319
  for raster_layer in list(settings.get("raster", {}).keys())
229
320
  ]
230
321
 
231
- # Init db sessions
232
-
233
- class R:
234
- settings = None
235
-
236
- class C:
237
- registry = R()
238
-
239
- def get_settings(self):
240
- return self.registry.settings
241
-
242
- def add_tween(self, *args, **kwargs):
243
- pass
244
-
245
- config_ = C()
246
- config_.registry.settings = settings
247
-
248
- c2cgeoportal_geoportal.init_dbsessions(settings, config_)
322
+ init_db(settings)
249
323
 
250
324
  # Collect layers enum values (for filters)
251
325
 
252
- from c2cgeoportal_commons.models import DBSessions # pylint: disable=import-outside-toplevel
326
+ from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel
327
+ DBSession,
328
+ DBSessions,
329
+ )
253
330
  from c2cgeoportal_commons.models.main import Metadata # pylint: disable=import-outside-toplevel
254
331
 
255
332
  enums = []
@@ -258,61 +335,95 @@ class GeomapfishConfigExtractor(Extractor): # pragma: no cover
258
335
  layerinfos = enum_layers.get(layername, {})
259
336
  attributes = layerinfos.get("attributes", {})
260
337
  for fieldname in list(attributes.keys()):
261
- values = self._enumerate_attributes_values(DBSessions, Layers, layerinfos, fieldname)
338
+ values = self._enumerate_attributes_values(DBSessions, layerinfos, fieldname)
262
339
  for (value,) in values:
263
340
  if isinstance(value, str) and value != "":
264
341
  msgid = value
265
- location = "/layers/{}/values/{}/{}".format(
266
- layername, fieldname, value.encode("ascii", errors="replace").decode("ascii"),
342
+ location = (
343
+ f"/layers/{layername}/values/{fieldname}/"
344
+ f"{value.encode('ascii', errors='replace').decode('ascii')}"
267
345
  )
346
+ assert msgid is not None
268
347
  enums.append(Message(None, msgid, None, [], "", "", (filename, location)))
269
348
 
270
349
  metadata_list = []
271
- defs = config["admin_interface"]["available_metadata"]
350
+ defs = config["admin_interface"]["available_metadata"] # pylint: disable=unsubscriptable-object
272
351
  names = [e["name"] for e in defs if e.get("translate", False)]
273
352
 
274
353
  if names:
275
- engine = sqlalchemy.create_engine(config["sqlalchemy.url"])
276
- Session = sqlalchemy.orm.session.sessionmaker() # noqa
277
- Session.configure(bind=engine)
278
- session = Session()
354
+ session = DBSession()
279
355
 
280
356
  query = session.query(Metadata).filter(Metadata.name.in_(names))
281
357
  for metadata in query.all():
282
- location = "metadata/{}/{}".format(metadata.name, metadata.id)
358
+ location = f"metadata/{metadata.name}/{metadata.id}"
359
+ assert metadata.value is not None
283
360
  metadata_list.append(Message(None, metadata.value, None, [], "", "", (filename, location)))
284
361
 
285
- return raster + enums + metadata_list
362
+ interfaces_messages = []
363
+ for interface, interface_config in config["interfaces_config"].items():
364
+ for ds_index, datasource in enumerate(
365
+ interface_config.get("constants", {}).get("gmfSearchOptions", {}).get("datasources", [])
366
+ ):
367
+ for a_index, action in enumerate(datasource.get("groupActions", [])):
368
+ location = (
369
+ f"interfaces_config/{interface}/constants/gmfSearchOptions/datasources[{ds_index}]/"
370
+ f"groupActions[{a_index}]/title"
371
+ )
372
+ assert action["title"] is not None
373
+ interfaces_messages.append(
374
+ Message(None, action["title"], None, [], "", "", (filename, location))
375
+ )
376
+
377
+ for merge_tab in (
378
+ interface_config.get("constants", {})
379
+ .get("gmfDisplayQueryGridOptions", {})
380
+ .get("mergeTabs", {})
381
+ .keys()
382
+ ):
383
+ location = (
384
+ f"interfaces_config/{interface}/constants/gmfDisplayQueryGridOptions/"
385
+ f"mergeTabs/{merge_tab}/"
386
+ )
387
+ assert merge_tab is not None
388
+ interfaces_messages.append(Message(None, merge_tab, None, [], "", "", (filename, location)))
389
+
390
+ return raster + enums + metadata_list + interfaces_messages
286
391
 
287
392
  @staticmethod
288
- def _enumerate_attributes_values(dbsessions, layers, layerinfos, fieldname):
393
+ def _enumerate_attributes_values(
394
+ dbsessions: Dict[str, Session], layerinfos: Dict[str, Any], fieldname: str
395
+ ) -> Set[Tuple[str, ...]]:
289
396
  dbname = layerinfos.get("dbsession", "dbsession")
290
- translate = layerinfos.get("attributes").get(fieldname, {}).get("translate", True)
397
+ translate = cast(Dict[str, Any], layerinfos["attributes"]).get(fieldname, {}).get("translate", True)
291
398
  if not translate:
292
- return []
399
+ return set()
293
400
  try:
294
401
  dbsession = dbsessions.get(dbname)
295
- return layers.query_enumerate_attribute_values(dbsession, layerinfos, fieldname)
402
+ return Layers.query_enumerate_attribute_values(dbsession, layerinfos, fieldname)
296
403
  except Exception as e:
297
- table = layerinfos.get("attributes").get(fieldname, {}).get("table")
404
+ table = cast(Dict[str, Any], layerinfos["attributes"]).get(fieldname, {}).get("table")
298
405
  print(
299
406
  colorize(
300
407
  "ERROR! Unable to collect enumerate attributes for "
301
- "db: {}, table: {}, column: {} ({})".format(dbname, table, fieldname, e),
302
- RED,
408
+ f"db: {dbname}, table: {table}, column: {fieldname} ({e!s})",
409
+ Color.RED,
303
410
  )
304
411
  )
305
- if os.environ.get("IGNORE_I18N_ERRORS", "FALSE") == "TRUE":
306
- return []
412
+ if _get_config_str("IGNORE_I18N_ERRORS", "FALSE") == "TRUE":
413
+ return set()
307
414
  raise
308
415
 
309
416
  @staticmethod
310
- def _collect_print_config(print_config, filename):
417
+ def _collect_print_config(print_config: Dict[str, Any], filename: str) -> List[Message]:
311
418
  result = []
312
- for template_ in list(print_config.get("templates").keys()):
313
- result.append(
314
- Message(None, template_, None, [], "", "", (filename, "template/{}".format(template_)))
315
- )
419
+ for template_ in list(cast(Dict[str, Any], print_config.get("templates")).keys()):
420
+ assert template_ is not None
421
+ result.append(Message(None, template_, None, [], "", "", (filename, f"template/{template_}")))
422
+ assert not [
423
+ attribute
424
+ for attribute in list(print_config["templates"][template_]["attributes"].keys())
425
+ if attribute is None
426
+ ]
316
427
  result += [
317
428
  Message(
318
429
  None,
@@ -321,22 +432,20 @@ class GeomapfishConfigExtractor(Extractor): # pragma: no cover
321
432
  [],
322
433
  "",
323
434
  "",
324
- (filename, "template/{}/{}".format(template_, attribute)),
435
+ (filename, f"template/{template_}/{attribute}"),
325
436
  )
326
437
  for attribute in list(print_config["templates"][template_]["attributes"].keys())
327
438
  ]
328
439
  return result
329
440
 
330
441
 
331
- class GeomapfishThemeExtractor(Extractor): # pragma: no cover
332
- """
333
- GeoMapFish theme extractor
334
- """
442
+ class GeomapfishThemeExtractor(Extractor): # type: ignore
443
+ """GeoMapFish theme extractor."""
335
444
 
336
445
  # Run on the development.ini file
337
446
  extensions = [".ini"]
338
- featuretype_cache: Dict[str, Optional[Dict]] = {}
339
- wmscap_cache: Dict[str, WebMapService] = {}
447
+ featuretype_cache: Dict[str, Optional[Dict[str, Any]]] = {}
448
+ wms_capabilities_cache: Dict[str, WebMapService] = {}
340
449
 
341
450
  def __init__(self) -> None:
342
451
  super().__init__()
@@ -347,33 +456,53 @@ class GeomapfishThemeExtractor(Extractor): # pragma: no cover
347
456
  self.config = None
348
457
  self.env = None
349
458
 
350
- def __call__(self, filename, options, fileobj=None, lineno=0):
459
+ def __call__(
460
+ self, filename: str, options: Dict[str, Any], fileobj: Optional[str] = None, lineno: int = 0
461
+ ) -> List[Message]:
351
462
  del fileobj, lineno
463
+
464
+ print(f"Running {self.__class__.__name__} on {filename}")
465
+
352
466
  messages: List[Message] = []
353
467
 
354
468
  try:
355
- engine = sqlalchemy.engine_from_config(self.config, "sqlalchemy_slave.")
356
- factory = sqlalchemy.orm.sessionmaker(bind=engine)
357
- db_session = sqlalchemy.orm.scoped_session(factory)
358
- c2cgeoportal_commons.models.DBSession = db_session
359
- c2cgeoportal_commons.models.Base.metadata.bind = engine
469
+ init_db(self.config)
470
+ from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
471
+
472
+ db_session = DBSession()
360
473
 
361
474
  try:
362
475
  from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
363
- Theme,
476
+ FullTextSearch,
364
477
  LayerGroup,
365
478
  LayerWMS,
366
479
  LayerWMTS,
367
- FullTextSearch,
480
+ Theme,
368
481
  )
369
482
 
370
- self._import(Theme, messages)
371
- self._import(LayerGroup, messages)
372
- self._import(LayerWMS, messages, self._import_layer_wms)
373
- self._import(LayerWMTS, messages, self._import_layer_wmts)
483
+ self._import(Theme, messages, name_regex=_get_config_str("THEME_REGEX", ".*"))
484
+ self._import(
485
+ LayerGroup,
486
+ messages,
487
+ name_regex=_get_config_str("GROUP_REGEX", ".*"),
488
+ has_interfaces=False,
489
+ )
490
+ self._import(
491
+ LayerWMS,
492
+ messages,
493
+ self._import_layer_wms,
494
+ name_regex=_get_config_str("WMSLAYER_REGEX", ".*"),
495
+ )
496
+ self._import(
497
+ LayerWMTS,
498
+ messages,
499
+ self._import_layer_wmts,
500
+ name_regex=_get_config_str("WMTSLAYER_REGEX", ".*"),
501
+ )
374
502
 
375
503
  for (layer_name,) in db_session.query(FullTextSearch.layer_name).distinct().all():
376
504
  if layer_name is not None and layer_name != "":
505
+ assert layer_name is not None
377
506
  messages.append(
378
507
  Message(
379
508
  None,
@@ -389,6 +518,7 @@ class GeomapfishThemeExtractor(Extractor): # pragma: no cover
389
518
  for (actions,) in db_session.query(FullTextSearch.actions).distinct().all():
390
519
  if actions is not None and actions != "":
391
520
  for action in actions:
521
+ assert action["data"] is not None
392
522
  messages.append(
393
523
  Message(
394
524
  None,
@@ -405,59 +535,75 @@ class GeomapfishThemeExtractor(Extractor): # pragma: no cover
405
535
  colorize(
406
536
  "ERROR! The database is probably not up to date "
407
537
  "(should be ignored when happen during the upgrade)",
408
- RED,
538
+ Color.RED,
409
539
  )
410
540
  )
411
- print(colorize(e, RED))
412
- if os.environ.get("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
541
+ print(colorize(e, Color.RED))
542
+ if _get_config_str("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
413
543
  raise
414
544
  except NoSuchTableError as e:
415
545
  print(
416
546
  colorize(
417
547
  "ERROR! The schema didn't seem to exists "
418
548
  "(should be ignored when happen during the deploy)",
419
- RED,
549
+ Color.RED,
420
550
  )
421
551
  )
422
- print(colorize(e, RED))
423
- if os.environ.get("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
552
+ print(colorize(e, Color.RED))
553
+ if _get_config_str("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
424
554
  raise
425
555
  except OperationalError as e:
426
556
  print(
427
557
  colorize(
428
558
  "ERROR! The database didn't seem to exists "
429
559
  "(should be ignored when happen during the deploy)",
430
- RED,
560
+ Color.RED,
431
561
  )
432
562
  )
433
- print(colorize(e, RED))
434
- if os.environ.get("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
563
+ print(colorize(e, Color.RED))
564
+ if _get_config_str("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
435
565
  raise
436
566
 
437
567
  return messages
438
568
 
439
569
  @staticmethod
440
- def _import(object_type, messages, callback=None):
570
+ def _import(
571
+ object_type: Type[Any],
572
+ messages: List[str],
573
+ callback: Optional[Callable[["main.Layer", List[str]], None]] = None,
574
+ has_interfaces: bool = True,
575
+ name_regex: str = ".*",
576
+ ) -> None:
441
577
  from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
442
-
443
- items = DBSession.query(object_type)
444
- for item in items:
445
- messages.append(
446
- Message(
447
- None,
448
- item.name,
449
- None,
450
- [],
451
- "",
452
- "",
453
- (item.item_type, item.name.encode("ascii", errors="replace")),
578
+ from c2cgeoportal_commons.models.main import Interface # pylint: disable=import-outside-toplevel
579
+
580
+ filter_re = re.compile(name_regex)
581
+
582
+ query = DBSession.query(object_type)
583
+
584
+ interfaces = _get_config("INTERFACES")
585
+ if has_interfaces and interfaces is not None:
586
+ query.join(object_type.interface).filter(Interface.name in interfaces.split("."))
587
+
588
+ for item in query.all():
589
+ assert item.name is not None
590
+ if filter_re.match(item.name):
591
+ messages.append(
592
+ Message(
593
+ None,
594
+ item.name,
595
+ None,
596
+ [],
597
+ "",
598
+ "",
599
+ (item.item_type, item.name.encode("ascii", errors="replace")),
600
+ )
454
601
  )
455
- )
456
602
 
457
603
  if callback is not None:
458
604
  callback(item, messages)
459
605
 
460
- def _import_layer_wms(self, layer, messages):
606
+ def _import_layer_wms(self, layer: "main.Layer", messages: List[str]) -> None:
461
607
  server = layer.ogc_server
462
608
  url = server.url_wfs or server.url
463
609
  if url is None:
@@ -481,6 +627,7 @@ class GeomapfishThemeExtractor(Extractor): # pragma: no cover
481
627
  name = column.name + "_"
482
628
  else:
483
629
  name = column_property.key
630
+ assert name is not None
484
631
  messages.append(
485
632
  Message(
486
633
  None,
@@ -495,14 +642,15 @@ class GeomapfishThemeExtractor(Extractor): # pragma: no cover
495
642
  except NoSuchTableError:
496
643
  print(
497
644
  colorize(
498
- "ERROR! No such table '{}' for layer '{}'.".format(layer.geo_table, layer.name), RED
645
+ f"ERROR! No such table '{layer.geo_table}' for layer '{layer.name}'.",
646
+ Color.RED,
499
647
  )
500
648
  )
501
- print(colorize(traceback.format_exc(), RED))
502
- if os.environ.get("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
649
+ print(colorize(traceback.format_exc(), Color.RED))
650
+ if _get_config_str("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
503
651
  raise
504
652
 
505
- def _import_layer_wmts(self, layer, messages):
653
+ def _import_layer_wmts(self, layer: "main.Layer", messages: List[str]) -> None:
506
654
  from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
507
655
  from c2cgeoportal_commons.models.main import OGCServer # pylint: disable=import-outside-toplevel
508
656
 
@@ -511,7 +659,7 @@ class GeomapfishThemeExtractor(Extractor): # pragma: no cover
511
659
  layers = [d.value for d in layer.metadatas if d.name == "wmsLayer"]
512
660
  server = [d.value for d in layer.metadatas if d.name == "ogcServer"]
513
661
  if server and layers:
514
- layers = [l for ls in layers for l in ls.split(",")]
662
+ layers = [layer for ls in layers for layer in ls.split(",")]
515
663
  for wms_layer in layers:
516
664
  try:
517
665
  db_server = DBSession.query(OGCServer).filter(OGCServer.name == server[0]).one()
@@ -526,18 +674,20 @@ class GeomapfishThemeExtractor(Extractor): # pragma: no cover
526
674
  except NoResultFound:
527
675
  print(
528
676
  colorize(
529
- "ERROR! the OGC server '{}' from the WMTS layer '{}' does not exist.".format(
530
- server[0], layer.name
531
- ),
532
- RED,
677
+ f"ERROR! the OGC server '{server[0]}' from the "
678
+ f"WMTS layer '{layer.name}' does not exist.",
679
+ Color.RED,
533
680
  )
534
681
  )
535
- if os.environ.get("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
682
+ if _get_config_str("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
536
683
  raise
537
684
 
538
- def _import_layer_attributes(self, url, layer, item_type, name, messages):
685
+ def _import_layer_attributes(
686
+ self, url: str, layer: "main.Layer", item_type: str, name: str, messages: List[str]
687
+ ) -> None:
539
688
  attributes, layers = self._layer_attributes(url, layer)
540
689
  for sub_layer in layers:
690
+ assert sub_layer is not None
541
691
  messages.append(
542
692
  Message(
543
693
  None,
@@ -550,6 +700,7 @@ class GeomapfishThemeExtractor(Extractor): # pragma: no cover
550
700
  )
551
701
  )
552
702
  for attribute in attributes:
703
+ assert attribute is not None
553
704
  messages.append(
554
705
  Message(
555
706
  None,
@@ -562,105 +713,151 @@ class GeomapfishThemeExtractor(Extractor): # pragma: no cover
562
713
  )
563
714
  )
564
715
 
565
- def _build_url(self, url):
566
- url_split = urlsplit(url)
567
- hostname = url_split.hostname
716
+ def _build_url(self, url: Url) -> Tuple[Url, Dict[str, str], Dict[str, Any]]:
717
+ hostname = url.hostname
568
718
  host_map = self.config.get("lingua_extractor", {}).get("host_map", {})
569
719
  if hostname in host_map:
570
720
  map_ = host_map[hostname]
571
721
  if "netloc" in map_:
572
- url_split = url_split._replace(netloc=map_["netloc"])
722
+ url.netloc = map_["netloc"]
573
723
  if "scheme" in map_:
574
- url_split = url_split._replace(scheme=map_["scheme"])
724
+ url.scheme = map_["scheme"]
575
725
  kwargs = {"verify": map_["verify"]} if "verify" in map_ else {}
576
- return url_split.geturl(), map_.get("headers", {}), kwargs
726
+ return url, map_.get("headers", {}), kwargs
577
727
  return url, {}, {}
578
728
 
579
- def _layer_attributes(self, url, layer):
729
+ def _layer_attributes(self, url: str, layer: str) -> Tuple[List[str], List[str]]:
580
730
  errors: Set[str] = set()
581
731
 
582
- request = _Request()
583
- request.registry.settings = self.config
732
+ request = pyramid.threadlocal.get_current_request()
733
+ if request is None:
734
+ request = _Request()
735
+ request.registry.settings = self.config
736
+
584
737
  # Static schema will not be supported
585
- url = get_url2("Layer", url, request, errors)
738
+ url_obj_ = get_url2("Layer", url, request, errors)
586
739
  if errors:
587
740
  print("\n".join(errors))
588
741
  return [], []
589
- url, headers, kwargs = self._build_url(url)
590
-
591
- if url not in self.wmscap_cache:
592
- print("Get WMS GetCapabilities for URL: {}".format(url))
593
- self.wmscap_cache[url] = None
594
-
595
- wms_getcap_url = add_url_params(
596
- url, {"SERVICE": "WMS", "VERSION": "1.1.1", "REQUEST": "GetCapabilities"}
742
+ if not url_obj_:
743
+ print(f"No URL for: {url}")
744
+ return [], []
745
+ url_obj: Url = url_obj_
746
+ url_obj, headers, kwargs = self._build_url(url_obj)
747
+
748
+ if url not in self.wms_capabilities_cache:
749
+ print(f"Get WMS GetCapabilities for URL: {url_obj}")
750
+ self.wms_capabilities_cache[url] = None
751
+
752
+ wms_getcap_url = (
753
+ url_obj.clone()
754
+ .add_query(
755
+ {
756
+ "SERVICE": "WMS",
757
+ "VERSION": "1.1.1",
758
+ "REQUEST": "GetCapabilities",
759
+ "ROLE_IDS": "0",
760
+ "USER_ID": "0",
761
+ }
762
+ )
763
+ .url()
597
764
  )
598
765
  try:
599
- print(
600
- "Get WMS GetCapabilities for URL {},\nwith headers: {}".format(
601
- wms_getcap_url, " ".join(["=".join(h) for h in headers.items()])
602
- )
766
+ rendered_headers = " ".join(
767
+ [
768
+ f"{h}={v if h not in ('Authorization', 'Cookies') else '***'}"
769
+ for h, v in headers.items()
770
+ ]
603
771
  )
772
+ print(f"Get WMS GetCapabilities for URL {wms_getcap_url},\nwith headers: {rendered_headers}")
604
773
  response = requests.get(wms_getcap_url, headers=headers, **kwargs)
605
774
 
606
- try:
607
- self.wmscap_cache[url] = WebMapService(None, xml=response.content)
608
- except Exception as e:
775
+ if response.ok:
776
+ try:
777
+ self.wms_capabilities_cache[url] = WebMapService(None, xml=response.content)
778
+ except Exception as e:
779
+ print(
780
+ colorize(
781
+ "ERROR! an error occurred while trying to parse "
782
+ "the GetCapabilities document.",
783
+ Color.RED,
784
+ )
785
+ )
786
+ print(colorize(str(e), Color.RED))
787
+ print(f"URL: {wms_getcap_url}\nxml:\n{response.text}")
788
+ if _get_config_str("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
789
+ raise
790
+ else:
609
791
  print(
610
792
  colorize(
611
- "ERROR! an error occurred while trying to " "parse the GetCapabilities document.",
612
- RED,
793
+ f"ERROR! Unable to GetCapabilities from URL: {wms_getcap_url},\n"
794
+ f"with headers: {rendered_headers}",
795
+ Color.RED,
613
796
  )
614
797
  )
615
- print(colorize(str(e), RED))
616
- print("URL: {}\nxml:\n{}".format(wms_getcap_url, response.text))
617
- if os.environ.get("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
618
- raise
619
- except Exception as e: # pragma: no cover
620
- print(colorize(str(e), RED))
798
+ print(f"Response: {response.status_code} {response.reason}\n{response.text}")
799
+ if _get_config_str("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
800
+ raise Exception(response.reason)
801
+ except Exception as e:
802
+ print(colorize(str(e), Color.RED))
803
+ rendered_headers = " ".join(
804
+ [
805
+ f"{h}={v if h not in ('Authorization', 'Cookies') else '***'}"
806
+ for h, v in headers.items()
807
+ ]
808
+ )
621
809
  print(
622
810
  colorize(
623
- "ERROR! Unable to GetCapabilities from URL: {},\nwith headers: {}".format(
624
- wms_getcap_url, " ".join(["=".join(h) for h in headers.items()])
625
- ),
626
- RED,
811
+ f"ERROR! Unable to GetCapabilities from URL: {wms_getcap_url},\n"
812
+ f"with headers: {rendered_headers}",
813
+ Color.RED,
627
814
  )
628
815
  )
629
- if os.environ.get("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
816
+ if _get_config_str("IGNORE_I18N_ERRORS", "FALSE") != "TRUE":
630
817
  raise
631
818
 
632
- wmscap = self.wmscap_cache[url]
819
+ wms_capabilities = self.wms_capabilities_cache[url]
633
820
 
634
821
  if url not in self.featuretype_cache:
635
- print("Get WFS DescribeFeatureType for URL: {}".format(url))
822
+ print(f"Get WFS DescribeFeatureType for URL: {url_obj}")
636
823
  self.featuretype_cache[url] = None
637
824
 
638
- wfs_descrfeat_url = add_url_params(
639
- url, {"SERVICE": "WFS", "VERSION": "1.1.0", "REQUEST": "DescribeFeatureType"}
825
+ wfs_describe_feature_url = (
826
+ url_obj.clone()
827
+ .add_query(
828
+ {
829
+ "SERVICE": "WFS",
830
+ "VERSION": "1.1.0",
831
+ "REQUEST": "DescribeFeatureType",
832
+ "ROLE_IDS": "0",
833
+ "USER_ID": "0",
834
+ }
835
+ )
836
+ .url()
640
837
  )
641
838
  try:
642
- response = requests.get(wfs_descrfeat_url, headers=headers, **kwargs)
643
- except Exception as e: # pragma: no cover
644
- print(colorize(str(e), RED))
839
+ response = requests.get(wfs_describe_feature_url, headers=headers, **kwargs)
840
+ except Exception as e:
841
+ print(colorize(str(e), Color.RED))
645
842
  print(
646
843
  colorize(
647
- "ERROR! Unable to DescribeFeatureType from URL: {}".format(wfs_descrfeat_url), RED
844
+ f"ERROR! Unable to DescribeFeatureType from URL: {wfs_describe_feature_url}",
845
+ Color.RED,
648
846
  )
649
847
  )
650
- if os.environ.get("IGNORE_I18N_ERRORS", "FALSE") == "TRUE":
848
+ if _get_config_str("IGNORE_I18N_ERRORS", "FALSE") == "TRUE":
651
849
  return [], []
652
850
  raise
653
851
 
654
- if not response.ok: # pragma: no cover
852
+ if not response.ok:
655
853
  print(
656
854
  colorize(
657
- "ERROR! DescribeFeatureType from URL {} return the error: {:d} {}".format(
658
- wfs_descrfeat_url, response.status_code, response.reason
659
- ),
660
- RED,
855
+ f"ERROR! DescribeFeatureType from URL {wfs_describe_feature_url} return the error: "
856
+ f"{response.status_code:d} {response.reason}",
857
+ Color.RED,
661
858
  )
662
859
  )
663
- if os.environ.get("IGNORE_I18N_ERRORS", "FALSE") == "TRUE":
860
+ if _get_config_str("IGNORE_I18N_ERRORS", "FALSE") == "TRUE":
664
861
  return [], []
665
862
  raise Exception("Aborted")
666
863
 
@@ -675,13 +872,13 @@ class GeomapfishThemeExtractor(Extractor): # pragma: no cover
675
872
  except ExpatError as e:
676
873
  print(
677
874
  colorize(
678
- "ERROR! an error occurred while trying to " "parse the DescribeFeatureType document.",
679
- RED,
875
+ "ERROR! an error occurred while trying to parse the DescribeFeatureType document.",
876
+ Color.RED,
680
877
  )
681
878
  )
682
- print(colorize(str(e), RED))
683
- print("URL: {}\nxml:\n{}".format(wfs_descrfeat_url, response.text))
684
- if os.environ.get("IGNORE_I18N_ERRORS", "FALSE") == "TRUE":
879
+ print(colorize(str(e), Color.RED))
880
+ print(f"URL: {wfs_describe_feature_url}\nxml:\n{response.text}")
881
+ if _get_config_str("IGNORE_I18N_ERRORS", "FALSE") == "TRUE":
685
882
  return [], []
686
883
  raise
687
884
  except AttributeError:
@@ -689,11 +886,11 @@ class GeomapfishThemeExtractor(Extractor): # pragma: no cover
689
886
  colorize(
690
887
  "ERROR! an error occurred while trying to "
691
888
  "read the Mapfile and recover the themes.",
692
- RED,
889
+ Color.RED,
693
890
  )
694
891
  )
695
- print("URL: {}\nxml:\n{}".format(wfs_descrfeat_url, response.text))
696
- if os.environ.get("IGNORE_I18N_ERRORS", "FALSE") == "TRUE":
892
+ print(f"URL: {wfs_describe_feature_url}\nxml:\n{response.text}")
893
+ if _get_config_str("IGNORE_I18N_ERRORS", "FALSE") == "TRUE":
697
894
  return [], []
698
895
  raise
699
896
  else:
@@ -702,16 +899,16 @@ class GeomapfishThemeExtractor(Extractor): # pragma: no cover
702
899
  if featurestype is None:
703
900
  return [], []
704
901
 
705
- layers = [layer]
706
- if wmscap is not None and layer in list(wmscap.contents):
707
- layer_obj = wmscap[layer]
902
+ layers: List[str] = [layer]
903
+ if wms_capabilities is not None and layer in list(wms_capabilities.contents):
904
+ layer_obj = wms_capabilities[layer]
708
905
  if layer_obj.layers:
709
- layers = [l.name for l in layer_obj.layers]
906
+ layers = [layer.name for layer in layer_obj.layers]
710
907
 
711
- attributes = []
908
+ attributes: List[str] = []
712
909
  for sub_layer in layers:
713
910
  # Should probably be adapted for other king of servers
714
- type_element = featurestype.get("{}Type".format(sub_layer))
911
+ type_element = featurestype.get(f"{sub_layer}Type")
715
912
  if type_element is not None:
716
913
  for element in type_element.getElementsByTagNameNS(
717
914
  "http://www.w3.org/2001/XMLSchema", "element"