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.
- c2cgeoportal_geoportal/__init__.py +261 -130
- c2cgeoportal_geoportal/lib/__init__.py +72 -120
- c2cgeoportal_geoportal/lib/authentication.py +170 -21
- c2cgeoportal_geoportal/lib/bashcolor.py +17 -13
- c2cgeoportal_geoportal/lib/cacheversion.py +19 -11
- c2cgeoportal_geoportal/lib/caching.py +66 -160
- c2cgeoportal_geoportal/lib/check_collector.py +17 -10
- c2cgeoportal_geoportal/lib/checker.py +62 -64
- c2cgeoportal_geoportal/lib/common_headers.py +170 -0
- c2cgeoportal_geoportal/lib/dbreflection.py +70 -31
- c2cgeoportal_geoportal/lib/filter_capabilities.py +124 -96
- c2cgeoportal_geoportal/lib/fulltextsearch.py +50 -0
- c2cgeoportal_geoportal/lib/functionality.py +36 -21
- c2cgeoportal_geoportal/lib/headers.py +14 -5
- c2cgeoportal_geoportal/lib/i18n.py +39 -0
- c2cgeoportal_geoportal/lib/layers.py +29 -10
- c2cgeoportal_geoportal/lib/lingua_extractor.py +408 -211
- c2cgeoportal_geoportal/lib/loader.py +18 -16
- c2cgeoportal_geoportal/lib/metrics.py +29 -18
- c2cgeoportal_geoportal/lib/oauth2.py +1036 -0
- c2cgeoportal_geoportal/lib/wmstparsing.py +115 -90
- c2cgeoportal_geoportal/lib/xsd.py +29 -19
- c2cgeoportal_geoportal/resources.py +15 -9
- c2cgeoportal_geoportal/scaffolds/advance_create/ci/config.yaml +26 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.dockerignore +6 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.eslintrc.yaml +19 -0
- c2cgeoportal_geoportal/scaffolds/{create/+dot+prospector.yaml → advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml} +8 -4
- c2cgeoportal_geoportal/scaffolds/{create/geoportal/Dockerfile_tmpl → advance_create/{{cookiecutter.project}}/geoportal/Dockerfile} +24 -15
- c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/alembic.ini +1 -0
- c2cgeoportal_geoportal/scaffolds/{create/geoportal/alembic.yaml_tmpl → advance_create/{{cookiecutter.project}}/geoportal/alembic.yaml} +1 -1
- c2cgeoportal_geoportal/scaffolds/{create/geoportal/development.ini_tmpl → advance_create/{{cookiecutter.project}}/geoportal/development.ini} +34 -15
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/gunicorn.conf.py +104 -0
- c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/lingua-client.cfg +1 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/production.ini +38 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/requirements.txt +2 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/setup.py +25 -0
- c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/tools/extract-messages.js +8 -6
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/tsconfig.json +8 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.api.js +75 -0
- c2cgeoportal_geoportal/scaffolds/{create/geoportal/webpack.apps.js_tmpl → advance_create/{{cookiecutter.project}}/geoportal/webpack.apps.js} +31 -28
- c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/webpack.commons.js +3 -7
- c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/webpack.config.js +4 -4
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/__init__.py +47 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/authentication.py +10 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/dev.py +14 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/models.py +8 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/multi_organization.py +7 -0
- c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/resources.py +4 -3
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/api/index.js +12 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/js/{{cookiecutter.package}}module.js +25 -0
- c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal/subscribers.py_tmpl → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/subscribers.py} +3 -6
- c2cgeoportal_geoportal/scaffolds/advance_update/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/{update/geoportal/CONST_Makefile_tmpl → advance_update/{{cookiecutter.project}}/geoportal/CONST_Makefile} +32 -20
- c2cgeoportal_geoportal/scaffolds/create/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.dockerignore +14 -0
- c2cgeoportal_geoportal/scaffolds/create/{+dot+editorconfig → {{cookiecutter.project}}/.editorconfig} +4 -8
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/main.yaml +43 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/rebuild.yaml +46 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/update_l10n.yaml +65 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.gitignore +16 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierignore +1 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierrc.yaml +2 -0
- c2cgeoportal_geoportal/scaffolds/create/{Dockerfile_tmpl → {{cookiecutter.project}}/Dockerfile} +34 -24
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile +14 -0
- c2cgeoportal_geoportal/scaffolds/create/{README.rst_tmpl → {{cookiecutter.project}}/README.rst} +4 -4
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build +158 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/config.yaml +25 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/requirements.txt +1 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml +474 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml +66 -0
- c2cgeoportal_geoportal/scaffolds/create/{docker-compose.yaml → {{cookiecutter.project}}/docker-compose.yaml} +43 -18
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.default +82 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.project +60 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/vars.yaml +396 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/mobile.css +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-blue.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-gold.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-green.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/data/Readme.txt +1 -1
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/demo.map.tmpl +224 -0
- c2cgeoportal_geoportal/scaffolds/create/{mapserver/mapserver.map.tmpl_tmpl → {{cookiecutter.project}}/mapserver/mapserver.map.tmpl} +7 -15
- c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A3_Landscape.jrxml +17 -9
- c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A3_Portrait.jrxml +17 -9
- c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A4_Landscape.jrxml +17 -9
- c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A4_Portrait.jrxml +17 -9
- c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/config.yaml.tmpl +30 -27
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/legend.jrxml +109 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation.properties +4 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation_fr.properties +4 -0
- c2cgeoportal_geoportal/scaffolds/create/{project.yaml_tmpl → {{cookiecutter.project}}/project.yaml} +6 -6
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml +3 -0
- c2cgeoportal_geoportal/scaffolds/create/{qgisserver/pg_service.conf.tmpl_tmpl → {{cookiecutter.project}}/qgisserver/pg_service.conf.tmpl} +2 -2
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-backup +107 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +111 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/setup.cfg +7 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/spell-ignore-words.txt +3 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tilegeneration/config.yaml.tmpl +195 -0
- c2cgeoportal_geoportal/scaffolds/update/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/.upgrade.yaml +191 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_CHANGELOG.txt +1153 -0
- c2cgeoportal_geoportal/scaffolds/update/{geoportal → {{cookiecutter.project}}/geoportal}/CONST_config-schema.yaml +99 -47
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_vars.yaml +1412 -0
- c2cgeoportal_geoportal/scripts/__init__.py +17 -33
- c2cgeoportal_geoportal/scripts/c2cupgrade.py +295 -200
- c2cgeoportal_geoportal/scripts/create_demo_theme.py +5 -6
- c2cgeoportal_geoportal/scripts/manage_users.py +34 -37
- c2cgeoportal_geoportal/scripts/pcreate.py +312 -0
- c2cgeoportal_geoportal/scripts/theme2fts.py +92 -25
- c2cgeoportal_geoportal/scripts/urllogin.py +23 -17
- c2cgeoportal_geoportal/templates/login.html +88 -84
- c2cgeoportal_geoportal/templates/notlogin.html +62 -0
- c2cgeoportal_geoportal/templates/testi18n.html +6 -8
- c2cgeoportal_geoportal/views/__init__.py +23 -4
- c2cgeoportal_geoportal/views/dev.py +9 -7
- c2cgeoportal_geoportal/views/dynamic.py +70 -40
- c2cgeoportal_geoportal/views/entry.py +93 -24
- c2cgeoportal_geoportal/views/fulltextsearch.py +36 -29
- c2cgeoportal_geoportal/views/geometry_processing.py +15 -7
- c2cgeoportal_geoportal/views/i18n.py +91 -9
- c2cgeoportal_geoportal/views/layers.py +173 -134
- c2cgeoportal_geoportal/views/login.py +206 -87
- c2cgeoportal_geoportal/views/mapserverproxy.py +59 -35
- c2cgeoportal_geoportal/views/memory.py +13 -13
- c2cgeoportal_geoportal/views/ogcproxy.py +48 -30
- c2cgeoportal_geoportal/views/pdfreport.py +31 -27
- c2cgeoportal_geoportal/views/printproxy.py +67 -53
- c2cgeoportal_geoportal/views/profile.py +25 -24
- c2cgeoportal_geoportal/views/proxy.py +97 -68
- c2cgeoportal_geoportal/views/raster.py +47 -29
- c2cgeoportal_geoportal/views/resourceproxy.py +13 -11
- c2cgeoportal_geoportal/views/shortener.py +31 -24
- c2cgeoportal_geoportal/views/theme.py +475 -365
- c2cgeoportal_geoportal/views/tinyowsproxy.py +46 -39
- c2cgeoportal_geoportal/views/vector_tiles.py +80 -0
- {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.83.dist-info}/METADATA +16 -11
- c2cgeoportal_geoportal-2.7.1.83.dist-info/RECORD +185 -0
- {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.83.dist-info}/WHEEL +1 -1
- {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.83.dist-info}/entry_points.txt +3 -1
- tests/__init__.py +24 -3
- tests/test_cachebuster.py +1 -3
- tests/test_caching.py +19 -26
- tests/test_checker.py +2 -3
- tests/test_decimaljson.py +4 -4
- tests/test_headerstween.py +0 -3
- tests/test_i18n.py +31 -0
- tests/test_init.py +12 -27
- tests/test_locale_negociator.py +6 -6
- tests/test_mapserverproxy_route_predicate.py +0 -2
- tests/test_raster.py +18 -5
- tests/test_wmstparsing.py +7 -8
- c2cgeoportal_geoportal/scaffolds/__init__.py +0 -226
- c2cgeoportal_geoportal/scaffolds/create/+dot+dockerignore_tmpl +0 -11
- c2cgeoportal_geoportal/scaffolds/create/+dot+github/workflows/ci.yaml_tmpl +0 -56
- c2cgeoportal_geoportal/scaffolds/create/+dot+gitignore_tmpl +0 -12
- c2cgeoportal_geoportal/scaffolds/create/build_tmpl +0 -144
- c2cgeoportal_geoportal/scaffolds/create/docker-compose-lib.yaml_tmpl +0 -302
- c2cgeoportal_geoportal/scaffolds/create/docker-compose.override.sample.yaml_tmpl +0 -54
- c2cgeoportal_geoportal/scaffolds/create/env.default_tmpl +0 -49
- c2cgeoportal_geoportal/scaffolds/create/env.project_tmpl +0 -39
- c2cgeoportal_geoportal/scaffolds/create/geoportal/+dot+dockerignore_tmpl +0 -5
- c2cgeoportal_geoportal/scaffolds/create/geoportal/+dot+eslintrc_tmpl +0 -19
- c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/__init__.py_tmpl +0 -48
- c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/models.py_tmpl +0 -10
- c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/static/images/favicon.ico +0 -0
- c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/static/robot.txt +0 -3
- c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/static-ngeo/api/index.js_tmpl +0 -37
- c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/static-ngeo/js/+package+module.js_tmpl +0 -22
- c2cgeoportal_geoportal/scaffolds/create/geoportal/production.ini_tmpl +0 -106
- c2cgeoportal_geoportal/scaffolds/create/geoportal/requirements.txt +0 -2
- c2cgeoportal_geoportal/scaffolds/create/geoportal/setup.py_tmpl +0 -18
- c2cgeoportal_geoportal/scaffolds/create/geoportal/tsconfig.json_tmpl +0 -9
- c2cgeoportal_geoportal/scaffolds/create/geoportal/vars.yaml_tmpl +0 -224
- c2cgeoportal_geoportal/scaffolds/create/geoportal/webpack.api.js_tmpl +0 -71
- c2cgeoportal_geoportal/scaffolds/create/mapserver/demo.map.tmpl_tmpl +0 -262
- c2cgeoportal_geoportal/scaffolds/create/mapserver/tinyows.xml +0 -36
- c2cgeoportal_geoportal/scaffolds/create/print/print-apps/+package+/config.yaml +0 -166
- c2cgeoportal_geoportal/scaffolds/create/print/print-apps/+package+/legend.jrxml +0 -27
- c2cgeoportal_geoportal/scaffolds/create/qgisserver/geomapfish.yaml.tmpl_tmpl +0 -29
- c2cgeoportal_geoportal/scaffolds/create/scripts/publish-docker +0 -124
- c2cgeoportal_geoportal/scaffolds/create/setup.cfg_tmpl +0 -3
- c2cgeoportal_geoportal/scaffolds/create/spell-ignore-words.txt +0 -1
- c2cgeoportal_geoportal/scaffolds/create/tilegeneration/config.yaml.tmpl_tmpl +0 -169
- c2cgeoportal_geoportal/scaffolds/create/yamllint.yaml +0 -11
- c2cgeoportal_geoportal/scaffolds/update/+dot+upgrade.yaml_tmpl +0 -171
- c2cgeoportal_geoportal/scaffolds/update/CONST_CHANGELOG.txt_tmpl +0 -64
- c2cgeoportal_geoportal/scaffolds/update/geoportal/CONST_vars.yaml_tmpl +0 -783
- c2cgeoportal_geoportal/templates/dynamic.js +0 -21
- c2cgeoportal_geoportal-2.5.0.100.dist-info/RECORD +0 -162
- tests/test_get_url.py +0 -96
- tests/test_lib.py +0 -77
- /c2cgeoportal_geoportal/{scaffolds/create/geoportal/+package+_geoportal/static/css/desktop.css → py.typed} +0 -0
- /c2cgeoportal_geoportal/scaffolds/{create/geoportal/Makefile_tmpl → advance_create/{{cookiecutter.project}}/geoportal/Makefile} +0 -0
- /c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/language_mapping +0 -0
- /c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/lingua-server.cfg +0 -0
- /c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/views/__init__.py +0 -0
- /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
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal/static/css/iframe_api.css → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/desktop.css} +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal/static/css/mobile.css → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/iframe_api.css} +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/banner_left.png +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/banner_right.png +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/blank.png +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/robot.txt.tmpl +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/data/TM_EUROPE_BORDERS-0.3.sql +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arial.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arialbd.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arialbi.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Ariali.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Bold.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-BoldItalic.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Italic.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Regular.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdana.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanab.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanai.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanaz.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts.conf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/tinyows.xml.tmpl +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/logo.png +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/north.svg +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/results.jrxml +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{run_alembic.sh → {{cookiecutter.project}}/run_alembic.sh} +0 -0
- {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
|
-
#
|
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
|
-
|
28
|
+
import json
|
31
29
|
import logging
|
32
30
|
import os
|
33
|
-
from
|
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,
|
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
|
-
|
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
|
-
|
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
|
-
"""
|
83
|
-
|
84
|
-
|
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
|
-
|
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
|
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
|
-
"""
|
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 {
|
112
|
-
except MultipleResultsFound:
|
113
|
-
raise HTTPInternalServerError("Too many layers found with id {
|
114
|
-
if not geo_table:
|
115
|
-
raise HTTPNotFound("Layer {
|
116
|
-
return layer
|
117
|
-
|
118
|
-
def _get_layers_for_request(self):
|
119
|
-
"""
|
120
|
-
|
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 '{
|
130
|
-
)
|
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
|
-
"""
|
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
|
-
"""
|
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
|
-
"""
|
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
|
-
"""
|
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:
|
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
|
-
|
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",
|
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",
|
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):
|
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",
|
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
|
-
|
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",
|
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
|
-
#
|
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",
|
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
|
-
#
|
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
|
-
|
385
|
-
if len(
|
386
|
-
metadata =
|
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
|
-
|
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",
|
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",
|
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:
|
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:
|
455
|
-
raise HTTPBadRequest("Unknown layer: {
|
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"]:
|
459
|
-
raise HTTPBadRequest("Unknown attribute: {
|
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:
|
478
|
+
if dbsession is None:
|
463
479
|
raise HTTPInternalServerError(
|
464
|
-
"No dbsession found for layer '{
|
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(
|
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(
|
500
|
+
return set(cast(List[Tuple[str, ...]], dbsession.query(attribute).order_by(attribute).all()))
|
483
501
|
|
484
502
|
|
485
|
-
def get_layer_class(
|
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
|
-
:
|
490
|
-
|
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
|
-
|
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
|
-
|
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
|
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:
|
602
|
-
|
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:
|
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
|
-
|
661
|
+
xsd_type2: ColumnProperties = {"type": "xsd:decimal"}
|
623
662
|
if column_type.scale is None and column_type.precision is None:
|
624
|
-
return
|
663
|
+
return xsd_type2
|
625
664
|
|
626
665
|
if column_type.scale is not None:
|
627
|
-
|
666
|
+
xsd_type2["fractionDigits"] = int(column_type.scale)
|
628
667
|
if column_type.precision is not None:
|
629
|
-
|
630
|
-
return
|
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"
|
635
|
-
)
|
672
|
+
f"The type '{type(column_type).__name__}' is not supported, supported types: "
|
673
|
+
"Geometry, Enum, String, Text, Unicode, UnicodeText, Numeric"
|
674
|
+
)
|