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,960 @@
|
|
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 datetime
|
29
|
+
import importlib
|
30
|
+
import json
|
31
|
+
import logging
|
32
|
+
import os
|
33
|
+
import re
|
34
|
+
import urllib.parse
|
35
|
+
from collections.abc import Callable
|
36
|
+
from functools import partial
|
37
|
+
from typing import TYPE_CHECKING, Any, Optional, cast
|
38
|
+
|
39
|
+
import c2cgeoform
|
40
|
+
import c2cwsgiutils
|
41
|
+
import c2cwsgiutils.db
|
42
|
+
import c2cwsgiutils.index
|
43
|
+
import dateutil.parser
|
44
|
+
import pyramid.config
|
45
|
+
import pyramid.renderers
|
46
|
+
import pyramid.request
|
47
|
+
import pyramid.response
|
48
|
+
import pyramid.security
|
49
|
+
import sqlalchemy
|
50
|
+
import sqlalchemy.orm
|
51
|
+
import zope.event.classhandler
|
52
|
+
from c2cgeoform import translator
|
53
|
+
from c2cwsgiutils.health_check import HealthCheck
|
54
|
+
from c2cwsgiutils.prometheus import MemoryMapCollector
|
55
|
+
from deform import Form
|
56
|
+
from dogpile.cache import register_backend # type: ignore[attr-defined]
|
57
|
+
from papyrus.renderers import GeoJSON
|
58
|
+
from prometheus_client.core import REGISTRY
|
59
|
+
from pyramid.config import Configurator
|
60
|
+
from pyramid.httpexceptions import HTTPException
|
61
|
+
from pyramid.path import AssetResolver
|
62
|
+
from pyramid_mako import add_mako_renderer
|
63
|
+
from sqlalchemy.orm import joinedload
|
64
|
+
|
65
|
+
import c2cgeoportal_commons.models
|
66
|
+
import c2cgeoportal_geoportal.views
|
67
|
+
from c2cgeoportal_commons.models import InvalidateCacheEvent
|
68
|
+
from c2cgeoportal_geoportal.lib import C2CPregenerator, caching, check_collector, checker, oidc
|
69
|
+
from c2cgeoportal_geoportal.lib.cacheversion import version_cache_buster
|
70
|
+
from c2cgeoportal_geoportal.lib.caching import get_region
|
71
|
+
from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
|
72
|
+
from c2cgeoportal_geoportal.lib.i18n import available_locale_names
|
73
|
+
from c2cgeoportal_geoportal.lib.metrics import (
|
74
|
+
MemoryCacheSizeCollector,
|
75
|
+
RasterDataSizeCollector,
|
76
|
+
TotalPythonObjectMemoryCollector,
|
77
|
+
)
|
78
|
+
from c2cgeoportal_geoportal.lib.xsd import XSD
|
79
|
+
from c2cgeoportal_geoportal.views.entry import Entry, canvas_view, custom_view
|
80
|
+
|
81
|
+
if TYPE_CHECKING:
|
82
|
+
from c2cgeoportal_commons.models import static # pylint: disable=ungrouped-imports,useless-suppression
|
83
|
+
|
84
|
+
|
85
|
+
_LOG = logging.getLogger(__name__)
|
86
|
+
|
87
|
+
_CACHE_REGION_OBJ = get_region("obj")
|
88
|
+
|
89
|
+
# Header predicate to accept only JSON content
|
90
|
+
_JSON_CONTENT_TYPE = "Content-Type:application/json"
|
91
|
+
_GEOJSON_CONTENT_TYPE = r"Content-Type:application/geo\+json"
|
92
|
+
|
93
|
+
|
94
|
+
class AssetRendererFactory:
|
95
|
+
"""Get a renderer for the assets."""
|
96
|
+
|
97
|
+
def __init__(self, info: Any):
|
98
|
+
del info # unused
|
99
|
+
self.resolver = AssetResolver("c2cgeoportal_geoportal")
|
100
|
+
|
101
|
+
def __call__(self, value: Any, system: dict[str, str]) -> bytes:
|
102
|
+
del value
|
103
|
+
asset = self.resolver.resolve(system["renderer_name"])
|
104
|
+
return cast(bytes, asset.stream().read())
|
105
|
+
|
106
|
+
|
107
|
+
INTERFACE_TYPE_NGEO = "ngeo"
|
108
|
+
INTERFACE_TYPE_CANVAS = "canvas"
|
109
|
+
INTERFACE_TYPE_CUSTOM = "custom"
|
110
|
+
|
111
|
+
|
112
|
+
def add_interface_config(config: pyramid.config.Configurator, interface_config: dict[str, Any]) -> None:
|
113
|
+
"""Add the interface (desktop, mobile, ...) views and routes with only the config."""
|
114
|
+
add_interface(
|
115
|
+
config,
|
116
|
+
interface_config["name"],
|
117
|
+
interface_config.get("type", INTERFACE_TYPE_NGEO),
|
118
|
+
interface_config,
|
119
|
+
default=interface_config.get("default", False),
|
120
|
+
)
|
121
|
+
|
122
|
+
|
123
|
+
def add_interface(
|
124
|
+
config: pyramid.config.Configurator,
|
125
|
+
interface_name: str = "desktop",
|
126
|
+
interface_type: str = INTERFACE_TYPE_NGEO,
|
127
|
+
interface_config: dict[str, Any] | None = None,
|
128
|
+
default: bool = False,
|
129
|
+
**kwargs: Any,
|
130
|
+
) -> None:
|
131
|
+
"""Add the interface (desktop, mobile, ...) views and routes."""
|
132
|
+
route = "/" if default else f"/{interface_name}"
|
133
|
+
if interface_type == INTERFACE_TYPE_NGEO:
|
134
|
+
add_interface_ngeo(
|
135
|
+
config,
|
136
|
+
route_name=interface_name,
|
137
|
+
route=route,
|
138
|
+
renderer=f"/etc/static-ngeo/{interface_name}.html",
|
139
|
+
**kwargs,
|
140
|
+
)
|
141
|
+
elif interface_type == INTERFACE_TYPE_CANVAS:
|
142
|
+
assert interface_config is not None
|
143
|
+
add_interface_canvas(
|
144
|
+
config,
|
145
|
+
route_name=interface_name,
|
146
|
+
route=route,
|
147
|
+
interface_config=interface_config,
|
148
|
+
**kwargs,
|
149
|
+
)
|
150
|
+
elif interface_type == INTERFACE_TYPE_CUSTOM:
|
151
|
+
assert interface_config is not None
|
152
|
+
add_interface_custom(
|
153
|
+
config,
|
154
|
+
route_name=interface_name,
|
155
|
+
route=route,
|
156
|
+
interface_config=interface_config,
|
157
|
+
**kwargs,
|
158
|
+
)
|
159
|
+
else:
|
160
|
+
_LOG.error(
|
161
|
+
"Unknown interface type '%s', should be '%s', '%s' or '%s'.",
|
162
|
+
interface_type,
|
163
|
+
INTERFACE_TYPE_NGEO,
|
164
|
+
INTERFACE_TYPE_CANVAS,
|
165
|
+
INTERFACE_TYPE_CUSTOM,
|
166
|
+
)
|
167
|
+
|
168
|
+
|
169
|
+
def add_interface_ngeo(
|
170
|
+
config: pyramid.config.Configurator,
|
171
|
+
route_name: str,
|
172
|
+
route: str,
|
173
|
+
renderer: str | None = None,
|
174
|
+
permission: str | None = None,
|
175
|
+
) -> None:
|
176
|
+
"""Add the ngeo interfaces views and routes."""
|
177
|
+
|
178
|
+
config.add_route(route_name, route, request_method="GET")
|
179
|
+
# Permalink theme: recover the theme for generating custom viewer.js url
|
180
|
+
config.add_route(
|
181
|
+
f"{route_name}theme",
|
182
|
+
f"{route}{'' if route[-1] == '/' else '/'}theme/{{themes}}",
|
183
|
+
request_method="GET",
|
184
|
+
)
|
185
|
+
config.add_view(
|
186
|
+
Entry, attr="get_ngeo_index_vars", route_name=route_name, renderer=renderer, permission=permission
|
187
|
+
)
|
188
|
+
config.add_view(
|
189
|
+
Entry,
|
190
|
+
attr="get_ngeo_index_vars",
|
191
|
+
route_name=f"{route_name}theme",
|
192
|
+
renderer=renderer,
|
193
|
+
permission=permission,
|
194
|
+
)
|
195
|
+
|
196
|
+
|
197
|
+
def add_interface_canvas(
|
198
|
+
config: pyramid.config.Configurator,
|
199
|
+
route_name: str,
|
200
|
+
route: str,
|
201
|
+
interface_config: dict[str, Any],
|
202
|
+
permission: str | None = None,
|
203
|
+
) -> None:
|
204
|
+
"""Add the ngeo interfaces views and routes."""
|
205
|
+
|
206
|
+
renderer = f"/etc/geomapfish/interfaces/{route_name}.html.mako"
|
207
|
+
config.add_route(route_name, route, request_method="GET")
|
208
|
+
# Permalink theme: recover the theme for generating custom viewer.js URL
|
209
|
+
config.add_route(
|
210
|
+
f"{route_name}theme",
|
211
|
+
f"{route}{'' if route[-1] == '/' else '/'}theme/{{themes}}",
|
212
|
+
request_method="GET",
|
213
|
+
)
|
214
|
+
view = partial(canvas_view, interface_config=interface_config)
|
215
|
+
view.__module__ = canvas_view.__module__
|
216
|
+
config.add_view(
|
217
|
+
view,
|
218
|
+
route_name=route_name,
|
219
|
+
renderer=renderer,
|
220
|
+
permission=permission,
|
221
|
+
)
|
222
|
+
config.add_view(
|
223
|
+
view,
|
224
|
+
route_name=f"{route_name}theme",
|
225
|
+
renderer=renderer,
|
226
|
+
permission=permission,
|
227
|
+
)
|
228
|
+
|
229
|
+
|
230
|
+
def add_interface_custom(
|
231
|
+
config: pyramid.config.Configurator,
|
232
|
+
route_name: str,
|
233
|
+
route: str,
|
234
|
+
interface_config: dict[str, Any],
|
235
|
+
permission: str | None = None,
|
236
|
+
) -> None:
|
237
|
+
"""Add custom interfaces views and routes."""
|
238
|
+
|
239
|
+
config.add_route(route_name, route, request_method="GET")
|
240
|
+
# Permalink theme: recover the theme for generating custom viewer.js URL
|
241
|
+
config.add_route(
|
242
|
+
f"{route_name}theme",
|
243
|
+
f"{route}{'' if route[-1] == '/' else '/'}theme/{{themes}}",
|
244
|
+
request_method="GET",
|
245
|
+
)
|
246
|
+
view = partial(custom_view, interface_config=interface_config)
|
247
|
+
view.__module__ = custom_view.__module__
|
248
|
+
config.add_view(
|
249
|
+
view,
|
250
|
+
route_name=route_name,
|
251
|
+
permission=permission,
|
252
|
+
)
|
253
|
+
config.add_view(
|
254
|
+
view,
|
255
|
+
route_name=f"{route_name}theme",
|
256
|
+
permission=permission,
|
257
|
+
)
|
258
|
+
|
259
|
+
|
260
|
+
def add_admin_interface(config: pyramid.config.Configurator) -> None:
|
261
|
+
"""Add the administration interface views and routes."""
|
262
|
+
|
263
|
+
assert c2cgeoportal_commons.models.DBSession is not None
|
264
|
+
|
265
|
+
config.add_request_method(
|
266
|
+
lambda request: c2cgeoportal_commons.models.DBSession(),
|
267
|
+
"dbsession",
|
268
|
+
reify=True,
|
269
|
+
)
|
270
|
+
config.add_view(c2cgeoportal_geoportal.views.add_ending_slash, route_name="admin_add_ending_slash")
|
271
|
+
config.add_route("admin_add_ending_slash", "/admin", request_method="GET")
|
272
|
+
config.include("c2cgeoportal_admin")
|
273
|
+
|
274
|
+
|
275
|
+
def add_getitfixed(config: pyramid.config.Configurator) -> None:
|
276
|
+
"""Add the get it fixed views and routes."""
|
277
|
+
if config.get_settings()["getitfixed"].get("enabled", False):
|
278
|
+
for route_name, pattern in (
|
279
|
+
("getitfixed_add_ending_slash", "/getitfixed"),
|
280
|
+
("getitfixed_admin_add_ending_slash", "/getitfixed_admin"),
|
281
|
+
):
|
282
|
+
config.add_view(c2cgeoportal_geoportal.views.add_ending_slash, route_name=route_name)
|
283
|
+
config.add_route(route_name, pattern, request_method="GET")
|
284
|
+
config.include("getitfixed")
|
285
|
+
# Register admin and getitfixed search paths together
|
286
|
+
Form.set_zpt_renderer(c2cgeoform.default_search_paths, translator=translator)
|
287
|
+
|
288
|
+
|
289
|
+
def locale_negotiator(request: pyramid.request.Request) -> str:
|
290
|
+
"""Get the current language."""
|
291
|
+
lang: str = request.params.get("lang")
|
292
|
+
if lang is None:
|
293
|
+
lang = request.cookies.get("_LOCALE_")
|
294
|
+
else:
|
295
|
+
request.response.set_cookie("_LOCALE_", lang)
|
296
|
+
if lang is None:
|
297
|
+
# If best_match returns None then use the default_locale_name configuration variable
|
298
|
+
return request.accept_language.best_match(
|
299
|
+
request.registry.settings.get("available_locale_names"),
|
300
|
+
default_match=request.registry.settings.get("default_locale_name"),
|
301
|
+
)
|
302
|
+
return lang
|
303
|
+
|
304
|
+
|
305
|
+
_URL_RE = re.compile(r"^[a-z]+://.*")
|
306
|
+
|
307
|
+
|
308
|
+
@_CACHE_REGION_OBJ.cache_on_arguments()
|
309
|
+
def _get_netlocs(hosts: list[str]) -> set[str]:
|
310
|
+
return {urllib.parse.urlparse(h).netloc if _URL_RE.match(h) else h.split("/", 1)[0] for h in hosts}
|
311
|
+
|
312
|
+
|
313
|
+
def is_allowed_url(
|
314
|
+
request: pyramid.request.Request, url: str, allowed_hosts: list[str]
|
315
|
+
) -> tuple[str | None, bool]:
|
316
|
+
"""
|
317
|
+
Check if the URL is allowed.
|
318
|
+
|
319
|
+
Allowed if URL netloc is request host or is found in allowed hosts.
|
320
|
+
"""
|
321
|
+
|
322
|
+
url_netloc = urllib.parse.urlparse(url).netloc
|
323
|
+
return url_netloc, url_netloc == request.host or url_netloc in _get_netlocs(allowed_hosts)
|
324
|
+
|
325
|
+
|
326
|
+
def is_allowed_host(request: pyramid.request.Request) -> bool:
|
327
|
+
"""
|
328
|
+
Check if the URL is allowed.
|
329
|
+
|
330
|
+
Allowed if URL netloc is request host or is found in allowed hosts.
|
331
|
+
"""
|
332
|
+
|
333
|
+
return request.host in request.registry.settings.get("allowed_hosts", [])
|
334
|
+
|
335
|
+
|
336
|
+
def is_valid_referrer(request: pyramid.request.Request, settings: dict[str, Any] | None = None) -> bool:
|
337
|
+
"""Check if the referrer is valid."""
|
338
|
+
if request.referrer is not None:
|
339
|
+
if settings is None:
|
340
|
+
settings = request.registry.settings
|
341
|
+
authorized_referrers = settings.get("authorized_referers", [])
|
342
|
+
referrer_hostname, ok = is_allowed_url(request, request.referrer, authorized_referrers)
|
343
|
+
if not ok:
|
344
|
+
_LOG.info(
|
345
|
+
"Invalid referrer hostname '%s', "
|
346
|
+
"is not the current host '%s' "
|
347
|
+
"or part of authorized_referers: %s",
|
348
|
+
referrer_hostname,
|
349
|
+
request.host,
|
350
|
+
", ".join(_get_netlocs(authorized_referrers)),
|
351
|
+
)
|
352
|
+
return False
|
353
|
+
return True
|
354
|
+
|
355
|
+
|
356
|
+
def create_get_user_from_request(
|
357
|
+
settings: dict[str, Any]
|
358
|
+
) -> Callable[[pyramid.request.Request, str | None], Optional["static.User"]]:
|
359
|
+
"""Get the get_user_from_request function."""
|
360
|
+
|
361
|
+
def get_user_from_request(
|
362
|
+
request: pyramid.request.Request, username: str | None = None
|
363
|
+
) -> Optional["static.User"]:
|
364
|
+
"""
|
365
|
+
Return the User object for the request.
|
366
|
+
|
367
|
+
Return ``None`` if:
|
368
|
+
* user is anonymous
|
369
|
+
* it does not exist in the database
|
370
|
+
* it has been deactivated
|
371
|
+
* the referrer is invalid
|
372
|
+
"""
|
373
|
+
from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
|
374
|
+
from c2cgeoportal_commons.models.static import User # pylint: disable=import-outside-toplevel
|
375
|
+
|
376
|
+
assert DBSession is not None
|
377
|
+
|
378
|
+
if not hasattr(request, "is_valid_referer"):
|
379
|
+
request.is_valid_referer = is_valid_referrer(request, settings)
|
380
|
+
if not request.is_valid_referer:
|
381
|
+
_LOG.debug("Invalid referrer for %s: %s", request.path_qs, repr(request.referrer))
|
382
|
+
return None
|
383
|
+
|
384
|
+
if not hasattr(request, "user_"):
|
385
|
+
request.user_ = None
|
386
|
+
user_info_remember: dict[str, Any] | None = None
|
387
|
+
openid_connect_configuration = settings.get("authentication", {}).get("openid_connect", {})
|
388
|
+
openid_connect_enabled = openid_connect_configuration.get("enabled", False)
|
389
|
+
if (
|
390
|
+
openid_connect_enabled
|
391
|
+
and "Authorization" in request.headers
|
392
|
+
and request.headers["Authorization"].startswith("Bearer ")
|
393
|
+
):
|
394
|
+
token = request.headers["Authorization"][7:]
|
395
|
+
client = oidc.get_oidc_client(request, request.host)
|
396
|
+
user_info = client.fetch_userinfo(token)
|
397
|
+
user_info_remember = {}
|
398
|
+
request.get_remember_from_user_info(user_info.dict(), user_info_remember)
|
399
|
+
elif username is None:
|
400
|
+
username = request.unauthenticated_userid
|
401
|
+
if username is not None or user_info_remember is not None:
|
402
|
+
if openid_connect_enabled:
|
403
|
+
if user_info_remember is None:
|
404
|
+
assert username is not None
|
405
|
+
user_info_remember = json.loads(username)
|
406
|
+
del username
|
407
|
+
if "access_token_expires" in user_info_remember:
|
408
|
+
access_token_expires = dateutil.parser.isoparse(
|
409
|
+
user_info_remember["access_token_expires"]
|
410
|
+
)
|
411
|
+
if access_token_expires < datetime.datetime.now():
|
412
|
+
if user_info_remember["refresh_token_expires"] is None:
|
413
|
+
return None
|
414
|
+
refresh_token_expires = dateutil.parser.isoparse(
|
415
|
+
user_info_remember["refresh_token_expires"]
|
416
|
+
)
|
417
|
+
if refresh_token_expires < datetime.datetime.now():
|
418
|
+
return None
|
419
|
+
token_response = oidc.get_oidc_client(
|
420
|
+
request, request.host
|
421
|
+
).exchange_refresh_token(user_info_remember["refresh_token"])
|
422
|
+
user_info_remember = oidc.OidcRemember(request).remember(
|
423
|
+
token_response, request.host
|
424
|
+
)
|
425
|
+
|
426
|
+
request.user_ = request.get_user_from_remember(user_info_remember)
|
427
|
+
else:
|
428
|
+
# We know we will need the role object of the
|
429
|
+
# user so we use joined loading
|
430
|
+
request.user_ = (
|
431
|
+
DBSession.query(User)
|
432
|
+
.filter_by(username=username, deactivated=False)
|
433
|
+
.options(joinedload(User.roles))
|
434
|
+
.first()
|
435
|
+
)
|
436
|
+
|
437
|
+
return cast(User, request.user_)
|
438
|
+
|
439
|
+
return get_user_from_request
|
440
|
+
|
441
|
+
|
442
|
+
def set_user_validator(
|
443
|
+
config: pyramid.config.Configurator, user_validator: Callable[[pyramid.request.Request, str, str], str]
|
444
|
+
) -> None:
|
445
|
+
"""
|
446
|
+
Call this function to register a user validator function.
|
447
|
+
|
448
|
+
The validator function is passed three arguments: ``request``,
|
449
|
+
``username``, and ``password``. The function should return the
|
450
|
+
user name if the credentials are valid, and ``None`` otherwise.
|
451
|
+
|
452
|
+
The validator should not do the actual authentication operation
|
453
|
+
by calling ``remember``, this is handled by the ``login`` view.
|
454
|
+
"""
|
455
|
+
|
456
|
+
def register() -> None:
|
457
|
+
config.registry.validate_user = user_validator
|
458
|
+
|
459
|
+
config.action("user_validator", register)
|
460
|
+
|
461
|
+
|
462
|
+
def default_user_validator(request: pyramid.request.Request, username: str, password: str) -> str | None:
|
463
|
+
"""
|
464
|
+
Validate the username/password.
|
465
|
+
|
466
|
+
This is c2cgeoportal's default user validator. Return None if we are anonymous, the string to remember
|
467
|
+
otherwise.
|
468
|
+
"""
|
469
|
+
del request # unused
|
470
|
+
from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
|
471
|
+
from c2cgeoportal_commons.models.static import User # pylint: disable=import-outside-toplevel
|
472
|
+
|
473
|
+
assert DBSession is not None
|
474
|
+
|
475
|
+
user = DBSession.query(User).filter_by(username=username).first()
|
476
|
+
if user is None:
|
477
|
+
_LOG.info('Unknown user "%s" tried to log in', username)
|
478
|
+
return None
|
479
|
+
if user.deactivated:
|
480
|
+
_LOG.info('Deactivated user "%s" tried to log in', username)
|
481
|
+
return None
|
482
|
+
if user.expired():
|
483
|
+
_LOG.info("Expired user %s tried to log in", username)
|
484
|
+
return None
|
485
|
+
if not user.validate_password(password):
|
486
|
+
_LOG.info('User "%s" tried to log in with bad credentials', username)
|
487
|
+
return None
|
488
|
+
return username
|
489
|
+
|
490
|
+
|
491
|
+
class MapserverproxyRoutePredicate:
|
492
|
+
"""
|
493
|
+
Serve as a custom route predicate function for mapserverproxy.
|
494
|
+
|
495
|
+
If the hide_capabilities setting is set and is true then we want to return 404s on GetCapabilities
|
496
|
+
requests.
|
497
|
+
"""
|
498
|
+
|
499
|
+
def __init__(self, val: Any, config: pyramid.config.Configurator) -> None:
|
500
|
+
del val, config
|
501
|
+
|
502
|
+
def __call__(self, context: Any, request: pyramid.request.Request) -> bool:
|
503
|
+
del context
|
504
|
+
hide_capabilities = request.registry.settings.get("hide_capabilities")
|
505
|
+
if not hide_capabilities:
|
506
|
+
return True
|
507
|
+
params = {k.lower(): v.lower() for k, v in request.params.items()}
|
508
|
+
return "request" not in params or params["request"] not in ("getcapabilities", "capabilities")
|
509
|
+
|
510
|
+
@staticmethod
|
511
|
+
def text() -> str:
|
512
|
+
return "mapserverproxy"
|
513
|
+
|
514
|
+
phash = text
|
515
|
+
|
516
|
+
|
517
|
+
def add_cors_route(config: pyramid.config.Configurator, pattern: str, service: str) -> None:
|
518
|
+
"""Add the OPTIONS route and view need for services supporting CORS."""
|
519
|
+
|
520
|
+
def view(request: pyramid.request.Request) -> pyramid.response.Response:
|
521
|
+
return set_common_headers(request, service, Cache.PRIVATE_NO)
|
522
|
+
|
523
|
+
name = pattern + "_options"
|
524
|
+
config.add_route(name, pattern, request_method="OPTIONS")
|
525
|
+
config.add_view(view, route_name=name)
|
526
|
+
|
527
|
+
|
528
|
+
def error_handler(
|
529
|
+
http_exception: HTTPException, request: pyramid.request.Request
|
530
|
+
) -> pyramid.response.Response:
|
531
|
+
"""View callable for handling all the exceptions that are not already handled."""
|
532
|
+
_LOG.warning("%s returned status code %s", request.url, http_exception.status_code)
|
533
|
+
return set_common_headers(request, "error", Cache.PRIVATE_NO, http_exception)
|
534
|
+
|
535
|
+
|
536
|
+
def call_hook(settings: pyramid.config.Configurator, name: str, *args: Any, **kwargs: Any) -> None:
|
537
|
+
"""Call the hook defined in the settings."""
|
538
|
+
hooks = settings.get("hooks", {})
|
539
|
+
hook = hooks.get(name)
|
540
|
+
if hook is None:
|
541
|
+
return
|
542
|
+
parts = hook.split(".")
|
543
|
+
module = importlib.import_module(".".join(parts[0:-1]))
|
544
|
+
function_ = getattr(module, parts[-1])
|
545
|
+
function_(*args, **kwargs)
|
546
|
+
|
547
|
+
|
548
|
+
def includeme(config: pyramid.config.Configurator) -> None:
|
549
|
+
"""Get the Pyramid WSGI application."""
|
550
|
+
settings = config.get_settings()
|
551
|
+
|
552
|
+
if "available_locale_names" not in settings:
|
553
|
+
settings["available_locale_names"] = available_locale_names()
|
554
|
+
|
555
|
+
call_hook(settings, "after_settings", settings)
|
556
|
+
|
557
|
+
get_user_from_request = create_get_user_from_request(settings)
|
558
|
+
config.add_request_method(get_user_from_request, name="user", property=True)
|
559
|
+
config.add_request_method(get_user_from_request, name="get_user")
|
560
|
+
# Be able for an organization to override the method to use alternate:
|
561
|
+
# - Organization roles name for the standard roles 'anonymous', 'registered' and 'intranet'.
|
562
|
+
config.add_request_method(lambda request, role_type: role_type, name="get_organization_role")
|
563
|
+
# - Organization print URL
|
564
|
+
config.add_request_method(
|
565
|
+
lambda request: request.registry.settings["print_url"], name="get_organization_print_url"
|
566
|
+
)
|
567
|
+
# - Organization interface name (in the config and in the admin interface)
|
568
|
+
config.add_request_method(lambda request, interface: interface, name="get_organization_interface")
|
569
|
+
|
570
|
+
# Configure 'locale' dir as the translation dir for c2cgeoportal app
|
571
|
+
config.add_translation_dirs("c2cgeoportal_geoportal:locale/")
|
572
|
+
|
573
|
+
config.include("pyramid_mako")
|
574
|
+
config.include("c2cwsgiutils.pyramid.includeme")
|
575
|
+
config.include(oidc.includeme)
|
576
|
+
health_check = HealthCheck(config)
|
577
|
+
config.registry["health_check"] = health_check
|
578
|
+
|
579
|
+
metrics_config = config.registry.settings["metrics"]
|
580
|
+
if metrics_config["memory_maps_rss"]:
|
581
|
+
REGISTRY.register(MemoryMapCollector("rss"))
|
582
|
+
if metrics_config["memory_maps_size"]:
|
583
|
+
REGISTRY.register(MemoryMapCollector("size"))
|
584
|
+
if metrics_config["memory_cache"]:
|
585
|
+
REGISTRY.register(MemoryCacheSizeCollector(metrics_config.get("memory_cache_all", False)))
|
586
|
+
if metrics_config["raster_data"]:
|
587
|
+
REGISTRY.register(RasterDataSizeCollector())
|
588
|
+
if metrics_config["total_python_object_memory"]:
|
589
|
+
REGISTRY.register(TotalPythonObjectMemoryCollector())
|
590
|
+
|
591
|
+
# Initialize DBSessions
|
592
|
+
init_db_sessions(settings, config, health_check)
|
593
|
+
|
594
|
+
checker.init(config, health_check)
|
595
|
+
check_collector.init(config, health_check)
|
596
|
+
|
597
|
+
# dogpile.cache configuration
|
598
|
+
if "cache" in settings:
|
599
|
+
register_backend(
|
600
|
+
"c2cgeoportal.hybrid",
|
601
|
+
"c2cgeoportal_geoportal.lib.caching",
|
602
|
+
"HybridRedisBackend",
|
603
|
+
) # type: ignore[no-untyped-call]
|
604
|
+
register_backend(
|
605
|
+
"c2cgeoportal.hybridsentinel", "c2cgeoportal_geoportal.lib.caching", "HybridRedisSentinelBackend"
|
606
|
+
) # type: ignore[no-untyped-call]
|
607
|
+
for name, cache_config in settings["cache"].items():
|
608
|
+
caching.init_region(cache_config, name)
|
609
|
+
|
610
|
+
@zope.event.classhandler.handler(InvalidateCacheEvent) # type: ignore[misc]
|
611
|
+
def handle(event: InvalidateCacheEvent) -> None:
|
612
|
+
del event
|
613
|
+
caching.invalidate_region("ogc-server")
|
614
|
+
caching.invalidate_region("std")
|
615
|
+
caching.invalidate_region("obj")
|
616
|
+
if caching.MEMORY_CACHE_DICT:
|
617
|
+
caching.get_region("std").delete_multi(list(caching.MEMORY_CACHE_DICT.keys()))
|
618
|
+
caching.MEMORY_CACHE_DICT.clear()
|
619
|
+
|
620
|
+
# Register a tween to get back the cache buster path.
|
621
|
+
if "cache_path" not in config.get_settings():
|
622
|
+
config.get_settings()["cache_path"] = ["static", "static-geomapfish"]
|
623
|
+
config.add_tween("c2cgeoportal_geoportal.lib.cacheversion.CachebusterTween")
|
624
|
+
config.add_tween("c2cgeoportal_geoportal.lib.headers.HeadersTween")
|
625
|
+
|
626
|
+
# Bind the mako renderer to other file extensions
|
627
|
+
add_mako_renderer(config, ".html")
|
628
|
+
add_mako_renderer(config, ".js")
|
629
|
+
|
630
|
+
# Add the "geojson" renderer
|
631
|
+
config.add_renderer("geojson", GeoJSON())
|
632
|
+
|
633
|
+
# Add the "xsd" renderer
|
634
|
+
config.add_renderer("xsd", XSD(include_foreign_keys=True))
|
635
|
+
|
636
|
+
# Add the set_user_validator directive, and set a default user validator
|
637
|
+
config.add_directive("set_user_validator", set_user_validator)
|
638
|
+
config.set_user_validator(default_user_validator)
|
639
|
+
|
640
|
+
config.add_route("oauth2introspect", "/oauth/introspect", request_method="POST")
|
641
|
+
config.add_route("oauth2token", "/oauth/token", request_method="POST")
|
642
|
+
config.add_route("oauth2loginform", "/oauth/login", request_method="GET")
|
643
|
+
config.add_route("oauth2revoke_token", "/oauth/revoke_token", request_method="GET")
|
644
|
+
config.add_route("notlogin", "/notlogin", request_method="GET")
|
645
|
+
|
646
|
+
config.add_route("dynamic", "/dynamic.json", request_method="GET")
|
647
|
+
|
648
|
+
# Add routes to the mapserver proxy
|
649
|
+
config.add_route_predicate("mapserverproxy", MapserverproxyRoutePredicate)
|
650
|
+
config.add_route(
|
651
|
+
"mapserverproxy",
|
652
|
+
"/mapserv_proxy",
|
653
|
+
mapserverproxy=True,
|
654
|
+
pregenerator=C2CPregenerator(role=True),
|
655
|
+
request_method="GET",
|
656
|
+
)
|
657
|
+
config.add_route(
|
658
|
+
"mapserverproxy_post",
|
659
|
+
"/mapserv_proxy",
|
660
|
+
mapserverproxy=True,
|
661
|
+
pregenerator=C2CPregenerator(role=True),
|
662
|
+
request_method="POST",
|
663
|
+
)
|
664
|
+
# OGC Api routes
|
665
|
+
config.add_route(
|
666
|
+
"mapserverproxy_ogcapi_mapserver",
|
667
|
+
"/mapserv_proxy/{ogcserver}/ogcapi/*path",
|
668
|
+
mapserverproxy=True,
|
669
|
+
pregenerator=C2CPregenerator(role=True),
|
670
|
+
request_method="GET",
|
671
|
+
)
|
672
|
+
config.add_route(
|
673
|
+
"mapserverproxy_ogcapi_qgisserver",
|
674
|
+
"/mapserv_proxy/{ogcserver}/wfs3/*path",
|
675
|
+
mapserverproxy=True,
|
676
|
+
pregenerator=C2CPregenerator(role=True),
|
677
|
+
request_method="GET",
|
678
|
+
)
|
679
|
+
add_cors_route(config, "/mapserv_proxy", "mapserver")
|
680
|
+
|
681
|
+
# Add route to the tinyows proxy
|
682
|
+
config.add_route("tinyowsproxy", "/tinyows_proxy", pregenerator=C2CPregenerator(role=True))
|
683
|
+
|
684
|
+
# Add routes to the entry view class
|
685
|
+
config.add_route("base", "/", static=True)
|
686
|
+
config.add_route("loginform", "/login.html", request_method="GET")
|
687
|
+
add_cors_route(config, "/login", "login")
|
688
|
+
config.add_route("login", "/login", request_method="POST")
|
689
|
+
add_cors_route(config, "/logout", "login")
|
690
|
+
config.add_route("logout", "/logout", request_method="GET")
|
691
|
+
add_cors_route(config, "/loginchangepassword", "login")
|
692
|
+
config.add_route("change_password", "/loginchangepassword", request_method="POST")
|
693
|
+
add_cors_route(config, "/loginresetpassword", "login")
|
694
|
+
config.add_route("loginresetpassword", "/loginresetpassword", request_method="POST")
|
695
|
+
add_cors_route(config, "/loginuser", "login")
|
696
|
+
config.add_route("loginuser", "/loginuser", request_method="GET")
|
697
|
+
config.add_route("testi18n", "/testi18n.html", request_method="GET")
|
698
|
+
config.add_route("oidc_login", "/oidc/login", request_method="GET")
|
699
|
+
config.add_route("oidc_callback", "/oidc/callback", request_method="GET")
|
700
|
+
|
701
|
+
config.add_renderer(".map", AssetRendererFactory)
|
702
|
+
config.add_renderer(".css", AssetRendererFactory)
|
703
|
+
config.add_renderer(".ico", AssetRendererFactory)
|
704
|
+
config.add_route("localejson", "/locale.json", request_method="GET")
|
705
|
+
config.add_route("localepot", "/locale.pot", request_method="GET")
|
706
|
+
|
707
|
+
def add_static_route(name: str, attr: str, path: str, renderer: str) -> None:
|
708
|
+
config.add_route(name, path, request_method="GET")
|
709
|
+
config.add_view(Entry, attr=attr, route_name=name, renderer=renderer)
|
710
|
+
|
711
|
+
static_files = config.get_settings().get("static_files", {})
|
712
|
+
for name, attr, path in [
|
713
|
+
("favicon.ico", "favicon", "/favicon.ico"),
|
714
|
+
("robot.txt", "robot_txt", "/robot.txt"),
|
715
|
+
("api.js.map", "apijsmap", "/api.js.map"),
|
716
|
+
("api.css", "apicss", "/api.css"),
|
717
|
+
("apihelp.html", "apihelp", "/apihelp/index.html"),
|
718
|
+
]:
|
719
|
+
if static_files.get(name):
|
720
|
+
add_static_route(name, attr, path, static_files[name])
|
721
|
+
config.add_route("apijs", "/api.js", request_method="GET")
|
722
|
+
c2cgeoportal_geoportal.views.add_redirect(config, "apihelp_redirect", "/apihelp.html", "apihelp.html")
|
723
|
+
|
724
|
+
config.add_route("themes", "/themes", request_method="GET", pregenerator=C2CPregenerator(role=True))
|
725
|
+
add_cors_route(config, "/themes", "themes")
|
726
|
+
|
727
|
+
config.add_route("invalidate", "/invalidate", request_method="GET")
|
728
|
+
|
729
|
+
# Print proxy routes
|
730
|
+
config.add_route("printproxy", "/printproxy", request_method="HEAD")
|
731
|
+
add_cors_route(config, "/printproxy/*all", "print")
|
732
|
+
config.add_route(
|
733
|
+
"printproxy_capabilities",
|
734
|
+
"/printproxy/capabilities.json",
|
735
|
+
request_method="GET",
|
736
|
+
pregenerator=C2CPregenerator(role=True),
|
737
|
+
)
|
738
|
+
config.add_route(
|
739
|
+
"printproxy_report_create",
|
740
|
+
"/printproxy/report.{format}",
|
741
|
+
request_method="POST",
|
742
|
+
header=_JSON_CONTENT_TYPE,
|
743
|
+
)
|
744
|
+
config.add_route("printproxy_status", "/printproxy/status/{ref}.json", request_method="GET")
|
745
|
+
config.add_route("printproxy_cancel", "/printproxy/cancel/{ref}", request_method="DELETE")
|
746
|
+
config.add_route("printproxy_report_get", "/printproxy/report/{ref}", request_method="GET")
|
747
|
+
|
748
|
+
# Full-text search routes
|
749
|
+
add_cors_route(config, "/search", "fulltextsearch")
|
750
|
+
config.add_route("fulltextsearch", "/search", request_method="GET")
|
751
|
+
|
752
|
+
# Access to raster data
|
753
|
+
add_cors_route(config, "/raster", "raster")
|
754
|
+
config.add_route("raster", "/raster", request_method="GET")
|
755
|
+
|
756
|
+
add_cors_route(config, "/profile.json", "profile")
|
757
|
+
config.add_route("profile.json", "/profile.json", request_method="POST")
|
758
|
+
|
759
|
+
# Access to vector tiles
|
760
|
+
add_cors_route(config, "/vector_tiles", "vector_tiles")
|
761
|
+
config.add_route("vector_tiles", "/vector_tiles/{layer_name}/{z}/{x}/{y}.pbf", request_method="GET")
|
762
|
+
# There is no view corresponding to that route, it is to be used from
|
763
|
+
# mako templates to get the root of the "vector_tiles" web service
|
764
|
+
config.add_route("vector_tiles_root", "/vector_tiles", request_method="HEAD")
|
765
|
+
|
766
|
+
# Shortener
|
767
|
+
add_cors_route(config, "/short/create", "shortener")
|
768
|
+
config.add_route("shortener_create", "/short/create", request_method="POST")
|
769
|
+
config.add_route("shortener_get", "/s/{ref}", request_method="GET")
|
770
|
+
|
771
|
+
# Geometry processing
|
772
|
+
config.add_route("difference", "/difference", request_method="POST")
|
773
|
+
|
774
|
+
# PDF report tool
|
775
|
+
config.add_route("pdfreport", "/pdfreport/{layername}/{ids}", request_method="GET")
|
776
|
+
|
777
|
+
# Add routes for the "layers" web service
|
778
|
+
add_cors_route(config, "/layers/*all", "layers")
|
779
|
+
config.add_route("layers_count", "/layers/{layer_id:\\d+}/count", request_method="GET")
|
780
|
+
config.add_route(
|
781
|
+
"layers_metadata",
|
782
|
+
"/layers/{layer_id:\\d+}/md.xsd",
|
783
|
+
request_method="GET",
|
784
|
+
pregenerator=C2CPregenerator(role=True),
|
785
|
+
)
|
786
|
+
config.add_route(
|
787
|
+
"layers_read_many", "/layers/{layer_id:\\d+,?(\\d+,)*\\d*$}", request_method="GET"
|
788
|
+
) # supports URLs like /layers/1,2,3
|
789
|
+
config.add_route("layers_read_one", "/layers/{layer_id:\\d+}/{feature_id}", request_method="GET")
|
790
|
+
config.add_route(
|
791
|
+
"layers_create", "/layers/{layer_id:\\d+}", request_method="POST", header=_GEOJSON_CONTENT_TYPE
|
792
|
+
)
|
793
|
+
config.add_route(
|
794
|
+
"layers_update",
|
795
|
+
"/layers/{layer_id:\\d+}/{feature_id}",
|
796
|
+
request_method="PUT",
|
797
|
+
header=_GEOJSON_CONTENT_TYPE,
|
798
|
+
)
|
799
|
+
config.add_route("layers_delete", "/layers/{layer_id:\\d+}/{feature_id}", request_method="DELETE")
|
800
|
+
config.add_route(
|
801
|
+
"layers_enumerate_attribute_values",
|
802
|
+
"/layers/{layer_name}/values/{field_name}",
|
803
|
+
request_method="GET",
|
804
|
+
pregenerator=C2CPregenerator(),
|
805
|
+
)
|
806
|
+
# There is no view corresponding to that route, it is to be used from
|
807
|
+
# mako templates to get the root of the "layers" web service
|
808
|
+
config.add_route("layers_root", "/layers", request_method="HEAD")
|
809
|
+
|
810
|
+
# Resource proxy (load external url, useful when loading non https content)
|
811
|
+
config.add_route("resourceproxy", "/resourceproxy", request_method="GET")
|
812
|
+
|
813
|
+
# Dev
|
814
|
+
config.add_route("dev", "/dev/*path", request_method="GET")
|
815
|
+
|
816
|
+
# Used memory in caches
|
817
|
+
config.add_route("memory", "/memory", request_method="GET")
|
818
|
+
|
819
|
+
config.add_route("ogc_server_clear_cache", "clear-ogc-server-cache/{id}", request_method="GET")
|
820
|
+
|
821
|
+
# Scan view decorator for adding routes
|
822
|
+
config.scan(
|
823
|
+
ignore=[
|
824
|
+
"c2cgeoportal_geoportal.lib",
|
825
|
+
"c2cgeoportal_geoportal.scaffolds",
|
826
|
+
"c2cgeoportal_geoportal.scripts",
|
827
|
+
]
|
828
|
+
)
|
829
|
+
|
830
|
+
admin_interface = (
|
831
|
+
config.get_settings().get("enable_admin_interface", False)
|
832
|
+
and importlib.util.find_spec("c2cgeoportal_admin") is not None
|
833
|
+
)
|
834
|
+
if admin_interface:
|
835
|
+
add_admin_interface(config)
|
836
|
+
else:
|
837
|
+
if not config.get_settings().get("enable_admin_interface", False):
|
838
|
+
_LOG.info("Admin interface disabled by configuration")
|
839
|
+
else:
|
840
|
+
_LOG.info("Admin interface disabled because c2cgeoportal_admin is not installed")
|
841
|
+
add_getitfixed(config)
|
842
|
+
|
843
|
+
# Add the project static view with cache buster
|
844
|
+
config.add_static_view(
|
845
|
+
name="static",
|
846
|
+
path="/etc/geomapfish/static",
|
847
|
+
cache_max_age=int(config.get_settings()["default_max_age"]),
|
848
|
+
)
|
849
|
+
config.add_cache_buster("/etc/geomapfish/static", version_cache_buster)
|
850
|
+
|
851
|
+
# Add the project static view without cache buster
|
852
|
+
config.add_static_view(
|
853
|
+
name="static-ngeo",
|
854
|
+
path="/etc/static-ngeo",
|
855
|
+
cache_max_age=int(config.get_settings()["default_max_age"]),
|
856
|
+
)
|
857
|
+
config.add_static_view(
|
858
|
+
name="static-frontend",
|
859
|
+
path="/etc/static-frontend",
|
860
|
+
cache_max_age=int(config.get_settings()["default_max_age"]),
|
861
|
+
)
|
862
|
+
|
863
|
+
# Add the c2cgeoportal static view with cache buster
|
864
|
+
config.add_static_view(
|
865
|
+
name="static-geomapfish",
|
866
|
+
path="c2cgeoportal_geoportal:static",
|
867
|
+
cache_max_age=int(config.get_settings()["default_max_age"]),
|
868
|
+
)
|
869
|
+
config.add_cache_buster("c2cgeoportal_geoportal:static", version_cache_buster)
|
870
|
+
|
871
|
+
# Add the project static view without cache buster
|
872
|
+
config.add_static_view(
|
873
|
+
name="static-ngeo-dist",
|
874
|
+
path="/opt/c2cgeoportal/geoportal/node_modules/ngeo/dist",
|
875
|
+
cache_max_age=int(config.get_settings()["default_max_age"]),
|
876
|
+
)
|
877
|
+
|
878
|
+
# Handles the other HTTP errors raised by the views. Without that,
|
879
|
+
# the client receives a status=200 without content.
|
880
|
+
config.add_view(error_handler, context=HTTPException)
|
881
|
+
|
882
|
+
c2cwsgiutils.index.additional_title = (
|
883
|
+
'<div class="row"><div class="col-lg-3"><h2>GeoMapFish</h2></div><div class="col-lg">'
|
884
|
+
)
|
885
|
+
|
886
|
+
c2cwsgiutils.index.additional_auth.extend(
|
887
|
+
[
|
888
|
+
'<a href="../tiles/admin/">TileCloud chain admin</a><br>',
|
889
|
+
'<a href="../tiles/c2c/">TileCloud chain c2c tools</a><br>',
|
890
|
+
'<a href="../invalidate">Invalidate the cache</a><br>',
|
891
|
+
'<a href="../memory">Memory status</a><br>',
|
892
|
+
]
|
893
|
+
)
|
894
|
+
if admin_interface:
|
895
|
+
c2cwsgiutils.index.additional_noauth.append('<a href="../admin/">Admin</a><br>')
|
896
|
+
|
897
|
+
c2cwsgiutils.index.additional_noauth.append(
|
898
|
+
'</div></div><div class="row"><div class="col-lg-3"><h3>Interfaces</h3></div><div class="col-lg">'
|
899
|
+
)
|
900
|
+
c2cwsgiutils.index.additional_noauth.append('<a href="../">Default</a><br>')
|
901
|
+
for interface in config.get_settings().get("interfaces", []):
|
902
|
+
if not interface.get("default", False):
|
903
|
+
c2cwsgiutils.index.additional_noauth.append(
|
904
|
+
'<a href="../{interface}">{interface}</a><br>'.format(interface=interface["name"])
|
905
|
+
)
|
906
|
+
c2cwsgiutils.index.additional_noauth.append('<a href="../apihelp/index.html">API help</a><br>')
|
907
|
+
c2cwsgiutils.index.additional_noauth.append("</div></div><hr>")
|
908
|
+
|
909
|
+
|
910
|
+
def init_db_sessions(
|
911
|
+
settings: dict[str, Any], config: Configurator, health_check: HealthCheck | None = None
|
912
|
+
) -> None:
|
913
|
+
"""Initialize the database sessions."""
|
914
|
+
db_chooser = settings.get("db_chooser", {})
|
915
|
+
master_paths = [i.replace("//", "/") for i in db_chooser.get("master", [])]
|
916
|
+
slave_paths = [i.replace("//", "/") for i in db_chooser.get("slave", [])]
|
917
|
+
|
918
|
+
slave_prefix = "sqlalchemy_slave" if "sqlalchemy_slave.url" in settings else None
|
919
|
+
|
920
|
+
c2cgeoportal_commons.models.DBSession, rw_bind, _ = c2cwsgiutils.db.setup_session( # type: ignore[assignment]
|
921
|
+
config, "sqlalchemy", slave_prefix, force_master=master_paths, force_slave=slave_paths
|
922
|
+
)
|
923
|
+
c2cgeoportal_commons.models.Base.metadata.bind = rw_bind # type: ignore[attr-defined]
|
924
|
+
assert c2cgeoportal_commons.models.DBSession is not None
|
925
|
+
c2cgeoportal_commons.models.DBSessions["dbsession"] = c2cgeoportal_commons.models.DBSession
|
926
|
+
|
927
|
+
for dbsession_name, dbsession_config in settings.get("dbsessions", {}).items(): # pragma: nocover
|
928
|
+
c2cgeoportal_commons.models.DBSessions[dbsession_name] = c2cwsgiutils.db.create_session( # type: ignore[assignment]
|
929
|
+
config, dbsession_name, **dbsession_config
|
930
|
+
)
|
931
|
+
|
932
|
+
c2cgeoportal_commons.models.Base.metadata.clear()
|
933
|
+
from c2cgeoportal_commons.models import main # pylint: disable=import-outside-toplevel
|
934
|
+
|
935
|
+
if health_check is not None:
|
936
|
+
for name, session in c2cgeoportal_commons.models.DBSessions.items():
|
937
|
+
if name == "dbsession":
|
938
|
+
health_check.add_db_session_check(session, at_least_one_model=main.Theme, level=1)
|
939
|
+
alembic_ini = os.path.join(os.path.abspath(os.path.curdir), "alembic.ini")
|
940
|
+
if os.path.exists(alembic_ini):
|
941
|
+
health_check.add_alembic_check(
|
942
|
+
session,
|
943
|
+
alembic_ini_path=alembic_ini,
|
944
|
+
name="main",
|
945
|
+
version_schema=settings["schema"],
|
946
|
+
level=1,
|
947
|
+
)
|
948
|
+
health_check.add_alembic_check(
|
949
|
+
session,
|
950
|
+
alembic_ini_path=alembic_ini,
|
951
|
+
name="static",
|
952
|
+
version_schema=settings["schema_static"],
|
953
|
+
level=1,
|
954
|
+
)
|
955
|
+
else:
|
956
|
+
|
957
|
+
def check(session_: sqlalchemy.orm.scoped_session[sqlalchemy.orm.Session]) -> None:
|
958
|
+
session_.execute(sqlalchemy.text("SELECT 1"))
|
959
|
+
|
960
|
+
health_check.add_db_session_check(session, query_cb=check, level=2)
|