geovisio 2.6.0__py3-none-any.whl → 2.7.1__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 (62) hide show
  1. geovisio/__init__.py +36 -7
  2. geovisio/admin_cli/cleanup.py +2 -2
  3. geovisio/admin_cli/db.py +1 -4
  4. geovisio/config_app.py +40 -1
  5. geovisio/db_migrations.py +24 -3
  6. geovisio/templates/main.html +13 -13
  7. geovisio/templates/viewer.html +3 -3
  8. geovisio/translations/de/LC_MESSAGES/messages.mo +0 -0
  9. geovisio/translations/de/LC_MESSAGES/messages.po +804 -0
  10. geovisio/translations/el/LC_MESSAGES/messages.mo +0 -0
  11. geovisio/translations/el/LC_MESSAGES/messages.po +685 -0
  12. geovisio/translations/en/LC_MESSAGES/messages.mo +0 -0
  13. geovisio/translations/en/LC_MESSAGES/messages.po +738 -0
  14. geovisio/translations/es/LC_MESSAGES/messages.mo +0 -0
  15. geovisio/translations/es/LC_MESSAGES/messages.po +778 -0
  16. geovisio/translations/fi/LC_MESSAGES/messages.mo +0 -0
  17. geovisio/translations/fi/LC_MESSAGES/messages.po +589 -0
  18. geovisio/translations/fr/LC_MESSAGES/messages.mo +0 -0
  19. geovisio/translations/fr/LC_MESSAGES/messages.po +814 -0
  20. geovisio/translations/hu/LC_MESSAGES/messages.mo +0 -0
  21. geovisio/translations/hu/LC_MESSAGES/messages.po +773 -0
  22. geovisio/translations/ko/LC_MESSAGES/messages.mo +0 -0
  23. geovisio/translations/ko/LC_MESSAGES/messages.po +685 -0
  24. geovisio/translations/messages.pot +694 -0
  25. geovisio/translations/nl/LC_MESSAGES/messages.mo +0 -0
  26. geovisio/translations/nl/LC_MESSAGES/messages.po +602 -0
  27. geovisio/utils/__init__.py +1 -1
  28. geovisio/utils/auth.py +50 -11
  29. geovisio/utils/db.py +65 -0
  30. geovisio/utils/excluded_areas.py +83 -0
  31. geovisio/utils/extent.py +30 -0
  32. geovisio/utils/fields.py +1 -1
  33. geovisio/utils/filesystems.py +0 -1
  34. geovisio/utils/link.py +14 -0
  35. geovisio/utils/params.py +20 -0
  36. geovisio/utils/pictures.py +110 -88
  37. geovisio/utils/reports.py +171 -0
  38. geovisio/utils/sequences.py +262 -126
  39. geovisio/utils/tokens.py +37 -42
  40. geovisio/utils/upload_set.py +642 -0
  41. geovisio/web/auth.py +37 -37
  42. geovisio/web/collections.py +304 -304
  43. geovisio/web/configuration.py +14 -0
  44. geovisio/web/docs.py +276 -15
  45. geovisio/web/excluded_areas.py +377 -0
  46. geovisio/web/items.py +169 -112
  47. geovisio/web/map.py +104 -36
  48. geovisio/web/params.py +69 -26
  49. geovisio/web/pictures.py +14 -31
  50. geovisio/web/reports.py +399 -0
  51. geovisio/web/rss.py +13 -7
  52. geovisio/web/stac.py +129 -134
  53. geovisio/web/tokens.py +98 -109
  54. geovisio/web/upload_set.py +771 -0
  55. geovisio/web/users.py +100 -73
  56. geovisio/web/utils.py +28 -9
  57. geovisio/workers/runner_pictures.py +241 -207
  58. {geovisio-2.6.0.dist-info → geovisio-2.7.1.dist-info}/METADATA +17 -14
  59. geovisio-2.7.1.dist-info/RECORD +70 -0
  60. {geovisio-2.6.0.dist-info → geovisio-2.7.1.dist-info}/WHEEL +1 -1
  61. geovisio-2.6.0.dist-info/RECORD +0 -41
  62. {geovisio-2.6.0.dist-info → geovisio-2.7.1.dist-info}/LICENSE +0 -0
geovisio/__init__.py CHANGED
@@ -1,19 +1,34 @@
1
1
  """GeoVisio API - Main"""
2
2
 
3
- __version__ = "2.6.0"
3
+ __version__ = "2.7.1"
4
4
 
5
5
  import os
6
- from flask import Flask, jsonify, stream_template, send_from_directory, redirect
6
+ from flask import Flask, jsonify, stream_template, send_from_directory, redirect, request
7
7
  from flask.cli import with_appcontext
8
8
  from flask_cors import CORS
9
9
  from flask_compress import Compress
10
+ from flask_babel import Babel
10
11
  from flasgger import Swagger
11
12
  import logging
12
13
  from logging.config import dictConfig
13
14
 
14
15
  from geovisio import db_migrations, config_app, admin_cli, errors, utils
15
- from geovisio.utils import filesystems, sentry
16
- from geovisio.web import auth, docs, pictures, stac, map, users, configuration, tokens, collections, items
16
+ from geovisio.utils import db, filesystems, sentry
17
+ from geovisio.web import (
18
+ auth,
19
+ docs,
20
+ pictures,
21
+ stac,
22
+ map,
23
+ users,
24
+ configuration,
25
+ tokens,
26
+ collections,
27
+ items,
28
+ upload_set,
29
+ reports,
30
+ excluded_areas,
31
+ )
17
32
  from geovisio.workers import runner_pictures
18
33
 
19
34
 
@@ -42,6 +57,14 @@ LOGGING_CONFIG = {
42
57
  dictConfig(LOGGING_CONFIG)
43
58
 
44
59
 
60
+ # Init i18n
61
+ def get_locale():
62
+ try:
63
+ return request.accept_languages.best_match(["de", "fr", "en", "es", "hu"]) or "en"
64
+ except:
65
+ return "en"
66
+
67
+
45
68
  def create_app(test_config=None, app=None):
46
69
  """API launcher method"""
47
70
  #
@@ -54,6 +77,8 @@ def create_app(test_config=None, app=None):
54
77
 
55
78
  config_app.read_config(app, test_config)
56
79
  sentry.init(app)
80
+ db.create_db_pool(app)
81
+ Babel(app, locale_selector=get_locale)
57
82
 
58
83
  # Prepare filesystem
59
84
  createDirNoFailure(app.instance_path)
@@ -78,7 +103,8 @@ def create_app(test_config=None, app=None):
78
103
 
79
104
  app.wsgi_app = ProxyFix(app.wsgi_app, x_for=nb_proxies, x_proto=nb_proxies, x_host=nb_proxies, x_prefix=nb_proxies)
80
105
 
81
- runner_pictures.background_processor.init_app(app)
106
+ # store the background processor in the app context
107
+ app.background_processor = runner_pictures.PictureBackgroundProcessor(app)
82
108
 
83
109
  #
84
110
  # List available routes/blueprints
@@ -92,6 +118,9 @@ def create_app(test_config=None, app=None):
92
118
  app.register_blueprint(users.bp)
93
119
  app.register_blueprint(configuration.bp)
94
120
  app.register_blueprint(tokens.bp)
121
+ app.register_blueprint(upload_set.bp)
122
+ app.register_blueprint(reports.bp)
123
+ app.register_blueprint(excluded_areas.bp)
95
124
 
96
125
  # Register CLI comands
97
126
  app.register_blueprint(admin_cli.bp, cli_group=None)
@@ -101,8 +130,8 @@ def create_app(test_config=None, app=None):
101
130
  def run_picture_worker():
102
131
  """Run a worker to process pictures after upload. Each worker use one thread, and several workers can be run in parallel"""
103
132
  logging.info("Running picture worker")
104
- worker = runner_pictures.PictureProcessor(config=app.config, stop=False)
105
- worker.process_next_pictures()
133
+ worker = runner_pictures.PictureProcessor(app=app, stop=False)
134
+ worker.process_jobs()
106
135
 
107
136
  #
108
137
  # API documentation
@@ -60,12 +60,12 @@ def cleanup(sequences=[], full=False, database=False, cache=False, permanentPics
60
60
  if database:
61
61
  log.info("Cleaning up database...")
62
62
  if allSequences:
63
- conn.execute("DELETE FROM pictures_to_process")
63
+ conn.execute("DELETE FROM job_queue")
64
64
  conn.execute("DELETE FROM sequences_pictures")
65
65
  conn.execute("DELETE FROM sequences")
66
66
  conn.execute("DELETE FROM pictures")
67
67
  else:
68
- conn.execute("DELETE FROM pictures_to_process WHERE picture_id = ANY(%s)", [pics])
68
+ conn.execute("DELETE FROM job_queue WHERE picture_id = ANY(%s)", [pics])
69
69
  conn.execute("DELETE FROM sequences_pictures WHERE seq_id = ANY(%s)", [sequences])
70
70
  conn.execute("DELETE FROM sequences WHERE id = ANY(%s)", [sequences])
71
71
  conn.execute("DELETE FROM pictures WHERE id = ANY(%s)", [pics])
geovisio/admin_cli/db.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from flask import Blueprint, current_app
2
2
  from flask.cli import with_appcontext
3
3
  import click
4
- import psycopg
5
4
  from geovisio import db_migrations
6
5
  from geovisio.utils import sequences
7
6
 
@@ -33,6 +32,4 @@ def rollback(all):
33
32
  @with_appcontext
34
33
  def refresh():
35
34
  """Refresh cached data (pictures_grid)"""
36
- with psycopg.connect(current_app.config["DB_URL"]) as db:
37
- sequences.update_pictures_grid(db)
38
- db.commit()
35
+ sequences.update_pictures_grid()
geovisio/config_app.py CHANGED
@@ -3,11 +3,23 @@ import os.path
3
3
  from urllib.parse import urlparse
4
4
  import datetime
5
5
  import logging
6
- from typing import Optional
6
+ from typing import Optional, Dict
7
7
  import croniter
8
+ from pydantic import BaseModel
9
+ from pydantic.color import Color
10
+ from pydantic.networks import HttpUrl
11
+ import json
12
+
13
+
14
+ class ApiSummary(BaseModel):
15
+ name: Dict[str, str] = {"en": "GeoVisio"}
16
+ description: Dict[str, str] = {"en": "The open source photo mapping solution"}
17
+ logo: HttpUrl = "https://gitlab.com/panoramax/gitlab-profile/-/raw/main/images/logo.svg"
18
+ color: Color = "#bf360c"
8
19
 
9
20
 
10
21
  class DefaultConfig:
22
+ API_SUMMARY = ApiSummary()
11
23
  API_VIEWER_PAGE = "viewer.html"
12
24
  API_MAIN_PAGE = "main.html"
13
25
  # we default we keep the session cookie 7 days, users would have to renew their loggin after this
@@ -24,6 +36,10 @@ class DefaultConfig:
24
36
  PICTURE_PROCESS_REFRESH_CRON = (
25
37
  "0 2 * * *" # Background worker will refresh by default some stats at 2 o'clock in the night (local time of the server)
26
38
  )
39
+ DB_MIN_CNX = 0
40
+ DB_MAX_CNX = 10
41
+ DB_STATEMENT_TIMEOUT = 5 * 60 * 1000 # default statement timeout in ms (5mn)
42
+ API_ACCEPT_DUPLICATE = False
27
43
 
28
44
 
29
45
  def read_config(app, test_config):
@@ -46,7 +62,11 @@ def read_config(app, test_config):
46
62
  "DB_PASSWORD",
47
63
  "DB_NAME",
48
64
  "DB_CHECK_SCHEMA",
65
+ "DB_MIN_CNX",
66
+ "DB_MAX_CNX",
67
+ "DB_STATEMENT_TIMEOUT",
49
68
  # API
69
+ "API_SUMMARY",
50
70
  "API_BLUR_URL",
51
71
  "API_VIEWER_PAGE",
52
72
  "API_MAIN_PAGE",
@@ -56,6 +76,8 @@ def read_config(app, test_config):
56
76
  "API_DERIVATES_PICTURES_PUBLIC_URL",
57
77
  "API_PICTURES_LICENSE_SPDX_ID",
58
78
  "API_PICTURES_LICENSE_URL",
79
+ "API_ACCEPT_DUPLICATE",
80
+ "API_GIT_VERSION",
59
81
  # Picture process
60
82
  "PICTURE_PROCESS_DERIVATES_STRATEGY",
61
83
  "PICTURE_PROCESS_THREADS_LIMIT",
@@ -130,6 +152,18 @@ def read_config(app, test_config):
130
152
  f"Unknown picture derivates strategy: '{app.config['PICTURE_PROCESS_DERIVATES_STRATEGY']}'. Please set to one of ON_DEMAND, PREPROCESS"
131
153
  )
132
154
 
155
+ # Parse API summary
156
+ if not isinstance(app.config.get("API_SUMMARY"), ApiSummary):
157
+ try:
158
+ if isinstance(app.config.get("API_SUMMARY"), str):
159
+ app.config["API_SUMMARY"] = ApiSummary(**json.loads(app.config["API_SUMMARY"]))
160
+ elif isinstance(app.config.get("API_SUMMARY"), dict):
161
+ app.config["API_SUMMARY"] = ApiSummary(**app.config["API_SUMMARY"])
162
+ elif app.config.get("API_SUMMARY") is not None:
163
+ raise Exception("Value is not a JSON")
164
+ except Exception as e:
165
+ raise Exception("Parameter API_SUMMARY is not recognized") from e
166
+
133
167
  # Checks on front-end related variables
134
168
  templateFolder = os.path.join(app.root_path, app.template_folder)
135
169
  for pageParam in ["API_MAIN_PAGE", "API_VIEWER_PAGE"]:
@@ -169,6 +203,11 @@ def read_config(app, test_config):
169
203
  cron_val = app.config["PICTURE_PROCESS_REFRESH_CRON"]
170
204
  if not croniter.croniter.is_valid(cron_val):
171
205
  raise Exception(f"PICTURE_PROCESS_REFRESH_CRON should be a valid cron syntax, got '{cron_val}'")
206
+
207
+ app.config["API_ACCEPT_DUPLICATE"] = _read_bool(app.config, "API_ACCEPT_DUPLICATE")
208
+
209
+ app.config["DB_STATEMENT_TIMEOUT"] = int(app.config["DB_STATEMENT_TIMEOUT"])
210
+
172
211
  #
173
212
  # Add generated config vars
174
213
  #
geovisio/db_migrations.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import yoyo
2
2
  import psycopg
3
+ from psycopg.sql import SQL, Identifier
3
4
  import os
4
5
  import sys
5
6
 
@@ -8,6 +9,26 @@ def update_db_schema(dbUrl, force=False):
8
9
  # Check if DB has its structure initialized
9
10
  with psycopg.connect(dbUrl) as conn:
10
11
  with conn.cursor() as cursor:
12
+ # Alert if database is not UTC
13
+ dbTz = cursor.execute("SHOW timezone").fetchone()[0]
14
+ isUtcOffset = cursor.execute(
15
+ "SELECT EXISTS(SELECT * FROM pg_timezone_names WHERE name = %s AND utc_offset='00:00:00' AND NOT is_dst)", [dbTz]
16
+ ).fetchone()[0]
17
+ if not isUtcOffset:
18
+ dbName = Identifier(dbUrl.split("/")[-1])
19
+ if force:
20
+ cursor.execute(SQL("ALTER DATABASE {db} SET TIMEZONE TO 'UTC'").format(db=dbName))
21
+ else:
22
+ raise Exception(
23
+ f"""Database is not running at UTC timezone !
24
+
25
+ Your database actually uses timezone \"{dbTz}\".
26
+ Issues could happen if your database runs at a different timezone.
27
+ You can set the database timezone using the following command:
28
+
29
+ ALTER DATABASE {dbName.as_string()} SET TIMEZONE TO 'UTC';"""
30
+ )
31
+
11
32
  picturesTableExists = cursor.execute("SELECT EXISTS(SELECT relname FROM pg_class WHERE relname = 'pictures')").fetchone()[0]
12
33
  yoyoExists = cursor.execute("SELECT EXISTS(SELECT relname FROM pg_class WHERE relname like '_yoyo_%')").fetchone()[0]
13
34
  conn.close()
@@ -22,7 +43,7 @@ def update_db_schema(dbUrl, force=False):
22
43
  handledMigrations = [
23
44
  m for m in migrations if m.id in ["20221201_01_wpCGc-initial-schema", "20221201_02_ZG8AR-camera-information"]
24
45
  ]
25
- print(f"Database migrated to use Yoyo tools...")
46
+ print("Database migrated to use Yoyo tools...")
26
47
  backend.mark_migrations(handledMigrations)
27
48
 
28
49
  migrationToApply = backend.to_apply(migrations)
@@ -69,7 +90,7 @@ def rollback_db_schema(dbUrl, rollback_all):
69
90
  if not rollback_all:
70
91
  migrations_to_rollback = migrations_to_rollback[:1] # we only rollback the last one
71
92
  if len(migrations_to_rollback) > 0:
72
- print(f"Starting rollback for migrations:")
93
+ print("Starting rollback for migrations:")
73
94
  for m in migrations_to_rollback:
74
95
  print(f" - {m.id}")
75
96
  backend.rollback_migrations(migrations_to_rollback)
@@ -79,7 +100,7 @@ def rollback_db_schema(dbUrl, rollback_all):
79
100
 
80
101
 
81
102
  def get_yoyo_backend(dbUrl):
82
- dbUrl = dbUrl.replace("postgres://", "postgresql+psycopg://") # force spycopg3 usage on yolo
103
+ dbUrl = dbUrl.replace("postgres://", "postgresql+psycopg://") # force psycopg3 usage on yolo
83
104
  return yoyo.get_backend(dbUrl)
84
105
 
85
106
 
@@ -29,7 +29,7 @@
29
29
 
30
30
  <script>
31
31
  var baseUrl = window.location.href.replace(/\/$/, '');
32
- var viewerUrl = "https://cdn.jsdelivr.net/npm/geovisio@~{{API_VERSION_MAJOR_MINOR}}/build";
32
+ var viewerUrl = "https://cdn.jsdelivr.net/npm/geovisio@~3.0/build";
33
33
  function encodeHTML(html) {
34
34
  return html
35
35
  .replace(/</g, "&lt;")
@@ -42,22 +42,22 @@
42
42
  </head>
43
43
  <body>
44
44
  <h1><img src="/static/img/logo_full.svg" style="width: 350px; max-width: 90%" alt="GeoVisio" /></h1>
45
- <p>Simple 360° geolocated pictures hosting</p>
45
+ <p>{%trans%}Simple 360° geolocated pictures hosting{%endtrans%}</p>
46
46
  <iframe src="/viewer" style="border: none; width: 95%; height: 300px"></iframe>
47
- <p><small><a href="/viewer">Full page version</a></small></p>
47
+ <p><small><a href="/viewer">{%trans%}Full page version{%endtrans%}</a></small></p>
48
48
 
49
49
  <hr />
50
50
 
51
- <h2>Viewer</h2>
52
- <h3>Embed pre-configured viewer</h3>
53
- <p>Easiest way to have a working GeoVisio viewer on your website</p>
51
+ <h2>{%trans%}Viewer{%endtrans%}</h2>
52
+ <h3>{%trans%}Embed pre-configured viewer{%endtrans%}</h3>
53
+ <p>{%trans%}Easiest way to have a working GeoVisio viewer on your website{%endtrans%}</p>
54
54
  <pre><code id="code-preconf"></code></pre>
55
55
  <script>
56
56
  document.getElementById("code-preconf").innerHTML = encodeHTML('<iframe \n\tsrc="'+baseUrl+'/viewer"\n\tstyle="border: none; width: 500px; height: 300px">\n</iframe>');
57
57
  </script>
58
58
 
59
- <h3>Use JS library</h3>
60
- <p>A completely configurable viewer for your website</p>
59
+ <h3>{%trans%}Use JS library{%endtrans%}</h3>
60
+ <p>{%trans%}A completely configurable viewer for your website{%endtrans%}</p>
61
61
  <pre><code id="code-js-html">
62
62
  </code></pre>
63
63
  <script>
@@ -79,15 +79,15 @@
79
79
 
80
80
  <hr />
81
81
 
82
- <h2>Links</h2>
82
+ <h2>{%trans%}Links{%endtrans%}</h2>
83
83
  <p>
84
- <a href="/viewer">Pictures viewer</a>
84
+ <a href="/viewer">{%trans%}Pictures viewer{%endtrans%}</a>
85
85
  -
86
- <a href="/api/docs/swagger">API docs</a>
86
+ <a href="/api/docs/swagger">{%trans%}API docs{%endtrans%}</a>
87
87
  -
88
- <a href="https://gitlab.com/panoramax/clients/web-viewer/-/tree/develop/docs">JS library docs</a>
88
+ <a href="https://gitlab.com/panoramax/clients/web-viewer/-/tree/develop/docs">{%trans%}JS library docs{%endtrans%}</a>
89
89
  -
90
- <a href="https://gitlab.com/geovisio">Repositories</a>
90
+ <a href="https://gitlab.com/panoramax">{%trans%}Repositories{%endtrans%}</a>
91
91
  </p>
92
92
  </body>
93
93
  </html>
@@ -6,7 +6,7 @@
6
6
  <link rel="shortcut icon" href="/favicon.ico" />
7
7
  <link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg" />
8
8
  <title>GeoVisio</title>
9
- <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/geovisio@~{{API_VERSION_MAJOR_MINOR}}/build/index.css" />
9
+ <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/geovisio@~3.0/build/index.css" />
10
10
  <style>
11
11
  #viewer {
12
12
  position: absolute;
@@ -19,10 +19,10 @@
19
19
  </head>
20
20
  <body>
21
21
  <div id="viewer">
22
- <noscript>You need to enable JavaScript to run this app.</noscript>
22
+ <noscript>{%trans%}You need to enable JavaScript to run this app.{%endtrans%}</noscript>
23
23
  </div>
24
24
 
25
- <script src="https://cdn.jsdelivr.net/npm/geovisio@~{{API_VERSION_MAJOR_MINOR}}/build/index.js"></script>
25
+ <script src="https://cdn.jsdelivr.net/npm/geovisio@~3.0/build/index.js"></script>
26
26
  <script>
27
27
  var instance = new GeoVisio.default(
28
28
  "viewer",