c2cgeoportal-geoportal 2.5.0.100__py2.py3-none-any.whl → 2.7.1.156__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 +74 -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 +127 -97
- 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 +102 -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 +162 -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 +67 -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 +110 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +114 -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 +1160 -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 +1410 -0
- c2cgeoportal_geoportal/scripts/__init__.py +18 -32
- 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 +71 -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 +70 -54
- c2cgeoportal_geoportal/views/profile.py +25 -24
- c2cgeoportal_geoportal/views/proxy.py +100 -70
- 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.156.dist-info}/METADATA +17 -11
- c2cgeoportal_geoportal-2.7.1.156.dist-info/RECORD +185 -0
- {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.156.dist-info}/WHEEL +1 -1
- {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.156.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.156.dist-info}/top_level.txt +0 -0
@@ -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
|
@@ -26,15 +24,18 @@
|
|
26
24
|
# The views and conclusions contained in the software and documentation are those
|
27
25
|
# of the authors and should not be interpreted as representing official policies,
|
28
26
|
# either expressed or implied, of the FreeBSD Project.
|
27
|
+
|
28
|
+
import random
|
29
29
|
import threading
|
30
|
-
from typing import Dict, Tuple # noqa, pylint: disable=unused-import
|
31
30
|
import warnings
|
31
|
+
from typing import Dict, Iterable, List, Optional, Tuple, Union
|
32
32
|
|
33
|
+
import sqlalchemy.ext.declarative
|
33
34
|
from papyrus.geo_interface import GeoInterface
|
34
35
|
from sqlalchemy import Column, Integer, MetaData, Table
|
35
36
|
from sqlalchemy.exc import SAWarning
|
36
|
-
from sqlalchemy.ext.declarative.api import DeclarativeMeta # noqa, pylint: disable=unused-import
|
37
37
|
from sqlalchemy.orm import relationship
|
38
|
+
from sqlalchemy.orm.session import Session
|
38
39
|
from sqlalchemy.orm.util import class_mapper
|
39
40
|
|
40
41
|
from c2cgeoportal_geoportal.lib.caching import get_region
|
@@ -53,12 +54,17 @@ SQL_GEOMETRY_COLUMNS = """
|
|
53
54
|
class _AssociationProxy:
|
54
55
|
# A specific "association proxy" implementation
|
55
56
|
|
56
|
-
def __init__(self, target, value_attr, nullable=True):
|
57
|
+
def __init__(self, target: str, value_attr: str, nullable: bool = True, order_by: Optional[str] = None):
|
57
58
|
self.target = target
|
58
59
|
self.value_attr = value_attr
|
59
60
|
self.nullable = nullable
|
61
|
+
self.order_by = order_by
|
60
62
|
|
61
|
-
def __get__(
|
63
|
+
def __get__(
|
64
|
+
self,
|
65
|
+
obj: sqlalchemy.ext.declarative.ConcreteBase,
|
66
|
+
type_: sqlalchemy.ext.declarative.DeclarativeMeta = None,
|
67
|
+
) -> Optional[Union["_AssociationProxy", str]]:
|
62
68
|
if obj is None:
|
63
69
|
# For "hybrid" descriptors that work both at the instance
|
64
70
|
# and class levels we could return an SQL expression here.
|
@@ -68,7 +74,7 @@ class _AssociationProxy:
|
|
68
74
|
target = getattr(obj, self.target)
|
69
75
|
return getattr(target, self.value_attr) if target else None
|
70
76
|
|
71
|
-
def __set__(self, obj, val):
|
77
|
+
def __set__(self, obj: str, val: str) -> None:
|
72
78
|
from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
|
73
79
|
|
74
80
|
o = getattr(obj, self.target)
|
@@ -82,7 +88,7 @@ class _AssociationProxy:
|
|
82
88
|
setattr(obj, self.target, o)
|
83
89
|
|
84
90
|
|
85
|
-
def _get_schema(tablename):
|
91
|
+
def _get_schema(tablename: str) -> Tuple[str, str]:
|
86
92
|
if "." in tablename:
|
87
93
|
schema, tablename = tablename.split(".", 1)
|
88
94
|
else:
|
@@ -94,7 +100,13 @@ def _get_schema(tablename):
|
|
94
100
|
_get_table_lock = threading.RLock()
|
95
101
|
|
96
102
|
|
97
|
-
def get_table(
|
103
|
+
def get_table(
|
104
|
+
tablename: str,
|
105
|
+
schema: Optional[str] = None,
|
106
|
+
session: Optional[Session] = None,
|
107
|
+
primary_key: Optional[str] = None,
|
108
|
+
) -> Table:
|
109
|
+
"""Build an SQLAlchemy table."""
|
98
110
|
if schema is None:
|
99
111
|
tablename, schema = _get_schema(tablename)
|
100
112
|
|
@@ -102,8 +114,8 @@ def get_table(tablename, schema=None, session=None, primary_key=None):
|
|
102
114
|
engine = session.bind.engine
|
103
115
|
metadata = MetaData(bind=engine)
|
104
116
|
else:
|
117
|
+
from c2cgeoportal_commons.models import Base # pylint: disable=import-outside-toplevel
|
105
118
|
from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
|
106
|
-
from c2cgeoportal_commons.models.main import Base # pylint: disable=import-outside-toplevel
|
107
119
|
|
108
120
|
engine = DBSession.bind.engine
|
109
121
|
metadata = Base.metadata
|
@@ -120,16 +132,21 @@ def get_table(tablename, schema=None, session=None, primary_key=None):
|
|
120
132
|
return table
|
121
133
|
|
122
134
|
|
123
|
-
@CACHE_REGION_OBJ.cache_on_arguments()
|
135
|
+
@CACHE_REGION_OBJ.cache_on_arguments() # type: ignore
|
124
136
|
def get_class(
|
125
|
-
tablename
|
126
|
-
|
137
|
+
tablename: str,
|
138
|
+
exclude_properties: Optional[List[str]] = None,
|
139
|
+
primary_key: Optional[str] = None,
|
140
|
+
attributes_order: Optional[List[str]] = None,
|
141
|
+
enumerations_config: Optional[Dict[str, str]] = None,
|
142
|
+
readonly_attributes: Optional[List[str]] = None,
|
143
|
+
) -> sqlalchemy.ext.declarative.DeclarativeMeta:
|
127
144
|
"""
|
128
|
-
Get the SQLAlchemy mapped class for "tablename".
|
129
|
-
|
130
|
-
|
131
|
-
tablename in the database a NoSuchTableError SQLAlchemy
|
132
|
-
is raised.
|
145
|
+
Get the SQLAlchemy mapped class for "tablename".
|
146
|
+
|
147
|
+
If no class exists for "tablename" one is created, and added to the cache. "tablename" must reference a
|
148
|
+
valid string. If there is no table identified by tablename in the database a NoSuchTableError SQLAlchemy
|
149
|
+
exception is raised.
|
133
150
|
"""
|
134
151
|
|
135
152
|
tablename, schema = _get_schema(tablename)
|
@@ -141,6 +158,7 @@ def get_class(
|
|
141
158
|
table,
|
142
159
|
exclude_properties=exclude_properties,
|
143
160
|
attributes_order=attributes_order,
|
161
|
+
enumerations_config=enumerations_config,
|
144
162
|
readonly_attributes=readonly_attributes,
|
145
163
|
)
|
146
164
|
|
@@ -148,19 +166,29 @@ def get_class(
|
|
148
166
|
|
149
167
|
|
150
168
|
def _create_class(
|
151
|
-
table
|
152
|
-
|
153
|
-
|
169
|
+
table: Table,
|
170
|
+
exclude_properties: Optional[Iterable[str]] = None,
|
171
|
+
attributes_order: Optional[List[str]] = None,
|
172
|
+
enumerations_config: Optional[Dict[str, str]] = None,
|
173
|
+
readonly_attributes: Optional[List[str]] = None,
|
174
|
+
pk_name: Optional[str] = None,
|
175
|
+
) -> sqlalchemy.ext.declarative.DeclarativeMeta:
|
176
|
+
from c2cgeoportal_commons.models import Base # pylint: disable=import-outside-toplevel
|
154
177
|
|
155
178
|
exclude_properties = exclude_properties or ()
|
156
179
|
attributes = dict(
|
157
180
|
__table__=table,
|
158
181
|
__mapper_args__={"exclude_properties": exclude_properties},
|
159
182
|
__attributes_order__=attributes_order,
|
183
|
+
__enumerations_config__=enumerations_config,
|
160
184
|
)
|
161
185
|
if pk_name is not None:
|
162
186
|
attributes[pk_name] = Column(Integer, primary_key=True)
|
163
|
-
|
187
|
+
# The randint is to fix the SAWarning: This declarative base already contains a class with the same
|
188
|
+
# class name and module nam
|
189
|
+
cls = type(
|
190
|
+
f"{table.name.capitalize()}_{random.randint(0, 9999999)}", (GeoInterface, Base), attributes # nosec
|
191
|
+
)
|
164
192
|
|
165
193
|
for col in table.columns:
|
166
194
|
if col.name in (readonly_attributes or []):
|
@@ -171,8 +199,10 @@ def _create_class(
|
|
171
199
|
return cls
|
172
200
|
|
173
201
|
|
174
|
-
def _add_association_proxy(
|
175
|
-
|
202
|
+
def _add_association_proxy(
|
203
|
+
cls: sqlalchemy.ext.declarative.DeclarativeMeta, col: sqlalchemy.sql.schema.Column
|
204
|
+
) -> None:
|
205
|
+
if len(col.foreign_keys) != 1:
|
176
206
|
raise NotImplementedError
|
177
207
|
|
178
208
|
fk = next(iter(col.foreign_keys))
|
@@ -180,10 +210,8 @@ def _add_association_proxy(cls, col):
|
|
180
210
|
child_cls = get_class(child_tablename)
|
181
211
|
|
182
212
|
try:
|
183
|
-
|
184
|
-
|
185
|
-
# fmt: on
|
186
|
-
except ValueError: # pragma: no cover
|
213
|
+
proxy = col.name[0 : col.name.rindex("_id")]
|
214
|
+
except ValueError:
|
187
215
|
proxy = col.name + "_"
|
188
216
|
|
189
217
|
# The following is a workaround for a bug in the geojson lib. The
|
@@ -191,7 +219,7 @@ def _add_association_proxy(cls, col):
|
|
191
219
|
# (this is a GeoJSON keyword), and produces a UnicodeEncodeError
|
192
220
|
# if the property includes non-ascii characters.
|
193
221
|
if proxy == "type":
|
194
|
-
proxy = "type_"
|
222
|
+
proxy = "type_"
|
195
223
|
|
196
224
|
rel = proxy + "_"
|
197
225
|
primaryjoin = getattr(cls, col.name) == getattr(child_cls, child_pk)
|
@@ -203,7 +231,18 @@ def _add_association_proxy(cls, col):
|
|
203
231
|
for column in cls_column_property.columns:
|
204
232
|
nullable = nullable and column.nullable
|
205
233
|
|
206
|
-
|
234
|
+
value_attr = "name"
|
235
|
+
order_by = value_attr
|
236
|
+
|
237
|
+
if cls.__enumerations_config__ and col.name in cls.__enumerations_config__:
|
238
|
+
enumeration_config = cls.__enumerations_config__[col.name]
|
239
|
+
if "value" in enumeration_config:
|
240
|
+
value_attr = enumeration_config["value"]
|
241
|
+
order_by = value_attr
|
242
|
+
if "order_by" in enumeration_config:
|
243
|
+
order_by = enumeration_config["order_by"]
|
244
|
+
|
245
|
+
setattr(cls, proxy, _AssociationProxy(rel, value_attr, nullable=nullable, order_by=order_by))
|
207
246
|
|
208
247
|
if cls.__add_properties__ is None:
|
209
248
|
cls.__add_properties__ = [proxy]
|
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Copyright (c) 2014-2019, Camptocamp SA
|
1
|
+
# Copyright (c) 2014-2024, Camptocamp SA
|
4
2
|
# All rights reserved.
|
5
3
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -29,56 +27,59 @@
|
|
29
27
|
|
30
28
|
|
31
29
|
import copy
|
32
|
-
from io import StringIO
|
33
30
|
import logging
|
34
|
-
|
35
|
-
from
|
36
|
-
import
|
37
|
-
from xml.sax.saxutils import XMLFilterBase, XMLGenerator
|
31
|
+
import xml.sax.handler # nosec
|
32
|
+
from io import StringIO
|
33
|
+
from typing import Any, Callable, Dict, List, Optional, Set, Union
|
34
|
+
from xml.sax.saxutils import XMLFilterBase, XMLGenerator # nosec
|
38
35
|
|
39
36
|
import defusedxml.expatreader
|
37
|
+
import pyramid.request
|
38
|
+
import requests
|
39
|
+
from owslib.map.wms111 import ContentMetadata as ContentMetadata111
|
40
|
+
from owslib.map.wms130 import ContentMetadata as ContentMetadata130
|
40
41
|
from owslib.wms import WebMapService
|
41
42
|
from pyramid.httpexceptions import HTTPBadGateway
|
42
|
-
import requests
|
43
43
|
|
44
|
-
from
|
45
|
-
|
46
|
-
caching,
|
47
|
-
get_ogc_server_wfs_url_ids,
|
48
|
-
get_ogc_server_wms_url_ids,
|
49
|
-
)
|
44
|
+
from c2cgeoportal_commons.lib.url import Url
|
45
|
+
from c2cgeoportal_geoportal.lib import caching, get_ogc_server_wfs_url_ids, get_ogc_server_wms_url_ids
|
50
46
|
from c2cgeoportal_geoportal.lib.layers import get_private_layers, get_protected_layers, get_writable_layers
|
51
47
|
|
52
48
|
CACHE_REGION = caching.get_region("std")
|
53
49
|
LOG = logging.getLogger(__name__)
|
50
|
+
ContentMetadata = Union[ContentMetadata111, ContentMetadata130]
|
54
51
|
|
55
52
|
|
56
|
-
@CACHE_REGION.cache_on_arguments()
|
57
|
-
def wms_structure(wms_url, host, request):
|
58
|
-
|
59
|
-
|
53
|
+
@CACHE_REGION.cache_on_arguments() # type: ignore
|
54
|
+
def wms_structure(wms_url: Url, host: str, request: pyramid.request.Request) -> Dict[str, List[str]]:
|
55
|
+
"""Get a simple serializable structure of the WMS capabilities."""
|
56
|
+
url = wms_url.clone().add_query({"SERVICE": "WMS", "VERSION": "1.1.1", "REQUEST": "GetCapabilities"})
|
60
57
|
|
61
58
|
# Forward request to target (without Host Header)
|
62
|
-
headers =
|
63
|
-
if url.hostname == "localhost" and host is not None:
|
59
|
+
headers = {}
|
60
|
+
if url.hostname == "localhost" and host is not None:
|
64
61
|
headers["Host"] = host
|
65
62
|
try:
|
66
|
-
response = requests.get(
|
67
|
-
|
68
|
-
|
63
|
+
response = requests.get(
|
64
|
+
url.url(), headers=headers, **request.registry.settings.get("http_options", {})
|
65
|
+
)
|
66
|
+
except Exception:
|
67
|
+
LOG.exception("Unable to GetCapabilities from wms_url '%s'", wms_url)
|
68
|
+
raise HTTPBadGateway( # pylint: disable=raise-missing-from
|
69
|
+
"Unable to GetCapabilities, see logs for details"
|
70
|
+
)
|
69
71
|
|
70
|
-
if not response.ok:
|
72
|
+
if not response.ok:
|
71
73
|
raise HTTPBadGateway(
|
72
|
-
"GetCapabilities from wms_url {
|
73
|
-
|
74
|
-
)
|
74
|
+
f"GetCapabilities from wms_url {url.url()} return the error: "
|
75
|
+
f"{response.status_code:d} {response.reason}"
|
75
76
|
)
|
76
77
|
|
77
78
|
try:
|
78
79
|
wms = WebMapService(None, xml=response.content)
|
79
80
|
result: Dict[str, List[str]] = {}
|
80
81
|
|
81
|
-
def _fill(name, parent):
|
82
|
+
def _fill(name: str, parent: ContentMetadata) -> None:
|
82
83
|
if parent is None:
|
83
84
|
return
|
84
85
|
if parent.name not in result:
|
@@ -90,37 +91,48 @@ def wms_structure(wms_url, host, request):
|
|
90
91
|
_fill(layer.name, layer.parent)
|
91
92
|
return result
|
92
93
|
|
93
|
-
except AttributeError:
|
94
|
-
error = "WARNING! an error occurred while trying to
|
95
|
-
error = "{
|
94
|
+
except AttributeError:
|
95
|
+
error = "WARNING! an error occurred while trying to read the mapfile and recover the themes."
|
96
|
+
error = f"{error}\nurl: {wms_url}\nxml:\n{response.text}"
|
96
97
|
LOG.exception(error)
|
97
|
-
raise HTTPBadGateway(error)
|
98
|
+
raise HTTPBadGateway(error) # pylint: disable=raise-missing-from
|
98
99
|
|
99
|
-
except SyntaxError:
|
100
|
-
error = "WARNING! an error occurred while trying to
|
101
|
-
error = "{
|
100
|
+
except SyntaxError:
|
101
|
+
error = "WARNING! an error occurred while trying to read the mapfile and recover the themes."
|
102
|
+
error = f"{error}\nurl: {wms_url}\nxml:\n{response.text}"
|
102
103
|
LOG.exception(error)
|
103
|
-
raise HTTPBadGateway(error)
|
104
|
+
raise HTTPBadGateway(error) # pylint: disable=raise-missing-from
|
104
105
|
|
105
106
|
|
106
|
-
def filter_capabilities(
|
107
|
+
def filter_capabilities(
|
108
|
+
content: str, wms: bool, url: Url, headers: Dict[str, str], request: pyramid.request.Request
|
109
|
+
) -> str:
|
110
|
+
"""Filter the WMS/WFS capabilities."""
|
107
111
|
|
108
112
|
wms_structure_ = wms_structure(url, headers.get("Host"), request)
|
109
113
|
|
110
114
|
ogc_server_ids = (
|
111
|
-
get_ogc_server_wms_url_ids(request
|
112
|
-
|
115
|
+
get_ogc_server_wms_url_ids(request, request.host)
|
116
|
+
if wms
|
117
|
+
else get_ogc_server_wfs_url_ids(request, request.host)
|
118
|
+
).get(url.url())
|
113
119
|
gmf_private_layers = copy.copy(get_private_layers(ogc_server_ids))
|
114
120
|
for id_ in list(get_protected_layers(request, ogc_server_ids).keys()):
|
115
121
|
if id_ in gmf_private_layers:
|
116
122
|
del gmf_private_layers[id_]
|
117
123
|
|
118
124
|
private_layers = set()
|
119
|
-
for
|
120
|
-
for
|
121
|
-
private_layers.add(
|
122
|
-
if
|
123
|
-
private_layers.update(wms_structure_[
|
125
|
+
for gmf_layer in list(gmf_private_layers.values()):
|
126
|
+
for ogc_layer in gmf_layer.layer.split(","):
|
127
|
+
private_layers.add(ogc_layer)
|
128
|
+
if ogc_layer in wms_structure_:
|
129
|
+
private_layers.update(wms_structure_[ogc_layer])
|
130
|
+
|
131
|
+
LOG.debug(
|
132
|
+
"Filter capabilities of OGC server %s\nprivate_layers: %s",
|
133
|
+
", ".join([str(e) for e in ogc_server_ids]),
|
134
|
+
", ".join(private_layers),
|
135
|
+
)
|
124
136
|
|
125
137
|
parser = defusedxml.expatreader.create_parser(forbid_external=False)
|
126
138
|
# skip inclusion of DTDs
|
@@ -132,15 +144,24 @@ def filter_capabilities(content, wms, url, headers, request):
|
|
132
144
|
filter_handler = _CapabilitiesFilter(
|
133
145
|
parser, downstream_handler, "Layer" if wms else "FeatureType", layers_blacklist=private_layers
|
134
146
|
)
|
135
|
-
filter_handler.parse(StringIO(content))
|
147
|
+
filter_handler.parse(StringIO(content)) # type: ignore
|
136
148
|
return result.getvalue()
|
137
149
|
|
138
150
|
|
139
|
-
def filter_wfst_capabilities(content, wfs_url, request):
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
151
|
+
def filter_wfst_capabilities(content: str, wfs_url: Url, request: pyramid.request.Request) -> str:
|
152
|
+
"""Filter the WTS capabilities."""
|
153
|
+
|
154
|
+
writable_layers: Set[str] = set()
|
155
|
+
ogc_server_ids = get_ogc_server_wfs_url_ids(request, request.host).get(wfs_url.url())
|
156
|
+
|
157
|
+
for gmf_layer in list(get_writable_layers(request, ogc_server_ids).values()):
|
158
|
+
writable_layers |= set(gmf_layer.layer.split(","))
|
159
|
+
|
160
|
+
LOG.debug(
|
161
|
+
"Filter WFS-T capabilities of OGC server %s\nlayers: %s",
|
162
|
+
", ".join([str(e) for e in ogc_server_ids]),
|
163
|
+
", ".join(writable_layers),
|
164
|
+
)
|
144
165
|
|
145
166
|
parser = defusedxml.expatreader.create_parser(forbid_external=False)
|
146
167
|
# skip inclusion of DTDs
|
@@ -152,13 +173,13 @@ def filter_wfst_capabilities(content, wfs_url, request):
|
|
152
173
|
filter_handler = _CapabilitiesFilter(
|
153
174
|
parser, downstream_handler, "FeatureType", layers_whitelist=writable_layers
|
154
175
|
)
|
155
|
-
filter_handler.parse(StringIO(content))
|
176
|
+
filter_handler.parse(StringIO(content)) # type: ignore
|
156
177
|
return result.getvalue()
|
157
178
|
|
158
179
|
|
159
180
|
class _Layer:
|
160
|
-
def __init__(self, self_hidden=False):
|
161
|
-
self.
|
181
|
+
def __init__(self, self_hidden: bool = False):
|
182
|
+
self.accumulator: List[Callable[[], None]] = []
|
162
183
|
self.hidden = True
|
163
184
|
self.self_hidden = self_hidden
|
164
185
|
self.has_children = False
|
@@ -168,13 +189,20 @@ class _Layer:
|
|
168
189
|
class _CapabilitiesFilter(XMLFilterBase):
|
169
190
|
"""
|
170
191
|
SAX filter to show only the allowed layers in a GetCapabilities request.
|
171
|
-
|
172
|
-
|
173
|
-
is given) or is not part of the set `layers_whitelist` (when
|
192
|
+
|
193
|
+
The filter removes elements of type `tag_name` where the `name` attribute is part of the set
|
194
|
+
`layers_blacklist` (when `layers_blacklist` is given) or is not part of the set `layers_whitelist` (when
|
174
195
|
`layers_whitelist` is given).
|
175
196
|
"""
|
176
197
|
|
177
|
-
def __init__(
|
198
|
+
def __init__(
|
199
|
+
self,
|
200
|
+
upstream: XMLFilterBase,
|
201
|
+
downstream: XMLGenerator,
|
202
|
+
tag_name: str,
|
203
|
+
layers_blacklist: Optional[Set[str]] = None,
|
204
|
+
layers_whitelist: Optional[Set[str]] = None,
|
205
|
+
):
|
178
206
|
XMLFilterBase.__init__(self, upstream)
|
179
207
|
self._downstream = downstream
|
180
208
|
self._accumulator: List[str] = []
|
@@ -187,9 +215,9 @@ class _CapabilitiesFilter(XMLFilterBase):
|
|
187
215
|
), "only either layers_blacklist OR layers_whitelist can be set"
|
188
216
|
|
189
217
|
if layers_blacklist is not None:
|
190
|
-
layers_blacklist =
|
218
|
+
layers_blacklist = {layer.lower() for layer in layers_blacklist}
|
191
219
|
if layers_whitelist is not None:
|
192
|
-
layers_whitelist =
|
220
|
+
layers_whitelist = {layer.lower() for layer in layers_whitelist}
|
193
221
|
self.layers_blacklist = layers_blacklist
|
194
222
|
self.layers_whitelist = layers_whitelist
|
195
223
|
|
@@ -200,39 +228,39 @@ class _CapabilitiesFilter(XMLFilterBase):
|
|
200
228
|
|
201
229
|
def _complete_text_node(self) -> None:
|
202
230
|
if self._accumulator:
|
203
|
-
self._downstream.characters("".join(self._accumulator))
|
231
|
+
self._downstream.characters("".join(self._accumulator)) # type: ignore
|
204
232
|
self._accumulator = []
|
205
233
|
|
206
|
-
def _do(self, action):
|
234
|
+
def _do(self, action: Callable[[], Any]) -> None:
|
207
235
|
if self.layers_path:
|
208
|
-
self.layers_path[-1].
|
236
|
+
self.layers_path[-1].accumulator.append(action)
|
209
237
|
else:
|
210
238
|
self._complete_text_node()
|
211
239
|
action()
|
212
240
|
|
213
|
-
def _add_child(self, layer):
|
241
|
+
def _add_child(self, layer: _Layer) -> None:
|
214
242
|
if not layer.hidden and not (layer.has_children and layer.children_nb == 0):
|
215
|
-
for action in layer.
|
243
|
+
for action in layer.accumulator:
|
216
244
|
self._complete_text_node()
|
217
245
|
action()
|
218
|
-
layer.
|
246
|
+
layer.accumulator = []
|
219
247
|
|
220
|
-
def setDocumentLocator(self, locator): # noqa
|
221
|
-
self._downstream.setDocumentLocator(locator)
|
248
|
+
def setDocumentLocator(self, locator: str) -> None: # noqa: ignore=N802
|
249
|
+
self._downstream.setDocumentLocator(locator) # type: ignore
|
222
250
|
|
223
|
-
def startDocument(self): # noqa
|
224
|
-
self._downstream.startDocument()
|
251
|
+
def startDocument(self) -> None: # noqa: ignore=N802
|
252
|
+
self._downstream.startDocument() # type: ignore
|
225
253
|
|
226
|
-
def endDocument(self): # noqa
|
227
|
-
self._downstream.endDocument()
|
254
|
+
def endDocument(self) -> None: # noqa: ignore=N802
|
255
|
+
self._downstream.endDocument() # type: ignore
|
228
256
|
|
229
|
-
def startPrefixMapping(self, prefix, uri
|
230
|
-
self._downstream.startPrefixMapping(prefix, uri)
|
257
|
+
def startPrefixMapping(self, prefix: str, uri: str) -> None: # noqa: ignore=N802
|
258
|
+
self._downstream.startPrefixMapping(prefix, uri) # type: ignore
|
231
259
|
|
232
|
-
def endPrefixMapping(self, prefix
|
233
|
-
self._downstream.endPrefixMapping(prefix)
|
260
|
+
def endPrefixMapping(self, prefix: str) -> None: # noqa: ignore=N802
|
261
|
+
self._downstream.endPrefixMapping(prefix) # type: ignore
|
234
262
|
|
235
|
-
def startElement(self, name, attrs): # noqa
|
263
|
+
def startElement(self, name: str, attrs: xml.sax.xmlreader.AttributesImpl) -> None: # noqa: ignore=N802
|
236
264
|
if name == self.tag_name:
|
237
265
|
self.level += 1
|
238
266
|
if self.layers_path:
|
@@ -242,40 +270,40 @@ class _CapabilitiesFilter(XMLFilterBase):
|
|
242
270
|
layer = _Layer(parent_layer.self_hidden) if len(self.layers_path) > 1 else _Layer()
|
243
271
|
self.layers_path.append(layer)
|
244
272
|
|
245
|
-
parent_layer.
|
273
|
+
parent_layer.accumulator.append(lambda: self._add_child(layer))
|
246
274
|
else:
|
247
275
|
layer = _Layer()
|
248
276
|
self.layers_path.append(layer)
|
249
277
|
elif name == "Name" and self.layers_path:
|
250
278
|
self.in_name = True
|
251
279
|
|
252
|
-
self._do(lambda: self._downstream.startElement(name, attrs))
|
280
|
+
self._do(lambda: self._downstream.startElement(name, attrs)) # type: ignore
|
253
281
|
|
254
|
-
def endElement(self, name): # noqa
|
255
|
-
self._do(lambda: self._downstream.endElement(name))
|
282
|
+
def endElement(self, name: str) -> None: # noqa: ignore=N802
|
283
|
+
self._do(lambda: self._downstream.endElement(name)) # type: ignore
|
256
284
|
|
257
285
|
if name == self.tag_name:
|
258
286
|
self.level -= 1
|
259
287
|
if self.level == 0 and not self.layers_path[0].hidden:
|
260
|
-
for action in self.layers_path[0].
|
288
|
+
for action in self.layers_path[0].accumulator:
|
261
289
|
self._complete_text_node()
|
262
290
|
action()
|
263
291
|
self.layers_path.pop()
|
264
292
|
elif name == "Name":
|
265
293
|
self.in_name = False
|
266
294
|
|
267
|
-
def startElementNS(self, name, qname, attrs
|
268
|
-
self._do(lambda: self._downstream.startElementNS(name, qname, attrs))
|
295
|
+
def startElementNS(self, name: str, qname: str, attrs: Dict[str, str]) -> None: # noqa: ignore=N802
|
296
|
+
self._do(lambda: self._downstream.startElementNS(name, qname, attrs)) # type: ignore
|
269
297
|
|
270
|
-
def endElementNS(self, name, qname
|
271
|
-
self._do(lambda: self._downstream.endElementNS(name, qname))
|
298
|
+
def endElementNS(self, name: str, qname: str) -> None: # noqa: ignore=N802
|
299
|
+
self._do(lambda: self._downstream.endElementNS(name, qname)) # type: ignore
|
272
300
|
|
273
|
-
def _keep_layer(self, layer_name):
|
301
|
+
def _keep_layer(self, layer_name: str) -> bool:
|
274
302
|
return (self.layers_blacklist is not None and layer_name not in self.layers_blacklist) or (
|
275
303
|
self.layers_whitelist is not None and layer_name in self.layers_whitelist
|
276
304
|
)
|
277
305
|
|
278
|
-
def characters(self, content):
|
306
|
+
def characters(self, content: str) -> None:
|
279
307
|
if self.in_name and self.layers_path and not self.layers_path[-1].self_hidden is True:
|
280
308
|
layer_name = normalize_typename(content)
|
281
309
|
if self._keep_layer(layer_name):
|
@@ -289,19 +317,20 @@ class _CapabilitiesFilter(XMLFilterBase):
|
|
289
317
|
|
290
318
|
self._do(lambda: self._accumulator.append(content))
|
291
319
|
|
292
|
-
def ignorableWhitespace(self, chars
|
320
|
+
def ignorableWhitespace(self, chars: str) -> None: # noqa: ignore=N802
|
293
321
|
self._do(lambda: self._accumulator.append(chars))
|
294
322
|
|
295
|
-
def processingInstruction(self, target, data
|
296
|
-
self._do(lambda: self._downstream.processingInstruction(target, data))
|
323
|
+
def processingInstruction(self, target: str, data: str) -> None: # noqa: ignore=N802
|
324
|
+
self._do(lambda: self._downstream.processingInstruction(target, data)) # type: ignore
|
297
325
|
|
298
|
-
def skippedEntity(self, name
|
299
|
-
self._downstream.skippedEntity(name)
|
326
|
+
def skippedEntity(self, name: str) -> None: # noqa: ignore=N802
|
327
|
+
self._downstream.skippedEntity(name) # type: ignore
|
300
328
|
|
301
329
|
|
302
|
-
def normalize_tag(tag):
|
330
|
+
def normalize_tag(tag: str) -> str:
|
303
331
|
"""
|
304
|
-
|
332
|
+
Drop the namespace from a tag and converts to lower case.
|
333
|
+
|
305
334
|
e.g. '{https://....}TypeName' -> 'TypeName'
|
306
335
|
"""
|
307
336
|
normalized = tag
|
@@ -311,9 +340,10 @@ def normalize_tag(tag):
|
|
311
340
|
return normalized.lower()
|
312
341
|
|
313
342
|
|
314
|
-
def normalize_typename(typename):
|
343
|
+
def normalize_typename(typename: str) -> str:
|
315
344
|
"""
|
316
|
-
|
345
|
+
Drop the namespace from a type name and converts to lower case.
|
346
|
+
|
317
347
|
e.g. 'tows:parks' -> 'parks'
|
318
348
|
"""
|
319
349
|
normalized = typename
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright (c) 2020-2021, Camptocamp SA
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
# list of conditions and the following disclaimer.
|
9
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer in the documentation
|
11
|
+
# and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
14
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
15
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
16
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
17
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
18
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
19
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
20
|
+
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
21
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
22
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
23
|
+
|
24
|
+
# The views and conclusions contained in the software and documentation are those
|
25
|
+
# of the authors and should not be interpreted as representing official policies,
|
26
|
+
# either expressed or implied, of the FreeBSD Project.
|
27
|
+
|
28
|
+
import re
|
29
|
+
from typing import Any, Dict
|
30
|
+
|
31
|
+
|
32
|
+
class Normalize:
|
33
|
+
"""Normalize a text for the Full text search."""
|
34
|
+
|
35
|
+
def __init__(self, config: Dict[str, Any]) -> None:
|
36
|
+
split = config.get("split_regex")
|
37
|
+
self.split_re = re.compile(split) if split is not None else None
|
38
|
+
|
39
|
+
self.word_replace = []
|
40
|
+
for search_regex, text in config.get("replace", {}).items():
|
41
|
+
self.word_replace.append((re.compile(search_regex), text))
|
42
|
+
|
43
|
+
def __call__(self, text: str) -> str:
|
44
|
+
if self.split_re is not None:
|
45
|
+
text = " ".join(self.split_re.split(text))
|
46
|
+
|
47
|
+
for search, replace in self.word_replace:
|
48
|
+
text = search.sub(replace, text)
|
49
|
+
|
50
|
+
return text
|