c2cgeoportal-geoportal 2.3.5.79__py3-none-any.whl → 2.9rc1__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 +302 -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.79.dist-info → c2cgeoportal_geoportal-2.9rc1.dist-info}/METADATA +21 -24
- c2cgeoportal_geoportal-2.9rc1.dist-info/RECORD +192 -0
- {c2cgeoportal_geoportal-2.3.5.79.dist-info → c2cgeoportal_geoportal-2.9rc1.dist-info}/WHEEL +1 -1
- c2cgeoportal_geoportal-2.9rc1.dist-info/entry_points.txt +28 -0
- c2cgeoportal_geoportal-2.9rc1.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.79.dist-info/DESCRIPTION.rst +0 -8
- c2cgeoportal_geoportal-2.3.5.79.dist-info/RECORD +0 -7
- c2cgeoportal_geoportal-2.3.5.79.dist-info/entry_points.txt +0 -22
- c2cgeoportal_geoportal-2.3.5.79.dist-info/metadata.json +0 -1
- c2cgeoportal_geoportal-2.3.5.79.dist-info/top_level.txt +0 -1
@@ -0,0 +1,170 @@
|
|
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 logging
|
30
|
+
from enum import Enum
|
31
|
+
from typing import Any, cast
|
32
|
+
|
33
|
+
import pyramid.request
|
34
|
+
import pyramid.response
|
35
|
+
|
36
|
+
from c2cgeoportal_geoportal.lib import is_intranet
|
37
|
+
|
38
|
+
_LOG = logging.getLogger(__name__)
|
39
|
+
|
40
|
+
|
41
|
+
class Cache(Enum):
|
42
|
+
"""Enumeration for the possible cache values."""
|
43
|
+
|
44
|
+
# For responses that do not depend on authentication
|
45
|
+
PUBLIC = 0
|
46
|
+
# For responses that do not depends on authentication
|
47
|
+
# We use a small cache (e.g. 10s) instead of no cache to protect the service against too high traffic.
|
48
|
+
PUBLIC_NO = 1
|
49
|
+
# For responses that can depend on authentication
|
50
|
+
PRIVATE = 2
|
51
|
+
# See PUBLIC_NO and PRIVATE
|
52
|
+
PRIVATE_NO = 3
|
53
|
+
|
54
|
+
|
55
|
+
CORS_METHODS = "HEAD, GET, POST, PUT, DELETE"
|
56
|
+
|
57
|
+
|
58
|
+
def _set_cors_headers(
|
59
|
+
request: pyramid.request.Request,
|
60
|
+
response: pyramid.response.Response,
|
61
|
+
service_name: str,
|
62
|
+
service_headers_settings: dict[str, Any],
|
63
|
+
credentials: bool,
|
64
|
+
) -> None:
|
65
|
+
"""Handle CORS requests, as specified in https://www.w3.org/TR/cors/."""
|
66
|
+
response.vary = (response.vary or ()) + ("Origin",)
|
67
|
+
|
68
|
+
if "Origin" not in request.headers:
|
69
|
+
return # Not a CORS request if this header is missing
|
70
|
+
origin = request.headers["Origin"]
|
71
|
+
|
72
|
+
if request.method == "OPTIONS" and "Access-Control-Request-Method" not in request.headers:
|
73
|
+
_LOG.warning("CORS preflight query missing the Access-Control-Request-Method header")
|
74
|
+
return
|
75
|
+
|
76
|
+
allowed_origins = cast(list[str], service_headers_settings.get("access_control_allow_origin", []))
|
77
|
+
if origin not in allowed_origins:
|
78
|
+
if "*" in allowed_origins:
|
79
|
+
origin = "*"
|
80
|
+
credentials = False # Force no credentials
|
81
|
+
else:
|
82
|
+
_LOG.warning("CORS query not allowed for origin=%s, service=%s", origin, service_name)
|
83
|
+
return
|
84
|
+
|
85
|
+
response.headers.update(
|
86
|
+
{"Access-Control-Allow-Origin": origin, "Access-Control-Allow-Methods": CORS_METHODS}
|
87
|
+
)
|
88
|
+
|
89
|
+
max_age = service_headers_settings.get("access_control_max_age", 3600)
|
90
|
+
response.headers["Access-Control-Max-Age"] = str(max_age)
|
91
|
+
|
92
|
+
if credentials:
|
93
|
+
response.headers["Access-Control-Allow-Credentials"] = "true"
|
94
|
+
|
95
|
+
if request.method != "OPTIONS":
|
96
|
+
return
|
97
|
+
|
98
|
+
response.cache_control.max_age = max_age
|
99
|
+
|
100
|
+
if not service_headers_settings or "access_control_allow_origin" not in service_headers_settings:
|
101
|
+
_LOG.warning("CORS query not configured for service=%s", service_name)
|
102
|
+
return
|
103
|
+
|
104
|
+
requested_headers = request.headers.get("Access-Control-Request-Headers", False)
|
105
|
+
if requested_headers:
|
106
|
+
# For the moment, we allow all requested headers
|
107
|
+
response.headers["Access-Control-Allow-Headers"] = requested_headers
|
108
|
+
|
109
|
+
# If we start using headers in responses, we'll have to add
|
110
|
+
# Access-Control-Expose-Headers
|
111
|
+
|
112
|
+
|
113
|
+
def _set_common_headers(
|
114
|
+
request: pyramid.request.Request,
|
115
|
+
response: pyramid.response.Response,
|
116
|
+
service_headers_settings: dict[str, dict[str, str]],
|
117
|
+
cache: Cache,
|
118
|
+
content_type: str | None,
|
119
|
+
) -> pyramid.response.Response:
|
120
|
+
"""Set the common headers."""
|
121
|
+
|
122
|
+
response.headers.update(service_headers_settings.get("headers", {}))
|
123
|
+
|
124
|
+
if cache in (Cache.PRIVATE, Cache.PRIVATE_NO):
|
125
|
+
response.vary = (response.vary or ()) + ("Cookie",)
|
126
|
+
|
127
|
+
maxage = (
|
128
|
+
service_headers_settings.get("cache_control_max_age", 3600)
|
129
|
+
if cache in (Cache.PUBLIC, Cache.PRIVATE)
|
130
|
+
else service_headers_settings.get("cache_control_max_age_nocache", 10)
|
131
|
+
)
|
132
|
+
response.cache_control.max_age = maxage
|
133
|
+
if maxage == 0:
|
134
|
+
response.cache_control.no_cache = True
|
135
|
+
response.cache_control.no_store = True
|
136
|
+
elif cache in (Cache.PUBLIC, Cache.PUBLIC_NO):
|
137
|
+
response.cache_control.public = True
|
138
|
+
elif cache in (Cache.PRIVATE, Cache.PRIVATE_NO):
|
139
|
+
if hasattr(request, "user") and request.user is not None or is_intranet(request):
|
140
|
+
response.cache_control.private = True
|
141
|
+
else:
|
142
|
+
response.cache_control.public = True
|
143
|
+
else:
|
144
|
+
raise Exception("Invalid cache type") # pylint: disable=broad-exception-raised
|
145
|
+
|
146
|
+
if content_type is not None:
|
147
|
+
response.content_type = content_type
|
148
|
+
|
149
|
+
return response
|
150
|
+
|
151
|
+
|
152
|
+
def set_common_headers(
|
153
|
+
request: pyramid.request.Request,
|
154
|
+
service_name: str,
|
155
|
+
cache: Cache,
|
156
|
+
response: pyramid.response.Response = None,
|
157
|
+
credentials: bool = True,
|
158
|
+
content_type: str | None = None,
|
159
|
+
) -> pyramid.response.Response:
|
160
|
+
"""Set the common headers."""
|
161
|
+
if response is None:
|
162
|
+
response = request.response
|
163
|
+
|
164
|
+
headers_settings = request.registry.settings.get("headers", {})
|
165
|
+
service_headers_settings = headers_settings.get(service_name, {})
|
166
|
+
|
167
|
+
_set_cors_headers(request, response, service_name, service_headers_settings, credentials)
|
168
|
+
if request.method == "OPTIONS":
|
169
|
+
return response
|
170
|
+
return _set_common_headers(request, response, service_headers_settings, cache, content_type)
|
@@ -0,0 +1,266 @@
|
|
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 random
|
29
|
+
import threading
|
30
|
+
import warnings
|
31
|
+
from collections.abc import Iterable
|
32
|
+
from typing import Any, Union
|
33
|
+
|
34
|
+
import sqlalchemy.ext.declarative
|
35
|
+
from papyrus.geo_interface import GeoInterface
|
36
|
+
from sqlalchemy import Column, Integer, MetaData, Table
|
37
|
+
from sqlalchemy.exc import SAWarning
|
38
|
+
from sqlalchemy.orm import relationship
|
39
|
+
from sqlalchemy.orm.session import Session
|
40
|
+
from sqlalchemy.orm.util import class_mapper
|
41
|
+
|
42
|
+
from c2cgeoportal_geoportal.lib.caching import get_region
|
43
|
+
|
44
|
+
CACHE_REGION_OBJ = get_region("obj")
|
45
|
+
SQL_GEOMETRY_COLUMNS = """
|
46
|
+
SELECT srid, type
|
47
|
+
FROM geometry_columns
|
48
|
+
WHERE
|
49
|
+
f_table_schema = :table_schema AND
|
50
|
+
f_table_name = :table_name AND
|
51
|
+
f_geometry_column = :geometry_column
|
52
|
+
"""
|
53
|
+
|
54
|
+
|
55
|
+
class _AssociationProxy:
|
56
|
+
# A specific "association proxy" implementation
|
57
|
+
|
58
|
+
def __init__(self, target: str, value_attr: str, nullable: bool = True, order_by: str | None = None):
|
59
|
+
self.target = target
|
60
|
+
self.value_attr = value_attr
|
61
|
+
self.nullable = nullable
|
62
|
+
self.order_by = order_by
|
63
|
+
|
64
|
+
def __get__(
|
65
|
+
self,
|
66
|
+
obj: sqlalchemy.ext.declarative.ConcreteBase,
|
67
|
+
type_: sqlalchemy.ext.declarative.DeclarativeMeta | None = None,
|
68
|
+
) -> Union["_AssociationProxy", str] | None:
|
69
|
+
if obj is None:
|
70
|
+
# For "hybrid" descriptors that work both at the instance
|
71
|
+
# and class levels we could return an SQL expression here.
|
72
|
+
# The code of hybrid_property in SQLAlchemy illustrates
|
73
|
+
# how to do that.
|
74
|
+
return self
|
75
|
+
target = getattr(obj, self.target)
|
76
|
+
return getattr(target, self.value_attr) if target else None
|
77
|
+
|
78
|
+
def __set__(self, obj: str, val: str) -> None:
|
79
|
+
from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
|
80
|
+
|
81
|
+
assert DBSession is not None
|
82
|
+
|
83
|
+
o = getattr(obj, self.target)
|
84
|
+
# if the obj as no child object or if the child object
|
85
|
+
# does not correspond to the new value then we need to
|
86
|
+
# read a new child object from the database
|
87
|
+
if not o or getattr(o, self.value_attr) != val:
|
88
|
+
relationship_property = class_mapper(obj.__class__).get_property(self.target)
|
89
|
+
child_cls = relationship_property.argument
|
90
|
+
o = DBSession.query(child_cls).filter(getattr(child_cls, self.value_attr) == val).first()
|
91
|
+
setattr(obj, self.target, o)
|
92
|
+
|
93
|
+
|
94
|
+
def _get_schema(tablename: str) -> tuple[str, str]:
|
95
|
+
if "." in tablename:
|
96
|
+
schema, tablename = tablename.split(".", 1)
|
97
|
+
else:
|
98
|
+
schema = "public"
|
99
|
+
|
100
|
+
return tablename, schema
|
101
|
+
|
102
|
+
|
103
|
+
_get_table_lock = threading.RLock()
|
104
|
+
|
105
|
+
|
106
|
+
def get_table(
|
107
|
+
tablename: str,
|
108
|
+
schema: str | None = None,
|
109
|
+
session: Session | None = None,
|
110
|
+
primary_key: str | None = None,
|
111
|
+
) -> Table:
|
112
|
+
"""Build an SQLAlchemy table."""
|
113
|
+
if schema is None:
|
114
|
+
tablename, schema = _get_schema(tablename)
|
115
|
+
|
116
|
+
if session is not None:
|
117
|
+
assert session.bind is not None
|
118
|
+
engine = session.bind.engine
|
119
|
+
metadata = MetaData()
|
120
|
+
else:
|
121
|
+
from c2cgeoportal_commons.models import Base # pylint: disable=import-outside-toplevel
|
122
|
+
from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
|
123
|
+
|
124
|
+
assert DBSession is not None
|
125
|
+
assert DBSession.bind is not None
|
126
|
+
|
127
|
+
engine = DBSession.bind.engine
|
128
|
+
metadata = Base.metadata
|
129
|
+
|
130
|
+
# create table and reflect it
|
131
|
+
with warnings.catch_warnings():
|
132
|
+
warnings.filterwarnings("ignore", "Did not recognize type 'geometry' of column", SAWarning)
|
133
|
+
args = []
|
134
|
+
if primary_key is not None:
|
135
|
+
# Ensure we have a primary key to be able to edit views
|
136
|
+
args.append(Column(primary_key, Integer, primary_key=True))
|
137
|
+
with _get_table_lock:
|
138
|
+
table = Table(
|
139
|
+
tablename,
|
140
|
+
metadata,
|
141
|
+
*args,
|
142
|
+
schema=schema,
|
143
|
+
autoload_with=engine,
|
144
|
+
keep_existing=True,
|
145
|
+
)
|
146
|
+
return table
|
147
|
+
|
148
|
+
|
149
|
+
@CACHE_REGION_OBJ.cache_on_arguments()
|
150
|
+
def get_class(
|
151
|
+
tablename: str,
|
152
|
+
exclude_properties: list[str] | None = None,
|
153
|
+
primary_key: str | None = None,
|
154
|
+
attributes_order: list[str] | None = None,
|
155
|
+
enumerations_config: dict[str, str] | None = None,
|
156
|
+
readonly_attributes: list[str] | None = None,
|
157
|
+
) -> type:
|
158
|
+
"""
|
159
|
+
Get the SQLAlchemy mapped class for "tablename".
|
160
|
+
|
161
|
+
If no class exists for "tablename" one is created, and added to the cache. "tablename" must reference a
|
162
|
+
valid string. If there is no table identified by tablename in the database a NoSuchTableError SQLAlchemy
|
163
|
+
exception is raised.
|
164
|
+
"""
|
165
|
+
|
166
|
+
tablename, schema = _get_schema(tablename)
|
167
|
+
|
168
|
+
table = get_table(tablename, schema, None, primary_key=primary_key)
|
169
|
+
|
170
|
+
# create the mapped class
|
171
|
+
cls = _create_class(
|
172
|
+
table,
|
173
|
+
exclude_properties=exclude_properties,
|
174
|
+
attributes_order=attributes_order,
|
175
|
+
enumerations_config=enumerations_config,
|
176
|
+
readonly_attributes=readonly_attributes,
|
177
|
+
)
|
178
|
+
|
179
|
+
return cls
|
180
|
+
|
181
|
+
|
182
|
+
def _create_class(
|
183
|
+
table: Table,
|
184
|
+
exclude_properties: Iterable[str] | None = None,
|
185
|
+
attributes_order: list[str] | None = None,
|
186
|
+
enumerations_config: dict[str, str] | None = None,
|
187
|
+
readonly_attributes: list[str] | None = None,
|
188
|
+
pk_name: str | None = None,
|
189
|
+
) -> type:
|
190
|
+
from c2cgeoportal_commons.models import Base # pylint: disable=import-outside-toplevel
|
191
|
+
|
192
|
+
exclude_properties = exclude_properties or ()
|
193
|
+
attributes = {
|
194
|
+
"__table__": table,
|
195
|
+
"__mapper_args__": {"exclude_properties": exclude_properties},
|
196
|
+
"__attributes_order__": attributes_order,
|
197
|
+
"__enumerations_config__": enumerations_config,
|
198
|
+
}
|
199
|
+
if pk_name is not None:
|
200
|
+
attributes[pk_name] = Column(Integer, primary_key=True)
|
201
|
+
# The randint is to fix the SAWarning: This declarative base already contains a class with the same
|
202
|
+
# class name and module name
|
203
|
+
cls = type(
|
204
|
+
f"{table.name.capitalize()}_{random.randint(0, 9999999)}", # nosec
|
205
|
+
(GeoInterface, Base),
|
206
|
+
attributes,
|
207
|
+
)
|
208
|
+
|
209
|
+
for col in table.columns:
|
210
|
+
if col.name in (readonly_attributes or []):
|
211
|
+
col.info["readonly"] = True
|
212
|
+
if col.foreign_keys and col.name not in exclude_properties:
|
213
|
+
_add_association_proxy(cls, col)
|
214
|
+
|
215
|
+
return cls
|
216
|
+
|
217
|
+
|
218
|
+
def _add_association_proxy(cls: type[Any], col: sqlalchemy.sql.schema.Column[Any]) -> None:
|
219
|
+
if len(col.foreign_keys) != 1:
|
220
|
+
raise NotImplementedError
|
221
|
+
|
222
|
+
fk = next(iter(col.foreign_keys))
|
223
|
+
child_tablename, child_pk = fk.target_fullname.rsplit(".", 1)
|
224
|
+
child_cls = get_class(child_tablename)
|
225
|
+
|
226
|
+
try:
|
227
|
+
proxy = col.name[0 : col.name.rindex("_id")]
|
228
|
+
except ValueError:
|
229
|
+
proxy = col.name + "_"
|
230
|
+
|
231
|
+
# The following is a workaround for a bug in the geojson lib. The
|
232
|
+
# geojson lib indeed treats properties named "type" specifically
|
233
|
+
# (this is a GeoJSON keyword), and produces a UnicodeEncodeError
|
234
|
+
# if the property includes non-ascii characters.
|
235
|
+
if proxy == "type":
|
236
|
+
proxy = "type_"
|
237
|
+
|
238
|
+
rel = proxy + "_"
|
239
|
+
primaryjoin = getattr(cls, col.name) == getattr(child_cls, child_pk)
|
240
|
+
relationship_ = relationship(child_cls, primaryjoin=primaryjoin, lazy="immediate")
|
241
|
+
setattr(cls, rel, relationship_)
|
242
|
+
|
243
|
+
nullable = True
|
244
|
+
cls_column_property = getattr(cls, col.name).property
|
245
|
+
for column in cls_column_property.columns:
|
246
|
+
nullable = nullable and column.nullable
|
247
|
+
|
248
|
+
value_attr = "name"
|
249
|
+
order_by = value_attr
|
250
|
+
|
251
|
+
if cls.__enumerations_config__ and col.name in cls.__enumerations_config__:
|
252
|
+
enumeration_config = cls.__enumerations_config__[col.name]
|
253
|
+
if "value" in enumeration_config:
|
254
|
+
value_attr = enumeration_config["value"]
|
255
|
+
order_by = value_attr
|
256
|
+
if "order_by" in enumeration_config:
|
257
|
+
order_by = enumeration_config["order_by"]
|
258
|
+
|
259
|
+
setattr(cls, proxy, _AssociationProxy(rel, value_attr, nullable=nullable, order_by=order_by))
|
260
|
+
|
261
|
+
if cls.__add_properties__ is None:
|
262
|
+
cls.__add_properties__ = [proxy]
|
263
|
+
else:
|
264
|
+
cls.__add_properties__.append(proxy)
|
265
|
+
|
266
|
+
col.info["association_proxy"] = proxy
|