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,6 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
|
3
|
-
# Copyright (c) 2011-
|
3
|
+
# Copyright (c) 2011-2022, Camptocamp SA
|
4
4
|
# All rights reserved.
|
5
5
|
|
6
6
|
# Redistribution and use in source and binary forms, with or without
|
@@ -30,64 +30,61 @@
|
|
30
30
|
|
31
31
|
import logging
|
32
32
|
import sys
|
33
|
-
from typing import Dict, List, Union
|
34
|
-
import urllib.parse
|
33
|
+
from typing import Any, Dict, List, Optional, Union
|
35
34
|
|
36
|
-
|
37
|
-
|
35
|
+
import pyramid.request
|
36
|
+
import pyramid.response
|
38
37
|
import requests
|
38
|
+
from pyramid.httpexceptions import HTTPBadGateway, exception_response
|
39
39
|
|
40
|
-
from
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
get_region,
|
45
|
-
set_common_headers,
|
46
|
-
)
|
40
|
+
from c2cgeoportal_commons.lib.url import Url
|
41
|
+
from c2cgeoportal_geoportal.lib.caching import get_region
|
42
|
+
from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
|
43
|
+
from c2cgeoportal_geoportal.views import restrict_headers
|
47
44
|
|
48
45
|
LOG = logging.getLogger(__name__)
|
49
46
|
CACHE_REGION = get_region("std")
|
50
47
|
|
51
48
|
|
52
49
|
class Proxy:
|
53
|
-
|
50
|
+
"""Some methodes used by all the proxy."""
|
51
|
+
|
52
|
+
def __init__(self, request: pyramid.request.Request):
|
54
53
|
self.request = request
|
55
|
-
self.host_forward_host = request.registry.settings
|
54
|
+
self.host_forward_host = request.registry.settings.get("host_forward_host", [])
|
55
|
+
self.headers_whitelist = request.registry.settings.get("headers_whitelist", [])
|
56
|
+
self.headers_blacklist = request.registry.settings.get("headers_blacklist", [])
|
56
57
|
self.http_options = self.request.registry.settings.get("http_options", {})
|
57
58
|
|
58
|
-
def _proxy(
|
59
|
-
|
59
|
+
def _proxy(
|
60
|
+
self,
|
61
|
+
url: Url,
|
62
|
+
params: Optional[Dict[str, str]] = None,
|
63
|
+
method: Optional[str] = None,
|
64
|
+
cache: bool = False,
|
65
|
+
body: Optional[bytes] = None,
|
66
|
+
headers: Optional[Dict[str, str]] = None,
|
67
|
+
) -> requests.models.Response:
|
68
|
+
# Get query string
|
60
69
|
params = dict(self.request.params) if params is None else params
|
61
|
-
|
62
|
-
url_params = urllib.parse.parse_qs(parsed_url.query)
|
63
|
-
all_params: Dict[str, Union[str]] = {}
|
64
|
-
all_params.update(params)
|
65
|
-
all_params.update({k: ",".join(v) for k, v in url_params.items()})
|
66
|
-
query_string = urllib.parse.urlencode(all_params)
|
67
|
-
|
68
|
-
if parsed_url.port is None:
|
69
|
-
url = "{}://{}{}?{}".format(parsed_url.scheme, parsed_url.hostname, parsed_url.path, query_string)
|
70
|
-
else: # pragma: no cover
|
71
|
-
url = "{}://{}:{:d}{}?{}".format(
|
72
|
-
parsed_url.scheme, parsed_url.hostname, parsed_url.port, parsed_url.path, query_string
|
73
|
-
)
|
70
|
+
url = url.clone().add_query(params, True)
|
74
71
|
|
75
72
|
LOG.debug("Send query to URL:\n%s.", url)
|
76
73
|
|
77
74
|
if method is None:
|
78
75
|
method = self.request.method
|
79
76
|
|
80
|
-
if headers is None
|
81
|
-
headers = dict(self.request.headers)
|
77
|
+
headers = dict(self.request.headers if headers is None else headers)
|
82
78
|
|
83
|
-
# Forward request to target (without Host Header)
|
84
|
-
|
79
|
+
# Forward request to target (without Host Header).
|
80
|
+
# The original Host will be added back by pyramid.
|
81
|
+
if url.hostname not in self.host_forward_host and "Host" in headers:
|
85
82
|
headers.pop("Host")
|
86
83
|
|
87
84
|
# Forward the request tracking ID to the other service. This will allow to follow the logs belonging
|
88
85
|
# to a single request coming from the user
|
89
86
|
headers.setdefault("X-Request-ID", self.request.c2c_request_id)
|
90
|
-
# If we
|
87
|
+
# If we really want to respect the specification, we should chain with the content of the previous
|
91
88
|
# proxy, see also:
|
92
89
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded
|
93
90
|
forwarded = {"for": self.request.client_addr, "proto": self.request.scheme}
|
@@ -98,34 +95,52 @@ class Proxy:
|
|
98
95
|
headers["Forwarded"] = ",".join([headers["Forwarded"], forwarded_str])
|
99
96
|
else:
|
100
97
|
headers["Forwarded"] = forwarded_str
|
98
|
+
# Set alternative "X-Forwarded" headers
|
99
|
+
for forwarded_elements in reversed(headers["Forwarded"].split(",")):
|
100
|
+
for element in forwarded_elements.split(";"):
|
101
|
+
key, value = element.split("=")
|
102
|
+
header_key = f"X-Forwarded-{key.capitalize()}"
|
103
|
+
header_value = headers.get(header_key)
|
104
|
+
headers[header_key] = value if header_value is None else ", ".join([header_value, value])
|
101
105
|
|
102
106
|
if not cache:
|
103
107
|
headers["Cache-Control"] = "no-cache"
|
104
108
|
|
105
|
-
if method in ("POST", "PUT") and body is None:
|
109
|
+
if method in ("POST", "PUT") and body is None:
|
106
110
|
body = self.request.body
|
107
111
|
|
112
|
+
headers = restrict_headers(headers, self.headers_whitelist, self.headers_blacklist)
|
113
|
+
|
108
114
|
try:
|
109
115
|
if method in ("POST", "PUT"):
|
110
|
-
response = requests.request(
|
116
|
+
response = requests.request(
|
117
|
+
method, url.url(), data=body, headers=headers, **self.http_options
|
118
|
+
)
|
111
119
|
else:
|
112
|
-
response = requests.request(method, url, headers=headers, **self.http_options)
|
113
|
-
except Exception:
|
120
|
+
response = requests.request(method, url.url(), headers=headers, **self.http_options)
|
121
|
+
except Exception:
|
114
122
|
errors = ["Error '%s' while getting the URL:", "%s", "Method: %s", "--- With headers ---", "%s"]
|
115
123
|
args1 = [
|
116
124
|
sys.exc_info()[0],
|
117
125
|
url,
|
118
126
|
method,
|
119
|
-
"\n".join(
|
127
|
+
"\n".join(
|
128
|
+
[
|
129
|
+
f"{h}: {v if h not in ('Authorization', 'Cookies') else '***'}"
|
130
|
+
for h, v in list(headers.items())
|
131
|
+
]
|
132
|
+
),
|
120
133
|
]
|
121
|
-
if method in ("POST", "PUT"):
|
134
|
+
if method in ("POST", "PUT") and body is not None:
|
122
135
|
errors += ["--- Query with body ---", "%s"]
|
123
136
|
args1.append(body.decode("utf-8"))
|
124
|
-
LOG.
|
137
|
+
LOG.exception("\n".join(errors), *args1)
|
125
138
|
|
126
|
-
raise HTTPBadGateway(
|
139
|
+
raise HTTPBadGateway( # pylint: disable=raise-missing-from
|
140
|
+
"Error on backend, See logs for detail"
|
141
|
+
)
|
127
142
|
|
128
|
-
if not response.ok:
|
143
|
+
if not response.ok:
|
129
144
|
errors = [
|
130
145
|
"Error '%s' in response of URL:",
|
131
146
|
"%s",
|
@@ -136,12 +151,17 @@ class Proxy:
|
|
136
151
|
]
|
137
152
|
args2: List[Union[str, int]] = [
|
138
153
|
response.reason,
|
139
|
-
url,
|
154
|
+
url.url(),
|
140
155
|
response.status_code,
|
141
156
|
method,
|
142
|
-
"\n".join(
|
157
|
+
"\n".join(
|
158
|
+
[
|
159
|
+
f"{h}: {v if h not in ('Authorization', 'Cookies') else '***'}"
|
160
|
+
for h, v in list(headers.items())
|
161
|
+
]
|
162
|
+
),
|
143
163
|
]
|
144
|
-
if method in ("POST", "PUT"):
|
164
|
+
if method in ("POST", "PUT") and body is not None:
|
145
165
|
errors += ["--- Query with body ---", "%s"]
|
146
166
|
args2.append(body.decode("utf-8"))
|
147
167
|
errors += ["--- Return content ---", "%s"]
|
@@ -149,19 +169,26 @@ class Proxy:
|
|
149
169
|
LOG.error("\n".join(errors), *args2)
|
150
170
|
|
151
171
|
raise exception_response(response.status_code)
|
172
|
+
if not response.headers.get("Content-Type", "").startswith("image/"):
|
173
|
+
LOG.debug("Get result for URL: %s:\n%s.", url, body)
|
152
174
|
|
153
175
|
return response
|
154
176
|
|
155
|
-
@CACHE_REGION.cache_on_arguments()
|
156
|
-
def _proxy_cache(self, method, *args, **kwargs
|
177
|
+
@CACHE_REGION.cache_on_arguments() # type: ignore
|
178
|
+
def _proxy_cache(self, method: str, *args: Any, **kwargs: Any) -> pyramid.response.Response:
|
157
179
|
# Method is only for the cache
|
158
180
|
del method
|
159
181
|
kwargs["cache"] = True
|
160
182
|
return self._proxy(*args, **kwargs)
|
161
183
|
|
162
184
|
def _proxy_response(
|
163
|
-
self,
|
164
|
-
|
185
|
+
self,
|
186
|
+
service_name: str,
|
187
|
+
url: Url,
|
188
|
+
headers_update: Optional[Dict[str, str]] = None,
|
189
|
+
public: bool = False,
|
190
|
+
**kwargs: Any,
|
191
|
+
) -> pyramid.response.Response:
|
165
192
|
if headers_update is None:
|
166
193
|
headers_update = {}
|
167
194
|
cache = kwargs.get("cache", False)
|
@@ -170,23 +197,25 @@ class Proxy:
|
|
170
197
|
else:
|
171
198
|
response = self._proxy(url, **kwargs)
|
172
199
|
|
173
|
-
cache_control = (
|
200
|
+
cache_control = (
|
201
|
+
(Cache.PUBLIC if public else Cache.PRIVATE)
|
202
|
+
if cache
|
203
|
+
else (Cache.PUBLIC_NO if public else Cache.PRIVATE_NO)
|
204
|
+
)
|
174
205
|
return self._build_response(
|
175
206
|
response, response.content, cache_control, service_name, headers_update=headers_update
|
176
207
|
)
|
177
208
|
|
178
209
|
def _build_response(
|
179
210
|
self,
|
180
|
-
response,
|
181
|
-
content,
|
182
|
-
cache_control,
|
183
|
-
service_name,
|
184
|
-
headers=None,
|
185
|
-
headers_update=None,
|
186
|
-
content_type=None,
|
187
|
-
):
|
188
|
-
if isinstance(content, str):
|
189
|
-
content = content.encode("utf-8")
|
211
|
+
response: pyramid.response.Response,
|
212
|
+
content: bytes,
|
213
|
+
cache_control: Cache,
|
214
|
+
service_name: str,
|
215
|
+
headers: Optional[Dict[str, str]] = None,
|
216
|
+
headers_update: Optional[Dict[str, str]] = None,
|
217
|
+
content_type: Optional[str] = None,
|
218
|
+
) -> pyramid.response.Response:
|
190
219
|
if headers_update is None:
|
191
220
|
headers_update = {}
|
192
221
|
headers = response.headers if headers is None else headers
|
@@ -204,28 +233,28 @@ class Proxy:
|
|
204
233
|
"Trailers",
|
205
234
|
"Transfer-Encoding",
|
206
235
|
"Upgrade",
|
207
|
-
]:
|
236
|
+
]:
|
208
237
|
if header in headers:
|
209
238
|
del headers[header]
|
210
239
|
# Other problematic headers
|
211
|
-
for header in ["Content-Length", "Content-Location", "Content-Encoding"]:
|
240
|
+
for header in ["Content-Length", "Content-Location", "Content-Encoding"]:
|
212
241
|
if header in headers:
|
213
242
|
del headers[header]
|
214
243
|
|
215
244
|
headers.update(headers_update)
|
216
245
|
|
217
|
-
response = Response(content, status=response.status_code, headers=headers)
|
246
|
+
response = pyramid.response.Response(content, status=response.status_code, headers=headers)
|
218
247
|
|
219
248
|
return set_common_headers(
|
220
249
|
self.request, service_name, cache_control, response=response, content_type=content_type
|
221
250
|
)
|
222
251
|
|
223
252
|
@staticmethod
|
224
|
-
def _get_lower_params(params):
|
253
|
+
def _get_lower_params(params: Dict[str, str]) -> Dict[str, str]:
|
225
254
|
return dict((k.lower(), str(v).lower()) for k, v in params.items())
|
226
255
|
|
227
|
-
def
|
228
|
-
headers = self.request.headers
|
229
|
-
if "Cookie" in headers:
|
256
|
+
def get_headers(self) -> Dict[str, str]:
|
257
|
+
headers: Dict[str, str] = self.request.headers
|
258
|
+
if "Cookie" in headers:
|
230
259
|
headers.pop("Cookie")
|
231
260
|
return headers
|
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Copyright (c) 2012-2019, Camptocamp SA
|
1
|
+
# Copyright (c) 2012-2021, Camptocamp SA
|
4
2
|
# All rights reserved.
|
5
3
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -28,31 +26,40 @@
|
|
28
26
|
# either expressed or implied, of the FreeBSD Project.
|
29
27
|
|
30
28
|
|
31
|
-
|
29
|
+
import decimal
|
32
30
|
import logging
|
33
31
|
import math
|
34
32
|
import os
|
35
|
-
|
33
|
+
import traceback
|
34
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple
|
36
35
|
|
36
|
+
import numpy
|
37
|
+
import pyramid.request
|
38
|
+
import zope.event.classhandler
|
37
39
|
from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound
|
38
40
|
from pyramid.view import view_config
|
39
|
-
import
|
41
|
+
from rasterio.io import DatasetReader
|
40
42
|
|
41
43
|
from c2cgeoportal_commons.models import InvalidateCacheEvent
|
42
|
-
from c2cgeoportal_geoportal.lib.
|
44
|
+
from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
|
45
|
+
|
46
|
+
if TYPE_CHECKING:
|
47
|
+
import fiona.collection
|
43
48
|
|
44
49
|
LOG = logging.getLogger(__name__)
|
45
50
|
|
46
51
|
|
47
52
|
class Raster:
|
48
|
-
|
53
|
+
"""All the view concerned the raster (point, not the profile profile)."""
|
49
54
|
|
50
|
-
|
55
|
+
data: Dict[str, "fiona.collection.Collection"] = {}
|
56
|
+
|
57
|
+
def __init__(self, request: pyramid.request.Request):
|
51
58
|
self.request = request
|
52
59
|
self.rasters = self.request.registry.settings["raster"]
|
53
60
|
|
54
|
-
@zope.event.classhandler.handler(InvalidateCacheEvent)
|
55
|
-
def handle(event: InvalidateCacheEvent)
|
61
|
+
@zope.event.classhandler.handler(InvalidateCacheEvent) # type: ignore
|
62
|
+
def handle(event: InvalidateCacheEvent) -> None:
|
56
63
|
del event
|
57
64
|
for _, v in Raster.data.items():
|
58
65
|
v.close()
|
@@ -60,21 +67,21 @@ class Raster:
|
|
60
67
|
|
61
68
|
def _get_required_finite_float_param(self, name: str) -> float:
|
62
69
|
if name not in self.request.params:
|
63
|
-
raise HTTPBadRequest("'{}' should be in the query string parameters"
|
70
|
+
raise HTTPBadRequest(f"'{name}' should be in the query string parameters")
|
64
71
|
try:
|
65
72
|
result = float(self.request.params[name])
|
66
73
|
except ValueError:
|
67
|
-
raise HTTPBadRequest(
|
68
|
-
"'{}' ({}) parameters should be a number"
|
74
|
+
raise HTTPBadRequest( # pylint: disable=raise-missing-from
|
75
|
+
f"'{name}' ({self.request.params[name]}) parameters should be a number"
|
69
76
|
)
|
70
77
|
if not math.isfinite(result):
|
71
78
|
raise HTTPBadRequest(
|
72
|
-
"'{}' ({}) parameters should be a finite number"
|
79
|
+
f"'{name}' ({self.request.params[name]}) parameters should be a finite number"
|
73
80
|
)
|
74
81
|
return result
|
75
82
|
|
76
|
-
@view_config(route_name="raster", renderer="fast_json")
|
77
|
-
def raster(self):
|
83
|
+
@view_config(route_name="raster", renderer="fast_json") # type: ignore
|
84
|
+
def raster(self) -> Dict[str, Any]:
|
78
85
|
lon = self._get_required_finite_float_param("lon")
|
79
86
|
lat = self._get_required_finite_float_param("lat")
|
80
87
|
|
@@ -85,7 +92,7 @@ class Raster:
|
|
85
92
|
if layer in self.rasters:
|
86
93
|
rasters[layer] = self.rasters[layer]
|
87
94
|
else:
|
88
|
-
raise HTTPNotFound("Layer {} not found"
|
95
|
+
raise HTTPNotFound(f"Layer {layer} not found")
|
89
96
|
else:
|
90
97
|
rasters = self.rasters
|
91
98
|
|
@@ -93,10 +100,10 @@ class Raster:
|
|
93
100
|
for ref in list(rasters.keys()):
|
94
101
|
result[ref] = self._get_raster_value(rasters[ref], ref, lon, lat)
|
95
102
|
|
96
|
-
set_common_headers(self.request, "raster",
|
103
|
+
set_common_headers(self.request, "raster", Cache.PUBLIC_NO)
|
97
104
|
return result
|
98
105
|
|
99
|
-
def _get_data(self, layer, name):
|
106
|
+
def _get_data(self, layer: Dict[str, Any], name: str) -> "fiona.collection.Collection":
|
100
107
|
if name not in self.data:
|
101
108
|
path = layer["file"]
|
102
109
|
if layer.get("type", "shp_index") == "shp_index":
|
@@ -112,7 +119,9 @@ class Raster:
|
|
112
119
|
|
113
120
|
return self.data[name]
|
114
121
|
|
115
|
-
def _get_raster_value(
|
122
|
+
def _get_raster_value(
|
123
|
+
self, layer: Dict[str, Any], name: str, lon: float, lat: float
|
124
|
+
) -> Optional[decimal.Decimal]:
|
116
125
|
data = self._get_data(layer, name)
|
117
126
|
type_ = layer.get("type", "shp_index")
|
118
127
|
if type_ == "shp_index":
|
@@ -133,21 +142,25 @@ class Raster:
|
|
133
142
|
else:
|
134
143
|
raise ValueError("Unsupported type " + type_)
|
135
144
|
|
136
|
-
|
137
|
-
|
145
|
+
result_d = None
|
146
|
+
if "round" in layer and result is not None:
|
147
|
+
result_d = self._round(result, layer["round"])
|
138
148
|
elif result is not None:
|
139
|
-
|
149
|
+
result_d = decimal.Decimal(str(result))
|
140
150
|
|
141
|
-
return
|
151
|
+
return result_d
|
142
152
|
|
143
153
|
@staticmethod
|
144
|
-
def _get_value(
|
154
|
+
def _get_value(
|
155
|
+
layer: Dict[str, Any], name: str, dataset: DatasetReader, lon: float, lat: float
|
156
|
+
) -> Optional[numpy.float32]:
|
145
157
|
index = dataset.index(lon, lat)
|
146
158
|
|
147
159
|
shape = dataset.shape
|
160
|
+
result: Optional[numpy.float32]
|
148
161
|
if 0 <= index[0] < shape[0] and 0 <= index[1] < shape[1]:
|
149
162
|
|
150
|
-
def get_index(index_):
|
163
|
+
def get_index(index_: int) -> Tuple[int, int]:
|
151
164
|
return index_, index_ + 1
|
152
165
|
|
153
166
|
result = dataset.read(1, window=(get_index(index[0]), get_index(index[1])))[0][0]
|
@@ -169,7 +182,12 @@ class Raster:
|
|
169
182
|
return result
|
170
183
|
|
171
184
|
@staticmethod
|
172
|
-
def _round(value, round_to):
|
185
|
+
def _round(value: numpy.float32, round_to: float) -> Optional[decimal.Decimal]:
|
173
186
|
if value is not None:
|
174
|
-
|
187
|
+
decimal_value = decimal.Decimal(str(value))
|
188
|
+
try:
|
189
|
+
return decimal_value.quantize(decimal.Decimal(str(round_to)))
|
190
|
+
except decimal.InvalidOperation:
|
191
|
+
LOG.info("Error on rounding %s: %s", decimal_value, traceback.format_exc())
|
192
|
+
return decimal_value
|
175
193
|
return None
|
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Copyright (c) 2011-2019, Camptocamp SA
|
1
|
+
# Copyright (c) 2011-2021, Camptocamp SA
|
4
2
|
# All rights reserved.
|
5
3
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -31,41 +29,45 @@
|
|
31
29
|
import ast
|
32
30
|
import logging
|
33
31
|
|
32
|
+
import pyramid.request
|
33
|
+
import pyramid.response
|
34
34
|
from pyramid.httpexceptions import HTTPBadRequest
|
35
35
|
from pyramid.view import view_config
|
36
36
|
|
37
|
-
from c2cgeoportal_geoportal.lib.
|
37
|
+
from c2cgeoportal_geoportal.lib.common_headers import Cache
|
38
38
|
from c2cgeoportal_geoportal.views.proxy import Proxy
|
39
39
|
|
40
40
|
LOG = logging.getLogger(__name__)
|
41
41
|
|
42
42
|
|
43
43
|
class ResourceProxy(Proxy):
|
44
|
-
|
44
|
+
"""All the views concerned the resources (it's a kind of proxy)."""
|
45
|
+
|
46
|
+
def __init__(self, request: pyramid.request.Request):
|
45
47
|
Proxy.__init__(self, request)
|
46
48
|
self.request = request
|
47
49
|
self.settings = request.registry.settings.get("resourceproxy", {})
|
48
50
|
|
49
|
-
@view_config(route_name="resourceproxy")
|
50
|
-
def proxy(self)
|
51
|
+
@view_config(route_name="resourceproxy") # type: ignore
|
52
|
+
def proxy(self) -> pyramid.response.Response:
|
51
53
|
target = self.request.params.get("target", "")
|
52
54
|
targets = self.settings.get("targets", [])
|
53
|
-
if target in targets:
|
55
|
+
if target in targets:
|
54
56
|
url = targets[target]
|
55
57
|
values = ast.literal_eval(self.request.params.get("values"))
|
56
58
|
url = url % values
|
57
59
|
|
58
60
|
response = self._proxy(url=url)
|
59
61
|
|
60
|
-
cache_control =
|
62
|
+
cache_control = Cache.PRIVATE_NO
|
61
63
|
content_type = response.headers["Content-Type"]
|
62
64
|
|
63
65
|
response = self._build_response(
|
64
|
-
response, response.
|
66
|
+
response, response.content, cache_control, "externalresource", content_type=content_type
|
65
67
|
)
|
66
68
|
for header in response.headers.keys():
|
67
69
|
if header not in self.settings["allowed_headers"]:
|
68
70
|
response.headers.pop(header)
|
69
71
|
return response
|
70
|
-
LOG.warning("Target
|
72
|
+
LOG.warning("Target URL not found: %s", target)
|
71
73
|
return HTTPBadRequest("URL not allowed")
|
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Copyright (c) 2013-2019, Camptocamp SA
|
1
|
+
# Copyright (c) 2013-2022, Camptocamp SA
|
4
2
|
# All rights reserved.
|
5
3
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -28,47 +26,51 @@
|
|
28
26
|
# either expressed or implied, of the FreeBSD Project.
|
29
27
|
|
30
28
|
|
31
|
-
from datetime import datetime
|
32
29
|
import logging
|
33
30
|
import random
|
34
31
|
import string
|
32
|
+
from datetime import datetime
|
33
|
+
from typing import Dict
|
35
34
|
from urllib.parse import urlparse
|
36
35
|
|
36
|
+
import pyramid.request
|
37
37
|
from pyramid.httpexceptions import HTTPBadRequest, HTTPFound, HTTPInternalServerError, HTTPNotFound
|
38
38
|
from pyramid.view import view_config
|
39
39
|
|
40
40
|
from c2cgeoportal_commons.lib.email_ import send_email_config
|
41
41
|
from c2cgeoportal_commons.models import DBSession
|
42
42
|
from c2cgeoportal_commons.models.static import Shorturl
|
43
|
-
from c2cgeoportal_geoportal.lib.
|
43
|
+
from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
|
44
44
|
|
45
45
|
logger = logging.getLogger(__name__)
|
46
46
|
|
47
47
|
|
48
48
|
class Shortener:
|
49
|
-
|
49
|
+
"""All the views conserne the shortener."""
|
50
|
+
|
51
|
+
def __init__(self, request: pyramid.request.Request):
|
50
52
|
self.request = request
|
51
53
|
self.settings = request.registry.settings.get("shortener", {})
|
52
54
|
self.short_bases = [self.request.route_url("shortener_get", ref="")]
|
53
55
|
if "base_url" in self.settings:
|
54
56
|
self.short_bases.append(self.settings["base_url"])
|
55
57
|
|
56
|
-
@view_config(route_name="shortener_get")
|
57
|
-
def get(self):
|
58
|
+
@view_config(route_name="shortener_get") # type: ignore
|
59
|
+
def get(self) -> HTTPFound:
|
58
60
|
ref = self.request.matchdict["ref"]
|
59
61
|
short_urls = DBSession.query(Shorturl).filter(Shorturl.ref == ref).all()
|
60
62
|
|
61
63
|
if len(short_urls) != 1:
|
62
|
-
raise HTTPNotFound("Ref '{
|
64
|
+
raise HTTPNotFound(f"Ref '{ref!s}' not found")
|
63
65
|
|
64
66
|
short_urls[0].nb_hits += 1
|
65
67
|
short_urls[0].last_hit = datetime.now()
|
66
68
|
|
67
|
-
set_common_headers(self.request, "shortener",
|
69
|
+
set_common_headers(self.request, "shortener", Cache.PUBLIC_NO)
|
68
70
|
return HTTPFound(location=short_urls[0].url)
|
69
71
|
|
70
|
-
@view_config(route_name="shortener_create", renderer="json")
|
71
|
-
def create(self):
|
72
|
+
@view_config(route_name="shortener_create", renderer="json") # type: ignore
|
73
|
+
def create(self) -> Dict[str, str]:
|
72
74
|
|
73
75
|
if "url" not in self.request.params:
|
74
76
|
raise HTTPBadRequest("The parameter url is required")
|
@@ -76,19 +78,22 @@ class Shortener:
|
|
76
78
|
url = self.request.params["url"]
|
77
79
|
|
78
80
|
# see: https://httpd.apache.org/docs/2.2/mod/core.html#limitrequestline
|
79
|
-
if len(url) > 8190:
|
80
|
-
raise HTTPBadRequest("The parameter url is too long ({} > {})"
|
81
|
+
if len(url) > 8190:
|
82
|
+
raise HTTPBadRequest(f"The parameter url is too long ({len(url)} > {8190})")
|
81
83
|
|
82
84
|
# Check that it is an internal URL...
|
83
85
|
uri_parts = urlparse(url)
|
84
|
-
hostname = uri_parts.hostname
|
85
86
|
if "allowed_hosts" in self.settings:
|
86
|
-
if
|
87
|
-
raise HTTPBadRequest(
|
87
|
+
if uri_parts.netloc not in self.settings["allowed_hosts"]:
|
88
|
+
raise HTTPBadRequest(
|
89
|
+
f"The requested host '{uri_parts.netloc}' is not part of allowed hosts: "
|
90
|
+
f"{', '.join(self.settings['allowed_hosts'])}"
|
91
|
+
)
|
88
92
|
else:
|
93
|
+
hostname = uri_parts.hostname
|
89
94
|
if hostname != self.request.server_name:
|
90
95
|
raise HTTPBadRequest(
|
91
|
-
"The requested host '{
|
96
|
+
f"The requested host '{hostname!s}' should be '{self.request.server_name!s}'"
|
92
97
|
)
|
93
98
|
|
94
99
|
shortened = False
|
@@ -102,16 +107,16 @@ class Shortener:
|
|
102
107
|
tries = 0
|
103
108
|
while not shortened:
|
104
109
|
ref = "".join(
|
105
|
-
random.choice(string.ascii_letters + string.digits)
|
110
|
+
random.choice(string.ascii_letters + string.digits) # nosec
|
106
111
|
for i in range(self.settings.get("length", 4))
|
107
112
|
)
|
108
113
|
test_url = DBSession.query(Shorturl).filter(Shorturl.ref == ref).all()
|
109
114
|
if not test_url:
|
110
115
|
break
|
111
|
-
tries += 1
|
112
|
-
if tries > 20:
|
116
|
+
tries += 1
|
117
|
+
if tries > 20:
|
113
118
|
message = "No free ref found, considered to increase the length"
|
114
|
-
|
119
|
+
logger.error(message)
|
115
120
|
raise HTTPInternalServerError(message)
|
116
121
|
|
117
122
|
user_email = self.request.user.email if self.request.user is not None else None
|
@@ -131,7 +136,7 @@ class Shortener:
|
|
131
136
|
else:
|
132
137
|
s_url = self.request.route_url("shortener_get", ref=ref)
|
133
138
|
|
134
|
-
if email is not None:
|
139
|
+
if email is not None:
|
135
140
|
send_email_config(
|
136
141
|
self.request.registry.settings,
|
137
142
|
"shortener",
|
@@ -139,7 +144,9 @@ class Shortener:
|
|
139
144
|
full_url=url,
|
140
145
|
short_url=s_url,
|
141
146
|
message=self.request.params.get("message", ""),
|
147
|
+
application_url=self.request.route_url("base"),
|
148
|
+
current_url=self.request.current_route_url(),
|
142
149
|
)
|
143
150
|
|
144
|
-
set_common_headers(self.request, "shortener",
|
151
|
+
set_common_headers(self.request, "shortener", Cache.PRIVATE_NO)
|
145
152
|
return {"short_url": s_url}
|