geovisio 2.5.0__py3-none-any.whl → 2.7.0__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.
- geovisio/__init__.py +38 -8
- geovisio/admin_cli/__init__.py +2 -2
- geovisio/admin_cli/db.py +8 -0
- geovisio/config_app.py +64 -0
- geovisio/db_migrations.py +24 -3
- geovisio/templates/main.html +14 -14
- geovisio/templates/viewer.html +3 -3
- geovisio/translations/de/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/de/LC_MESSAGES/messages.po +667 -0
- geovisio/translations/en/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/en/LC_MESSAGES/messages.po +730 -0
- geovisio/translations/es/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/es/LC_MESSAGES/messages.po +778 -0
- geovisio/translations/fi/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/fi/LC_MESSAGES/messages.po +589 -0
- geovisio/translations/fr/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/fr/LC_MESSAGES/messages.po +814 -0
- geovisio/translations/ko/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/ko/LC_MESSAGES/messages.po +685 -0
- geovisio/translations/messages.pot +686 -0
- geovisio/translations/nl/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/nl/LC_MESSAGES/messages.po +594 -0
- geovisio/utils/__init__.py +1 -1
- geovisio/utils/auth.py +50 -11
- geovisio/utils/db.py +65 -0
- geovisio/utils/excluded_areas.py +83 -0
- geovisio/utils/extent.py +30 -0
- geovisio/utils/fields.py +1 -1
- geovisio/utils/filesystems.py +0 -1
- geovisio/utils/link.py +14 -0
- geovisio/utils/params.py +20 -0
- geovisio/utils/pictures.py +94 -69
- geovisio/utils/reports.py +171 -0
- geovisio/utils/sequences.py +288 -126
- geovisio/utils/tokens.py +37 -42
- geovisio/utils/upload_set.py +654 -0
- geovisio/web/auth.py +50 -37
- geovisio/web/collections.py +305 -319
- geovisio/web/configuration.py +14 -0
- geovisio/web/docs.py +288 -12
- geovisio/web/excluded_areas.py +377 -0
- geovisio/web/items.py +203 -151
- geovisio/web/map.py +322 -106
- geovisio/web/params.py +69 -26
- geovisio/web/pictures.py +14 -31
- geovisio/web/reports.py +399 -0
- geovisio/web/rss.py +13 -7
- geovisio/web/stac.py +129 -121
- geovisio/web/tokens.py +105 -112
- geovisio/web/upload_set.py +768 -0
- geovisio/web/users.py +100 -73
- geovisio/web/utils.py +38 -9
- geovisio/workers/runner_pictures.py +278 -183
- geovisio-2.7.0.dist-info/METADATA +95 -0
- geovisio-2.7.0.dist-info/RECORD +66 -0
- geovisio-2.5.0.dist-info/METADATA +0 -115
- geovisio-2.5.0.dist-info/RECORD +0 -41
- {geovisio-2.5.0.dist-info → geovisio-2.7.0.dist-info}/LICENSE +0 -0
- {geovisio-2.5.0.dist-info → geovisio-2.7.0.dist-info}/WHEEL +0 -0
geovisio/web/params.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from uuid import UUID
|
|
2
|
+
|
|
2
3
|
from geovisio import errors
|
|
3
4
|
import dateutil.parser
|
|
4
5
|
from dateutil import tz
|
|
@@ -9,9 +10,12 @@ from werkzeug.datastructures import MultiDict
|
|
|
9
10
|
from typing import Optional, Tuple, Any, List
|
|
10
11
|
from pygeofilter.backends.sql import to_sql_where
|
|
11
12
|
from pygeofilter.parsers.ecql import parse as ecql_parser
|
|
13
|
+
from pygeofilter import ast
|
|
14
|
+
from pygeofilter.backends.evaluator import Evaluator, handle
|
|
12
15
|
from psycopg import sql
|
|
13
16
|
from geovisio.utils.sequences import STAC_FIELD_MAPPINGS, STAC_FIELD_TO_SQL_FILTER
|
|
14
17
|
from geovisio.utils.fields import SortBy, SQLDirection, SortByField
|
|
18
|
+
from flask_babel import gettext as _
|
|
15
19
|
|
|
16
20
|
|
|
17
21
|
RGX_SORTBY = re.compile("[+-]?[A-Za-z_].*(,[+-]?[A-Za-z_].*)*")
|
|
@@ -47,7 +51,7 @@ def parse_datetime(value, error, fallback_as_UTC=False):
|
|
|
47
51
|
d = None
|
|
48
52
|
try:
|
|
49
53
|
d = datetime.datetime.fromisoformat(value)
|
|
50
|
-
except ValueError
|
|
54
|
+
except ValueError:
|
|
51
55
|
pass
|
|
52
56
|
if not d:
|
|
53
57
|
try:
|
|
@@ -81,17 +85,17 @@ def parse_datetime_interval(value: Optional[str]) -> Tuple[Optional[datetime.dat
|
|
|
81
85
|
dates = value.split("/")
|
|
82
86
|
|
|
83
87
|
if len(dates) == 1:
|
|
84
|
-
d = parse_datetime(dates[0], error=
|
|
88
|
+
d = parse_datetime(dates[0], error="Invalid `datetime` argument", fallback_as_UTC=True)
|
|
85
89
|
return (d, d)
|
|
86
90
|
|
|
87
91
|
elif len(dates) == 2:
|
|
88
92
|
# Check if interval is closed or open-ended
|
|
89
93
|
mind, maxd = dates
|
|
90
|
-
mind = None if mind == ".." else parse_datetime(mind, error=
|
|
91
|
-
maxd = None if maxd == ".." else parse_datetime(maxd, error=
|
|
94
|
+
mind = None if mind == ".." else parse_datetime(mind, error="Invalid start date in `datetime` argument", fallback_as_UTC=True)
|
|
95
|
+
maxd = None if maxd == ".." else parse_datetime(maxd, error="Invalid end date in `datetime` argument", fallback_as_UTC=True)
|
|
92
96
|
return (mind, maxd)
|
|
93
97
|
else:
|
|
94
|
-
raise errors.InvalidAPIUsage("Parameter datetime should contain one or two dates", status_code=400)
|
|
98
|
+
raise errors.InvalidAPIUsage(_("Parameter datetime should contain one or two dates"), status_code=400)
|
|
95
99
|
|
|
96
100
|
|
|
97
101
|
def parse_bbox(value: Optional[Any], tryFallbacks=True):
|
|
@@ -161,12 +165,12 @@ def parse_bbox(value: Optional[Any], tryFallbacks=True):
|
|
|
161
165
|
or bbox[3] > 90
|
|
162
166
|
):
|
|
163
167
|
raise errors.InvalidAPIUsage(
|
|
164
|
-
"Parameter bbox must contain valid longitude (-180 to 180) and latitude (-90 to 90) values", status_code=400
|
|
168
|
+
_("Parameter bbox must contain valid longitude (-180 to 180) and latitude (-90 to 90) values"), status_code=400
|
|
165
169
|
)
|
|
166
170
|
else:
|
|
167
171
|
return bbox
|
|
168
172
|
except ValueError:
|
|
169
|
-
raise errors.InvalidAPIUsage("Parameter bbox must be in format [minX, minY, maxX, maxY]", status_code=400)
|
|
173
|
+
raise errors.InvalidAPIUsage(_("Parameter bbox must be in format [minX, minY, maxX, maxY]"), status_code=400)
|
|
170
174
|
else:
|
|
171
175
|
return None
|
|
172
176
|
|
|
@@ -203,11 +207,11 @@ def parse_lonlat(values: Optional[Any], paramName: Optional[str] = None) -> Opti
|
|
|
203
207
|
entries = parse_list(values, paramName=paramName)
|
|
204
208
|
|
|
205
209
|
if entries is None or len(entries) != 2:
|
|
206
|
-
raise errors.InvalidAPIUsage(
|
|
210
|
+
raise errors.InvalidAPIUsage(_("Parameter %(p)s must be coordinates in lat,lon format", p=paramName or ""), status_code=400)
|
|
207
211
|
|
|
208
212
|
return [
|
|
209
|
-
as_longitude(entries[0],
|
|
210
|
-
as_latitude(entries[1],
|
|
213
|
+
as_longitude(entries[0], _("Longitude in parameter %(p)s is not valid (should be between -180 and 180)", p=paramName or "")),
|
|
214
|
+
as_latitude(entries[1], _("Latitude in parameter %(p)s is not valid (should be between -90 and 90)", p=paramName or "")),
|
|
211
215
|
]
|
|
212
216
|
|
|
213
217
|
|
|
@@ -230,18 +234,20 @@ def parse_distance_range(values: Optional[str], paramName: Optional[str] = None)
|
|
|
230
234
|
dists = values.split("-")
|
|
231
235
|
if len(dists) != 2:
|
|
232
236
|
raise errors.InvalidAPIUsage(
|
|
233
|
-
|
|
237
|
+
_('Parameter %(p)s is invalid (should be a distance range in meters like "5-15")', p={paramName or ""}), status_code=400
|
|
234
238
|
)
|
|
235
239
|
try:
|
|
236
240
|
dists = [int(d) for d in dists]
|
|
237
241
|
if dists[0] > dists[1]:
|
|
238
|
-
raise errors.InvalidAPIUsage(
|
|
242
|
+
raise errors.InvalidAPIUsage(
|
|
243
|
+
_("Parameter %(p)s has a min value greater than its max value", p=paramName or ""), status_code=400
|
|
244
|
+
)
|
|
239
245
|
else:
|
|
240
246
|
return dists
|
|
241
247
|
|
|
242
248
|
except ValueError:
|
|
243
249
|
raise errors.InvalidAPIUsage(
|
|
244
|
-
|
|
250
|
+
_('Parameter %(p)s is invalid (should be a distance range in meters like "5-15")', p={paramName or ""}), status_code=400
|
|
245
251
|
)
|
|
246
252
|
else:
|
|
247
253
|
return None
|
|
@@ -308,7 +314,7 @@ def parse_list(value: Optional[Any], tryFallbacks: bool = True, paramName: Optio
|
|
|
308
314
|
value = value.replace("[", "").replace("]", "")
|
|
309
315
|
res = [n.strip() for n in value.split(",")]
|
|
310
316
|
else:
|
|
311
|
-
raise errors.InvalidAPIUsage(
|
|
317
|
+
raise errors.InvalidAPIUsage(_("Parameter %(p)s must be a valid list", p=paramName or ""), status_code=400)
|
|
312
318
|
|
|
313
319
|
if len(res) == 0:
|
|
314
320
|
return None
|
|
@@ -328,10 +334,10 @@ def parse_filter(value: Optional[str]) -> Optional[sql.SQL]:
|
|
|
328
334
|
SQL("(s.updated_at >= '2023-12-31')")
|
|
329
335
|
>>> parse_filter("updated >= '2023-12-31' AND created < '2023-10-31'")
|
|
330
336
|
SQL("((s.updated_at >= '2023-12-31') AND (s.inserted_at < '2023-10-31'))")
|
|
331
|
-
>>> parse_filter("status IN ('deleted','ready')")
|
|
332
|
-
SQL("s.status IN ('deleted', 'ready')")
|
|
337
|
+
>>> parse_filter("status IN ('deleted','ready')") # when we ask for deleted, we should also have hidden collections
|
|
338
|
+
SQL("s.status IN ('deleted', 'ready', 'hidden')")
|
|
333
339
|
>>> parse_filter("status = 'deleted' OR status = 'ready'")
|
|
334
|
-
SQL("((s.status = 'deleted') OR (s.status = 'ready'))")
|
|
340
|
+
SQL("(((s.status = 'deleted') OR (s.status = 'hidden')) OR (s.status = 'ready'))")
|
|
335
341
|
>>> parse_filter('invalid = 10') # doctest: +IGNORE_EXCEPTION_DETAIL
|
|
336
342
|
Traceback (most recent call last):
|
|
337
343
|
geovisio.errors.InvalidAPIUsage: Unsupported filter parameter
|
|
@@ -342,14 +348,51 @@ def parse_filter(value: Optional[str]) -> Optional[sql.SQL]:
|
|
|
342
348
|
if value is not None and len(value) > 0:
|
|
343
349
|
try:
|
|
344
350
|
filterAst = ecql_parser(value)
|
|
345
|
-
|
|
346
|
-
|
|
351
|
+
altered_ast = _alterFilterAst(filterAst) # type: ignore
|
|
352
|
+
|
|
353
|
+
f = to_sql_where(altered_ast, STAC_FIELD_TO_SQL_FILTER).replace('"', "")
|
|
354
|
+
return sql.SQL(f) # type: ignore
|
|
347
355
|
except:
|
|
348
|
-
raise errors.InvalidAPIUsage(
|
|
356
|
+
raise errors.InvalidAPIUsage(_("Unsupported filter parameter"), status_code=400)
|
|
349
357
|
else:
|
|
350
358
|
return None
|
|
351
359
|
|
|
352
360
|
|
|
361
|
+
class _FilterAstUpdated(Evaluator):
|
|
362
|
+
"""
|
|
363
|
+
We alter the parsed AST in order to always query for 'hidden' pictures when we query for 'deleted' ones
|
|
364
|
+
|
|
365
|
+
The rational here is that for non-owned pictures/sequences, when a pictures/sequence is 'hidden' it should be advertised as 'deleted'.
|
|
366
|
+
|
|
367
|
+
This is especially important for crawler like the meta-catalog, since they should also delete the sequence/picture when it is hidden
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
@handle(ast.Equal)
|
|
371
|
+
def eq(self, node, lhs, rhs):
|
|
372
|
+
if lhs == ast.Attribute("status") and rhs == "deleted":
|
|
373
|
+
return ast.Or(node, ast.Equal(ast.Attribute("status"), "hidden")) # type: ignore
|
|
374
|
+
return node
|
|
375
|
+
|
|
376
|
+
@handle(ast.Or)
|
|
377
|
+
def or_(self, node, lhs, rhs):
|
|
378
|
+
return ast.Or(lhs, rhs)
|
|
379
|
+
|
|
380
|
+
@handle(ast.In)
|
|
381
|
+
def in_(self, node, lhs, *options):
|
|
382
|
+
if "deleted" in node.sub_nodes:
|
|
383
|
+
node.sub_nodes.append("hidden")
|
|
384
|
+
return node
|
|
385
|
+
|
|
386
|
+
def adopt(self, node, *sub_args):
|
|
387
|
+
return node
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _alterFilterAst(ast: ast.Node):
|
|
391
|
+
filtered = _FilterAstUpdated().evaluate(ast)
|
|
392
|
+
|
|
393
|
+
return filtered
|
|
394
|
+
|
|
395
|
+
|
|
353
396
|
def parse_sortby(value: Optional[str]) -> Optional[SortBy]:
|
|
354
397
|
"""Reads STAC/OGC sortby parameter, and sends a SQL ORDER BY string.
|
|
355
398
|
|
|
@@ -395,7 +438,7 @@ def parse_sortby(value: Optional[str]) -> Optional[SortBy]:
|
|
|
395
438
|
|
|
396
439
|
# Check if in value mapping
|
|
397
440
|
if vOnly not in STAC_FIELD_MAPPINGS:
|
|
398
|
-
raise errors.InvalidAPIUsage(
|
|
441
|
+
raise errors.InvalidAPIUsage(_("Unsupported sortby parameter: invalid column name"), status_code=400)
|
|
399
442
|
field_mapping = STAC_FIELD_MAPPINGS[vOnly]
|
|
400
443
|
|
|
401
444
|
orders.append(SortByField(field=field_mapping, direction=direction))
|
|
@@ -403,7 +446,7 @@ def parse_sortby(value: Optional[str]) -> Optional[SortBy]:
|
|
|
403
446
|
# Create definitive ORDER string
|
|
404
447
|
return SortBy(fields=orders)
|
|
405
448
|
else:
|
|
406
|
-
raise errors.InvalidAPIUsage(
|
|
449
|
+
raise errors.InvalidAPIUsage(_("Unsupported sortby parameter: syntax isn't correct"), status_code=400)
|
|
407
450
|
else:
|
|
408
451
|
return None
|
|
409
452
|
|
|
@@ -429,10 +472,10 @@ def parse_collections_limit(limit: Optional[str]) -> int:
|
|
|
429
472
|
try:
|
|
430
473
|
int_limit = int(limit)
|
|
431
474
|
except ValueError:
|
|
432
|
-
raise errors.InvalidAPIUsage(
|
|
475
|
+
raise errors.InvalidAPIUsage(_("limit parameter should be a valid, positive integer (between 1 and %(v)s)", v=SEQUENCES_MAX_FETCH))
|
|
433
476
|
|
|
434
477
|
if int_limit < 1 or int_limit > SEQUENCES_MAX_FETCH:
|
|
435
|
-
raise errors.InvalidAPIUsage(
|
|
478
|
+
raise errors.InvalidAPIUsage(_("limit parameter should be an integer between 1 and %(v)s", v=SEQUENCES_MAX_FETCH))
|
|
436
479
|
else:
|
|
437
480
|
return int_limit
|
|
438
481
|
|
|
@@ -443,7 +486,7 @@ def as_longitude(value: str, error):
|
|
|
443
486
|
except ValueError as e:
|
|
444
487
|
raise errors.InvalidAPIUsage(message=error, payload={"details": {"error": str(e)}})
|
|
445
488
|
if l < -180 or l > 180:
|
|
446
|
-
raise errors.InvalidAPIUsage(message=error, payload={"details": {"error": "longitude needs to be between -180 and 180"}})
|
|
489
|
+
raise errors.InvalidAPIUsage(message=error, payload={"details": {"error": _("longitude needs to be between -180 and 180")}})
|
|
447
490
|
return l
|
|
448
491
|
|
|
449
492
|
|
|
@@ -453,7 +496,7 @@ def as_latitude(value: str, error):
|
|
|
453
496
|
except ValueError as e:
|
|
454
497
|
raise errors.InvalidAPIUsage(message=error, payload={"details": {"error": str(e)}})
|
|
455
498
|
if l < -90 or l > 90:
|
|
456
|
-
raise errors.InvalidAPIUsage(message=error, payload={"details": {"error": "latitude needs to be between -90 and 90"}})
|
|
499
|
+
raise errors.InvalidAPIUsage(message=error, payload={"details": {"error": _("latitude needs to be between -90 and 90")}})
|
|
457
500
|
return l
|
|
458
501
|
|
|
459
502
|
|
geovisio/web/pictures.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
from itertools import repeat
|
|
3
|
-
from flask import Blueprint, current_app, request
|
|
1
|
+
from flask import Blueprint, current_app
|
|
4
2
|
from geovisio import utils, errors
|
|
5
3
|
from flask import redirect
|
|
4
|
+
from flask_babel import gettext as _
|
|
6
5
|
import logging
|
|
7
6
|
|
|
8
7
|
bp = Blueprint("pictures", __name__, url_prefix="/api/pictures")
|
|
@@ -25,7 +24,7 @@ def getPictureHD(pictureId, format):
|
|
|
25
24
|
type: string
|
|
26
25
|
- name: format
|
|
27
26
|
in: path
|
|
28
|
-
description: Wanted format for output image (
|
|
27
|
+
description: Wanted format for output image (for the moment only jpg)
|
|
29
28
|
required: true
|
|
30
29
|
schema:
|
|
31
30
|
type: string
|
|
@@ -37,10 +36,6 @@ def getPictureHD(pictureId, format):
|
|
|
37
36
|
schema:
|
|
38
37
|
type: string
|
|
39
38
|
format: binary
|
|
40
|
-
image/webp:
|
|
41
|
-
schema:
|
|
42
|
-
type: string
|
|
43
|
-
format: binary
|
|
44
39
|
"""
|
|
45
40
|
|
|
46
41
|
utils.pictures.checkFormatParam(format)
|
|
@@ -55,7 +50,7 @@ def getPictureHD(pictureId, format):
|
|
|
55
50
|
try:
|
|
56
51
|
picture = fses.permanent.openbin(utils.pictures.getHDPicturePath(pictureId))
|
|
57
52
|
except:
|
|
58
|
-
raise errors.InvalidAPIUsage("Unable to read picture on filesystem", status_code=500)
|
|
53
|
+
raise errors.InvalidAPIUsage(_("Unable to read picture on filesystem"), status_code=500)
|
|
59
54
|
|
|
60
55
|
return utils.pictures.sendInFormat(picture, "jpeg", format)
|
|
61
56
|
|
|
@@ -75,7 +70,7 @@ def getPictureSD(pictureId, format):
|
|
|
75
70
|
type: string
|
|
76
71
|
- name: format
|
|
77
72
|
in: path
|
|
78
|
-
description: Wanted format for output image (
|
|
73
|
+
description: Wanted format for output image (for the moment only jpg)
|
|
79
74
|
required: true
|
|
80
75
|
schema:
|
|
81
76
|
type: string
|
|
@@ -87,10 +82,6 @@ def getPictureSD(pictureId, format):
|
|
|
87
82
|
schema:
|
|
88
83
|
type: string
|
|
89
84
|
format: binary
|
|
90
|
-
image/webp:
|
|
91
|
-
schema:
|
|
92
|
-
type: string
|
|
93
|
-
format: binary
|
|
94
85
|
"""
|
|
95
86
|
utils.pictures.checkFormatParam(format)
|
|
96
87
|
|
|
@@ -104,7 +95,7 @@ def getPictureSD(pictureId, format):
|
|
|
104
95
|
try:
|
|
105
96
|
picture = fses.derivates.openbin(utils.pictures.getPictureFolderPath(pictureId) + "/sd.jpg")
|
|
106
97
|
except:
|
|
107
|
-
raise errors.InvalidAPIUsage("Unable to read picture on filesystem", status_code=500)
|
|
98
|
+
raise errors.InvalidAPIUsage(_("Unable to read picture on filesystem"), status_code=500)
|
|
108
99
|
|
|
109
100
|
return utils.pictures.sendInFormat(picture, "jpeg", format)
|
|
110
101
|
|
|
@@ -124,7 +115,7 @@ def getPictureThumb(pictureId, format):
|
|
|
124
115
|
type: string
|
|
125
116
|
- name: format
|
|
126
117
|
in: path
|
|
127
|
-
description: Wanted format for output image (
|
|
118
|
+
description: Wanted format for output image (for the moment only jpg)
|
|
128
119
|
required: true
|
|
129
120
|
schema:
|
|
130
121
|
type: string
|
|
@@ -136,10 +127,6 @@ def getPictureThumb(pictureId, format):
|
|
|
136
127
|
schema:
|
|
137
128
|
type: string
|
|
138
129
|
format: binary
|
|
139
|
-
image/webp:
|
|
140
|
-
schema:
|
|
141
|
-
type: string
|
|
142
|
-
format: binary
|
|
143
130
|
"""
|
|
144
131
|
return utils.pictures.sendThumbnail(pictureId, format)
|
|
145
132
|
|
|
@@ -171,7 +158,7 @@ def getPictureTile(pictureId, col, row, format):
|
|
|
171
158
|
type: number
|
|
172
159
|
- name: format
|
|
173
160
|
in: path
|
|
174
|
-
description: Wanted format for output image (
|
|
161
|
+
description: Wanted format for output image (for the moment only jpg)
|
|
175
162
|
required: true
|
|
176
163
|
schema:
|
|
177
164
|
type: string
|
|
@@ -183,10 +170,6 @@ def getPictureTile(pictureId, col, row, format):
|
|
|
183
170
|
schema:
|
|
184
171
|
type: string
|
|
185
172
|
format: binary
|
|
186
|
-
image/webp:
|
|
187
|
-
schema:
|
|
188
|
-
type: string
|
|
189
|
-
format: binary
|
|
190
173
|
"""
|
|
191
174
|
|
|
192
175
|
utils.pictures.checkFormatParam(format)
|
|
@@ -201,27 +184,27 @@ def getPictureTile(pictureId, col, row, format):
|
|
|
201
184
|
picPath = f"{utils.pictures.getPictureFolderPath(pictureId)}/tiles/{col}_{row}.jpg"
|
|
202
185
|
|
|
203
186
|
if metadata["type"] == "flat":
|
|
204
|
-
raise errors.InvalidAPIUsage("Tiles are not available for flat pictures", status_code=404)
|
|
187
|
+
raise errors.InvalidAPIUsage(_("Tiles are not available for flat pictures"), status_code=404)
|
|
205
188
|
|
|
206
189
|
try:
|
|
207
190
|
col = int(col)
|
|
208
191
|
except:
|
|
209
|
-
raise errors.InvalidAPIUsage("Column parameter is invalid, should be an integer", status_code=404)
|
|
192
|
+
raise errors.InvalidAPIUsage(_("Column parameter is invalid, should be an integer"), status_code=404)
|
|
210
193
|
|
|
211
194
|
if col < 0 or col >= metadata["cols"]:
|
|
212
|
-
raise errors.InvalidAPIUsage("Column parameter is invalid", status_code=404)
|
|
195
|
+
raise errors.InvalidAPIUsage(_("Column parameter is invalid"), status_code=404)
|
|
213
196
|
|
|
214
197
|
try:
|
|
215
198
|
row = int(row)
|
|
216
199
|
except:
|
|
217
|
-
raise errors.InvalidAPIUsage("Row parameter is invalid, should be an integer", status_code=404)
|
|
200
|
+
raise errors.InvalidAPIUsage(_("Row parameter is invalid, should be an integer"), status_code=404)
|
|
218
201
|
|
|
219
202
|
if row < 0 or row >= metadata["rows"]:
|
|
220
|
-
raise errors.InvalidAPIUsage("Row parameter is invalid", status_code=404)
|
|
203
|
+
raise errors.InvalidAPIUsage(_("Row parameter is invalid"), status_code=404)
|
|
221
204
|
|
|
222
205
|
try:
|
|
223
206
|
picture = fses.derivates.openbin(picPath)
|
|
224
207
|
except:
|
|
225
|
-
raise errors.InvalidAPIUsage("Unable to read picture on filesystem", status_code=500)
|
|
208
|
+
raise errors.InvalidAPIUsage(_("Unable to read picture on filesystem"), status_code=500)
|
|
226
209
|
|
|
227
210
|
return utils.pictures.sendInFormat(picture, "jpeg", format)
|