c2cgeoportal-geoportal 2.3.5.80__py3-none-any.whl → 2.9rc45__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 +77 -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 +172 -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 +513 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-qgis.yaml +21 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml +65 -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 +16 -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 +78 -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 +304 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_create_template/tests/test_testapp.py +48 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/.CONST_vars.yaml.swp +0 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_config-schema.yaml +927 -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 +83 -0
- c2cgeoportal_geoportal/scripts/manage_users.py +140 -0
- c2cgeoportal_geoportal/scripts/pcreate.py +296 -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 +209 -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 +684 -0
- c2cgeoportal_geoportal/views/mapserverproxy.py +234 -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 +192 -0
- c2cgeoportal_geoportal/views/proxy.py +261 -0
- c2cgeoportal_geoportal/views/raster.py +233 -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.9rc45.dist-info}/METADATA +21 -24
- c2cgeoportal_geoportal-2.9rc45.dist-info/RECORD +193 -0
- {c2cgeoportal_geoportal-2.3.5.80.dist-info → c2cgeoportal_geoportal-2.9rc45.dist-info}/WHEEL +1 -1
- c2cgeoportal_geoportal-2.9rc45.dist-info/entry_points.txt +28 -0
- c2cgeoportal_geoportal-2.9rc45.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,233 @@
|
|
1
|
+
# Copyright (c) 2012-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 decimal
|
30
|
+
import json
|
31
|
+
import logging
|
32
|
+
import math
|
33
|
+
import os
|
34
|
+
import traceback
|
35
|
+
import urllib.parse
|
36
|
+
from json.decoder import JSONDecodeError
|
37
|
+
from typing import TYPE_CHECKING, Any
|
38
|
+
|
39
|
+
import numpy
|
40
|
+
import pyramid.request
|
41
|
+
import requests
|
42
|
+
import zope.event.classhandler
|
43
|
+
from pyramid.httpexceptions import HTTPBadRequest, HTTPInternalServerError, HTTPNotFound
|
44
|
+
from pyramid.view import view_config
|
45
|
+
from rasterio.io import DatasetReader
|
46
|
+
|
47
|
+
from c2cgeoportal_commons.models import InvalidateCacheEvent
|
48
|
+
from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
|
49
|
+
|
50
|
+
if TYPE_CHECKING:
|
51
|
+
import fiona.collection
|
52
|
+
|
53
|
+
_LOG = logging.getLogger(__name__)
|
54
|
+
|
55
|
+
|
56
|
+
class Raster:
|
57
|
+
"""All the view concerned the raster (point, not the profile profile)."""
|
58
|
+
|
59
|
+
data: dict[str, "fiona.collection.Collection"] = {}
|
60
|
+
|
61
|
+
def __init__(self, request: pyramid.request.Request):
|
62
|
+
self.request = request
|
63
|
+
self.rasters = self.request.registry.settings["raster"]
|
64
|
+
|
65
|
+
@zope.event.classhandler.handler(InvalidateCacheEvent) # type: ignore
|
66
|
+
def handle(event: InvalidateCacheEvent) -> None:
|
67
|
+
del event
|
68
|
+
for _, v in Raster.data.items():
|
69
|
+
v.close()
|
70
|
+
Raster.data = {}
|
71
|
+
|
72
|
+
def _get_required_finite_float_param(self, name: str) -> float:
|
73
|
+
if name not in self.request.params:
|
74
|
+
raise HTTPBadRequest(f"'{name}' should be in the query string parameters")
|
75
|
+
try:
|
76
|
+
result = float(self.request.params[name])
|
77
|
+
except ValueError:
|
78
|
+
raise HTTPBadRequest( # pylint: disable=raise-missing-from
|
79
|
+
f"'{name}' ({self.request.params[name]}) parameters should be a number"
|
80
|
+
)
|
81
|
+
if not math.isfinite(result):
|
82
|
+
raise HTTPBadRequest(
|
83
|
+
f"'{name}' ({self.request.params[name]}) parameters should be a finite number"
|
84
|
+
)
|
85
|
+
return result
|
86
|
+
|
87
|
+
@view_config(route_name="raster", renderer="fast_json") # type: ignore[misc]
|
88
|
+
def raster(self) -> dict[str, Any]:
|
89
|
+
lon = self._get_required_finite_float_param("lon")
|
90
|
+
lat = self._get_required_finite_float_param("lat")
|
91
|
+
|
92
|
+
if "layers" in self.request.params:
|
93
|
+
rasters = {}
|
94
|
+
layers = self.request.params["layers"].split(",")
|
95
|
+
for layer in layers:
|
96
|
+
if layer in self.rasters:
|
97
|
+
rasters[layer] = self.rasters[layer]
|
98
|
+
else:
|
99
|
+
raise HTTPNotFound(f"Layer {layer} not found")
|
100
|
+
else:
|
101
|
+
rasters = self.rasters
|
102
|
+
layers = list(rasters.keys())
|
103
|
+
layers.sort()
|
104
|
+
|
105
|
+
result: dict[str, Any] = {}
|
106
|
+
|
107
|
+
service_layers = [layer for layer in layers if rasters[layer].get("type") == "external_url"]
|
108
|
+
|
109
|
+
if len(service_layers) > 0:
|
110
|
+
for layer in service_layers:
|
111
|
+
service_result: dict[str, Any] = self._get_service_data(layer, lat, lon, rasters)
|
112
|
+
result.update(service_result)
|
113
|
+
|
114
|
+
for ref in list(rasters.keys()):
|
115
|
+
if ref not in service_layers:
|
116
|
+
result[ref] = self._get_raster_value(rasters[ref], ref, lon, lat)
|
117
|
+
|
118
|
+
set_common_headers(self.request, "raster", Cache.PUBLIC_NO)
|
119
|
+
return result
|
120
|
+
|
121
|
+
def _get_data(self, layer: dict[str, Any], name: str) -> "fiona.collection.Collection":
|
122
|
+
if name not in self.data:
|
123
|
+
path = layer["file"]
|
124
|
+
if layer.get("type", "shp_index") == "shp_index":
|
125
|
+
# Avoid loading if not needed
|
126
|
+
from fiona.collection import Collection # pylint: disable=import-outside-toplevel
|
127
|
+
|
128
|
+
self.data[name] = Collection(path)
|
129
|
+
elif layer.get("type") == "gdal":
|
130
|
+
# Avoid loading if not needed
|
131
|
+
import rasterio # pylint: disable=import-outside-toplevel
|
132
|
+
|
133
|
+
self.data[name] = rasterio.open(path)
|
134
|
+
|
135
|
+
return self.data[name]
|
136
|
+
|
137
|
+
def _get_raster_value(
|
138
|
+
self, layer: dict[str, Any], name: str, lon: float, lat: float
|
139
|
+
) -> decimal.Decimal | None:
|
140
|
+
data = self._get_data(layer, name)
|
141
|
+
type_ = layer.get("type", "shp_index")
|
142
|
+
if type_ == "shp_index":
|
143
|
+
tiles = list(data.filter(mask={"type": "Point", "coordinates": [lon, lat]}))
|
144
|
+
|
145
|
+
if not tiles:
|
146
|
+
return None
|
147
|
+
|
148
|
+
path = os.path.join(os.path.dirname(layer["file"]), tiles[0]["properties"]["location"])
|
149
|
+
|
150
|
+
# Avoid loading if not needed
|
151
|
+
import rasterio # pylint: disable=import-outside-toplevel
|
152
|
+
|
153
|
+
with rasterio.open(path) as dataset:
|
154
|
+
result = self._get_value(layer, name, dataset, lon, lat)
|
155
|
+
elif type_ == "gdal":
|
156
|
+
result = self._get_value(layer, name, data, lon, lat)
|
157
|
+
else:
|
158
|
+
raise ValueError("Unsupported type " + type_)
|
159
|
+
|
160
|
+
result_d = None
|
161
|
+
if "round" in layer and result is not None:
|
162
|
+
result_d = self._round(result, layer["round"])
|
163
|
+
elif result is not None:
|
164
|
+
result_d = decimal.Decimal(str(result))
|
165
|
+
|
166
|
+
return result_d
|
167
|
+
|
168
|
+
@staticmethod
|
169
|
+
def _get_value(
|
170
|
+
layer: dict[str, Any], name: str, dataset: DatasetReader, lon: float, lat: float
|
171
|
+
) -> numpy.float32 | None:
|
172
|
+
index = dataset.index(lon, lat)
|
173
|
+
|
174
|
+
shape = dataset.shape
|
175
|
+
result: numpy.float32 | None
|
176
|
+
if 0 <= index[0] < shape[0] and 0 <= index[1] < shape[1]:
|
177
|
+
|
178
|
+
def get_index(index_: int) -> tuple[int, int]:
|
179
|
+
return index_, index_ + 1
|
180
|
+
|
181
|
+
result = dataset.read(1, window=(get_index(index[0]), get_index(index[1])))[0][0]
|
182
|
+
result = None if result == layer.get("nodata", dataset.nodata) else result
|
183
|
+
else:
|
184
|
+
_LOG.debug(
|
185
|
+
"Out of index for layer: %s (%s), lon/lat: %dx%d, index: %dx%d, shape: %dx%d.",
|
186
|
+
name,
|
187
|
+
layer["file"],
|
188
|
+
lon,
|
189
|
+
lat,
|
190
|
+
index[0],
|
191
|
+
index[1],
|
192
|
+
dataset.shape[0],
|
193
|
+
dataset.shape[1],
|
194
|
+
)
|
195
|
+
result = None
|
196
|
+
|
197
|
+
return result
|
198
|
+
|
199
|
+
def _get_service_data(
|
200
|
+
self, layer: str, lat: float, lon: float, rasters: dict[str, Any]
|
201
|
+
) -> dict[str, Any]:
|
202
|
+
request = (
|
203
|
+
f"{rasters[layer]['url']}/height?{urllib.parse.urlencode({'easting': lon, 'northing': lat})}"
|
204
|
+
)
|
205
|
+
_LOG.info("Doing height request to %s", request)
|
206
|
+
response = requests.get(request, timeout=10)
|
207
|
+
if not response.ok:
|
208
|
+
_LOG.error("Elevation request %s failed with status code %s", request, response.status_code)
|
209
|
+
raise HTTPInternalServerError(
|
210
|
+
f"Failed to fetch elevation data from the internal request: \
|
211
|
+
{response.status_code} {response.reason}"
|
212
|
+
)
|
213
|
+
|
214
|
+
try:
|
215
|
+
result = json.loads(response.content).get(rasters[layer]["elevation_name"])
|
216
|
+
except (TypeError, JSONDecodeError) as exc:
|
217
|
+
_LOG.exception("Height request to %s failed", request)
|
218
|
+
raise HTTPInternalServerError("Failed to decode JSON response from the internal request") from exc
|
219
|
+
|
220
|
+
set_common_headers(self.request, "raster", Cache.PUBLIC_NO)
|
221
|
+
|
222
|
+
return {layer: result}
|
223
|
+
|
224
|
+
@staticmethod
|
225
|
+
def _round(value: numpy.float32, round_to: float) -> decimal.Decimal | None:
|
226
|
+
if value is not None:
|
227
|
+
decimal_value = decimal.Decimal(str(value))
|
228
|
+
try:
|
229
|
+
return decimal_value.quantize(decimal.Decimal(str(round_to)))
|
230
|
+
except decimal.InvalidOperation:
|
231
|
+
_LOG.info("Error on rounding %s: %s", decimal_value, traceback.format_exc())
|
232
|
+
return decimal_value
|
233
|
+
return None
|
@@ -0,0 +1,73 @@
|
|
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 ast
|
30
|
+
import logging
|
31
|
+
|
32
|
+
import pyramid.request
|
33
|
+
import pyramid.response
|
34
|
+
from pyramid.httpexceptions import HTTPBadRequest
|
35
|
+
from pyramid.view import view_config
|
36
|
+
|
37
|
+
from c2cgeoportal_geoportal.lib.common_headers import Cache
|
38
|
+
from c2cgeoportal_geoportal.views.proxy import Proxy
|
39
|
+
|
40
|
+
_LOG = logging.getLogger(__name__)
|
41
|
+
|
42
|
+
|
43
|
+
class ResourceProxy(Proxy):
|
44
|
+
"""All the views concerned the resources (it's a kind of proxy)."""
|
45
|
+
|
46
|
+
def __init__(self, request: pyramid.request.Request):
|
47
|
+
Proxy.__init__(self, request)
|
48
|
+
self.request = request
|
49
|
+
self.settings = request.registry.settings.get("resourceproxy", {})
|
50
|
+
|
51
|
+
@view_config(route_name="resourceproxy") # type: ignore[misc]
|
52
|
+
def proxy(self) -> pyramid.response.Response:
|
53
|
+
target = self.request.params.get("target", "")
|
54
|
+
targets = self.settings.get("targets", [])
|
55
|
+
if target in targets:
|
56
|
+
url = targets[target]
|
57
|
+
values = ast.literal_eval(self.request.params.get("values"))
|
58
|
+
url = url % values
|
59
|
+
|
60
|
+
response = self._proxy(url=url)
|
61
|
+
|
62
|
+
cache_control = Cache.PRIVATE_NO
|
63
|
+
content_type = response.headers["Content-Type"]
|
64
|
+
|
65
|
+
response = self._build_response(
|
66
|
+
response, response.content, cache_control, "externalresource", content_type=content_type
|
67
|
+
)
|
68
|
+
for header in response.headers.keys():
|
69
|
+
if header not in self.settings["allowed_headers"]:
|
70
|
+
response.headers.pop(header)
|
71
|
+
return response
|
72
|
+
_LOG.warning("Target URL not found: %s", target)
|
73
|
+
return HTTPBadRequest("URL not allowed")
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# Copyright (c) 2013-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 random
|
31
|
+
import string
|
32
|
+
from datetime import datetime
|
33
|
+
from typing import cast
|
34
|
+
from urllib.parse import urlparse
|
35
|
+
|
36
|
+
import pyramid.request
|
37
|
+
from pyramid.httpexceptions import HTTPBadRequest, HTTPFound, HTTPInternalServerError, HTTPNotFound
|
38
|
+
from pyramid.view import view_config
|
39
|
+
|
40
|
+
from c2cgeoportal_commons.lib.email_ import send_email_config
|
41
|
+
from c2cgeoportal_commons.models import DBSession, static
|
42
|
+
from c2cgeoportal_geoportal import is_allowed_url
|
43
|
+
from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
|
44
|
+
|
45
|
+
logger = logging.getLogger(__name__)
|
46
|
+
|
47
|
+
|
48
|
+
class Shortener:
|
49
|
+
"""All the views conserne the shortener."""
|
50
|
+
|
51
|
+
def __init__(self, request: pyramid.request.Request):
|
52
|
+
self.request = request
|
53
|
+
self.settings = request.registry.settings.get("shortener", {})
|
54
|
+
self.short_bases = [self.request.route_url("shortener_get", ref="")]
|
55
|
+
if "base_url" in self.settings:
|
56
|
+
self.short_bases.append(self.settings["base_url"])
|
57
|
+
|
58
|
+
@view_config(route_name="shortener_get") # type: ignore[misc]
|
59
|
+
def get(self) -> HTTPFound:
|
60
|
+
assert DBSession is not None
|
61
|
+
|
62
|
+
ref = self.request.matchdict["ref"]
|
63
|
+
short_urls = DBSession.query(static.Shorturl).filter(static.Shorturl.ref == ref).all()
|
64
|
+
|
65
|
+
if len(short_urls) != 1:
|
66
|
+
raise HTTPNotFound(f"Ref '{ref!s}' not found")
|
67
|
+
|
68
|
+
short_urls[0].nb_hits += 1
|
69
|
+
short_urls[0].last_hit = datetime.now()
|
70
|
+
|
71
|
+
set_common_headers(self.request, "shortener", Cache.PUBLIC_NO)
|
72
|
+
return HTTPFound(location=short_urls[0].url)
|
73
|
+
|
74
|
+
@view_config(route_name="shortener_create", renderer="json") # type: ignore[misc]
|
75
|
+
def create(self) -> dict[str, str]:
|
76
|
+
assert DBSession is not None
|
77
|
+
|
78
|
+
if "url" not in self.request.params:
|
79
|
+
raise HTTPBadRequest("The parameter url is required")
|
80
|
+
|
81
|
+
url = self.request.params["url"]
|
82
|
+
|
83
|
+
# see: https://httpd.apache.org/docs/2.2/mod/core.html#limitrequestline
|
84
|
+
if len(url) > 8190:
|
85
|
+
raise HTTPBadRequest(f"The parameter url is too long ({len(url)} > {8190})")
|
86
|
+
|
87
|
+
allowed_hosts = self.settings.get("allowed_hosts", [])
|
88
|
+
url_hostname, ok = is_allowed_url(self.request, url, allowed_hosts)
|
89
|
+
if not ok:
|
90
|
+
message = (
|
91
|
+
f"Invalid requested host '{url_hostname}', "
|
92
|
+
f"is not the current host '{self.request.host}' "
|
93
|
+
f"or part of allowed hosts: {', '.join(allowed_hosts)}"
|
94
|
+
)
|
95
|
+
logging.debug(message)
|
96
|
+
raise HTTPBadRequest(message)
|
97
|
+
|
98
|
+
shortened = False
|
99
|
+
|
100
|
+
uri_parts = urlparse(url)
|
101
|
+
for base in self.short_bases:
|
102
|
+
base_parts = urlparse(base)
|
103
|
+
if uri_parts.path.startswith(base_parts.path):
|
104
|
+
shortened = True
|
105
|
+
ref = uri_parts.path.split("/")[-1]
|
106
|
+
|
107
|
+
tries = 0
|
108
|
+
while not shortened:
|
109
|
+
ref = "".join(
|
110
|
+
random.choice(string.ascii_letters + string.digits) # nosec
|
111
|
+
for i in range(self.settings.get("length", 4))
|
112
|
+
)
|
113
|
+
test_url = DBSession.query(static.Shorturl).filter(static.Shorturl.ref == ref).all()
|
114
|
+
if not test_url:
|
115
|
+
break
|
116
|
+
tries += 1
|
117
|
+
if tries > 20:
|
118
|
+
message = "No free ref found, considered to increase the length"
|
119
|
+
logger.error(message)
|
120
|
+
raise HTTPInternalServerError(message)
|
121
|
+
|
122
|
+
user_email = cast(static.User, self.request.user).email if self.request.user is not None else None
|
123
|
+
email = self.request.params.get("email")
|
124
|
+
if not shortened:
|
125
|
+
short_url = static.Shorturl()
|
126
|
+
short_url.url = url
|
127
|
+
short_url.ref = ref
|
128
|
+
short_url.creator_email = user_email
|
129
|
+
short_url.creation = datetime.now()
|
130
|
+
short_url.nb_hits = 0
|
131
|
+
|
132
|
+
DBSession.add(short_url)
|
133
|
+
|
134
|
+
if "base_url" in self.settings:
|
135
|
+
s_url = self.settings["base_url"] + ref
|
136
|
+
else:
|
137
|
+
s_url = self.request.route_url("shortener_get", ref=ref)
|
138
|
+
|
139
|
+
if email is not None:
|
140
|
+
send_email_config(
|
141
|
+
self.request.registry.settings,
|
142
|
+
"shortener",
|
143
|
+
email,
|
144
|
+
full_url=url,
|
145
|
+
short_url=s_url,
|
146
|
+
message=self.request.params.get("message", ""),
|
147
|
+
application_url=self.request.route_url("base"),
|
148
|
+
current_url=self.request.current_route_url(),
|
149
|
+
)
|
150
|
+
|
151
|
+
set_common_headers(self.request, "shortener", Cache.PRIVATE_NO)
|
152
|
+
return {"short_url": s_url}
|