c2cgeoportal-geoportal 2.6.0__py2.py3-none-any.whl → 2.8.1.180__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 (225) hide show
  1. c2cgeoportal_geoportal/__init__.py +245 -95
  2. c2cgeoportal_geoportal/lib/__init__.py +67 -43
  3. c2cgeoportal_geoportal/lib/authentication.py +50 -26
  4. c2cgeoportal_geoportal/lib/bashcolor.py +17 -13
  5. c2cgeoportal_geoportal/lib/cacheversion.py +16 -8
  6. c2cgeoportal_geoportal/lib/caching.py +65 -193
  7. c2cgeoportal_geoportal/lib/check_collector.py +17 -10
  8. c2cgeoportal_geoportal/lib/checker.py +67 -65
  9. c2cgeoportal_geoportal/lib/common_headers.py +167 -0
  10. c2cgeoportal_geoportal/lib/dbreflection.py +61 -46
  11. c2cgeoportal_geoportal/lib/filter_capabilities.py +126 -88
  12. c2cgeoportal_geoportal/lib/fulltextsearch.py +6 -5
  13. c2cgeoportal_geoportal/lib/functionality.py +20 -17
  14. c2cgeoportal_geoportal/lib/headers.py +14 -5
  15. c2cgeoportal_geoportal/lib/i18n.py +4 -4
  16. c2cgeoportal_geoportal/lib/layers.py +30 -11
  17. c2cgeoportal_geoportal/lib/lingua_extractor.py +363 -240
  18. c2cgeoportal_geoportal/lib/loader.py +11 -16
  19. c2cgeoportal_geoportal/lib/metrics.py +28 -17
  20. c2cgeoportal_geoportal/lib/oauth2.py +392 -206
  21. c2cgeoportal_geoportal/lib/wmstparsing.py +105 -84
  22. c2cgeoportal_geoportal/lib/xsd.py +26 -16
  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/geoportal/+dot+prospector.yaml → advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml} +8 -2
  29. c2cgeoportal_geoportal/scaffolds/{create/geoportal/Dockerfile_tmpl → advance_create/{{cookiecutter.project}}/geoportal/Dockerfile} +22 -15
  30. c2cgeoportal_geoportal/scaffolds/{create/geoportal/alembic.yaml_tmpl → advance_create/{{cookiecutter.project}}/geoportal/alembic.yaml} +1 -1
  31. c2cgeoportal_geoportal/scaffolds/{create/geoportal/development.ini_tmpl → advance_create/{{cookiecutter.project}}/geoportal/development.ini} +34 -15
  32. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/gunicorn.conf.py +100 -0
  33. c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/lingua-client.cfg +1 -0
  34. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/production.ini +38 -0
  35. c2cgeoportal_geoportal/scaffolds/{create/geoportal/setup.py_tmpl → advance_create/{{cookiecutter.project}}/geoportal/setup.py} +6 -7
  36. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/tsconfig.json +8 -0
  37. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.api.js +77 -0
  38. c2cgeoportal_geoportal/scaffolds/{create/geoportal/webpack.apps.js_tmpl → advance_create/{{cookiecutter.project}}/geoportal/webpack.apps.js} +29 -28
  39. c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/webpack.commons.js +4 -7
  40. c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/webpack.config.js +1 -1
  41. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/__init__.py +42 -0
  42. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/authentication.py +10 -0
  43. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/dev.py +14 -0
  44. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/models.py +8 -0
  45. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/multi_organization.py +7 -0
  46. c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/resources.py +4 -3
  47. c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal/static-ngeo/api/index.js_tmpl → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/api/index.js} +1 -2
  48. c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal/static-ngeo/js/+package+module.js_tmpl → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/js/{{cookiecutter.package}}module.js} +4 -4
  49. c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal/subscribers.py_tmpl → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/subscribers.py} +1 -3
  50. c2cgeoportal_geoportal/scaffolds/advance_update/cookiecutter.json +18 -0
  51. c2cgeoportal_geoportal/scaffolds/{update/geoportal/CONST_Makefile_tmpl → advance_update/{{cookiecutter.project}}/geoportal/CONST_Makefile} +3 -27
  52. c2cgeoportal_geoportal/scaffolds/create/cookiecutter.json +18 -0
  53. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.dockerignore +14 -0
  54. c2cgeoportal_geoportal/scaffolds/create/{+dot+editorconfig → {{cookiecutter.project}}/.editorconfig} +2 -5
  55. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/main.yaml +57 -0
  56. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/rebuild.yaml +46 -0
  57. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/update_l10n.yaml +66 -0
  58. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.gitignore +16 -0
  59. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierignore +1 -0
  60. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierrc.yaml +2 -0
  61. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Dockerfile +76 -0
  62. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile +70 -0
  63. c2cgeoportal_geoportal/scaffolds/create/{README.rst_tmpl → {{cookiecutter.project}}/README.rst} +4 -4
  64. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build +186 -0
  65. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/config.yaml +22 -0
  66. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/docker-compose-check +25 -0
  67. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/requirements.txt +1 -0
  68. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-db.yaml +26 -0
  69. c2cgeoportal_geoportal/scaffolds/create/{docker-compose-lib.yaml → {{cookiecutter.project}}/docker-compose-lib.yaml} +165 -22
  70. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-qgis.yaml +23 -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} +20 -15
  73. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.default +101 -0
  74. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.project +69 -0
  75. c2cgeoportal_geoportal/scaffolds/create/{geoportal/vars.yaml_tmpl → {{cookiecutter.project}}/geoportal/vars.yaml} +126 -36
  76. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/mobile.css +0 -0
  77. c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/data/Readme.txt +3 -3
  78. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/demo.map.tmpl +224 -0
  79. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.conf +15 -0
  80. c2cgeoportal_geoportal/scaffolds/create/{mapserver/mapserver.map.tmpl_tmpl → {{cookiecutter.project}}/mapserver/mapserver.map.tmpl} +9 -18
  81. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A3_Landscape.jrxml +13 -8
  82. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A3_Portrait.jrxml +13 -8
  83. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A4_Landscape.jrxml +13 -8
  84. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A4_Portrait.jrxml +13 -8
  85. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/config.yaml.tmpl +11 -4
  86. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation.properties +4 -0
  87. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation_fr.properties +4 -0
  88. c2cgeoportal_geoportal/scaffolds/create/{project.yaml_tmpl → {{cookiecutter.project}}/project.yaml} +6 -6
  89. c2cgeoportal_geoportal/scaffolds/create/{pyproject.toml → {{cookiecutter.project}}/pyproject.toml} +4 -0
  90. c2cgeoportal_geoportal/scaffolds/create/{qgisserver/pg_service.conf.tmpl_tmpl → {{cookiecutter.project}}/qgisserver/pg_service.conf.tmpl} +2 -2
  91. c2cgeoportal_geoportal/scaffolds/create/{run_alembic.sh → {{cookiecutter.project}}/run_alembic.sh} +3 -5
  92. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-backup +110 -0
  93. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +114 -0
  94. c2cgeoportal_geoportal/scaffolds/create/{setup.cfg_tmpl → {{cookiecutter.project}}/setup.cfg} +1 -1
  95. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/spell-ignore-words.txt +5 -0
  96. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/__init__.py +0 -0
  97. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/test_app.py +38 -0
  98. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tilegeneration/config.yaml.tmpl +195 -0
  99. c2cgeoportal_geoportal/scaffolds/update/cookiecutter.json +18 -0
  100. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/.upgrade.yaml +61 -0
  101. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_CHANGELOG.txt +273 -0
  102. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_create_template/tests/test_testapp.py +48 -0
  103. c2cgeoportal_geoportal/scaffolds/update/{geoportal → {{cookiecutter.project}}/geoportal}/CONST_config-schema.yaml +64 -17
  104. c2cgeoportal_geoportal/scaffolds/update/{geoportal/CONST_vars.yaml_tmpl → {{cookiecutter.project}}/geoportal/CONST_vars.yaml} +396 -19
  105. c2cgeoportal_geoportal/scripts/__init__.py +16 -30
  106. c2cgeoportal_geoportal/scripts/c2cupgrade.py +272 -234
  107. c2cgeoportal_geoportal/scripts/create_demo_theme.py +3 -6
  108. c2cgeoportal_geoportal/scripts/manage_users.py +34 -39
  109. c2cgeoportal_geoportal/scripts/pcreate.py +310 -0
  110. c2cgeoportal_geoportal/scripts/theme2fts.py +128 -24
  111. c2cgeoportal_geoportal/scripts/urllogin.py +19 -11
  112. c2cgeoportal_geoportal/templates/login.html +88 -84
  113. c2cgeoportal_geoportal/templates/notlogin.html +59 -59
  114. c2cgeoportal_geoportal/templates/testi18n.html +6 -8
  115. c2cgeoportal_geoportal/views/__init__.py +23 -6
  116. c2cgeoportal_geoportal/views/dev.py +9 -7
  117. c2cgeoportal_geoportal/views/dynamic.py +56 -19
  118. c2cgeoportal_geoportal/views/entry.py +85 -24
  119. c2cgeoportal_geoportal/views/fulltextsearch.py +29 -23
  120. c2cgeoportal_geoportal/views/geometry_processing.py +17 -9
  121. c2cgeoportal_geoportal/views/i18n.py +91 -9
  122. c2cgeoportal_geoportal/views/layers.py +166 -133
  123. c2cgeoportal_geoportal/views/login.py +161 -93
  124. c2cgeoportal_geoportal/views/mapserverproxy.py +47 -31
  125. c2cgeoportal_geoportal/views/memory.py +12 -12
  126. c2cgeoportal_geoportal/views/ogcproxy.py +52 -30
  127. c2cgeoportal_geoportal/views/pdfreport.py +30 -26
  128. c2cgeoportal_geoportal/views/printproxy.py +60 -52
  129. c2cgeoportal_geoportal/views/profile.py +24 -23
  130. c2cgeoportal_geoportal/views/proxy.py +88 -72
  131. c2cgeoportal_geoportal/views/raster.py +37 -26
  132. c2cgeoportal_geoportal/views/resourceproxy.py +13 -11
  133. c2cgeoportal_geoportal/views/shortener.py +27 -25
  134. c2cgeoportal_geoportal/views/theme.py +472 -332
  135. c2cgeoportal_geoportal/views/tinyowsproxy.py +42 -44
  136. c2cgeoportal_geoportal/views/vector_tiles.py +80 -0
  137. {c2cgeoportal_geoportal-2.6.0.dist-info → c2cgeoportal_geoportal-2.8.1.180.dist-info}/METADATA +19 -8
  138. c2cgeoportal_geoportal-2.8.1.180.dist-info/RECORD +191 -0
  139. {c2cgeoportal_geoportal-2.6.0.dist-info → c2cgeoportal_geoportal-2.8.1.180.dist-info}/WHEEL +1 -1
  140. {c2cgeoportal_geoportal-2.6.0.dist-info → c2cgeoportal_geoportal-2.8.1.180.dist-info}/entry_points.txt +3 -0
  141. tests/__init__.py +10 -5
  142. tests/test_cachebuster.py +3 -5
  143. tests/test_caching.py +18 -26
  144. tests/test_checker.py +1 -3
  145. tests/test_decimaljson.py +5 -5
  146. tests/test_headerstween.py +1 -3
  147. tests/test_i18n.py +2 -2
  148. tests/test_init.py +16 -20
  149. tests/test_locale_negociator.py +4 -6
  150. tests/test_mapserverproxy_route_predicate.py +1 -4
  151. tests/test_raster.py +15 -17
  152. tests/test_wmstparsing.py +10 -12
  153. tests/xmlstr.py +1 -3
  154. c2cgeoportal_geoportal/scaffolds/__init__.py +0 -227
  155. c2cgeoportal_geoportal/scaffolds/create/+dot+dockerignore_tmpl +0 -12
  156. c2cgeoportal_geoportal/scaffolds/create/+dot+github/workflows/main.yaml_tmpl +0 -89
  157. c2cgeoportal_geoportal/scaffolds/create/+dot+github/workflows/rebuild.yaml_tmpl +0 -78
  158. c2cgeoportal_geoportal/scaffolds/create/+dot+gitignore_tmpl +0 -16
  159. c2cgeoportal_geoportal/scaffolds/create/Dockerfile_tmpl +0 -67
  160. c2cgeoportal_geoportal/scaffolds/create/Makefile +0 -3
  161. c2cgeoportal_geoportal/scaffolds/create/build_tmpl +0 -167
  162. c2cgeoportal_geoportal/scaffolds/create/ci/config.yaml_tmpl +0 -23
  163. c2cgeoportal_geoportal/scaffolds/create/ci/requirements.txt +0 -1
  164. c2cgeoportal_geoportal/scaffolds/create/ci/trigger +0 -68
  165. c2cgeoportal_geoportal/scaffolds/create/docker-compose.override.sample.yaml +0 -54
  166. c2cgeoportal_geoportal/scaffolds/create/env.default_tmpl +0 -67
  167. c2cgeoportal_geoportal/scaffolds/create/env.project_tmpl +0 -48
  168. c2cgeoportal_geoportal/scaffolds/create/geoportal/+dot+dockerignore_tmpl +0 -6
  169. c2cgeoportal_geoportal/scaffolds/create/geoportal/+dot+eslintrc_tmpl +0 -15
  170. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/__init__.py_tmpl +0 -58
  171. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/models.py_tmpl +0 -10
  172. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/static/robot.txt +0 -3
  173. c2cgeoportal_geoportal/scaffolds/create/geoportal/production.ini_tmpl +0 -106
  174. c2cgeoportal_geoportal/scaffolds/create/geoportal/tools/extract-messages.js +0 -39
  175. c2cgeoportal_geoportal/scaffolds/create/geoportal/tsconfig.json_tmpl +0 -9
  176. c2cgeoportal_geoportal/scaffolds/create/geoportal/webpack.api.js_tmpl +0 -72
  177. c2cgeoportal_geoportal/scaffolds/create/mapserver/demo.map.tmpl_tmpl +0 -262
  178. c2cgeoportal_geoportal/scaffolds/create/mapserver/tinyows.xml +0 -36
  179. c2cgeoportal_geoportal/scaffolds/create/print/print-apps/+package+/config.yaml +0 -168
  180. c2cgeoportal_geoportal/scaffolds/create/qgisserver/geomapfish.yaml.tmpl_tmpl +0 -16
  181. c2cgeoportal_geoportal/scaffolds/create/spell-ignore-words.txt +0 -1
  182. c2cgeoportal_geoportal/scaffolds/create/tilegeneration/config.yaml.tmpl_tmpl +0 -185
  183. c2cgeoportal_geoportal/scaffolds/create/yamllint.yaml +0 -11
  184. c2cgeoportal_geoportal/scaffolds/update/+dot+upgrade.yaml_tmpl +0 -181
  185. c2cgeoportal_geoportal/scaffolds/update/CONST_CHANGELOG.txt_tmpl +0 -454
  186. c2cgeoportal_geoportal/templates/dynamic.js +0 -21
  187. c2cgeoportal_geoportal-2.6.0.dist-info/RECORD +0 -173
  188. /c2cgeoportal_geoportal/{scaffolds/create/geoportal/+package+_geoportal/static/css/desktop.css → py.typed} +0 -0
  189. /c2cgeoportal_geoportal/scaffolds/{create/geoportal/Makefile_tmpl → advance_create/{{cookiecutter.project}}/geoportal/Makefile} +0 -0
  190. /c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/alembic.ini +0 -0
  191. /c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/language_mapping +0 -0
  192. /c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/lingua-server.cfg +0 -0
  193. /c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/requirements.txt +0 -0
  194. /c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/views/__init__.py +0 -0
  195. /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
  196. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal/static/css/iframe_api.css → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/desktop.css} +0 -0
  197. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal/static/css/mobile.css → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/iframe_api.css} +0 -0
  198. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/banner_left.png +0 -0
  199. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/banner_right.png +0 -0
  200. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/blank.png +0 -0
  201. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/markers/marker-blue.png +0 -0
  202. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/markers/marker-gold.png +0 -0
  203. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/markers/marker-green.png +0 -0
  204. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/markers/marker.png +0 -0
  205. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/robot.txt.tmpl +0 -0
  206. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/data/TM_EUROPE_BORDERS-0.3.sql +0 -0
  207. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arial.ttf +0 -0
  208. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arialbd.ttf +0 -0
  209. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arialbi.ttf +0 -0
  210. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Ariali.ttf +0 -0
  211. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Bold.ttf +0 -0
  212. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-BoldItalic.ttf +0 -0
  213. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Italic.ttf +0 -0
  214. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Regular.ttf +0 -0
  215. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdana.ttf +0 -0
  216. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanab.ttf +0 -0
  217. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanai.ttf +0 -0
  218. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanaz.ttf +0 -0
  219. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts.conf +0 -0
  220. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/tinyows.xml.tmpl +0 -0
  221. /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/legend.jrxml +0 -0
  222. /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/logo.png +0 -0
  223. /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/north.svg +0 -0
  224. /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/results.jrxml +0 -0
  225. {c2cgeoportal_geoportal-2.6.0.dist-info → c2cgeoportal_geoportal-2.8.1.180.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2012-2021, Camptocamp SA
1
+ # Copyright (c) 2012-2023, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -31,9 +29,13 @@ import json
31
29
  import logging
32
30
  import os
33
31
  from datetime import datetime
34
- from typing import Dict, List, Union
32
+ from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Set, Tuple, TypedDict, cast
35
33
 
36
- import geojson
34
+ import geojson.geometry
35
+ import pyramid.request
36
+ import pyramid.response
37
+ import shapely.geometry
38
+ import sqlalchemy.ext.declarative
37
39
  from geoalchemy2 import Geometry
38
40
  from geoalchemy2 import func as ga_func
39
41
  from geoalchemy2.shape import from_shape, to_shape
@@ -48,10 +50,9 @@ from pyramid.httpexceptions import (
48
50
  HTTPNotFound,
49
51
  )
50
52
  from pyramid.view import view_config
51
- from shapely.geometry import asShape
52
- from shapely.geos import TopologicalError
53
+ from shapely.errors import TopologicalError
53
54
  from shapely.ops import cascaded_union
54
- from sqlalchemy import Enum, Numeric, String, Text, Unicode, UnicodeText, distinct, exc, func
55
+ from sqlalchemy import Enum, Numeric, String, Text, Unicode, UnicodeText, exc, func
55
56
  from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
56
57
  from sqlalchemy.orm.properties import ColumnProperty
57
58
  from sqlalchemy.orm.util import class_mapper
@@ -59,47 +60,50 @@ from sqlalchemy.sql import and_, or_
59
60
 
60
61
  from c2cgeoportal_commons import models
61
62
  from c2cgeoportal_geoportal.lib import get_roles_id
62
- from c2cgeoportal_geoportal.lib.caching import (
63
- NO_CACHE,
64
- PRIVATE_CACHE,
65
- PUBLIC_CACHE,
66
- get_region,
67
- set_common_headers,
68
- )
63
+ from c2cgeoportal_geoportal.lib.caching import get_region
64
+ from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
69
65
  from c2cgeoportal_geoportal.lib.dbreflection import _AssociationProxy, get_class, get_table
70
66
 
67
+ if TYPE_CHECKING:
68
+ from c2cgeoportal_commons.models import main # pylint: disable=ungrouped-imports.useless-suppression
71
69
  LOG = logging.getLogger(__name__)
72
70
  CACHE_REGION = get_region("std")
73
71
 
74
72
 
75
73
  class Layers:
76
- def __init__(self, request):
74
+ """
75
+ All the layers view (editing).
76
+
77
+ Mapfish protocol implementation
78
+ """
79
+
80
+ def __init__(self, request: pyramid.request.Request):
77
81
  self.request = request
78
82
  self.settings = request.registry.settings.get("layers", {})
79
83
  self.layers_enum_config = self.settings.get("enum")
80
84
 
81
85
  @staticmethod
82
- def _get_geom_col_info(layer):
83
- """Return information about the layer's geometry column, namely
84
- a ``(name, srid)`` tuple, where ``name`` is the name of the
85
- geometry column, and ``srid`` its srid.
86
+ def _get_geom_col_info(layer: "main.Layer") -> Tuple[str, int]:
87
+ """
88
+ Return information about the layer's geometry column.
89
+
90
+ Namely a ``(name, srid)`` tuple, where ``name`` is the name of the geometry column,
91
+ and ``srid`` its srid.
86
92
 
87
- This function assumes that the names of geometry attributes
88
- in the mapped class are the same as those of geometry columns.
93
+ This function assumes that the names of geometry attributes in the mapped class are the same as those
94
+ of geometry columns.
89
95
  """
90
96
  mapped_class = get_layer_class(layer)
91
97
  for p in class_mapper(mapped_class).iterate_properties:
92
98
  if not isinstance(p, ColumnProperty):
93
- continue # pragma: no cover
99
+ continue
94
100
  col = p.columns[0]
95
101
  if isinstance(col.type, Geometry):
96
102
  return col.name, col.type.srid
97
- raise HTTPInternalServerError(
98
- 'Failed getting geometry column info for table "{0!s}".'.format(layer.geo_table)
99
- ) # pragma: no cover
103
+ raise HTTPInternalServerError(f'Failed getting geometry column info for table "{layer.geo_table!s}".')
100
104
 
101
105
  @staticmethod
102
- def _get_layer(layer_id):
106
+ def _get_layer(layer_id: int) -> "main.Layer":
103
107
  """Return a ``Layer`` object for ``layer_id``."""
104
108
  from c2cgeoportal_commons.models.main import Layer # pylint: disable=import-outside-toplevel
105
109
 
@@ -109,16 +113,19 @@ class Layers:
109
113
  query = query.filter(Layer.id == layer_id)
110
114
  layer, geo_table = query.one()
111
115
  except NoResultFound:
112
- raise HTTPNotFound("Layer {0:d} not found".format(layer_id))
113
- except MultipleResultsFound: # pragma: no cover
114
- raise HTTPInternalServerError("Too many layers found with id {0:d}".format(layer_id))
115
- if not geo_table: # pragma: no cover
116
- raise HTTPNotFound("Layer {0:d} has no geo table".format(layer_id))
117
- return layer
118
-
119
- def _get_layers_for_request(self):
120
- """A generator function that yields ``Layer`` objects based
121
- on the layer ids found in the ``layer_id`` matchdict."""
116
+ raise HTTPNotFound(f"Layer {layer_id:d} not found") from None
117
+ except MultipleResultsFound:
118
+ raise HTTPInternalServerError(f"Too many layers found with id {layer_id:d}") from None
119
+ if not geo_table:
120
+ raise HTTPNotFound(f"Layer {layer_id:d} has no geo table")
121
+ return cast("main.Layer", layer)
122
+
123
+ def _get_layers_for_request(self) -> Generator["main.Layer", None, None]:
124
+ """
125
+ Get a generator function that yields ``Layer`` objects.
126
+
127
+ Based on the layer ids found in the ``layer_id`` matchdict.
128
+ """
122
129
  try:
123
130
  layer_ids = (
124
131
  int(layer_id) for layer_id in self.request.matchdict["layer_id"].split(",") if layer_id
@@ -126,28 +133,26 @@ class Layers:
126
133
  for layer_id in layer_ids:
127
134
  yield self._get_layer(layer_id)
128
135
  except ValueError:
129
- raise HTTPBadRequest(
130
- "A Layer id in '{0!s}' is not an integer".format(self.request.matchdict["layer_id"])
131
- ) # pragma: no cover
136
+ raise HTTPBadRequest( # pylint: disable=raise-missing-from
137
+ f"A Layer id in '{self.request.matchdict['layer_id']}' is not an integer"
138
+ )
132
139
 
133
- def _get_layer_for_request(self):
134
- """Return a ``Layer`` object for the first layer id found
135
- in the ``layer_id`` matchdict."""
140
+ def _get_layer_for_request(self) -> "main.Layer":
141
+ """Return a ``Layer`` object for the first layer id found in the ``layer_id`` matchdict."""
136
142
  return next(self._get_layers_for_request())
137
143
 
138
- def _get_protocol_for_layer(self, layer, **kwargs):
139
- """Returns a papyrus ``Protocol`` for the ``Layer`` object."""
144
+ def _get_protocol_for_layer(self, layer: "main.Layer", **kwargs: Any) -> Protocol:
145
+ """Return a papyrus ``Protocol`` for the ``Layer`` object."""
140
146
  cls = get_layer_class(layer)
141
147
  geom_attr = self._get_geom_col_info(layer)[0]
142
148
  return Protocol(models.DBSession, cls, geom_attr, **kwargs)
143
149
 
144
- def _get_protocol_for_request(self, **kwargs):
145
- """Returns a papyrus ``Protocol`` for the first layer
146
- id found in the ``layer_id`` matchdict."""
150
+ def _get_protocol_for_request(self, **kwargs: Any) -> Protocol:
151
+ """Return a papyrus ``Protocol`` for the first layer id found in the ``layer_id`` matchdict."""
147
152
  layer = self._get_layer_for_request()
148
153
  return self._get_protocol_for_layer(layer, **kwargs)
149
154
 
150
- def _proto_read(self, layer):
155
+ def _proto_read(self, layer: "main.Layer") -> FeatureCollection:
151
156
  """Read features for the layer based on the self.request."""
152
157
  from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
153
158
  Layer,
@@ -175,7 +180,7 @@ class Layers:
175
180
  return proto.read(self.request)
176
181
  use_srid = srid
177
182
  collect_ra.append(to_shape(ra))
178
- if not collect_ra: # pragma: no cover
183
+ if not collect_ra:
179
184
  raise HTTPForbidden()
180
185
 
181
186
  filter1_ = create_filter(self.request, cls, geom_attr)
@@ -183,11 +188,14 @@ class Layers:
183
188
  filter2_ = ga_func.ST_Contains(from_shape(ra, use_srid), getattr(cls, geom_attr))
184
189
  filter_ = filter2_ if filter1_ is None else and_(filter1_, filter2_)
185
190
 
186
- return proto.read(self.request, filter=filter_)
191
+ feature = proto.read(self.request, filter=filter_)
192
+ if isinstance(feature, HTTPException):
193
+ raise feature
194
+ return feature
187
195
 
188
- @view_config(route_name="layers_read_many", renderer="geojson")
189
- def read_many(self):
190
- set_common_headers(self.request, "layers", NO_CACHE)
196
+ @view_config(route_name="layers_read_many", renderer="geojson") # type: ignore
197
+ def read_many(self) -> FeatureCollection:
198
+ set_common_headers(self.request, "layers", Cache.PRIVATE_NO)
191
199
 
192
200
  features = []
193
201
  for layer in self._get_layers_for_request():
@@ -197,15 +205,15 @@ class Layers:
197
205
 
198
206
  return FeatureCollection(features)
199
207
 
200
- @view_config(route_name="layers_read_one", renderer="geojson")
201
- def read_one(self):
208
+ @view_config(route_name="layers_read_one", renderer="geojson") # type: ignore
209
+ def read_one(self) -> Feature:
202
210
  from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
203
211
  Layer,
204
212
  RestrictionArea,
205
213
  Role,
206
214
  )
207
215
 
208
- set_common_headers(self.request, "layers", NO_CACHE)
216
+ set_common_headers(self.request, "layers", Cache.PRIVATE_NO)
209
217
 
210
218
  layer = self._get_layer_for_request()
211
219
  protocol = self._get_protocol_for_layer(layer)
@@ -218,9 +226,9 @@ class Layers:
218
226
  if self.request.user is None:
219
227
  raise HTTPForbidden()
220
228
  geom = feature.geometry
221
- if not geom or isinstance(geom, geojson.geometry.Default): # pragma: no cover
229
+ if not geom or isinstance(geom, geojson.geometry.Default):
222
230
  return feature
223
- shape = asShape(geom)
231
+ shape = shapely.geometry.shape(geom)
224
232
  srid = self._get_geom_col_info(layer)[1]
225
233
  spatial_elt = from_shape(shape, srid=srid)
226
234
  allowed = models.DBSession.query(func.count(RestrictionArea.id))
@@ -236,22 +244,25 @@ class Layers:
236
244
 
237
245
  return feature
238
246
 
239
- @view_config(route_name="layers_count", renderer="string")
240
- def count(self):
241
- set_common_headers(self.request, "layers", NO_CACHE)
247
+ @view_config(route_name="layers_count", renderer="string") # type: ignore
248
+ def count(self) -> int:
249
+ set_common_headers(self.request, "layers", Cache.PRIVATE_NO)
242
250
 
243
251
  protocol = self._get_protocol_for_request()
244
- return protocol.count(self.request)
252
+ count = protocol.count(self.request)
253
+ if isinstance(count, HTTPException):
254
+ raise count
255
+ return cast(int, count)
245
256
 
246
- @view_config(route_name="layers_create", renderer="geojson")
247
- def create(self):
257
+ @view_config(route_name="layers_create", renderer="geojson") # type: ignore
258
+ def create(self) -> Optional[FeatureCollection]:
248
259
  from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
249
260
  Layer,
250
261
  RestrictionArea,
251
262
  Role,
252
263
  )
253
264
 
254
- set_common_headers(self.request, "layers", NO_CACHE)
265
+ set_common_headers(self.request, "layers", Cache.PRIVATE_NO)
255
266
 
256
267
  if self.request.user is None:
257
268
  raise HTTPForbidden()
@@ -260,11 +271,11 @@ class Layers:
260
271
 
261
272
  layer = self._get_layer_for_request()
262
273
 
263
- def check_geometry(_, feature, obj):
274
+ def check_geometry(_: Any, feature: Feature, obj: Any) -> None:
264
275
  del obj # unused
265
276
  geom = feature.geometry
266
277
  if geom and not isinstance(geom, geojson.geometry.Default):
267
- shape = asShape(geom)
278
+ shape = shapely.geometry.shape(geom)
268
279
  srid = self._get_geom_col_info(layer)[1]
269
280
  spatial_elt = from_shape(shape, srid=srid)
270
281
  allowed = models.DBSession.query(func.count(RestrictionArea.id))
@@ -287,7 +298,7 @@ class Layers:
287
298
  try:
288
299
  features = protocol.create(self.request)
289
300
  if isinstance(features, HTTPException):
290
- raise features # pylint: disable=raising-bad-type
301
+ raise features
291
302
  if features is not None:
292
303
  for feature in features.features: # pylint: disable=no-member
293
304
  self._log_last_update(layer, feature)
@@ -300,15 +311,15 @@ class Layers:
300
311
  self.request.response.status_int = 400
301
312
  return {"error_type": "integrity_error", "message": str(e.orig.diag.message_primary)}
302
313
 
303
- @view_config(route_name="layers_update", renderer="geojson")
304
- def update(self):
314
+ @view_config(route_name="layers_update", renderer="geojson") # type: ignore
315
+ def update(self) -> Feature:
305
316
  from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
306
317
  Layer,
307
318
  RestrictionArea,
308
319
  Role,
309
320
  )
310
321
 
311
- set_common_headers(self.request, "layers", NO_CACHE)
322
+ set_common_headers(self.request, "layers", Cache.PRIVATE_NO)
312
323
 
313
324
  if self.request.user is None:
314
325
  raise HTTPForbidden()
@@ -318,7 +329,7 @@ class Layers:
318
329
  feature_id = self.request.matchdict.get("feature_id")
319
330
  layer = self._get_layer_for_request()
320
331
 
321
- def check_geometry(_, feature, obj):
332
+ def check_geometry(_: Any, feature: Feature, obj: Any) -> None:
322
333
  # we need both the "original" and "new" geometry to be
323
334
  # within the restriction area
324
335
  geom_attr, srid = self._get_geom_col_info(layer)
@@ -335,7 +346,7 @@ class Layers:
335
346
  )
336
347
  spatial_elt = None
337
348
  if geom and not isinstance(geom, geojson.geometry.Default):
338
- shape = asShape(geom)
349
+ shape = shapely.geometry.shape(geom)
339
350
  spatial_elt = from_shape(shape, srid=srid)
340
351
  allowed = allowed.filter(
341
352
  or_(RestrictionArea.area.is_(None), RestrictionArea.area.ST_Contains(spatial_elt))
@@ -350,8 +361,10 @@ class Layers:
350
361
  protocol = self._get_protocol_for_layer(layer, before_update=check_geometry)
351
362
  try:
352
363
  feature = protocol.update(self.request, feature_id)
364
+ if isinstance(feature, HTTPException):
365
+ raise feature
353
366
  self._log_last_update(layer, feature)
354
- return feature
367
+ return cast(Feature, feature)
355
368
  except TopologicalError as e:
356
369
  self.request.response.status_int = 400
357
370
  return {"error_type": "validation_error", "message": str(e)}
@@ -361,7 +374,7 @@ class Layers:
361
374
  return {"error_type": "integrity_error", "message": str(e.orig.diag.message_primary)}
362
375
 
363
376
  @staticmethod
364
- def _validate_geometry(geom):
377
+ def _validate_geometry(geom: Geometry) -> None:
365
378
  if geom is not None:
366
379
  simple = models.DBSession.query(func.ST_IsSimple(func.ST_GeomFromEWKB(geom))).scalar()
367
380
  if not simple:
@@ -371,7 +384,7 @@ class Layers:
371
384
  reason = models.DBSession.query(func.ST_IsValidReason(func.ST_GeomFromEWKB(geom))).scalar()
372
385
  raise TopologicalError(reason)
373
386
 
374
- def _log_last_update(self, layer, feature):
387
+ def _log_last_update(self, layer: "main.Layer", feature: Feature) -> None:
375
388
  last_update_date = self.get_metadata(layer, "lastUpdateDateColumn")
376
389
  if last_update_date is not None:
377
390
  setattr(feature, last_update_date, datetime.now())
@@ -381,22 +394,22 @@ class Layers:
381
394
  setattr(feature, last_update_user, self.request.user.id)
382
395
 
383
396
  @staticmethod
384
- def get_metadata(layer, key, default=None):
385
- metadatas = layer.get_metadatas(key)
386
- if len(metadatas) == 1:
387
- metadata = metadatas[0]
388
- return metadata.value
397
+ def get_metadata(layer: "main.Layer", key: str, default: Optional[str] = None) -> Optional[str]:
398
+ metadata = layer.get_metadata(key)
399
+ if len(metadata) == 1:
400
+ metadata = metadata[0]
401
+ return cast(str, metadata.value)
389
402
  return default
390
403
 
391
- def _get_validation_setting(self, layer):
404
+ def _get_validation_setting(self, layer: "main.Layer") -> bool:
392
405
  # The validation UIMetadata is stored as a string, not a boolean
393
406
  should_validate = self.get_metadata(layer, "geometryValidation", None)
394
407
  if should_validate:
395
408
  return should_validate.lower() != "false"
396
- return self.settings.get("geometry_validation", False)
409
+ return cast(bool, self.settings.get("geometry_validation", False))
397
410
 
398
- @view_config(route_name="layers_delete")
399
- def delete(self):
411
+ @view_config(route_name="layers_delete") # type: ignore
412
+ def delete(self) -> pyramid.response.Response:
400
413
  from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
401
414
  Layer,
402
415
  RestrictionArea,
@@ -409,7 +422,7 @@ class Layers:
409
422
  feature_id = self.request.matchdict.get("feature_id")
410
423
  layer = self._get_layer_for_request()
411
424
 
412
- def security_cb(_, obj):
425
+ def security_cb(_: Any, obj: Any) -> None:
413
426
  geom_attr = getattr(obj, self._get_geom_col_info(layer)[0])
414
427
  allowed = models.DBSession.query(func.count(RestrictionArea.id))
415
428
  allowed = allowed.join(RestrictionArea.roles)
@@ -425,12 +438,14 @@ class Layers:
425
438
 
426
439
  protocol = self._get_protocol_for_layer(layer, before_delete=security_cb)
427
440
  response = protocol.delete(self.request, feature_id)
428
- set_common_headers(self.request, "layers", NO_CACHE, response=response)
441
+ if isinstance(response, HTTPException):
442
+ raise response
443
+ set_common_headers(self.request, "layers", Cache.PRIVATE_NO, response=response)
429
444
  return response
430
445
 
431
- @view_config(route_name="layers_metadata", renderer="xsd")
432
- def metadata(self):
433
- set_common_headers(self.request, "layers", PRIVATE_CACHE)
446
+ @view_config(route_name="layers_metadata", renderer="xsd") # type: ignore
447
+ def metadata(self) -> pyramid.response.Response:
448
+ set_common_headers(self.request, "layers", Cache.PRIVATE)
434
449
 
435
450
  layer = self._get_layer_for_request()
436
451
  if not layer.public and self.request.user is None:
@@ -438,38 +453,40 @@ class Layers:
438
453
 
439
454
  return get_layer_class(layer, with_last_update_columns=True)
440
455
 
441
- @view_config(route_name="layers_enumerate_attribute_values", renderer="json")
442
- def enumerate_attribute_values(self):
443
- set_common_headers(self.request, "layers", PUBLIC_CACHE)
456
+ @view_config(route_name="layers_enumerate_attribute_values", renderer="json") # type: ignore
457
+ def enumerate_attribute_values(self) -> Dict[str, Any]:
458
+ set_common_headers(self.request, "layers", Cache.PUBLIC)
444
459
 
445
- if self.layers_enum_config is None: # pragma: no cover
460
+ if self.layers_enum_config is None:
446
461
  raise HTTPInternalServerError("Missing configuration")
447
462
  layername = self.request.matchdict["layer_name"]
448
463
  fieldname = self.request.matchdict["field_name"]
449
464
  # TODO check if layer is public or not
450
465
 
451
- return self._enumerate_attribute_values(layername, fieldname)
466
+ return cast(Dict[str, Any], self._enumerate_attribute_values(layername, fieldname))
452
467
 
453
- @CACHE_REGION.cache_on_arguments()
454
- def _enumerate_attribute_values(self, layername, fieldname):
455
- if layername not in self.layers_enum_config: # pragma: no cover
456
- raise HTTPBadRequest("Unknown layer: {0!s}".format(layername))
468
+ @CACHE_REGION.cache_on_arguments() # type: ignore
469
+ def _enumerate_attribute_values(self, layername: str, fieldname: str) -> Dict[str, Any]:
470
+ if layername not in self.layers_enum_config:
471
+ raise HTTPBadRequest(f"Unknown layer: {layername!s}")
457
472
 
458
473
  layerinfos = self.layers_enum_config[layername]
459
- if fieldname not in layerinfos["attributes"]: # pragma: no cover
460
- raise HTTPBadRequest("Unknown attribute: {0!s}".format(fieldname))
474
+ if fieldname not in layerinfos["attributes"]:
475
+ raise HTTPBadRequest(f"Unknown attribute: {fieldname!s}")
461
476
  dbsession_name = layerinfos.get("dbsession", "dbsession")
462
477
  dbsession = models.DBSessions.get(dbsession_name)
463
- if dbsession is None: # pragma: no cover
478
+ if dbsession is None:
464
479
  raise HTTPInternalServerError(
465
- "No dbsession found for layer '{0!s}' ({1!s})".format(layername, dbsession_name)
480
+ f"No dbsession found for layer '{layername!s}' ({dbsession_name!s})"
466
481
  )
467
- values = self.query_enumerate_attribute_values(dbsession, layerinfos, fieldname)
482
+ values = sorted(self.query_enumerate_attribute_values(dbsession, layerinfos, fieldname))
468
483
  enum = {"items": [{"value": value[0]} for value in values]}
469
484
  return enum
470
485
 
471
486
  @staticmethod
472
- def query_enumerate_attribute_values(dbsession, layerinfos, fieldname):
487
+ def query_enumerate_attribute_values(
488
+ dbsession: sqlalchemy.orm.Session, layerinfos: Dict[str, Any], fieldname: str
489
+ ) -> Set[Tuple[str, ...]]:
473
490
  attrinfos = layerinfos["attributes"][fieldname]
474
491
  table = attrinfos["table"]
475
492
  layertable = get_table(table, session=dbsession)
@@ -480,18 +497,23 @@ class Layers:
480
497
  if "separator" in attrinfos:
481
498
  separator = attrinfos["separator"]
482
499
  attribute = func.unnest(func.string_to_array(func.string_agg(attribute, separator), separator))
483
- return dbsession.query(distinct(attribute)).order_by(attribute).all()
500
+ return set(cast(List[Tuple[str, ...]], dbsession.query(attribute).order_by(attribute).all()))
484
501
 
485
502
 
486
- def get_layer_class(layer, with_last_update_columns=False):
503
+ def get_layer_class(
504
+ layer: "main.Layer", with_last_update_columns: bool = False
505
+ ) -> sqlalchemy.ext.declarative.ConcreteBase:
487
506
  """
488
- Get the SQLAlchemy class to edit a GeoMapFish layer
507
+ Get the SQLAlchemy class to edit a GeoMapFish layer.
508
+
509
+ Keyword Arguments:
489
510
 
490
- :param layer:
491
- :param with_last_update_columns: False to just have a class to access to the table and be able to
511
+ layer: The GeoMapFish layer
512
+ with_last_update_columns: False to just have a class to access to the table and be able to
492
513
  modify the last_update_columns, True to have a correct class to build the UI
493
514
  (without the hidden column).
494
- :return: SQLAlchemy class
515
+
516
+ Returns: SQLAlchemy class
495
517
  """
496
518
  # Exclude the columns used to record the last features update
497
519
  exclude = [] if layer.exclude_properties is None else layer.exclude_properties.split(",")
@@ -502,8 +524,6 @@ def get_layer_class(layer, with_last_update_columns=False):
502
524
  last_update_user = Layers.get_metadata(layer, "lastUpdateUserColumn")
503
525
  if last_update_user is not None:
504
526
  exclude.append(last_update_user)
505
- else:
506
- exclude = []
507
527
 
508
528
  m = Layers.get_metadata(layer, "editingAttributesOrder")
509
529
  attributes_order = m.split(",") if m else None
@@ -543,21 +563,35 @@ def get_layer_class(layer, with_last_update_columns=False):
543
563
  return cls
544
564
 
545
565
 
546
- def get_layer_metadatas(layer):
566
+ class ColumnProperties(TypedDict, total=False):
567
+ """Collected metadata information related to an editing attribute."""
568
+
569
+ name: str
570
+ type: str
571
+ nillable: bool
572
+ srid: int
573
+ enumeration: List[str]
574
+ restriction: str
575
+ maxLength: int # noqa
576
+ fractionDigits: int # noqa
577
+ totalDigits: int # noqa
578
+
579
+
580
+ def get_layer_metadata(layer: "main.Layer") -> List[ColumnProperties]:
581
+ """Get the metadata related to a layer."""
547
582
  cls = get_layer_class(layer, with_last_update_columns=True)
548
- edit_columns = []
583
+ edit_columns: List[ColumnProperties] = []
549
584
 
550
585
  for column_property in class_mapper(cls).iterate_properties:
551
586
  if isinstance(column_property, ColumnProperty):
552
-
553
587
  if len(column_property.columns) != 1:
554
- raise NotImplementedError # pragma: no cover
588
+ raise NotImplementedError
555
589
 
556
590
  column = column_property.columns[0]
557
591
 
558
592
  # Exclude columns that are primary keys
559
593
  if not column.primary_key:
560
- properties = _convert_column_type(column.type)
594
+ properties: ColumnProperties = _convert_column_type(column.type)
561
595
  properties["name"] = column.key
562
596
 
563
597
  if column.nullable:
@@ -586,7 +620,7 @@ def get_layer_metadatas(layer):
586
620
  return edit_columns
587
621
 
588
622
 
589
- def _convert_column_type(column_type):
623
+ def _convert_column_type(column_type: object) -> ColumnProperties:
590
624
  # SIMPLE_XSD_TYPES
591
625
  for cls, xsd_type in XSDGenerator.SIMPLE_XSD_TYPES.items():
592
626
  if isinstance(column_type, cls):
@@ -603,14 +637,13 @@ def _convert_column_type(column_type):
603
637
  return {"type": xsd_type, "srid": int(column_type.srid)}
604
638
 
605
639
  raise NotImplementedError(
606
- "The geometry type '{}' is not supported, supported types: {}".format(
607
- geometry_type, ",".join(XSDGenerator.SIMPLE_GEOMETRY_XSD_TYPES)
608
- )
609
- ) # pragma: no cover
640
+ f"The geometry type '{geometry_type}' is not supported, supported types: "
641
+ f"{','.join(XSDGenerator.SIMPLE_GEOMETRY_XSD_TYPES)}"
642
+ )
610
643
 
611
644
  # Enumeration type
612
645
  if isinstance(column_type, Enum):
613
- restriction: Dict[str, Union[str, List[str]]] = {}
646
+ restriction: ColumnProperties = {}
614
647
  restriction["restriction"] = "enumeration"
615
648
  restriction["type"] = "xsd:string"
616
649
  restriction["enumeration"] = column_type.enums
@@ -624,17 +657,17 @@ def _convert_column_type(column_type):
624
657
 
625
658
  # Numeric Type
626
659
  if isinstance(column_type, Numeric):
627
- xsd_type = {"type": "xsd:decimal"}
660
+ xsd_type2: ColumnProperties = {"type": "xsd:decimal"}
628
661
  if column_type.scale is None and column_type.precision is None:
629
- return xsd_type
662
+ return xsd_type2
630
663
 
631
664
  if column_type.scale is not None:
632
- xsd_type["fractionDigits"] = int(column_type.scale)
665
+ xsd_type2["fractionDigits"] = int(column_type.scale)
633
666
  if column_type.precision is not None:
634
- xsd_type["totalDigits"] = int(column_type.precision)
635
- return xsd_type
667
+ xsd_type2["totalDigits"] = int(column_type.precision)
668
+ return xsd_type2
636
669
 
637
670
  raise NotImplementedError(
638
- "The type '{}' is not supported, supported types: "
639
- "Geometry, Enum, String, Text, Unicode, UnicodeText, Numeric".format(type(column_type).__name__)
640
- ) # pragma: no cover
671
+ f"The type '{type(column_type).__name__}' is not supported, supported types: "
672
+ "Geometry, Enum, String, Text, Unicode, UnicodeText, Numeric"
673
+ )