geovisio 2.9.0__py3-none-any.whl → 2.11.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 +8 -1
- geovisio/admin_cli/user.py +7 -2
- geovisio/config_app.py +26 -12
- geovisio/translations/ar/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/ar/LC_MESSAGES/messages.po +818 -0
- geovisio/translations/be/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/be/LC_MESSAGES/messages.po +886 -0
- geovisio/translations/br/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/da/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/da/LC_MESSAGES/messages.po +96 -4
- geovisio/translations/de/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/de/LC_MESSAGES/messages.po +214 -122
- geovisio/translations/el/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/en/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/en/LC_MESSAGES/messages.po +234 -157
- geovisio/translations/eo/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/eo/LC_MESSAGES/messages.po +55 -5
- geovisio/translations/es/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/fi/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/fr/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/fr/LC_MESSAGES/messages.po +92 -3
- geovisio/translations/hu/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/it/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/it/LC_MESSAGES/messages.po +63 -3
- geovisio/translations/ja/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/ko/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/messages.pot +216 -139
- geovisio/translations/nl/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/nl/LC_MESSAGES/messages.po +333 -62
- geovisio/translations/oc/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/oc/LC_MESSAGES/messages.po +821 -0
- geovisio/translations/pl/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/pt/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/pt/LC_MESSAGES/messages.po +944 -0
- geovisio/translations/pt_BR/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/pt_BR/LC_MESSAGES/messages.po +942 -0
- geovisio/translations/sv/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/sv/LC_MESSAGES/messages.po +4 -3
- geovisio/translations/ti/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/ti/LC_MESSAGES/messages.po +762 -0
- geovisio/translations/tr/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/tr/LC_MESSAGES/messages.po +927 -0
- geovisio/translations/uk/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/uk/LC_MESSAGES/messages.po +920 -0
- geovisio/translations/zh_Hant/LC_MESSAGES/messages.po +1 -1
- geovisio/utils/annotations.py +21 -21
- geovisio/utils/auth.py +47 -13
- geovisio/utils/cql2.py +22 -5
- geovisio/utils/fields.py +14 -2
- geovisio/utils/items.py +44 -0
- geovisio/utils/model_query.py +2 -2
- geovisio/utils/pic_shape.py +1 -1
- geovisio/utils/pictures.py +127 -36
- geovisio/utils/semantics.py +32 -3
- geovisio/utils/sentry.py +1 -1
- geovisio/utils/sequences.py +155 -109
- geovisio/utils/upload_set.py +303 -206
- geovisio/utils/users.py +18 -0
- geovisio/utils/website.py +1 -1
- geovisio/web/annotations.py +303 -69
- geovisio/web/auth.py +1 -1
- geovisio/web/collections.py +194 -97
- geovisio/web/configuration.py +36 -4
- geovisio/web/docs.py +109 -13
- geovisio/web/items.py +319 -186
- geovisio/web/map.py +92 -54
- geovisio/web/pages.py +48 -4
- geovisio/web/params.py +100 -42
- geovisio/web/pictures.py +37 -3
- geovisio/web/prepare.py +4 -2
- geovisio/web/queryables.py +57 -0
- geovisio/web/stac.py +8 -2
- geovisio/web/tokens.py +49 -1
- geovisio/web/upload_set.py +226 -51
- geovisio/web/users.py +89 -8
- geovisio/web/utils.py +26 -8
- geovisio/workers/runner_pictures.py +128 -23
- {geovisio-2.9.0.dist-info → geovisio-2.11.0.dist-info}/METADATA +15 -14
- geovisio-2.11.0.dist-info/RECORD +117 -0
- geovisio-2.9.0.dist-info/RECORD +0 -98
- {geovisio-2.9.0.dist-info → geovisio-2.11.0.dist-info}/WHEEL +0 -0
- {geovisio-2.9.0.dist-info → geovisio-2.11.0.dist-info}/licenses/LICENSE +0 -0
geovisio/utils/users.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from geovisio.utils.auth import Account
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def delete_user_data(conn, account: Account):
|
|
6
|
+
"""Delete all the pictures of a user
|
|
7
|
+
|
|
8
|
+
Note that the database changes will be done synchronously but the picture deletion will be an asynchronous task,
|
|
9
|
+
so some background workers need to be run in order for the pictures deletion to be effective.
|
|
10
|
+
"""
|
|
11
|
+
with conn.transaction(), conn.cursor() as cursor:
|
|
12
|
+
logging.info(f"Deleting pictures of account {account.name} ({account.id})")
|
|
13
|
+
# Note: deleting a picture's row add a new `delete` async task to the queue, to delete the associated files
|
|
14
|
+
nb_deleted_pics = cursor.execute("DELETE FROM pictures WHERE account_id = %s", [account.id]).rowcount
|
|
15
|
+
cursor.execute("DELETE FROM upload_sets WHERE account_id = %s", [account.id])
|
|
16
|
+
cursor.execute("UPDATE sequences SET status = 'deleted' WHERE account_id = %s", [account.id])
|
|
17
|
+
logging.info(f"Deleted {nb_deleted_pics} pictures from account {account.name} ({account.id})")
|
|
18
|
+
return nb_deleted_pics
|
geovisio/utils/website.py
CHANGED
|
@@ -14,7 +14,7 @@ class Website:
|
|
|
14
14
|
"""Website associated to the API.
|
|
15
15
|
This wrapper will define the routes we expect from the website.
|
|
16
16
|
|
|
17
|
-
We should limit the
|
|
17
|
+
We should limit the interaction from the api to the website, but for some flow (especially auth flows), it's can be useful to redirect to website's page
|
|
18
18
|
|
|
19
19
|
If the url is:
|
|
20
20
|
* set to `false`, there is no associated website
|
geovisio/web/annotations.py
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
from typing import List, Optional
|
|
2
2
|
from geovisio.utils import auth, db
|
|
3
|
-
from geovisio.utils.annotations import
|
|
3
|
+
from geovisio.utils.annotations import (
|
|
4
|
+
AnnotationCreationParameter,
|
|
5
|
+
creation_annotation,
|
|
6
|
+
get_annotation,
|
|
7
|
+
update_annotation,
|
|
8
|
+
InputAnnotationShape,
|
|
9
|
+
delete_annotation,
|
|
10
|
+
)
|
|
4
11
|
from geovisio.utils.tags import SemanticTagUpdate
|
|
5
|
-
from geovisio.web.utils import accountIdOrDefault
|
|
6
12
|
from geovisio.utils.params import validation_error
|
|
7
13
|
from geovisio import errors
|
|
8
|
-
from pydantic import BaseModel, ValidationError
|
|
14
|
+
from pydantic import BaseModel, ValidationError, Field
|
|
9
15
|
from uuid import UUID
|
|
10
16
|
from flask import Blueprint, current_app, request, url_for
|
|
11
17
|
from flask_babel import gettext as _
|
|
@@ -14,86 +20,144 @@ from flask_babel import gettext as _
|
|
|
14
20
|
bp = Blueprint("annotations", __name__, url_prefix="/api")
|
|
15
21
|
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
class AnnotationPostParameter(BaseModel):
|
|
24
|
+
shape: InputAnnotationShape
|
|
25
|
+
"""Shape defining the annotation.
|
|
26
|
+
The annotation shape is either a full geojson geometry or only a bounding box (4 floats).
|
|
27
|
+
|
|
28
|
+
The coordinates should be given in pixel, starting from the bottom left of the picture.
|
|
29
|
+
|
|
30
|
+
Note that the API will always output geometry as geojson geometry (thus will transform the bbox into a polygon).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
semantics: List[SemanticTagUpdate] = Field(default_factory=list)
|
|
34
|
+
"""Semantic tags associated to the annotation"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@bp.route("/pictures/<uuid:itemId>/annotations", methods=["POST"])
|
|
18
38
|
@auth.login_required()
|
|
19
|
-
def
|
|
39
|
+
def postAnnotationNonStacAlias(itemId, account):
|
|
20
40
|
"""Create an annotation on a picture.
|
|
21
41
|
|
|
22
42
|
The geometry can be provided as a bounding box (a list of 4 integers, minx, miny, maxx, maxy) or as a geojson geometry.
|
|
23
43
|
All coordinates must be in pixel, starting from the top left of the picture.
|
|
24
44
|
|
|
25
45
|
If an annotation already exists on the picture with the same shape, it will be used.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
|
|
47
|
+
The is an alias to the `/api/collections/<collectionId>/items/<itemId>/annotations` endpoint (but you don't need to know the collection ID here).
|
|
48
|
+
---
|
|
49
|
+
tags:
|
|
50
|
+
- Editing
|
|
51
|
+
- Semantics
|
|
52
|
+
parameters:
|
|
53
|
+
- name: itemId
|
|
54
|
+
in: path
|
|
55
|
+
description: ID of item to retrieve
|
|
56
|
+
required: true
|
|
57
|
+
schema:
|
|
58
|
+
type: string
|
|
59
|
+
requestBody:
|
|
60
|
+
content:
|
|
61
|
+
application/json:
|
|
62
|
+
schema:
|
|
63
|
+
$ref: '#/components/schemas/GeoVisioPostAnnotation'
|
|
64
|
+
security:
|
|
65
|
+
- bearerToken: []
|
|
66
|
+
- cookieAuth: []
|
|
67
|
+
responses:
|
|
68
|
+
200:
|
|
69
|
+
description: the annotation metadata
|
|
44
70
|
content:
|
|
45
71
|
application/json:
|
|
46
72
|
schema:
|
|
47
|
-
$ref: '#/components/schemas/
|
|
48
|
-
security:
|
|
49
|
-
- bearerToken: []
|
|
50
|
-
- cookieAuth: []
|
|
51
|
-
responses:
|
|
52
|
-
200:
|
|
53
|
-
description: the annotation metadata
|
|
54
|
-
content:
|
|
55
|
-
application/json:
|
|
56
|
-
schema:
|
|
57
|
-
$ref: '#/components/schemas/GeoVisioAnnotation'
|
|
73
|
+
$ref: '#/components/schemas/GeoVisioAnnotation'
|
|
58
74
|
"""
|
|
59
75
|
|
|
60
|
-
account_id =
|
|
61
|
-
|
|
76
|
+
account_id = account.id
|
|
62
77
|
pic = db.fetchone(
|
|
63
78
|
current_app,
|
|
64
|
-
"SELECT 1 FROM
|
|
65
|
-
{"
|
|
79
|
+
"SELECT 1 FROM pictures WHERE id = %(pic)s",
|
|
80
|
+
{"pic": itemId},
|
|
66
81
|
)
|
|
67
82
|
if not pic:
|
|
68
83
|
raise errors.InvalidAPIUsage(_("Picture %(p)s wasn't found in database", p=itemId), status_code=404)
|
|
69
84
|
|
|
70
85
|
if request.is_json and request.json is not None:
|
|
71
86
|
try:
|
|
72
|
-
|
|
87
|
+
post_params = AnnotationPostParameter(**request.json, account_id=account_id, picture_id=itemId)
|
|
73
88
|
except ValidationError as ve:
|
|
74
89
|
raise errors.InvalidAPIUsage(_("Impossible to create an annotation"), payload=validation_error(ve))
|
|
75
90
|
else:
|
|
76
91
|
raise errors.InvalidAPIUsage(_("Parameter for creating an annotation should be a valid JSON"), status_code=415)
|
|
77
92
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
annotation.model_dump_json(exclude_none=True),
|
|
82
|
-
200,
|
|
83
|
-
{
|
|
84
|
-
"Content-Type": "application/json",
|
|
85
|
-
"Access-Control-Expose-Headers": "Location", # Needed for allowing web browsers access Location header
|
|
86
|
-
"Location": url_for(
|
|
87
|
-
"annotations.getAnnotation", _external=True, annotationId=annotation.id, collectionId=collectionId, itemId=itemId
|
|
88
|
-
),
|
|
89
|
-
},
|
|
93
|
+
creation_params = AnnotationCreationParameter(
|
|
94
|
+
account_id=account_id, picture_id=itemId, shape=post_params.shape, semantics=post_params.semantics
|
|
90
95
|
)
|
|
91
96
|
|
|
97
|
+
with db.conn(current_app) as conn:
|
|
98
|
+
annotation = creation_annotation(creation_params, conn)
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
annotation.model_dump_json(exclude_none=True),
|
|
102
|
+
200,
|
|
103
|
+
{
|
|
104
|
+
"Content-Type": "application/json",
|
|
105
|
+
"Access-Control-Expose-Headers": "Location", # Needed for allowing web browsers access Location header
|
|
106
|
+
"Location": url_for("annotations.getAnnotationById", _external=True, annotationId=annotation.id),
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@bp.route("/collections/<uuid:collectionId>/items/<uuid:itemId>/annotations", methods=["POST"])
|
|
112
|
+
@auth.login_required()
|
|
113
|
+
def postAnnotation(collectionId, itemId, account):
|
|
114
|
+
"""Create an annotation on a picture.
|
|
115
|
+
|
|
116
|
+
The geometry can be provided as a bounding box (a list of 4 integers, minx, miny, maxx, maxy) or as a geojson geometry.
|
|
117
|
+
All coordinates must be in pixel, starting from the top left of the picture.
|
|
118
|
+
|
|
119
|
+
If an annotation already exists on the picture with the same shape, it will be used.
|
|
120
|
+
---
|
|
121
|
+
tags:
|
|
122
|
+
- Editing
|
|
123
|
+
- Semantics
|
|
124
|
+
parameters:
|
|
125
|
+
- name: collectionId
|
|
126
|
+
in: path
|
|
127
|
+
description: ID of collection to retrieve
|
|
128
|
+
required: true
|
|
129
|
+
schema:
|
|
130
|
+
type: string
|
|
131
|
+
- name: itemId
|
|
132
|
+
in: path
|
|
133
|
+
description: ID of item to retrieve
|
|
134
|
+
required: true
|
|
135
|
+
schema:
|
|
136
|
+
type: string
|
|
137
|
+
requestBody:
|
|
138
|
+
content:
|
|
139
|
+
application/json:
|
|
140
|
+
schema:
|
|
141
|
+
$ref: '#/components/schemas/GeoVisioPostAnnotation'
|
|
142
|
+
security:
|
|
143
|
+
- bearerToken: []
|
|
144
|
+
- cookieAuth: []
|
|
145
|
+
responses:
|
|
146
|
+
200:
|
|
147
|
+
description: the annotation metadata
|
|
148
|
+
content:
|
|
149
|
+
application/json:
|
|
150
|
+
schema:
|
|
151
|
+
$ref: '#/components/schemas/GeoVisioAnnotation'
|
|
152
|
+
"""
|
|
153
|
+
return postAnnotationNonStacAlias(itemId=itemId, account=account)
|
|
154
|
+
|
|
92
155
|
|
|
93
156
|
@bp.route("/collections/<uuid:collectionId>/items/<uuid:itemId>/annotations/<uuid:annotationId>", methods=["GET"])
|
|
94
157
|
def getAnnotation(collectionId, itemId, annotationId):
|
|
95
158
|
"""Get an annotation
|
|
96
159
|
|
|
160
|
+
Note that this is the same route as `/api/annotations/<uuid:annotationId>` but you need to know the picture's and collection's IDs.
|
|
97
161
|
---
|
|
98
162
|
tags:
|
|
99
163
|
- Semantics
|
|
@@ -103,19 +167,19 @@ def getAnnotation(collectionId, itemId, annotationId):
|
|
|
103
167
|
description: ID of collection
|
|
104
168
|
required: true
|
|
105
169
|
schema:
|
|
106
|
-
|
|
170
|
+
type: string
|
|
107
171
|
- name: itemId
|
|
108
172
|
in: path
|
|
109
173
|
description: ID of item
|
|
110
174
|
required: true
|
|
111
175
|
schema:
|
|
112
|
-
|
|
176
|
+
type: string
|
|
113
177
|
- name: annotationId
|
|
114
178
|
in: path
|
|
115
179
|
description: ID of annotation
|
|
116
180
|
required: true
|
|
117
181
|
schema:
|
|
118
|
-
|
|
182
|
+
type: string
|
|
119
183
|
security:
|
|
120
184
|
- bearerToken: []
|
|
121
185
|
- cookieAuth: []
|
|
@@ -136,6 +200,41 @@ def getAnnotation(collectionId, itemId, annotationId):
|
|
|
136
200
|
return annotation.model_dump_json(exclude_none=True), 200, {"Content-Type": "application/json"}
|
|
137
201
|
|
|
138
202
|
|
|
203
|
+
@bp.route("/annotations/<uuid:annotationId>", methods=["GET"])
|
|
204
|
+
def getAnnotationById(annotationId):
|
|
205
|
+
"""Get an annotation.
|
|
206
|
+
|
|
207
|
+
This is the same route as `/api/collections/<uuid:collectionId>/items/<uuid:itemId>/annotations/<uuid:annotationId>` but you don't need to know the picture's and collection's IDs.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
tags:
|
|
211
|
+
- Semantics
|
|
212
|
+
parameters:
|
|
213
|
+
- name: annotationId
|
|
214
|
+
in: path
|
|
215
|
+
description: ID of annotation
|
|
216
|
+
required: true
|
|
217
|
+
schema:
|
|
218
|
+
type: string
|
|
219
|
+
security:
|
|
220
|
+
- bearerToken: []
|
|
221
|
+
- cookieAuth: []
|
|
222
|
+
responses:
|
|
223
|
+
200:
|
|
224
|
+
description: the annotation metadata
|
|
225
|
+
content:
|
|
226
|
+
application/json:
|
|
227
|
+
schema:
|
|
228
|
+
$ref: '#/components/schemas/GeoVisioAnnotation'
|
|
229
|
+
"""
|
|
230
|
+
with db.conn(current_app) as conn:
|
|
231
|
+
annotation = get_annotation(conn, annotationId)
|
|
232
|
+
if not annotation:
|
|
233
|
+
raise errors.InvalidAPIUsage(_("Annotation %(p)s not found", p=annotationId), status_code=404)
|
|
234
|
+
|
|
235
|
+
return annotation.model_dump_json(exclude_none=True), 200, {"Content-Type": "application/json"}
|
|
236
|
+
|
|
237
|
+
|
|
139
238
|
class AnnotationPatchParameter(BaseModel):
|
|
140
239
|
"""Parameters used to update an annotation"""
|
|
141
240
|
|
|
@@ -152,12 +251,70 @@ class AnnotationPatchParameter(BaseModel):
|
|
|
152
251
|
"""
|
|
153
252
|
|
|
154
253
|
|
|
254
|
+
@bp.route("/annotations/<uuid:annotationId>", methods=["PATCH"])
|
|
255
|
+
@auth.login_required()
|
|
256
|
+
def patchAnnotationNonStacAlias(annotationId, account):
|
|
257
|
+
"""Patch an annotation
|
|
258
|
+
|
|
259
|
+
Note that if the annotation has no associated tags anymore, it will be deleted.
|
|
260
|
+
|
|
261
|
+
Note that is an alias to the `/api/collections/<collectionId>/items/<itemId>/annotations/<annotationId>` endpoint (but you don't need to know the collection/item ID here).
|
|
262
|
+
---
|
|
263
|
+
tags:
|
|
264
|
+
- Semantics
|
|
265
|
+
parameters:
|
|
266
|
+
- name: annotationId
|
|
267
|
+
in: path
|
|
268
|
+
description: ID of annotation
|
|
269
|
+
required: true
|
|
270
|
+
schema:
|
|
271
|
+
type: string
|
|
272
|
+
requestBody:
|
|
273
|
+
content:
|
|
274
|
+
application/json:
|
|
275
|
+
schema:
|
|
276
|
+
$ref: '#/components/schemas/GeoVisioPatchAnnotation'
|
|
277
|
+
security:
|
|
278
|
+
- bearerToken: []
|
|
279
|
+
- cookieAuth: []
|
|
280
|
+
responses:
|
|
281
|
+
200:
|
|
282
|
+
description: the annotation metadata
|
|
283
|
+
content:
|
|
284
|
+
application/json:
|
|
285
|
+
schema:
|
|
286
|
+
$ref: '#/components/schemas/GeoVisioAnnotation'
|
|
287
|
+
204:
|
|
288
|
+
description: The annotation was empty, it has been correctly deleted
|
|
289
|
+
"""
|
|
290
|
+
if request.is_json and request.json is not None:
|
|
291
|
+
try:
|
|
292
|
+
params = AnnotationPatchParameter(**request.json)
|
|
293
|
+
except ValidationError as ve:
|
|
294
|
+
raise errors.InvalidAPIUsage(_("Impossible to patch annotation, invalid parameters"), payload=validation_error(ve))
|
|
295
|
+
else:
|
|
296
|
+
raise errors.InvalidAPIUsage(_("Parameter for updating an annotation should be a valid JSON"), status_code=415)
|
|
297
|
+
|
|
298
|
+
with db.conn(current_app) as conn:
|
|
299
|
+
|
|
300
|
+
annotation = get_annotation(conn, annotationId)
|
|
301
|
+
if not annotation:
|
|
302
|
+
raise errors.InvalidAPIUsage(_("Annotation %(p)s not found", p=annotationId), status_code=404)
|
|
303
|
+
|
|
304
|
+
a = update_annotation(annotation, params.semantics, account.id)
|
|
305
|
+
if a is None:
|
|
306
|
+
return "", 204
|
|
307
|
+
return a.model_dump_json(exclude_none=True), 200, {"Content-Type": "application/json"}
|
|
308
|
+
|
|
309
|
+
|
|
155
310
|
@bp.route("/collections/<uuid:collectionId>/items/<uuid:itemId>/annotations/<uuid:annotationId>", methods=["PATCH"])
|
|
156
311
|
@auth.login_required()
|
|
157
312
|
def patchAnnotation(collectionId, itemId, annotationId, account):
|
|
158
313
|
"""Patch an annotation
|
|
159
314
|
|
|
160
315
|
Note that if the annotation has no associated tags anymore, it will be deleted.
|
|
316
|
+
|
|
317
|
+
Note that is the an alias to the `/api/annotations/<annotationId>` endpoint (but you need to know the collection/item ID here).
|
|
161
318
|
---
|
|
162
319
|
tags:
|
|
163
320
|
- Semantics
|
|
@@ -167,19 +324,19 @@ def patchAnnotation(collectionId, itemId, annotationId, account):
|
|
|
167
324
|
description: ID of collection
|
|
168
325
|
required: true
|
|
169
326
|
schema:
|
|
170
|
-
|
|
327
|
+
type: string
|
|
171
328
|
- name: itemId
|
|
172
329
|
in: path
|
|
173
330
|
description: ID of item
|
|
174
331
|
required: true
|
|
175
332
|
schema:
|
|
176
|
-
|
|
333
|
+
type: string
|
|
177
334
|
- name: annotationId
|
|
178
335
|
in: path
|
|
179
336
|
description: ID of annotation
|
|
180
337
|
required: true
|
|
181
338
|
schema:
|
|
182
|
-
|
|
339
|
+
type: string
|
|
183
340
|
security:
|
|
184
341
|
- bearerToken: []
|
|
185
342
|
- cookieAuth: []
|
|
@@ -193,21 +350,98 @@ def patchAnnotation(collectionId, itemId, annotationId, account):
|
|
|
193
350
|
204:
|
|
194
351
|
description: The annotation was empty, it has been correctly deleted
|
|
195
352
|
"""
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
353
|
+
return patchAnnotationNonStacAlias(annotationId=annotationId, account=account)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
@bp.route("/collections/<uuid:collectionId>/items/<uuid:itemId>/annotations/<uuid:annotationId>", methods=["DELETE"])
|
|
357
|
+
@auth.login_required()
|
|
358
|
+
def deleteAnnotation(collectionId, itemId, annotationId, account):
|
|
359
|
+
"""Delete an annotation
|
|
203
360
|
|
|
361
|
+
It is mandatory to be authenticated to delete an annotation, but anyone can delete do it. The changes are tracked in the history.
|
|
362
|
+
|
|
363
|
+
Note that this is the same route as `DELETE /api/annotations/<uuid:annotationId>` but you need to know the picture's and collection's IDs.
|
|
364
|
+
---
|
|
365
|
+
tags:
|
|
366
|
+
- Semantics
|
|
367
|
+
parameters:
|
|
368
|
+
- name: collectionId
|
|
369
|
+
in: path
|
|
370
|
+
description: ID of collection
|
|
371
|
+
required: true
|
|
372
|
+
schema:
|
|
373
|
+
type: string
|
|
374
|
+
- name: itemId
|
|
375
|
+
in: path
|
|
376
|
+
description: ID of item
|
|
377
|
+
required: true
|
|
378
|
+
schema:
|
|
379
|
+
type: string
|
|
380
|
+
- name: annotationId
|
|
381
|
+
in: path
|
|
382
|
+
description: ID of annotation
|
|
383
|
+
required: true
|
|
384
|
+
schema:
|
|
385
|
+
type: string
|
|
386
|
+
security:
|
|
387
|
+
- bearerToken: []
|
|
388
|
+
- cookieAuth: []
|
|
389
|
+
responses:
|
|
390
|
+
204:
|
|
391
|
+
description: The Annotation has been correctly deleted
|
|
392
|
+
"""
|
|
204
393
|
with db.conn(current_app) as conn:
|
|
205
394
|
|
|
206
395
|
annotation = get_annotation(conn, annotationId)
|
|
207
396
|
if not annotation or annotation.picture_id != itemId:
|
|
208
|
-
raise errors.InvalidAPIUsage(_("Annotation %(p)s not found", p=
|
|
397
|
+
raise errors.InvalidAPIUsage(_("Annotation %(p)s not found", p=annotationId), status_code=404)
|
|
209
398
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
399
|
+
delete_annotation(conn, annotation)
|
|
400
|
+
|
|
401
|
+
return "", 204
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@bp.route("/annotations/<uuid:annotationId>", methods=["DELETE"])
|
|
405
|
+
@auth.login_required()
|
|
406
|
+
def deleteAnnotationNonStacAlias(annotationId, account):
|
|
407
|
+
"""Delete an annotation.
|
|
408
|
+
|
|
409
|
+
It is mandatory to be authenticated to delete an annotation, but anyone can delete do it. The changes are tracked in the history.
|
|
410
|
+
|
|
411
|
+
The is an alias to the `DELETE /api/collections/<collectionId>/items/<itemId>/annotations/<annotationId>` endpoint (but you don't need to know the collection/item ID here).
|
|
412
|
+
---
|
|
413
|
+
tags:
|
|
414
|
+
- Semantics
|
|
415
|
+
parameters:
|
|
416
|
+
- name: collectionId
|
|
417
|
+
in: path
|
|
418
|
+
description: ID of collection
|
|
419
|
+
required: true
|
|
420
|
+
schema:
|
|
421
|
+
type: string
|
|
422
|
+
- name: itemId
|
|
423
|
+
in: path
|
|
424
|
+
description: ID of item
|
|
425
|
+
required: true
|
|
426
|
+
schema:
|
|
427
|
+
type: string
|
|
428
|
+
- name: annotationId
|
|
429
|
+
in: path
|
|
430
|
+
description: ID of annotation
|
|
431
|
+
required: true
|
|
432
|
+
schema:
|
|
433
|
+
type: string
|
|
434
|
+
security:
|
|
435
|
+
- bearerToken: []
|
|
436
|
+
- cookieAuth: []
|
|
437
|
+
responses:
|
|
438
|
+
204:
|
|
439
|
+
description: The Annotation has been correctly deleted
|
|
440
|
+
"""
|
|
441
|
+
with db.conn(current_app) as conn:
|
|
442
|
+
annotation = get_annotation(conn, annotationId)
|
|
443
|
+
if not annotation:
|
|
444
|
+
raise errors.InvalidAPIUsage(_("Annotation %(p)s not found", p=annotationId), status_code=404)
|
|
445
|
+
delete_annotation(conn, annotation, account.id)
|
|
446
|
+
|
|
447
|
+
return "", 204
|
geovisio/web/auth.py
CHANGED
|
@@ -112,7 +112,7 @@ def auth():
|
|
|
112
112
|
def logout():
|
|
113
113
|
"""Log out from geovisio
|
|
114
114
|
* If the OAuth Provider is keycloak, this will redirect to a keycloak confirmation page,
|
|
115
|
-
and
|
|
115
|
+
and upon confirmation keycloak will call post_logout_redirect that will invalidate the session
|
|
116
116
|
* If the OAuth Provider is not keycloak, this will invalidate the session
|
|
117
117
|
---
|
|
118
118
|
tags:
|