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) 2012-2020, Camptocamp SA
1
+ # Copyright (c) 2012-2022, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -27,15 +25,19 @@
27
25
  # of the authors and should not be interpreted as representing official policies,
28
26
  # either expressed or implied, of the FreeBSD Project.
29
27
 
30
- from datetime import datetime
28
+ import json
31
29
  import logging
32
30
  import os
33
- from typing import Dict, List, Union
31
+ from datetime import datetime
32
+ from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Set, Tuple, TypedDict, cast
34
33
 
34
+ import geojson.geometry
35
+ import pyramid.request
36
+ import pyramid.response
37
+ import sqlalchemy.ext.declarative
35
38
  from geoalchemy2 import Geometry
36
39
  from geoalchemy2 import func as ga_func
37
40
  from geoalchemy2.shape import from_shape, to_shape
38
- import geojson
39
41
  from geojson.feature import Feature, FeatureCollection
40
42
  from papyrus.protocol import Protocol, create_filter
41
43
  from papyrus.xsd import XSDGenerator
@@ -50,7 +52,7 @@ from pyramid.view import view_config
50
52
  from shapely.geometry import asShape
51
53
  from shapely.geos import TopologicalError
52
54
  from shapely.ops import cascaded_union
53
- from sqlalchemy import Enum, Numeric, String, Text, Unicode, UnicodeText, distinct, exc, func
55
+ from sqlalchemy import Enum, Numeric, String, Text, Unicode, UnicodeText, exc, func
54
56
  from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
55
57
  from sqlalchemy.orm.properties import ColumnProperty
56
58
  from sqlalchemy.orm.util import class_mapper
@@ -58,48 +60,51 @@ from sqlalchemy.sql import and_, or_
58
60
 
59
61
  from c2cgeoportal_commons import models
60
62
  from c2cgeoportal_geoportal.lib import get_roles_id
61
- from c2cgeoportal_geoportal.lib.caching import (
62
- NO_CACHE,
63
- PRIVATE_CACHE,
64
- PUBLIC_CACHE,
65
- get_region,
66
- set_common_headers,
67
- )
63
+ from c2cgeoportal_geoportal.lib.caching import get_region
64
+ from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
68
65
  from c2cgeoportal_geoportal.lib.dbreflection import _AssociationProxy, get_class, get_table
69
66
 
67
+ if TYPE_CHECKING:
68
+ from c2cgeoportal_commons.models import main # pylint: disable=ungrouped-imports.useless-suppression
70
69
  LOG = logging.getLogger(__name__)
71
70
  CACHE_REGION = get_region("std")
72
71
 
73
72
 
74
73
  class Layers:
75
- 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):
76
81
  self.request = request
77
82
  self.settings = request.registry.settings.get("layers", {})
78
83
  self.layers_enum_config = self.settings.get("enum")
79
84
 
80
85
  @staticmethod
81
- def _get_geom_col_info(layer):
82
- """ Return information about the layer's geometry column, namely
83
- a ``(name, srid)`` tuple, where ``name`` is the name of the
84
- 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.
85
92
 
86
- This function assumes that the names of geometry attributes
87
- 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.
88
95
  """
89
96
  mapped_class = get_layer_class(layer)
90
97
  for p in class_mapper(mapped_class).iterate_properties:
91
98
  if not isinstance(p, ColumnProperty):
92
- continue # pragma: no cover
99
+ continue
93
100
  col = p.columns[0]
94
101
  if isinstance(col.type, Geometry):
95
102
  return col.name, col.type.srid
96
- raise HTTPInternalServerError(
97
- 'Failed getting geometry column info for table "{0!s}".'.format(layer.geo_table)
98
- ) # pragma: no cover
103
+ raise HTTPInternalServerError(f'Failed getting geometry column info for table "{layer.geo_table!s}".')
99
104
 
100
105
  @staticmethod
101
- def _get_layer(layer_id):
102
- """ Return a ``Layer`` object for ``layer_id``. """
106
+ def _get_layer(layer_id: int) -> "main.Layer":
107
+ """Return a ``Layer`` object for ``layer_id``."""
103
108
  from c2cgeoportal_commons.models.main import Layer # pylint: disable=import-outside-toplevel
104
109
 
105
110
  layer_id = int(layer_id)
@@ -108,16 +113,19 @@ class Layers:
108
113
  query = query.filter(Layer.id == layer_id)
109
114
  layer, geo_table = query.one()
110
115
  except NoResultFound:
111
- raise HTTPNotFound("Layer {0:d} not found".format(layer_id))
112
- except MultipleResultsFound: # pragma: no cover
113
- raise HTTPInternalServerError("Too many layers found with id {0:d}".format(layer_id))
114
- if not geo_table: # pragma: no cover
115
- raise HTTPNotFound("Layer {0:d} has no geo table".format(layer_id))
116
- return layer
117
-
118
- def _get_layers_for_request(self):
119
- """ A generator function that yields ``Layer`` objects based
120
- 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
+ """
121
129
  try:
122
130
  layer_ids = (
123
131
  int(layer_id) for layer_id in self.request.matchdict["layer_id"].split(",") if layer_id
@@ -125,29 +133,27 @@ class Layers:
125
133
  for layer_id in layer_ids:
126
134
  yield self._get_layer(layer_id)
127
135
  except ValueError:
128
- raise HTTPBadRequest(
129
- "A Layer id in '{0!s}' is not an integer".format(self.request.matchdict["layer_id"])
130
- ) # 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
+ )
131
139
 
132
- def _get_layer_for_request(self):
133
- """ Return a ``Layer`` object for the first layer id found
134
- 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."""
135
142
  return next(self._get_layers_for_request())
136
143
 
137
- def _get_protocol_for_layer(self, layer, **kwargs):
138
- """ 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."""
139
146
  cls = get_layer_class(layer)
140
147
  geom_attr = self._get_geom_col_info(layer)[0]
141
148
  return Protocol(models.DBSession, cls, geom_attr, **kwargs)
142
149
 
143
- def _get_protocol_for_request(self, **kwargs):
144
- """ Returns a papyrus ``Protocol`` for the first layer
145
- 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."""
146
152
  layer = self._get_layer_for_request()
147
153
  return self._get_protocol_for_layer(layer, **kwargs)
148
154
 
149
- def _proto_read(self, layer):
150
- """ Read features for the layer based on the self.request. """
155
+ def _proto_read(self, layer: "main.Layer") -> FeatureCollection:
156
+ """Read features for the layer based on the self.request."""
151
157
  from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
152
158
  Layer,
153
159
  RestrictionArea,
@@ -174,7 +180,7 @@ class Layers:
174
180
  return proto.read(self.request)
175
181
  use_srid = srid
176
182
  collect_ra.append(to_shape(ra))
177
- if not collect_ra: # pragma: no cover
183
+ if not collect_ra:
178
184
  raise HTTPForbidden()
179
185
 
180
186
  filter1_ = create_filter(self.request, cls, geom_attr)
@@ -182,11 +188,14 @@ class Layers:
182
188
  filter2_ = ga_func.ST_Contains(from_shape(ra, use_srid), getattr(cls, geom_attr))
183
189
  filter_ = filter2_ if filter1_ is None else and_(filter1_, filter2_)
184
190
 
185
- return proto.read(self.request, filter=filter_)
191
+ feature = proto.read(self.request, filter=filter_)
192
+ if isinstance(feature, HTTPException):
193
+ raise feature # pylint: disable=raising-non-exception
194
+ return feature
186
195
 
187
- @view_config(route_name="layers_read_many", renderer="geojson")
188
- def read_many(self):
189
- 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)
190
199
 
191
200
  features = []
192
201
  for layer in self._get_layers_for_request():
@@ -196,15 +205,15 @@ class Layers:
196
205
 
197
206
  return FeatureCollection(features)
198
207
 
199
- @view_config(route_name="layers_read_one", renderer="geojson")
200
- def read_one(self):
208
+ @view_config(route_name="layers_read_one", renderer="geojson") # type: ignore
209
+ def read_one(self) -> Feature:
201
210
  from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
202
211
  Layer,
203
212
  RestrictionArea,
204
213
  Role,
205
214
  )
206
215
 
207
- set_common_headers(self.request, "layers", NO_CACHE)
216
+ set_common_headers(self.request, "layers", Cache.PRIVATE_NO)
208
217
 
209
218
  layer = self._get_layer_for_request()
210
219
  protocol = self._get_protocol_for_layer(layer)
@@ -217,7 +226,7 @@ class Layers:
217
226
  if self.request.user is None:
218
227
  raise HTTPForbidden()
219
228
  geom = feature.geometry
220
- if not geom or isinstance(geom, geojson.geometry.Default): # pragma: no cover
229
+ if not geom or isinstance(geom, geojson.geometry.Default):
221
230
  return feature
222
231
  shape = asShape(geom)
223
232
  srid = self._get_geom_col_info(layer)[1]
@@ -235,22 +244,25 @@ class Layers:
235
244
 
236
245
  return feature
237
246
 
238
- @view_config(route_name="layers_count", renderer="string")
239
- def count(self):
240
- 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)
241
250
 
242
251
  protocol = self._get_protocol_for_request()
243
- return protocol.count(self.request)
252
+ count = protocol.count(self.request)
253
+ if isinstance(count, HTTPException):
254
+ raise count
255
+ return cast(int, count)
244
256
 
245
- @view_config(route_name="layers_create", renderer="geojson")
246
- def create(self):
257
+ @view_config(route_name="layers_create", renderer="geojson") # type: ignore
258
+ def create(self) -> Optional[FeatureCollection]:
247
259
  from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
248
260
  Layer,
249
261
  RestrictionArea,
250
262
  Role,
251
263
  )
252
264
 
253
- set_common_headers(self.request, "layers", NO_CACHE)
265
+ set_common_headers(self.request, "layers", Cache.PRIVATE_NO)
254
266
 
255
267
  if self.request.user is None:
256
268
  raise HTTPForbidden()
@@ -259,7 +271,7 @@ class Layers:
259
271
 
260
272
  layer = self._get_layer_for_request()
261
273
 
262
- def check_geometry(_, feature, obj):
274
+ def check_geometry(_: Any, feature: Feature, obj: Any) -> None:
263
275
  del obj # unused
264
276
  geom = feature.geometry
265
277
  if geom and not isinstance(geom, geojson.geometry.Default):
@@ -278,7 +290,7 @@ class Layers:
278
290
  if allowed.scalar() == 0:
279
291
  raise HTTPForbidden()
280
292
 
281
- # check if geometry is valid
293
+ # Check if geometry is valid
282
294
  if self._get_validation_setting(layer):
283
295
  self._validate_geometry(spatial_elt)
284
296
 
@@ -299,15 +311,15 @@ class Layers:
299
311
  self.request.response.status_int = 400
300
312
  return {"error_type": "integrity_error", "message": str(e.orig.diag.message_primary)}
301
313
 
302
- @view_config(route_name="layers_update", renderer="geojson")
303
- def update(self):
314
+ @view_config(route_name="layers_update", renderer="geojson") # type: ignore
315
+ def update(self) -> Feature:
304
316
  from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
305
317
  Layer,
306
318
  RestrictionArea,
307
319
  Role,
308
320
  )
309
321
 
310
- set_common_headers(self.request, "layers", NO_CACHE)
322
+ set_common_headers(self.request, "layers", Cache.PRIVATE_NO)
311
323
 
312
324
  if self.request.user is None:
313
325
  raise HTTPForbidden()
@@ -317,7 +329,7 @@ class Layers:
317
329
  feature_id = self.request.matchdict.get("feature_id")
318
330
  layer = self._get_layer_for_request()
319
331
 
320
- def check_geometry(_, feature, obj):
332
+ def check_geometry(_: Any, feature: Feature, obj: Any) -> None:
321
333
  # we need both the "original" and "new" geometry to be
322
334
  # within the restriction area
323
335
  geom_attr, srid = self._get_geom_col_info(layer)
@@ -342,15 +354,17 @@ class Layers:
342
354
  if allowed.scalar() == 0:
343
355
  raise HTTPForbidden()
344
356
 
345
- # check is geometry is valid
357
+ # Check is geometry is valid
346
358
  if self._get_validation_setting(layer):
347
359
  self._validate_geometry(spatial_elt)
348
360
 
349
361
  protocol = self._get_protocol_for_layer(layer, before_update=check_geometry)
350
362
  try:
351
363
  feature = protocol.update(self.request, feature_id)
364
+ if isinstance(feature, HTTPException):
365
+ raise feature
352
366
  self._log_last_update(layer, feature)
353
- return feature
367
+ return cast(Feature, feature)
354
368
  except TopologicalError as e:
355
369
  self.request.response.status_int = 400
356
370
  return {"error_type": "validation_error", "message": str(e)}
@@ -360,17 +374,17 @@ class Layers:
360
374
  return {"error_type": "integrity_error", "message": str(e.orig.diag.message_primary)}
361
375
 
362
376
  @staticmethod
363
- def _validate_geometry(geom):
377
+ def _validate_geometry(geom: Geometry) -> None:
364
378
  if geom is not None:
365
- simple = models.DBSession.query(func.ST_IsSimple(geom)).scalar()
379
+ simple = models.DBSession.query(func.ST_IsSimple(func.ST_GeomFromEWKB(geom))).scalar()
366
380
  if not simple:
367
381
  raise TopologicalError("Not simple")
368
- valid = models.DBSession.query(func.ST_IsValid(geom)).scalar()
382
+ valid = models.DBSession.query(func.ST_IsValid(func.ST_GeomFromEWKB(geom))).scalar()
369
383
  if not valid:
370
- reason = models.DBSession.query(func.ST_IsValidReason(geom)).scalar()
384
+ reason = models.DBSession.query(func.ST_IsValidReason(func.ST_GeomFromEWKB(geom))).scalar()
371
385
  raise TopologicalError(reason)
372
386
 
373
- def _log_last_update(self, layer, feature):
387
+ def _log_last_update(self, layer: "main.Layer", feature: Feature) -> None:
374
388
  last_update_date = self.get_metadata(layer, "lastUpdateDateColumn")
375
389
  if last_update_date is not None:
376
390
  setattr(feature, last_update_date, datetime.now())
@@ -380,22 +394,22 @@ class Layers:
380
394
  setattr(feature, last_update_user, self.request.user.id)
381
395
 
382
396
  @staticmethod
383
- def get_metadata(layer, key, default=None):
384
- metadatas = layer.get_metadatas(key)
385
- if len(metadatas) == 1:
386
- metadata = metadatas[0]
387
- 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)
388
402
  return default
389
403
 
390
- def _get_validation_setting(self, layer):
404
+ def _get_validation_setting(self, layer: "main.Layer") -> bool:
391
405
  # The validation UIMetadata is stored as a string, not a boolean
392
406
  should_validate = self.get_metadata(layer, "geometryValidation", None)
393
407
  if should_validate:
394
408
  return should_validate.lower() != "false"
395
- return self.settings.get("geometry_validation", False)
409
+ return cast(bool, self.settings.get("geometry_validation", False))
396
410
 
397
- @view_config(route_name="layers_delete")
398
- def delete(self):
411
+ @view_config(route_name="layers_delete") # type: ignore
412
+ def delete(self) -> pyramid.response.Response:
399
413
  from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
400
414
  Layer,
401
415
  RestrictionArea,
@@ -408,7 +422,7 @@ class Layers:
408
422
  feature_id = self.request.matchdict.get("feature_id")
409
423
  layer = self._get_layer_for_request()
410
424
 
411
- def security_cb(_, obj):
425
+ def security_cb(_: Any, obj: Any) -> None:
412
426
  geom_attr = getattr(obj, self._get_geom_col_info(layer)[0])
413
427
  allowed = models.DBSession.query(func.count(RestrictionArea.id))
414
428
  allowed = allowed.join(RestrictionArea.roles)
@@ -424,12 +438,14 @@ class Layers:
424
438
 
425
439
  protocol = self._get_protocol_for_layer(layer, before_delete=security_cb)
426
440
  response = protocol.delete(self.request, feature_id)
427
- set_common_headers(self.request, "layers", NO_CACHE, response=response)
441
+ if isinstance(response, HTTPException):
442
+ raise response # pylint: disable=raising-non-exception
443
+ set_common_headers(self.request, "layers", Cache.PRIVATE_NO, response=response)
428
444
  return response
429
445
 
430
- @view_config(route_name="layers_metadata", renderer="xsd")
431
- def metadata(self):
432
- 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)
433
449
 
434
450
  layer = self._get_layer_for_request()
435
451
  if not layer.public and self.request.user is None:
@@ -437,38 +453,40 @@ class Layers:
437
453
 
438
454
  return get_layer_class(layer, with_last_update_columns=True)
439
455
 
440
- @view_config(route_name="layers_enumerate_attribute_values", renderer="json")
441
- def enumerate_attribute_values(self):
442
- 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)
443
459
 
444
- if self.layers_enum_config is None: # pragma: no cover
460
+ if self.layers_enum_config is None:
445
461
  raise HTTPInternalServerError("Missing configuration")
446
462
  layername = self.request.matchdict["layer_name"]
447
463
  fieldname = self.request.matchdict["field_name"]
448
464
  # TODO check if layer is public or not
449
465
 
450
- return self._enumerate_attribute_values(layername, fieldname)
466
+ return cast(Dict[str, Any], self._enumerate_attribute_values(layername, fieldname))
451
467
 
452
- @CACHE_REGION.cache_on_arguments()
453
- def _enumerate_attribute_values(self, layername, fieldname):
454
- if layername not in self.layers_enum_config: # pragma: no cover
455
- 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}")
456
472
 
457
473
  layerinfos = self.layers_enum_config[layername]
458
- if fieldname not in layerinfos["attributes"]: # pragma: no cover
459
- raise HTTPBadRequest("Unknown attribute: {0!s}".format(fieldname))
474
+ if fieldname not in layerinfos["attributes"]:
475
+ raise HTTPBadRequest(f"Unknown attribute: {fieldname!s}")
460
476
  dbsession_name = layerinfos.get("dbsession", "dbsession")
461
477
  dbsession = models.DBSessions.get(dbsession_name)
462
- if dbsession is None: # pragma: no cover
478
+ if dbsession is None:
463
479
  raise HTTPInternalServerError(
464
- "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})"
465
481
  )
466
- values = self.query_enumerate_attribute_values(dbsession, layerinfos, fieldname)
482
+ values = sorted(self.query_enumerate_attribute_values(dbsession, layerinfos, fieldname))
467
483
  enum = {"items": [{"value": value[0]} for value in values]}
468
484
  return enum
469
485
 
470
486
  @staticmethod
471
- 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, ...]]:
472
490
  attrinfos = layerinfos["attributes"][fieldname]
473
491
  table = attrinfos["table"]
474
492
  layertable = get_table(table, session=dbsession)
@@ -479,18 +497,23 @@ class Layers:
479
497
  if "separator" in attrinfos:
480
498
  separator = attrinfos["separator"]
481
499
  attribute = func.unnest(func.string_to_array(func.string_agg(attribute, separator), separator))
482
- return dbsession.query(distinct(attribute)).order_by(attribute).all()
500
+ return set(cast(List[Tuple[str, ...]], dbsession.query(attribute).order_by(attribute).all()))
483
501
 
484
502
 
485
- 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:
486
506
  """
487
- Get the SQLAlchemy class to edit a GeoMapFish layer
507
+ Get the SQLAlchemy class to edit a GeoMapFish layer.
488
508
 
489
- :param layer:
490
- :param with_last_update_columns: False to just have a class to access to the table and be able to
509
+ Arguments:
510
+
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
491
513
  modify the last_update_columns, True to have a correct class to build the UI
492
514
  (without the hidden column).
493
- :return: SQLAlchemy class
515
+
516
+ Returns: SQLAlchemy class
494
517
  """
495
518
  # Exclude the columns used to record the last features update
496
519
  exclude = [] if layer.exclude_properties is None else layer.exclude_properties.split(",")
@@ -501,13 +524,13 @@ def get_layer_class(layer, with_last_update_columns=False):
501
524
  last_update_user = Layers.get_metadata(layer, "lastUpdateUserColumn")
502
525
  if last_update_user is not None:
503
526
  exclude.append(last_update_user)
504
- else:
505
- exclude = []
506
527
 
507
528
  m = Layers.get_metadata(layer, "editingAttributesOrder")
508
529
  attributes_order = m.split(",") if m else None
509
530
  m = Layers.get_metadata(layer, "readonlyAttributes")
510
531
  readonly_attributes = m.split(",") if m else None
532
+ m = Layers.get_metadata(layer, "editingEnumerations")
533
+ enumerations_config = json.loads(m) if m else None
511
534
 
512
535
  primary_key = Layers.get_metadata(layer, "geotablePrimaryKey")
513
536
  cls = get_class(
@@ -515,11 +538,13 @@ def get_layer_class(layer, with_last_update_columns=False):
515
538
  exclude_properties=exclude,
516
539
  primary_key=primary_key,
517
540
  attributes_order=attributes_order,
541
+ enumerations_config=enumerations_config,
518
542
  readonly_attributes=readonly_attributes,
519
543
  )
520
544
 
521
545
  mapper = class_mapper(cls)
522
546
  column_properties = [p.key for p in mapper.iterate_properties if isinstance(p, ColumnProperty)]
547
+
523
548
  for attribute_name in attributes_order or []:
524
549
  if attribute_name not in column_properties:
525
550
  table = mapper.mapped_table
@@ -538,21 +563,36 @@ def get_layer_class(layer, with_last_update_columns=False):
538
563
  return cls
539
564
 
540
565
 
541
- 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."""
542
582
  cls = get_layer_class(layer, with_last_update_columns=True)
543
- edit_columns = []
583
+ edit_columns: List[ColumnProperties] = []
544
584
 
545
585
  for column_property in class_mapper(cls).iterate_properties:
546
586
  if isinstance(column_property, ColumnProperty):
547
587
 
548
588
  if len(column_property.columns) != 1:
549
- raise NotImplementedError # pragma: no cover
589
+ raise NotImplementedError
550
590
 
551
591
  column = column_property.columns[0]
552
592
 
553
593
  # Exclude columns that are primary keys
554
594
  if not column.primary_key:
555
- properties = _convert_column_type(column.type)
595
+ properties: ColumnProperties = _convert_column_type(column.type)
556
596
  properties["name"] = column.key
557
597
 
558
598
  if column.nullable:
@@ -581,7 +621,7 @@ def get_layer_metadatas(layer):
581
621
  return edit_columns
582
622
 
583
623
 
584
- def _convert_column_type(column_type):
624
+ def _convert_column_type(column_type: object) -> ColumnProperties:
585
625
  # SIMPLE_XSD_TYPES
586
626
  for cls, xsd_type in XSDGenerator.SIMPLE_XSD_TYPES.items():
587
627
  if isinstance(column_type, cls):
@@ -598,14 +638,13 @@ def _convert_column_type(column_type):
598
638
  return {"type": xsd_type, "srid": int(column_type.srid)}
599
639
 
600
640
  raise NotImplementedError(
601
- "The geometry type '{}' is not supported, supported types: {}".format(
602
- geometry_type, ",".join(XSDGenerator.SIMPLE_GEOMETRY_XSD_TYPES)
603
- )
604
- ) # pragma: no cover
641
+ f"The geometry type '{geometry_type}' is not supported, supported types: "
642
+ f"{','.join(XSDGenerator.SIMPLE_GEOMETRY_XSD_TYPES)}"
643
+ )
605
644
 
606
645
  # Enumeration type
607
646
  if isinstance(column_type, Enum):
608
- restriction: Dict[str, Union[str, List[str]]] = {}
647
+ restriction: ColumnProperties = {}
609
648
  restriction["restriction"] = "enumeration"
610
649
  restriction["type"] = "xsd:string"
611
650
  restriction["enumeration"] = column_type.enums
@@ -619,17 +658,17 @@ def _convert_column_type(column_type):
619
658
 
620
659
  # Numeric Type
621
660
  if isinstance(column_type, Numeric):
622
- xsd_type = {"type": "xsd:decimal"}
661
+ xsd_type2: ColumnProperties = {"type": "xsd:decimal"}
623
662
  if column_type.scale is None and column_type.precision is None:
624
- return xsd_type
663
+ return xsd_type2
625
664
 
626
665
  if column_type.scale is not None:
627
- xsd_type["fractionDigits"] = int(column_type.scale)
666
+ xsd_type2["fractionDigits"] = int(column_type.scale)
628
667
  if column_type.precision is not None:
629
- xsd_type["totalDigits"] = int(column_type.precision)
630
- return xsd_type
668
+ xsd_type2["totalDigits"] = int(column_type.precision)
669
+ return xsd_type2
631
670
 
632
671
  raise NotImplementedError(
633
- "The type '{}' is not supported, supported types: "
634
- "Geometry, Enum, String, Text, Unicode, UnicodeText, Numeric".format(type(column_type).__name__)
635
- ) # pragma: no cover
672
+ f"The type '{type(column_type).__name__}' is not supported, supported types: "
673
+ "Geometry, Enum, String, Text, Unicode, UnicodeText, Numeric"
674
+ )