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.
Files changed (197) hide show
  1. c2cgeoportal_geoportal/__init__.py +960 -0
  2. c2cgeoportal_geoportal/lib/__init__.py +256 -0
  3. c2cgeoportal_geoportal/lib/authentication.py +250 -0
  4. c2cgeoportal_geoportal/lib/bashcolor.py +46 -0
  5. c2cgeoportal_geoportal/lib/cacheversion.py +75 -0
  6. c2cgeoportal_geoportal/lib/caching.py +176 -0
  7. c2cgeoportal_geoportal/lib/check_collector.py +80 -0
  8. c2cgeoportal_geoportal/lib/checker.py +295 -0
  9. c2cgeoportal_geoportal/lib/common_headers.py +170 -0
  10. c2cgeoportal_geoportal/lib/dbreflection.py +266 -0
  11. c2cgeoportal_geoportal/lib/filter_capabilities.py +360 -0
  12. c2cgeoportal_geoportal/lib/fulltextsearch.py +50 -0
  13. c2cgeoportal_geoportal/lib/functionality.py +166 -0
  14. c2cgeoportal_geoportal/lib/headers.py +62 -0
  15. c2cgeoportal_geoportal/lib/i18n.py +38 -0
  16. c2cgeoportal_geoportal/lib/layers.py +132 -0
  17. c2cgeoportal_geoportal/lib/lingva_extractor.py +937 -0
  18. c2cgeoportal_geoportal/lib/loader.py +57 -0
  19. c2cgeoportal_geoportal/lib/metrics.py +117 -0
  20. c2cgeoportal_geoportal/lib/oauth2.py +1186 -0
  21. c2cgeoportal_geoportal/lib/oidc.py +302 -0
  22. c2cgeoportal_geoportal/lib/wmstparsing.py +353 -0
  23. c2cgeoportal_geoportal/lib/xsd.py +166 -0
  24. c2cgeoportal_geoportal/py.typed +0 -0
  25. c2cgeoportal_geoportal/resources.py +49 -0
  26. c2cgeoportal_geoportal/scaffolds/advance_create/ci/config.yaml +26 -0
  27. c2cgeoportal_geoportal/scaffolds/advance_create/cookiecutter.json +18 -0
  28. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.dockerignore +6 -0
  29. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.eslintrc.yaml +19 -0
  30. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml +30 -0
  31. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Dockerfile +75 -0
  32. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Makefile +6 -0
  33. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/alembic.ini +58 -0
  34. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/alembic.yaml +19 -0
  35. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/development.ini +121 -0
  36. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/gunicorn.conf.py +139 -0
  37. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/language_mapping +3 -0
  38. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/lingva-client.cfg +5 -0
  39. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/lingva-server.cfg +6 -0
  40. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/production.ini +38 -0
  41. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/requirements.txt +2 -0
  42. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/setup.py +25 -0
  43. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.api.js +41 -0
  44. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.apps.js +64 -0
  45. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.commons.js +11 -0
  46. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.config.js +22 -0
  47. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/__init__.py +42 -0
  48. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/authentication.py +10 -0
  49. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/dev.py +14 -0
  50. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/models.py +8 -0
  51. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/multi_organization.py +7 -0
  52. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/resources.py +11 -0
  53. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/api/index.js +12 -0
  54. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/js/{{cookiecutter.package}}module.js +25 -0
  55. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/subscribers.py +39 -0
  56. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/views/__init__.py +0 -0
  57. c2cgeoportal_geoportal/scaffolds/advance_update/cookiecutter.json +18 -0
  58. c2cgeoportal_geoportal/scaffolds/advance_update/{{cookiecutter.project}}/geoportal/CONST_Makefile +121 -0
  59. c2cgeoportal_geoportal/scaffolds/create/cookiecutter.json +18 -0
  60. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.dockerignore +14 -0
  61. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.editorconfig +17 -0
  62. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/main.yaml +73 -0
  63. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/rebuild.yaml +50 -0
  64. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/update_l10n.yaml +66 -0
  65. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.gitignore +16 -0
  66. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.pre-commit-config.yaml +35 -0
  67. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierignore +1 -0
  68. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierrc.yaml +2 -0
  69. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Dockerfile +75 -0
  70. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile +70 -0
  71. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/README.rst +29 -0
  72. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build +179 -0
  73. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/config.yaml +22 -0
  74. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/docker-compose-check +25 -0
  75. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/requirements.txt +2 -0
  76. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-db.yaml +24 -0
  77. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml +511 -0
  78. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-qgis.yaml +21 -0
  79. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml +59 -0
  80. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.yaml +121 -0
  81. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.default +102 -0
  82. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.project +69 -0
  83. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/vars.yaml +430 -0
  84. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/locale/en/LC_MESSAGES/{{cookiecutter.package}}_geoportal-client.po +6 -0
  85. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/desktop.css +0 -0
  86. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/iframe_api.css +0 -0
  87. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/mobile.css +0 -0
  88. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/banner_left.png +0 -0
  89. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/banner_right.png +0 -0
  90. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/blank.png +0 -0
  91. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-blue.png +0 -0
  92. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-gold.png +0 -0
  93. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-green.png +0 -0
  94. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker.png +0 -0
  95. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/robot.txt.tmpl +3 -0
  96. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/Readme.txt +69 -0
  97. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/TM_EUROPE_BORDERS-0.3.sql +70 -0
  98. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/demo.map.tmpl +224 -0
  99. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arial.ttf +0 -0
  100. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arialbd.ttf +0 -0
  101. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arialbi.ttf +0 -0
  102. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Ariali.ttf +0 -0
  103. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Bold.ttf +0 -0
  104. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-BoldItalic.ttf +0 -0
  105. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Italic.ttf +0 -0
  106. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Regular.ttf +0 -0
  107. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdana.ttf +0 -0
  108. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanab.ttf +0 -0
  109. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanai.ttf +0 -0
  110. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanaz.ttf +0 -0
  111. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts.conf +12 -0
  112. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.conf +15 -0
  113. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.map.tmpl +87 -0
  114. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/tinyows.xml.tmpl +36 -0
  115. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Landscape.jrxml +207 -0
  116. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Portrait.jrxml +185 -0
  117. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Landscape.jrxml +200 -0
  118. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Portrait.jrxml +170 -0
  119. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/config.yaml.tmpl +175 -0
  120. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/legend.jrxml +109 -0
  121. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation.properties +4 -0
  122. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation_fr.properties +4 -0
  123. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/logo.png +0 -0
  124. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/north.svg +93 -0
  125. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/results.jrxml +25 -0
  126. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/project.yaml +18 -0
  127. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml +7 -0
  128. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/qgisserver/pg_service.conf.tmpl +15 -0
  129. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/run_alembic.sh +11 -0
  130. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-backup +126 -0
  131. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +132 -0
  132. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/setup.cfg +7 -0
  133. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/spell-ignore-words.txt +5 -0
  134. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/__init__.py +0 -0
  135. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/test_app.py +43 -0
  136. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tilegeneration/config.yaml.tmpl +195 -0
  137. c2cgeoportal_geoportal/scaffolds/update/cookiecutter.json +18 -0
  138. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/.upgrade.yaml +67 -0
  139. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_CHANGELOG.txt +295 -0
  140. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_create_template/tests/test_testapp.py +48 -0
  141. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_config-schema.yaml +922 -0
  142. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_vars.yaml +1503 -0
  143. c2cgeoportal_geoportal/scripts/__init__.py +64 -0
  144. c2cgeoportal_geoportal/scripts/c2cupgrade.py +879 -0
  145. c2cgeoportal_geoportal/scripts/create_demo_theme.py +80 -0
  146. c2cgeoportal_geoportal/scripts/manage_users.py +140 -0
  147. c2cgeoportal_geoportal/scripts/pcreate.py +314 -0
  148. c2cgeoportal_geoportal/scripts/theme2fts.py +347 -0
  149. c2cgeoportal_geoportal/scripts/urllogin.py +81 -0
  150. c2cgeoportal_geoportal/templates/login.html +90 -0
  151. c2cgeoportal_geoportal/templates/notlogin.html +62 -0
  152. c2cgeoportal_geoportal/templates/testi18n.html +12 -0
  153. c2cgeoportal_geoportal/views/__init__.py +59 -0
  154. c2cgeoportal_geoportal/views/dev.py +57 -0
  155. c2cgeoportal_geoportal/views/dynamic.py +208 -0
  156. c2cgeoportal_geoportal/views/entry.py +174 -0
  157. c2cgeoportal_geoportal/views/fulltextsearch.py +189 -0
  158. c2cgeoportal_geoportal/views/geometry_processing.py +75 -0
  159. c2cgeoportal_geoportal/views/i18n.py +129 -0
  160. c2cgeoportal_geoportal/views/layers.py +713 -0
  161. c2cgeoportal_geoportal/views/login.py +679 -0
  162. c2cgeoportal_geoportal/views/mapserverproxy.py +191 -0
  163. c2cgeoportal_geoportal/views/memory.py +90 -0
  164. c2cgeoportal_geoportal/views/ogcproxy.py +120 -0
  165. c2cgeoportal_geoportal/views/pdfreport.py +245 -0
  166. c2cgeoportal_geoportal/views/printproxy.py +143 -0
  167. c2cgeoportal_geoportal/views/profile.py +127 -0
  168. c2cgeoportal_geoportal/views/proxy.py +259 -0
  169. c2cgeoportal_geoportal/views/raster.py +193 -0
  170. c2cgeoportal_geoportal/views/resourceproxy.py +73 -0
  171. c2cgeoportal_geoportal/views/shortener.py +152 -0
  172. c2cgeoportal_geoportal/views/theme.py +1322 -0
  173. c2cgeoportal_geoportal/views/tinyowsproxy.py +189 -0
  174. c2cgeoportal_geoportal/views/vector_tiles.py +83 -0
  175. {c2cgeoportal_geoportal-2.3.5.79.dist-info → c2cgeoportal_geoportal-2.9rc1.dist-info}/METADATA +21 -24
  176. c2cgeoportal_geoportal-2.9rc1.dist-info/RECORD +192 -0
  177. {c2cgeoportal_geoportal-2.3.5.79.dist-info → c2cgeoportal_geoportal-2.9rc1.dist-info}/WHEEL +1 -1
  178. c2cgeoportal_geoportal-2.9rc1.dist-info/entry_points.txt +28 -0
  179. c2cgeoportal_geoportal-2.9rc1.dist-info/top_level.txt +2 -0
  180. tests/__init__.py +100 -0
  181. tests/test_cachebuster.py +71 -0
  182. tests/test_caching.py +275 -0
  183. tests/test_checker.py +85 -0
  184. tests/test_decimaljson.py +47 -0
  185. tests/test_headerstween.py +64 -0
  186. tests/test_i18n.py +31 -0
  187. tests/test_init.py +193 -0
  188. tests/test_locale_negociator.py +69 -0
  189. tests/test_mapserverproxy_route_predicate.py +64 -0
  190. tests/test_raster.py +267 -0
  191. tests/test_wmstparsing.py +238 -0
  192. tests/xmlstr.py +103 -0
  193. c2cgeoportal_geoportal-2.3.5.79.dist-info/DESCRIPTION.rst +0 -8
  194. c2cgeoportal_geoportal-2.3.5.79.dist-info/RECORD +0 -7
  195. c2cgeoportal_geoportal-2.3.5.79.dist-info/entry_points.txt +0 -22
  196. c2cgeoportal_geoportal-2.3.5.79.dist-info/metadata.json +0 -1
  197. c2cgeoportal_geoportal-2.3.5.79.dist-info/top_level.txt +0 -1
@@ -0,0 +1,57 @@
1
+ # Copyright (c) 2011-2021, Camptocamp SA
2
+ # All rights reserved.
3
+
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the FreeBSD Project.
27
+
28
+
29
+ import logging
30
+ import re
31
+
32
+ import pyramid.request
33
+ import pyramid.response
34
+ from pyramid.httpexceptions import HTTPFound
35
+ from pyramid.view import view_config
36
+
37
+ from c2cgeoportal_commons.lib.url import Url
38
+ from c2cgeoportal_geoportal.views.proxy import Proxy
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ class Dev(Proxy):
44
+ """All the development views."""
45
+
46
+ THEME_RE = re.compile(r"/theme/.*$")
47
+
48
+ def __init__(self, request: pyramid.request.Request):
49
+ super().__init__(request)
50
+ self.dev_url = self.request.registry.settings["devserver_url"]
51
+
52
+ @view_config(route_name="dev") # type: ignore
53
+ def dev(self) -> pyramid.response.Response:
54
+ path = self.THEME_RE.sub("", self.request.path_info)
55
+ if self.request.path.endswith("/dynamic.js"):
56
+ return HTTPFound(location=self.request.route_url("dynamic", _query=self.request.params))
57
+ return self._proxy_response("dev", Url(f"{self.dev_url.rstrip('/')}/{path.lstrip('/')}"))
@@ -0,0 +1,208 @@
1
+ # Copyright (c) 2018-2024, Camptocamp SA
2
+ # All rights reserved.
3
+
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the FreeBSD Project.
27
+
28
+
29
+ import re
30
+ import urllib.parse
31
+ from typing import Any, cast
32
+
33
+ import pyramid.request
34
+ from pyramid.httpexceptions import HTTPNotFound
35
+ from pyramid.view import view_config
36
+ from sqlalchemy import func
37
+
38
+ from c2cgeoportal_commons import models
39
+ from c2cgeoportal_commons.models import main
40
+ from c2cgeoportal_geoportal import is_allowed_host
41
+ from c2cgeoportal_geoportal.lib.cacheversion import get_cache_version
42
+ from c2cgeoportal_geoportal.lib.caching import get_region
43
+ from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
44
+
45
+ CACHE_REGION = get_region("std")
46
+
47
+
48
+ class DynamicView:
49
+ """The dynamic vies that provide the configuration of the client application."""
50
+
51
+ def __init__(self, request: pyramid.request.Request):
52
+ self.request = request
53
+ self.settings = request.registry.settings
54
+ self.interfaces_config = self.settings["interfaces_config"]
55
+
56
+ def get(self, value: dict[str, Any], interface: str) -> dict[str, Any]:
57
+ return cast(dict[str, Any], self.interfaces_config.get(interface, {}).get(value, {}))
58
+
59
+ @CACHE_REGION.cache_on_arguments()
60
+ def _fulltextsearch_groups(self) -> list[str]:
61
+ assert models.DBSession is not None
62
+
63
+ return [
64
+ group[0]
65
+ for group in models.DBSession.query(func.distinct(main.FullTextSearch.layer_name))
66
+ .filter(main.FullTextSearch.layer_name.isnot(None))
67
+ .all()
68
+ ]
69
+
70
+ def _interface(
71
+ self,
72
+ interface_config: dict[str, Any],
73
+ interface_name: str,
74
+ original_interface_name: str,
75
+ dynamic: dict[str, Any],
76
+ ) -> dict[str, Any]:
77
+ """
78
+ Get the interface configuration.
79
+
80
+ Arguments:
81
+
82
+ interface_config: Current interface configuration
83
+ interface_name: Interface name (we use in the configuration)
84
+ original_interface_name: Original interface name (directly for the query string)
85
+ dynamic: The values that's dynamically generated
86
+ """
87
+
88
+ if "extends" in interface_config:
89
+ constants = self._interface(
90
+ self.interfaces_config[interface_config["extends"]],
91
+ interface_name,
92
+ original_interface_name,
93
+ dynamic,
94
+ )
95
+ else:
96
+ constants = {}
97
+
98
+ constants.update(interface_config.get("constants", {}))
99
+ constants.update(
100
+ {
101
+ name: dynamic[value]
102
+ for name, value in interface_config.get("dynamic_constants", {}).items()
103
+ if value is not None
104
+ }
105
+ )
106
+ constants.update(
107
+ {
108
+ name: self.request.static_url(static_["name"]) + static_.get("append", "")
109
+ for name, static_ in interface_config.get("static", {}).items()
110
+ }
111
+ )
112
+
113
+ for constant, config in interface_config.get("routes", {}).items():
114
+ route_name = original_interface_name if config.get("currentInterface", False) else config["name"]
115
+ params: dict[str, str] = {}
116
+ params.update(config.get("params", {}))
117
+ for name, dyn in config.get("dynamic_params", {}).items():
118
+ params[name] = dynamic[dyn]
119
+ constants[constant] = self.request.route_url(
120
+ route_name, *config.get("elements", []), _query=params, **config.get("kw", {})
121
+ )
122
+
123
+ return constants
124
+
125
+ @view_config(route_name="dynamic", renderer="json") # type: ignore
126
+ def dynamic(self) -> dict[str, Any]:
127
+ is_allowed_host(self.request)
128
+
129
+ original_interface_name = self.request.params.get("interface")
130
+ interface_name = self.request.get_organization_interface(original_interface_name)
131
+
132
+ if interface_name not in self.interfaces_config:
133
+ interface_separator = "', '"
134
+ raise HTTPNotFound(
135
+ f"Interface '{interface_name}' doesn't exist in the 'interfaces_config', "
136
+ f"available interfaces are: '{interface_separator.join(self.interfaces_config.keys())}'."
137
+ )
138
+
139
+ interface_config = self.interfaces_config[interface_name]
140
+ lang_urls_suffix = interface_config.get("lang_urls_suffix", "")
141
+
142
+ i18next_configuration = self.settings.get("i18next", {})
143
+ i18next_configuration.setdefault("backend", {})
144
+ if "loadPath" not in i18next_configuration["backend"]:
145
+ path: list[str] = [
146
+ self.request.route_url("base").rstrip("/"),
147
+ "static-{{ns}}",
148
+ get_cache_version(),
149
+ "locales",
150
+ "{{lng}}.json",
151
+ ]
152
+ i18next_configuration["backend"]["loadPath"] = "/".join(path)
153
+
154
+ dynamic = {
155
+ "interface": interface_name,
156
+ "cache_version": get_cache_version(),
157
+ "two_factor": self.request.registry.settings.get("authentication", {}).get("two_factor", False),
158
+ "lang_urls": {
159
+ lang: self.request.static_url(
160
+ f"/etc/geomapfish/static/{lang}{lang_urls_suffix}.json",
161
+ )
162
+ for lang in self.request.registry.settings["available_locale_names"]
163
+ },
164
+ "i18next_configuration": i18next_configuration,
165
+ "fulltextsearch_groups": self._fulltextsearch_groups(),
166
+ }
167
+
168
+ constants = self._interface(interface_config, interface_name, original_interface_name, dynamic)
169
+
170
+ do_redirect = False
171
+ url = None
172
+ if "redirect_interface" in interface_config:
173
+ no_redirect_query: dict[str, str | list[str]] = {"no_redirect": "t"}
174
+ if "query" in self.request.params:
175
+ query = urllib.parse.parse_qs(self.request.params["query"][1:], keep_blank_values=True)
176
+ no_redirect_query.update(query)
177
+ else:
178
+ query = {}
179
+ theme = None
180
+ if "path" in self.request.params:
181
+ match = re.match(".*/theme/(.*)", self.request.params["path"])
182
+ if match is not None:
183
+ theme = match.group(1)
184
+ if theme is not None:
185
+ no_redirect_url = self.request.route_url(
186
+ interface_config["redirect_interface"] + "theme", themes=theme, _query=no_redirect_query
187
+ )
188
+ url = self.request.route_url(
189
+ interface_config["redirect_interface"] + "theme", themes=theme, _query=query
190
+ ).replace("+", "%20")
191
+ else:
192
+ no_redirect_url = self.request.route_url(
193
+ interface_config["redirect_interface"], _query=no_redirect_query
194
+ )
195
+ url = self.request.route_url(interface_config["redirect_interface"], _query=query).replace(
196
+ "+", "%20"
197
+ )
198
+
199
+ if "no_redirect" in query:
200
+ constants["redirectUrl"] = ""
201
+ else:
202
+ if interface_config.get("do_redirect", False):
203
+ do_redirect = True
204
+ else:
205
+ constants["redirectUrl"] = no_redirect_url
206
+
207
+ set_common_headers(self.request, "dynamic", Cache.PUBLIC_NO)
208
+ return {"constants": constants, "doRedirect": do_redirect, "redirectUrl": url}
@@ -0,0 +1,174 @@
1
+ # Copyright (c) 2011-2024, Camptocamp SA
2
+ # All rights reserved.
3
+
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the FreeBSD Project.
27
+
28
+
29
+ import glob
30
+ import logging
31
+ import os
32
+ from typing import Any
33
+
34
+ import pyramid.request
35
+ from bs4 import BeautifulSoup
36
+ from pyramid.i18n import TranslationStringFactory
37
+ from pyramid.view import view_config
38
+
39
+ from c2cgeoportal_geoportal.lib.caching import get_region
40
+ from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
41
+
42
+ _ = TranslationStringFactory("c2cgeoportal")
43
+ _LOG = logging.getLogger(__name__)
44
+ _CACHE_REGION = get_region("std")
45
+
46
+
47
+ class Entry:
48
+ """All the entry points views."""
49
+
50
+ def __init__(self, request: pyramid.request.Request):
51
+ self.request = request
52
+
53
+ @view_config(route_name="testi18n", renderer="testi18n.html") # type: ignore[misc]
54
+ def testi18n(self) -> dict[str, Any]:
55
+ _ = self.request.translate
56
+ return {"title": _("title i18n")}
57
+
58
+ def get_ngeo_index_vars(self) -> dict[str, Any]:
59
+ set_common_headers(self.request, "index", Cache.PUBLIC_NO, content_type="text/html")
60
+ # Force urllogin to be converted to cookie when requesting the main HTML page
61
+ self.request.user # noqa
62
+ return {}
63
+
64
+ @staticmethod
65
+ @_CACHE_REGION.cache_on_arguments()
66
+ def get_apijs(api_filename: str, api_name: str | None) -> str:
67
+ with open(api_filename, encoding="utf-8") as api_file:
68
+ api = api_file.read().split("\n")
69
+ sourcemap = api.pop(-1)
70
+ if api_name:
71
+ api += [
72
+ f"if (window.{api_name} === undefined && window.geomapfishapp) {{",
73
+ f" window.{api_name} = window.geomapfishapp;",
74
+ "}",
75
+ ]
76
+ api.append(sourcemap)
77
+
78
+ return "\n".join(api)
79
+
80
+ @view_config(route_name="apijs") # type: ignore
81
+ def apijs(self) -> pyramid.response.Response:
82
+ self.request.response.text = self.get_apijs(
83
+ self.request.registry.settings["static_files"]["api.js"],
84
+ self.request.registry.settings["api"].get("name"),
85
+ )
86
+ set_common_headers(self.request, "api", Cache.PUBLIC, content_type="application/javascript")
87
+ return self.request.response
88
+
89
+ def favicon(self) -> dict[str, Any]:
90
+ set_common_headers(self.request, "index", Cache.PUBLIC, content_type="image/vnd.microsoft.icon")
91
+ return {}
92
+
93
+ def robot_txt(self) -> dict[str, Any]:
94
+ set_common_headers(self.request, "index", Cache.PUBLIC, content_type="text/plain")
95
+ return {}
96
+
97
+ def apijsmap(self) -> dict[str, Any]:
98
+ set_common_headers(self.request, "api", Cache.PUBLIC, content_type="application/octet-stream")
99
+ return {}
100
+
101
+ def apicss(self) -> dict[str, Any]:
102
+ set_common_headers(self.request, "api", Cache.PUBLIC, content_type="text/css")
103
+ return {}
104
+
105
+ def apihelp(self) -> dict[str, Any]:
106
+ set_common_headers(self.request, "apihelp", Cache.PUBLIC)
107
+ return {}
108
+
109
+
110
+ def _get_ngeo_resources(pattern: str) -> list[str]:
111
+ """Return the list of ngeo dist files matching the pattern."""
112
+ return glob.glob(f"/opt/c2cgeoportal/geoportal/node_modules/ngeo/dist/{pattern}")
113
+
114
+
115
+ def canvas_view(request: pyramid.request.Request, interface_config: dict[str, Any]) -> dict[str, Any]:
116
+ """Get view used as entry point of a canvas interface."""
117
+
118
+ js_files = _get_ngeo_resources(f"{interface_config.get('layout', interface_config['name'])}*.js")
119
+ css_files = _get_ngeo_resources(f"{interface_config.get('layout', interface_config['name'])}*.css")
120
+ css = "\n ".join(
121
+ [
122
+ f'<link href="{request.static_url(css)}" rel="stylesheet" crossorigin="anonymous">'
123
+ for css in css_files
124
+ ]
125
+ )
126
+
127
+ set_common_headers(request, "index", Cache.PUBLIC_NO, content_type="text/html")
128
+
129
+ spinner = ""
130
+ spinner_filenames = _get_ngeo_resources("spinner*.svg")
131
+ if spinner_filenames:
132
+ with open(spinner_filenames[0], encoding="utf-8") as spinner_file:
133
+ spinner = spinner_file.read()
134
+
135
+ return {
136
+ "request": request,
137
+ "header": f"""
138
+ <meta name="dynamicUrl" content="{request.route_url("dynamic")}">
139
+ <meta name="interface" content="{interface_config['name']}">
140
+ {css}""",
141
+ "footer": "\n ".join(
142
+ [f'<script src="{request.static_url(js)}" crossorigin="anonymous"></script>' for js in js_files]
143
+ ),
144
+ "spinner": spinner,
145
+ }
146
+
147
+
148
+ def custom_view(
149
+ request: pyramid.request.Request, interface_config: dict[str, Any]
150
+ ) -> pyramid.response.Response:
151
+ """Get view used as entry point of a canvas interface."""
152
+
153
+ set_common_headers(request, "index", Cache.PUBLIC_NO, content_type="text/html")
154
+
155
+ html_filename = interface_config.get("html_filename", f"{interface_config['name']}.html")
156
+ if not html_filename.startswith("/"):
157
+ html_filename = os.path.join("/etc/static-frontend/", html_filename)
158
+
159
+ with open(html_filename, encoding="utf-8") as html_file:
160
+ html = BeautifulSoup(html_file, "html.parser")
161
+
162
+ meta = html.find("meta", attrs={"name": "interface"})
163
+ if meta is not None:
164
+ meta["content"] = interface_config["name"]
165
+ meta = html.find("meta", attrs={"name": "dynamicUrl"})
166
+ if meta is not None:
167
+ meta["content"] = request.route_url("dynamic")
168
+
169
+ if hasattr(request, "custom_interface_transformer"):
170
+ request.custom_interface_transformer(html, interface_config)
171
+
172
+ request.response.text = str(html)
173
+
174
+ return request.response
@@ -0,0 +1,189 @@
1
+ # Copyright (c) 2011-2024, Camptocamp SA
2
+ # All rights reserved.
3
+
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the FreeBSD Project.
27
+
28
+
29
+ import re
30
+
31
+ import pyramid.request
32
+ from geoalchemy2.shape import to_shape
33
+ from geojson import Feature, FeatureCollection
34
+ from pyramid.httpexceptions import HTTPBadRequest, HTTPInternalServerError
35
+ from pyramid.view import view_config
36
+ from sqlalchemy import ColumnElement, and_, desc, func, or_
37
+
38
+ from c2cgeoportal_commons.models import DBSession
39
+ from c2cgeoportal_commons.models.main import FullTextSearch, Interface
40
+ from c2cgeoportal_geoportal import locale_negotiator
41
+ from c2cgeoportal_geoportal.lib.caching import get_region
42
+ from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
43
+ from c2cgeoportal_geoportal.lib.fulltextsearch import Normalize
44
+
45
+ CACHE_REGION = get_region("std")
46
+ IGNORED_CHARS_RE = re.compile(r"[()&|!:<>\t]")
47
+ IGNORED_STARTUP_CHARS_RE = re.compile(r"^[']*")
48
+
49
+
50
+ class FullTextSearchView:
51
+ """All the full-text search view."""
52
+
53
+ def __init__(self, request: pyramid.request.Request):
54
+ self.request = request
55
+ set_common_headers(request, "fulltextsearch", Cache.PUBLIC_NO)
56
+ self.settings = request.registry.settings.get("fulltextsearch", {})
57
+ self.languages = self.settings.get("languages", {})
58
+ self.fts_normalizer = Normalize(self.settings)
59
+
60
+ @staticmethod
61
+ @CACHE_REGION.cache_on_arguments()
62
+ def _get_interface_id(interface: str) -> int:
63
+ assert DBSession is not None
64
+
65
+ return DBSession.query(Interface).filter_by(name=interface).one().id
66
+
67
+ @view_config(route_name="fulltextsearch", renderer="geojson") # type: ignore[misc]
68
+ def fulltextsearch(self) -> FeatureCollection:
69
+ assert DBSession is not None
70
+
71
+ lang = locale_negotiator(self.request)
72
+
73
+ try:
74
+ language = self.languages[lang]
75
+ except KeyError:
76
+ return HTTPInternalServerError(detail=f"{lang!s} not defined in languages")
77
+
78
+ if "query" not in self.request.params:
79
+ return HTTPBadRequest(detail="no query")
80
+ terms = self.fts_normalizer(self.request.params.get("query"))
81
+
82
+ maxlimit = self.settings.get("maxlimit", 200)
83
+
84
+ try:
85
+ limit = int(self.request.params.get("limit", self.settings.get("defaultlimit", 30)))
86
+ except ValueError:
87
+ return HTTPBadRequest(detail="limit value is incorrect")
88
+ limit = min(limit, maxlimit)
89
+
90
+ try:
91
+ partitionlimit = int(self.request.params.get("partitionlimit", 0))
92
+ except ValueError:
93
+ return HTTPBadRequest(detail="partitionlimit value is incorrect")
94
+ partitionlimit = min(partitionlimit, maxlimit)
95
+
96
+ terms_array = [
97
+ IGNORED_STARTUP_CHARS_RE.sub("", elem) for elem in IGNORED_CHARS_RE.sub(" ", terms).split(" ")
98
+ ]
99
+ terms_ts = "&".join(w + ":*" for w in terms_array if w != "")
100
+ _filter: ColumnElement[bool] = FullTextSearch.ts.op("@@")(func.to_tsquery(language, terms_ts))
101
+
102
+ if self.request.user is None:
103
+ _filter = and_(_filter, FullTextSearch.public.is_(True))
104
+ else:
105
+ _filter = and_(
106
+ _filter,
107
+ or_(
108
+ FullTextSearch.public.is_(True),
109
+ FullTextSearch.role_id.is_(None),
110
+ FullTextSearch.role_id.in_([r.id for r in self.request.user.roles]),
111
+ ),
112
+ )
113
+
114
+ if "interface" in self.request.params:
115
+ _filter = and_(
116
+ _filter,
117
+ or_(
118
+ FullTextSearch.interface_id.is_(None),
119
+ FullTextSearch.interface_id == self._get_interface_id(self.request.params["interface"]),
120
+ ),
121
+ )
122
+ else:
123
+ _filter = and_(_filter, FullTextSearch.interface_id.is_(None))
124
+
125
+ _filter = and_(_filter, or_(FullTextSearch.lang.is_(None), FullTextSearch.lang == lang))
126
+
127
+ rank_system = self.request.params.get("ranksystem")
128
+ if rank_system == "ts_rank_cd":
129
+ # The numbers used in ts_rank_cd() below indicate a normalization method.
130
+ # Several normalization methods can be combined using |.
131
+ # 2 divides the rank by the document length
132
+ # 8 divides the rank by the number of unique words in document
133
+ # By combining them, shorter results seem to be preferred over longer ones
134
+ # with the same ratio of matching words. But this relies only on testing it
135
+ # and on some assumptions about how it might be calculated
136
+ # (the normalization is applied two times with the combination of 2 and 8,
137
+ # so the effect on at least the one-word-results is therefore stronger).
138
+ rank = func.ts_rank_cd(FullTextSearch.ts, func.to_tsquery(language, terms_ts), 2 | 8)
139
+ else:
140
+ # Use similarity ranking system from module pg_trgm.
141
+ rank = func.similarity(FullTextSearch.label, terms)
142
+
143
+ if partitionlimit:
144
+ # Here we want to partition the search results based on
145
+ # layer_name and limit each partition.
146
+ row_number = (
147
+ func.row_number()
148
+ .over(partition_by=FullTextSearch.layer_name, order_by=(desc(rank), FullTextSearch.label))
149
+ .label("row_number")
150
+ )
151
+ sub_query = DBSession.query(FullTextSearch).add_columns(row_number).filter(_filter).subquery()
152
+ query = DBSession.query(
153
+ sub_query.c.id,
154
+ sub_query.c.label,
155
+ sub_query.c.params,
156
+ sub_query.c.layer_name,
157
+ sub_query.c.the_geom,
158
+ sub_query.c.actions,
159
+ )
160
+ query = query.filter(sub_query.c.row_number <= partitionlimit)
161
+ else:
162
+ query = DBSession.query(FullTextSearch).filter(_filter)
163
+ query = query.order_by(desc(rank))
164
+ query = query.order_by(FullTextSearch.label)
165
+
166
+ query = query.limit(limit)
167
+ objects = query.all()
168
+
169
+ features = []
170
+ for o in objects:
171
+ properties = {"label": o.label}
172
+ if o.layer_name is not None:
173
+ properties["layer_name"] = o.layer_name
174
+ if o.params is not None:
175
+ properties["params"] = o.params
176
+ if o.actions is not None:
177
+ properties["actions"] = o.actions
178
+ if o.actions is None and o.layer_name is not None:
179
+ properties["actions"] = [{"action": "add_layer", "data": o.layer_name}]
180
+
181
+ if o.the_geom is not None:
182
+ geom = to_shape(o.the_geom)
183
+ feature = Feature(id=o.id, geometry=geom, properties=properties, bbox=geom.bounds)
184
+ features.append(feature)
185
+ else:
186
+ feature = Feature(id=o.id, properties=properties)
187
+ features.append(feature)
188
+
189
+ return FeatureCollection(features)