c2cgeoportal-geoportal 2.3.5.80__py3-none-any.whl → 2.9rc2__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 +960 -0
- c2cgeoportal_geoportal/lib/__init__.py +256 -0
- c2cgeoportal_geoportal/lib/authentication.py +250 -0
- c2cgeoportal_geoportal/lib/bashcolor.py +46 -0
- c2cgeoportal_geoportal/lib/cacheversion.py +75 -0
- c2cgeoportal_geoportal/lib/caching.py +176 -0
- c2cgeoportal_geoportal/lib/check_collector.py +80 -0
- c2cgeoportal_geoportal/lib/checker.py +295 -0
- c2cgeoportal_geoportal/lib/common_headers.py +170 -0
- c2cgeoportal_geoportal/lib/dbreflection.py +266 -0
- c2cgeoportal_geoportal/lib/filter_capabilities.py +360 -0
- c2cgeoportal_geoportal/lib/fulltextsearch.py +50 -0
- c2cgeoportal_geoportal/lib/functionality.py +166 -0
- c2cgeoportal_geoportal/lib/headers.py +62 -0
- c2cgeoportal_geoportal/lib/i18n.py +38 -0
- c2cgeoportal_geoportal/lib/layers.py +132 -0
- c2cgeoportal_geoportal/lib/lingva_extractor.py +937 -0
- c2cgeoportal_geoportal/lib/loader.py +57 -0
- c2cgeoportal_geoportal/lib/metrics.py +117 -0
- c2cgeoportal_geoportal/lib/oauth2.py +1186 -0
- c2cgeoportal_geoportal/lib/oidc.py +304 -0
- c2cgeoportal_geoportal/lib/wmstparsing.py +353 -0
- c2cgeoportal_geoportal/lib/xsd.py +166 -0
- c2cgeoportal_geoportal/py.typed +0 -0
- c2cgeoportal_geoportal/resources.py +49 -0
- 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/advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml +30 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Dockerfile +75 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Makefile +6 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/alembic.ini +58 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/alembic.yaml +19 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/development.ini +121 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/gunicorn.conf.py +139 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/language_mapping +3 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/lingva-client.cfg +5 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/lingva-server.cfg +6 -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/advance_create/{{cookiecutter.project}}/geoportal/webpack.api.js +41 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.apps.js +64 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.commons.js +11 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.config.js +22 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/__init__.py +42 -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/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/resources.py +11 -0
- 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/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/subscribers.py +39 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/views/__init__.py +0 -0
- c2cgeoportal_geoportal/scaffolds/advance_update/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/advance_update/{{cookiecutter.project}}/geoportal/CONST_Makefile +121 -0
- c2cgeoportal_geoportal/scaffolds/create/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.dockerignore +14 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.editorconfig +17 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/main.yaml +73 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/rebuild.yaml +50 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/update_l10n.yaml +66 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.gitignore +16 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.pre-commit-config.yaml +35 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierignore +1 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierrc.yaml +2 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Dockerfile +75 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile +70 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/README.rst +29 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build +179 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/config.yaml +22 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/docker-compose-check +25 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/requirements.txt +2 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-db.yaml +24 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml +511 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-qgis.yaml +21 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml +59 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.yaml +121 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.default +102 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.project +69 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/vars.yaml +430 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/locale/en/LC_MESSAGES/{{cookiecutter.package}}_geoportal-client.po +6 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/desktop.css +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/iframe_api.css +0 -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/banner_left.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/banner_right.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/blank.png +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/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/robot.txt.tmpl +3 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/Readme.txt +69 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/TM_EUROPE_BORDERS-0.3.sql +70 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/demo.map.tmpl +224 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arial.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arialbd.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arialbi.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Ariali.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Bold.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-BoldItalic.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Italic.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Regular.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdana.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanab.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanai.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanaz.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts.conf +12 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.conf +15 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.map.tmpl +87 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/tinyows.xml.tmpl +36 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Landscape.jrxml +207 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Portrait.jrxml +185 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Landscape.jrxml +200 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Portrait.jrxml +170 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/config.yaml.tmpl +175 -0
- 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/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/logo.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/north.svg +93 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/results.jrxml +25 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/project.yaml +18 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml +7 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/qgisserver/pg_service.conf.tmpl +15 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/run_alembic.sh +11 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-backup +126 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +132 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/setup.cfg +7 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/spell-ignore-words.txt +5 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/__init__.py +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/test_app.py +43 -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 +67 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_CHANGELOG.txt +295 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_create_template/tests/test_testapp.py +48 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_config-schema.yaml +922 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_vars.yaml +1503 -0
- c2cgeoportal_geoportal/scripts/__init__.py +64 -0
- c2cgeoportal_geoportal/scripts/c2cupgrade.py +879 -0
- c2cgeoportal_geoportal/scripts/create_demo_theme.py +80 -0
- c2cgeoportal_geoportal/scripts/manage_users.py +140 -0
- c2cgeoportal_geoportal/scripts/pcreate.py +314 -0
- c2cgeoportal_geoportal/scripts/theme2fts.py +347 -0
- c2cgeoportal_geoportal/scripts/urllogin.py +81 -0
- c2cgeoportal_geoportal/templates/login.html +90 -0
- c2cgeoportal_geoportal/templates/notlogin.html +62 -0
- c2cgeoportal_geoportal/templates/testi18n.html +12 -0
- c2cgeoportal_geoportal/views/__init__.py +59 -0
- c2cgeoportal_geoportal/views/dev.py +57 -0
- c2cgeoportal_geoportal/views/dynamic.py +208 -0
- c2cgeoportal_geoportal/views/entry.py +174 -0
- c2cgeoportal_geoportal/views/fulltextsearch.py +189 -0
- c2cgeoportal_geoportal/views/geometry_processing.py +75 -0
- c2cgeoportal_geoportal/views/i18n.py +129 -0
- c2cgeoportal_geoportal/views/layers.py +713 -0
- c2cgeoportal_geoportal/views/login.py +679 -0
- c2cgeoportal_geoportal/views/mapserverproxy.py +191 -0
- c2cgeoportal_geoportal/views/memory.py +90 -0
- c2cgeoportal_geoportal/views/ogcproxy.py +120 -0
- c2cgeoportal_geoportal/views/pdfreport.py +245 -0
- c2cgeoportal_geoportal/views/printproxy.py +143 -0
- c2cgeoportal_geoportal/views/profile.py +127 -0
- c2cgeoportal_geoportal/views/proxy.py +259 -0
- c2cgeoportal_geoportal/views/raster.py +193 -0
- c2cgeoportal_geoportal/views/resourceproxy.py +73 -0
- c2cgeoportal_geoportal/views/shortener.py +152 -0
- c2cgeoportal_geoportal/views/theme.py +1322 -0
- c2cgeoportal_geoportal/views/tinyowsproxy.py +189 -0
- c2cgeoportal_geoportal/views/vector_tiles.py +83 -0
- {c2cgeoportal_geoportal-2.3.5.80.dist-info → c2cgeoportal_geoportal-2.9rc2.dist-info}/METADATA +21 -24
- c2cgeoportal_geoportal-2.9rc2.dist-info/RECORD +192 -0
- {c2cgeoportal_geoportal-2.3.5.80.dist-info → c2cgeoportal_geoportal-2.9rc2.dist-info}/WHEEL +1 -1
- c2cgeoportal_geoportal-2.9rc2.dist-info/entry_points.txt +28 -0
- c2cgeoportal_geoportal-2.9rc2.dist-info/top_level.txt +2 -0
- tests/__init__.py +100 -0
- tests/test_cachebuster.py +71 -0
- tests/test_caching.py +275 -0
- tests/test_checker.py +85 -0
- tests/test_decimaljson.py +47 -0
- tests/test_headerstween.py +64 -0
- tests/test_i18n.py +31 -0
- tests/test_init.py +193 -0
- tests/test_locale_negociator.py +69 -0
- tests/test_mapserverproxy_route_predicate.py +64 -0
- tests/test_raster.py +267 -0
- tests/test_wmstparsing.py +238 -0
- tests/xmlstr.py +103 -0
- c2cgeoportal_geoportal-2.3.5.80.dist-info/DESCRIPTION.rst +0 -8
- c2cgeoportal_geoportal-2.3.5.80.dist-info/RECORD +0 -7
- c2cgeoportal_geoportal-2.3.5.80.dist-info/entry_points.txt +0 -22
- c2cgeoportal_geoportal-2.3.5.80.dist-info/metadata.json +0 -1
- c2cgeoportal_geoportal-2.3.5.80.dist-info/top_level.txt +0 -1
@@ -0,0 +1,191 @@
|
|
1
|
+
# Copyright (c) 2011-2024, 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
|
+
|
29
|
+
import logging
|
30
|
+
from typing import Any
|
31
|
+
|
32
|
+
from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPInternalServerError, HTTPUnauthorized
|
33
|
+
from pyramid.request import Request
|
34
|
+
from pyramid.response import Response
|
35
|
+
from pyramid.view import view_config
|
36
|
+
|
37
|
+
from c2cgeoportal_commons.lib.url import Url
|
38
|
+
from c2cgeoportal_commons.models import main
|
39
|
+
from c2cgeoportal_geoportal.lib import get_roles_id, get_roles_name
|
40
|
+
from c2cgeoportal_geoportal.lib.caching import get_region
|
41
|
+
from c2cgeoportal_geoportal.lib.common_headers import Cache
|
42
|
+
from c2cgeoportal_geoportal.lib.filter_capabilities import filter_capabilities
|
43
|
+
from c2cgeoportal_geoportal.lib.functionality import get_mapserver_substitution_params
|
44
|
+
from c2cgeoportal_geoportal.views.ogcproxy import OGCProxy
|
45
|
+
|
46
|
+
_CACHE_REGION = get_region("std")
|
47
|
+
_LOG = logging.getLogger(__name__)
|
48
|
+
|
49
|
+
|
50
|
+
class MapservProxy(OGCProxy):
|
51
|
+
"""Proxy for OGC (WMS/WFS) servers."""
|
52
|
+
|
53
|
+
params: dict[str, str] = {}
|
54
|
+
|
55
|
+
def __init__(self, request: Request) -> None:
|
56
|
+
OGCProxy.__init__(self, request)
|
57
|
+
self.user = self.request.user
|
58
|
+
|
59
|
+
@view_config(route_name="mapserverproxy") # type: ignore
|
60
|
+
@view_config(route_name="mapserverproxy_post") # type: ignore
|
61
|
+
@view_config(route_name="mapserverproxy_get_path") # type: ignore
|
62
|
+
@view_config(route_name="mapserverproxy_post_path") # type: ignore
|
63
|
+
def proxy(self) -> Response:
|
64
|
+
if self.user is None and "authentication_required" in self.request.params:
|
65
|
+
_LOG.debug("proxy() detected authentication_required")
|
66
|
+
if self.request.registry.settings.get("basicauth", "False").lower() == "true":
|
67
|
+
raise HTTPUnauthorized(
|
68
|
+
headers={"WWW-Authenticate": 'Basic realm="Access to restricted layers"'}
|
69
|
+
)
|
70
|
+
raise HTTPForbidden("Basic auth is not enabled")
|
71
|
+
|
72
|
+
# We have a user logged in. We need to set group_id and possible layer_name in the params. We set
|
73
|
+
# layer_name when either QUERY_PARAMS or LAYERS is set in the WMS params, i.e. for GetMap and
|
74
|
+
# GetFeatureInfo requests. For GetLegendGraphic requests we do not send layer_name, but MapServer
|
75
|
+
# should not use the DATA string for GetLegendGraphic.
|
76
|
+
|
77
|
+
if self.ogc_server.auth == main.OGCSERVER_AUTH_STANDARD:
|
78
|
+
self.params["role_ids"] = ",".join([str(e) for e in get_roles_id(self.request)])
|
79
|
+
|
80
|
+
# In some application we want to display the features owned by a user than we need his id.
|
81
|
+
self.params["user_id"] = self.user.id if self.user is not None else "-1"
|
82
|
+
|
83
|
+
# Do not allows direct variable substitution
|
84
|
+
for k in list(self.params.keys()):
|
85
|
+
if len(k) > 1 and k[:2].capitalize() == "S_":
|
86
|
+
_LOG.warning("Direct substitution not allowed (%s=%s).", k, self.params[k])
|
87
|
+
del self.params[k]
|
88
|
+
|
89
|
+
if (
|
90
|
+
self.ogc_server.auth == main.OGCSERVER_AUTH_STANDARD
|
91
|
+
and self.ogc_server.type == main.OGCSERVER_TYPE_MAPSERVER
|
92
|
+
):
|
93
|
+
# Add functionalities params
|
94
|
+
self.params.update(get_mapserver_substitution_params(self.request))
|
95
|
+
|
96
|
+
# Get method
|
97
|
+
method = self.request.method
|
98
|
+
|
99
|
+
# we want the browser to cache GetLegendGraphic and
|
100
|
+
# DescribeFeatureType requests
|
101
|
+
use_cache = False
|
102
|
+
|
103
|
+
errors: set[str] = set()
|
104
|
+
if method == "GET":
|
105
|
+
# For GET requests, params are added only if the self.request
|
106
|
+
# parameter is actually provided.
|
107
|
+
if "request" not in self.lower_params:
|
108
|
+
self.params = {}
|
109
|
+
else:
|
110
|
+
if self.ogc_server.type != main.OGCSERVER_TYPE_QGISSERVER or "user_id" not in self.params:
|
111
|
+
use_cache = self.lower_params["request"] in ("getlegendgraphic",)
|
112
|
+
|
113
|
+
# no user_id and role_id or cached queries
|
114
|
+
if use_cache and "user_id" in self.params:
|
115
|
+
del self.params["user_id"]
|
116
|
+
if use_cache and "role_ids" in self.params:
|
117
|
+
del self.params["role_ids"]
|
118
|
+
|
119
|
+
if "service" in self.lower_params and self.lower_params["service"] == "wfs":
|
120
|
+
_url = self._get_wfs_url(errors)
|
121
|
+
else:
|
122
|
+
_url = self._get_wms_url(errors)
|
123
|
+
else:
|
124
|
+
# POST means WFS
|
125
|
+
_url = self._get_wfs_url(errors)
|
126
|
+
|
127
|
+
if _url is None:
|
128
|
+
_LOG.error("Error getting the URL:\n%s", "\n".join(errors))
|
129
|
+
raise HTTPInternalServerError()
|
130
|
+
|
131
|
+
cache_control = (
|
132
|
+
Cache.PRIVATE
|
133
|
+
if method == "GET"
|
134
|
+
and self.lower_params.get("request")
|
135
|
+
in (
|
136
|
+
"getcapabilities",
|
137
|
+
"getlegendgraphic",
|
138
|
+
"describefeaturetype",
|
139
|
+
"describelayer",
|
140
|
+
)
|
141
|
+
else Cache.PRIVATE_NO
|
142
|
+
)
|
143
|
+
|
144
|
+
headers = self.get_headers()
|
145
|
+
# Add headers for Geoserver
|
146
|
+
if self.ogc_server.auth == main.OGCSERVER_AUTH_GEOSERVER and self.user is not None:
|
147
|
+
headers["sec-username"] = self.user.username
|
148
|
+
headers["sec-roles"] = ";".join(get_roles_name(self.request))
|
149
|
+
|
150
|
+
response = self._proxy_callback(
|
151
|
+
cache_control,
|
152
|
+
url=_url,
|
153
|
+
params=self.params,
|
154
|
+
cache=use_cache,
|
155
|
+
headers=headers,
|
156
|
+
body=self.request.body,
|
157
|
+
)
|
158
|
+
|
159
|
+
if (
|
160
|
+
self.lower_params.get("request") == "getmap"
|
161
|
+
and not response.content_type.startswith("image/")
|
162
|
+
and response.status_code < 400
|
163
|
+
):
|
164
|
+
response.status_code = 400
|
165
|
+
|
166
|
+
return response
|
167
|
+
|
168
|
+
def _proxy_callback(
|
169
|
+
self, cache_control: Cache, url: Url, params: dict[str, str], **kwargs: Any
|
170
|
+
) -> Response:
|
171
|
+
if self.request.matched_route.name.endswith("_path"):
|
172
|
+
if self.request.matchdict["path"] == ("favicon.ico",):
|
173
|
+
return HTTPFound("/favicon.ico")
|
174
|
+
url = url.clone()
|
175
|
+
url.path = self.request.path
|
176
|
+
|
177
|
+
response = self._proxy(url=url, params=params, **kwargs)
|
178
|
+
|
179
|
+
content = response.content
|
180
|
+
if self.lower_params.get("request") == "getcapabilities":
|
181
|
+
content = filter_capabilities(
|
182
|
+
self.request,
|
183
|
+
response.text,
|
184
|
+
self.lower_params.get("service") == "wms",
|
185
|
+
url,
|
186
|
+
self.request.headers,
|
187
|
+
).encode("utf-8")
|
188
|
+
|
189
|
+
content_type = response.headers["Content-Type"]
|
190
|
+
|
191
|
+
return self._build_response(response, content, cache_control, "mapserver", content_type=content_type)
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# Copyright (c) 2018-2024, 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
|
+
|
29
|
+
import logging
|
30
|
+
import os
|
31
|
+
import time
|
32
|
+
from typing import Any, cast
|
33
|
+
|
34
|
+
import pyramid.request
|
35
|
+
from c2cwsgiutils import broadcast
|
36
|
+
from c2cwsgiutils.auth import auth_view
|
37
|
+
from c2cwsgiutils.debug import get_size
|
38
|
+
from pyramid.view import view_config
|
39
|
+
|
40
|
+
from c2cgeoportal_geoportal.lib.caching import MEMORY_CACHE_DICT
|
41
|
+
from c2cgeoportal_geoportal.views import raster
|
42
|
+
|
43
|
+
_LOG = logging.getLogger(__name__)
|
44
|
+
|
45
|
+
|
46
|
+
@view_config(route_name="memory", renderer="fast_json") # type: ignore
|
47
|
+
def memory(request: pyramid.request.Request) -> dict[str, Any]:
|
48
|
+
"""Offer an authenticated view throw c2cwsgiutils to provide some memory information."""
|
49
|
+
auth_view(request)
|
50
|
+
return cast(dict[str, Any], _memory())
|
51
|
+
|
52
|
+
|
53
|
+
def _nice_type_name(obj: Any, dogpile_cache: bool = False) -> str:
|
54
|
+
# See: https://dogpilecache.sqlalchemy.org/en/latest/api.html#dogpile.cache.api.CachedValue
|
55
|
+
if dogpile_cache:
|
56
|
+
obj, _ = obj
|
57
|
+
type_ = type(obj)
|
58
|
+
return f"{type_.__module__}.{type_.__name__}"
|
59
|
+
|
60
|
+
|
61
|
+
def _process_dict(dict_: dict[str, Any], dogpile_cache: bool = False) -> dict[str, Any]:
|
62
|
+
# Timeout after one minute, must be set to a bit less that the timeout of the broadcast
|
63
|
+
timeout = time.monotonic() + 20
|
64
|
+
|
65
|
+
return {
|
66
|
+
"elements": sorted(
|
67
|
+
(
|
68
|
+
{
|
69
|
+
"key": key,
|
70
|
+
"type": _nice_type_name(value, dogpile_cache),
|
71
|
+
"repr": repr(value),
|
72
|
+
"id": id(value),
|
73
|
+
"size_kb": get_size(value) / 1024 if time.monotonic() < timeout else -1,
|
74
|
+
}
|
75
|
+
for key, value in dict_.items()
|
76
|
+
),
|
77
|
+
key=lambda i: cast(float, -i["size_kb"]),
|
78
|
+
),
|
79
|
+
"id": id(dict_),
|
80
|
+
"size_kb": get_size(dict_) / 1024 if time.monotonic() < timeout else -1,
|
81
|
+
"timeout": time.monotonic() > timeout,
|
82
|
+
}
|
83
|
+
|
84
|
+
|
85
|
+
@broadcast.decorator(expect_answers=True, timeout=110)
|
86
|
+
def _memory() -> dict[str, Any]:
|
87
|
+
result = {"raster_data": _process_dict(raster.Raster.data)}
|
88
|
+
if os.environ.get("GEOMAPFISH_DEBUG_MEMORY_CACHE", "false").lower() in ("true", "1", "yes", "on"):
|
89
|
+
result["memory_cache"] = _process_dict(MEMORY_CACHE_DICT, True)
|
90
|
+
return result
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# Copyright (c) 2011-2024, 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 logging
|
29
|
+
|
30
|
+
import pyramid.request
|
31
|
+
from pyramid.httpexceptions import HTTPBadRequest
|
32
|
+
from sqlalchemy.orm.exc import NoResultFound # type: ignore[attr-defined]
|
33
|
+
|
34
|
+
from c2cgeoportal_commons.lib.url import Url, get_url2
|
35
|
+
from c2cgeoportal_commons.models import DBSession, main
|
36
|
+
from c2cgeoportal_geoportal.lib.caching import get_region
|
37
|
+
from c2cgeoportal_geoportal.views.proxy import Proxy
|
38
|
+
|
39
|
+
_CACHE_REGION = get_region("std")
|
40
|
+
_LOG = logging.getLogger(__name__)
|
41
|
+
|
42
|
+
|
43
|
+
class OGCProxy(Proxy):
|
44
|
+
"""
|
45
|
+
Proxy implementation that manly manage the ogcserver parameter.
|
46
|
+
|
47
|
+
Then load the corresponding OGCServer.
|
48
|
+
"""
|
49
|
+
|
50
|
+
def __init__(self, request: pyramid.request.Request, has_default_ogc_server: bool = False):
|
51
|
+
Proxy.__init__(self, request)
|
52
|
+
|
53
|
+
# params hold the parameters we"re going to send to backend
|
54
|
+
self.params = dict(self.request.params)
|
55
|
+
|
56
|
+
# reset possible value of role_id and user_id
|
57
|
+
if "role_id" in self.params:
|
58
|
+
del self.params["role_id"]
|
59
|
+
if "user_id" in self.params:
|
60
|
+
del self.params["user_id"]
|
61
|
+
|
62
|
+
main_ogc_server = self.request.registry.settings.get("main_ogc_server")
|
63
|
+
|
64
|
+
self.lower_params = self._get_lower_params(self.params)
|
65
|
+
|
66
|
+
# We need original case for OGCSERVER parameter value
|
67
|
+
self.lower_key_params = {k.lower(): v for k, v in self.params.items()}
|
68
|
+
|
69
|
+
if "ogcserver" in request.matchdict:
|
70
|
+
self.ogc_server = self._get_ogcserver_byname(request.matchdict["ogcserver"])
|
71
|
+
elif "ogcserver" in self.lower_key_params:
|
72
|
+
self.ogc_server = self._get_ogcserver_byname(self.lower_key_params["ogcserver"])
|
73
|
+
elif main_ogc_server is not None:
|
74
|
+
self.ogc_server = self._get_ogcserver_byname(main_ogc_server)
|
75
|
+
elif not has_default_ogc_server:
|
76
|
+
raise HTTPBadRequest("The querystring argument 'ogcserver' is required")
|
77
|
+
|
78
|
+
@_CACHE_REGION.cache_on_arguments()
|
79
|
+
def _get_ogcserver_byname(self, name: str) -> main.OGCServer:
|
80
|
+
assert DBSession is not None
|
81
|
+
|
82
|
+
try:
|
83
|
+
result = DBSession.query(main.OGCServer).filter(main.OGCServer.name == name).one()
|
84
|
+
DBSession.expunge(result)
|
85
|
+
return result
|
86
|
+
except NoResultFound:
|
87
|
+
raise HTTPBadRequest( # pylint: disable=raise-missing-from
|
88
|
+
f"The OGC Server '{name}' does not exist (existing: "
|
89
|
+
f"{','.join([t[0] for t in DBSession.query(main.OGCServer.name).all()])})."
|
90
|
+
)
|
91
|
+
|
92
|
+
def _get_wms_url(self, errors: set[str]) -> Url | None:
|
93
|
+
ogc_server = self.ogc_server
|
94
|
+
url = get_url2(f"The OGC server '{ogc_server.name}'", ogc_server.url, self.request, errors)
|
95
|
+
if errors:
|
96
|
+
_LOG.error("\n".join(errors))
|
97
|
+
return url
|
98
|
+
|
99
|
+
def _get_wfs_url(self, errors: set[str]) -> Url | None:
|
100
|
+
ogc_server = self.ogc_server
|
101
|
+
url = get_url2(
|
102
|
+
f"The OGC server (WFS) '{ogc_server.name}'",
|
103
|
+
ogc_server.url_wfs or ogc_server.url,
|
104
|
+
self.request,
|
105
|
+
errors,
|
106
|
+
)
|
107
|
+
if errors:
|
108
|
+
_LOG.error("\n".join(errors))
|
109
|
+
return url
|
110
|
+
|
111
|
+
def get_headers(self) -> dict[str, str]:
|
112
|
+
headers: dict[str, str] = super().get_headers()
|
113
|
+
if self.ogc_server.type == main.OGCSERVER_TYPE_QGISSERVER:
|
114
|
+
if self.request.matched_route.name.endswith("_path"):
|
115
|
+
headers["X-Qgis-Service-Url"] = self.request.current_route_url(path=[], _query={})
|
116
|
+
else:
|
117
|
+
headers["X-Qgis-Service-Url"] = self.request.current_route_url(
|
118
|
+
_query={"ogcserver": self.ogc_server.name}
|
119
|
+
)
|
120
|
+
return headers
|
@@ -0,0 +1,245 @@
|
|
1
|
+
# Copyright (c) 2011-2024, 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 logging
|
29
|
+
from json import dumps, loads
|
30
|
+
from typing import Any
|
31
|
+
|
32
|
+
import pyramid.request
|
33
|
+
import pyramid.response
|
34
|
+
from pyramid.httpexceptions import HTTPBadRequest, HTTPForbidden
|
35
|
+
from pyramid.view import view_config
|
36
|
+
|
37
|
+
from c2cgeoportal_commons import models
|
38
|
+
from c2cgeoportal_commons.lib.url import Url
|
39
|
+
from c2cgeoportal_commons.models import main
|
40
|
+
from c2cgeoportal_geoportal.lib.common_headers import Cache
|
41
|
+
from c2cgeoportal_geoportal.lib.layers import get_private_layers, get_protected_layers
|
42
|
+
from c2cgeoportal_geoportal.views.ogcproxy import OGCProxy
|
43
|
+
|
44
|
+
_LOG = logging.getLogger(__name__)
|
45
|
+
|
46
|
+
|
47
|
+
class PdfReport(OGCProxy):
|
48
|
+
"""All the views concerned the PDF report."""
|
49
|
+
|
50
|
+
layername = None
|
51
|
+
|
52
|
+
def __init__(self, request: pyramid.request.Request):
|
53
|
+
OGCProxy.__init__(self, request)
|
54
|
+
self.config = self.request.registry.settings.get("pdfreport", {})
|
55
|
+
|
56
|
+
def _do_print(self, spec: dict[str, Any]) -> pyramid.response.Response:
|
57
|
+
"""Create and get report PDF."""
|
58
|
+
headers = dict(self.request.headers)
|
59
|
+
headers["Content-Type"] = "application/json"
|
60
|
+
response = self._proxy(
|
61
|
+
Url(f"{self.config['print_url']}/buildreport.{spec['outputFormat']}"),
|
62
|
+
method="POST",
|
63
|
+
body=dumps(spec).encode("utf-8"),
|
64
|
+
headers=headers,
|
65
|
+
)
|
66
|
+
|
67
|
+
return self._build_response(response, response.content, Cache.PRIVATE_NO, "pdfreport")
|
68
|
+
|
69
|
+
@staticmethod
|
70
|
+
def _build_map(
|
71
|
+
mapserv_url: str, vector_request_url: str, srs: str, map_config: dict[str, Any]
|
72
|
+
) -> dict[str, Any]:
|
73
|
+
backgroundlayers = map_config["backgroundlayers"]
|
74
|
+
imageformat = map_config["imageformat"]
|
75
|
+
return {
|
76
|
+
"projection": srs,
|
77
|
+
"dpi": 254,
|
78
|
+
"rotation": 0,
|
79
|
+
"bbox": [0, 0, 1000000, 1000000],
|
80
|
+
"zoomToFeatures": {
|
81
|
+
"zoomType": map_config["zoomType"],
|
82
|
+
"layer": "vector",
|
83
|
+
"minScale": map_config["minScale"],
|
84
|
+
},
|
85
|
+
"layers": [
|
86
|
+
{
|
87
|
+
"type": "gml",
|
88
|
+
"name": "vector",
|
89
|
+
"style": {"version": "2", "[1 > 0]": map_config["style"]},
|
90
|
+
"opacity": 1,
|
91
|
+
"url": vector_request_url,
|
92
|
+
},
|
93
|
+
{
|
94
|
+
"baseURL": mapserv_url,
|
95
|
+
"opacity": 1,
|
96
|
+
"type": "WMS",
|
97
|
+
"serverType": "mapserver",
|
98
|
+
"layers": backgroundlayers,
|
99
|
+
"imageFormat": imageformat,
|
100
|
+
},
|
101
|
+
],
|
102
|
+
}
|
103
|
+
|
104
|
+
@view_config(route_name="pdfreport", renderer="json") # type: ignore
|
105
|
+
def get_report(self) -> pyramid.response.Response:
|
106
|
+
assert models.DBSession is not None
|
107
|
+
|
108
|
+
self.layername = self.request.matchdict["layername"]
|
109
|
+
layer_config = self.config["layers"].get(self.layername)
|
110
|
+
|
111
|
+
if layer_config is None:
|
112
|
+
raise HTTPBadRequest("Layer not found: " + self.layername)
|
113
|
+
|
114
|
+
multiple = layer_config.get("multiple", False)
|
115
|
+
ids = self.request.matchdict["ids"]
|
116
|
+
if multiple:
|
117
|
+
ids = ids.split(",")
|
118
|
+
|
119
|
+
features_ids = (
|
120
|
+
[self.layername + "." + id_ for id_ in ids] if multiple else [self.layername + "." + ids]
|
121
|
+
)
|
122
|
+
|
123
|
+
if layer_config["check_credentials"]:
|
124
|
+
# FIXME: support of mapserver groups
|
125
|
+
ogc_server = (
|
126
|
+
models.DBSession.query(main.OGCServer)
|
127
|
+
.filter(main.OGCServer.name == layer_config["ogc_server"])
|
128
|
+
.one()
|
129
|
+
)
|
130
|
+
ogc_server_ids = [ogc_server]
|
131
|
+
|
132
|
+
private_layers_object = get_private_layers(ogc_server_ids)
|
133
|
+
private_layers_names = [private_layers_object[oid].name for oid in private_layers_object]
|
134
|
+
|
135
|
+
protected_layers_object = get_protected_layers(self.request.user, ogc_server_ids)
|
136
|
+
protected_layers_names = [protected_layers_object[oid].name for oid in protected_layers_object]
|
137
|
+
|
138
|
+
if self.layername in private_layers_names and self.layername not in protected_layers_names:
|
139
|
+
raise HTTPForbidden
|
140
|
+
|
141
|
+
srs = layer_config["srs"]
|
142
|
+
|
143
|
+
mapserv_url = self.request.route_url(
|
144
|
+
"mapserverproxy", _query={"ogcserver": layer_config["ogc_server"]}
|
145
|
+
)
|
146
|
+
url = Url(mapserv_url)
|
147
|
+
url.add_query(
|
148
|
+
{
|
149
|
+
"service": "WFS",
|
150
|
+
"version": "1.1.0",
|
151
|
+
"outputformat": "gml3",
|
152
|
+
"request": "GetFeature",
|
153
|
+
"typeName": self.layername,
|
154
|
+
"featureid": ",".join(features_ids),
|
155
|
+
"srsName": srs,
|
156
|
+
}
|
157
|
+
)
|
158
|
+
vector_request_url = url.url()
|
159
|
+
|
160
|
+
spec = layer_config["spec"]
|
161
|
+
if spec is None:
|
162
|
+
spec = {
|
163
|
+
"layout": self.layername,
|
164
|
+
"outputFormat": "pdf",
|
165
|
+
"attributes": {"ids": [{"id": id_} for id_ in ids]} if multiple else {"id": id},
|
166
|
+
}
|
167
|
+
map_config = layer_config.get("map")
|
168
|
+
if map_config is not None:
|
169
|
+
spec["attributes"]["map"] = self._build_map(mapserv_url, vector_request_url, srs, map_config)
|
170
|
+
|
171
|
+
maps_config = layer_config.get("maps")
|
172
|
+
if maps_config is not None:
|
173
|
+
spec["attributes"]["maps"] = []
|
174
|
+
for map_config in maps_config:
|
175
|
+
spec["attributes"]["maps"].append(
|
176
|
+
self._build_map(mapserv_url, vector_request_url, srs, map_config)
|
177
|
+
)
|
178
|
+
else:
|
179
|
+
datasource = layer_config.get("datasource", True)
|
180
|
+
if multiple and datasource:
|
181
|
+
data = dumps(layer_config["data"])
|
182
|
+
data_list = [
|
183
|
+
loads(
|
184
|
+
data
|
185
|
+
% {
|
186
|
+
"layername": self.layername,
|
187
|
+
"id": id_,
|
188
|
+
"srs": srs,
|
189
|
+
"mapserv_url": mapserv_url,
|
190
|
+
"vector_request_url": vector_request_url,
|
191
|
+
}
|
192
|
+
)
|
193
|
+
for id_ in ids
|
194
|
+
]
|
195
|
+
self.walker(spec, "%(datasource)s", data_list)
|
196
|
+
spec = loads(
|
197
|
+
dumps(spec)
|
198
|
+
% {
|
199
|
+
"layername": self.layername,
|
200
|
+
"srs": srs,
|
201
|
+
"mapserv_url": mapserv_url,
|
202
|
+
"vector_request_url": vector_request_url,
|
203
|
+
}
|
204
|
+
)
|
205
|
+
elif multiple:
|
206
|
+
spec = loads(
|
207
|
+
dumps(spec)
|
208
|
+
% {
|
209
|
+
"layername": self.layername,
|
210
|
+
"ids": ",".join(ids),
|
211
|
+
"srs": srs,
|
212
|
+
"mapserv_url": mapserv_url,
|
213
|
+
"vector_request_url": vector_request_url,
|
214
|
+
}
|
215
|
+
)
|
216
|
+
else:
|
217
|
+
spec = loads(
|
218
|
+
dumps(spec)
|
219
|
+
% {
|
220
|
+
"layername": self.layername,
|
221
|
+
"id": ids,
|
222
|
+
"srs": srs,
|
223
|
+
"mapserv_url": mapserv_url,
|
224
|
+
"vector_request_url": vector_request_url,
|
225
|
+
}
|
226
|
+
)
|
227
|
+
|
228
|
+
return self._do_print(spec)
|
229
|
+
|
230
|
+
def walker(self, spec: dict[str, Any] | list[dict[str, Any]], name: str, value: Any) -> None:
|
231
|
+
if isinstance(spec, dict):
|
232
|
+
for k, v in spec.items():
|
233
|
+
if isinstance(v, str):
|
234
|
+
if v == name:
|
235
|
+
spec[k] = value
|
236
|
+
else:
|
237
|
+
self.walker(v, name, value)
|
238
|
+
|
239
|
+
if isinstance(spec, list):
|
240
|
+
for k2, v2 in enumerate(spec):
|
241
|
+
if isinstance(v2, str):
|
242
|
+
if v2 == name:
|
243
|
+
spec[k2] = value
|
244
|
+
else:
|
245
|
+
self.walker(v2, name, value)
|