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.
- geovisio/__init__.py +36 -7
- geovisio/admin_cli/cleanup.py +2 -2
- geovisio/admin_cli/db.py +1 -4
- geovisio/config_app.py +40 -1
- geovisio/db_migrations.py +24 -3
- geovisio/templates/main.html +13 -13
- geovisio/templates/viewer.html +3 -3
- geovisio/translations/de/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/de/LC_MESSAGES/messages.po +804 -0
- geovisio/translations/el/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/el/LC_MESSAGES/messages.po +685 -0
- geovisio/translations/en/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/en/LC_MESSAGES/messages.po +738 -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/hu/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/hu/LC_MESSAGES/messages.po +773 -0
- geovisio/translations/ko/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/ko/LC_MESSAGES/messages.po +685 -0
- geovisio/translations/messages.pot +694 -0
- geovisio/translations/nl/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/nl/LC_MESSAGES/messages.po +602 -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 +110 -88
- geovisio/utils/reports.py +171 -0
- geovisio/utils/sequences.py +262 -126
- geovisio/utils/tokens.py +37 -42
- geovisio/utils/upload_set.py +642 -0
- geovisio/web/auth.py +37 -37
- geovisio/web/collections.py +304 -304
- geovisio/web/configuration.py +14 -0
- geovisio/web/docs.py +276 -15
- geovisio/web/excluded_areas.py +377 -0
- geovisio/web/items.py +169 -112
- geovisio/web/map.py +104 -36
- 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 -134
- geovisio/web/tokens.py +98 -109
- geovisio/web/upload_set.py +771 -0
- geovisio/web/users.py +100 -73
- geovisio/web/utils.py +28 -9
- geovisio/workers/runner_pictures.py +241 -207
- {geovisio-2.6.0.dist-info → geovisio-2.7.1.dist-info}/METADATA +17 -14
- geovisio-2.7.1.dist-info/RECORD +70 -0
- {geovisio-2.6.0.dist-info → geovisio-2.7.1.dist-info}/WHEEL +1 -1
- geovisio-2.6.0.dist-info/RECORD +0 -41
- {geovisio-2.6.0.dist-info → geovisio-2.7.1.dist-info}/LICENSE +0 -0
geovisio/web/reports.py
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
from flask import current_app, request, Blueprint, url_for
|
|
2
|
+
from flask_babel import gettext as _
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from typing_extensions import Self
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, ValidationError, model_validator, Field
|
|
8
|
+
from psycopg.rows import class_row
|
|
9
|
+
from psycopg.sql import SQL, Identifier, Literal
|
|
10
|
+
from geovisio.utils import db, auth
|
|
11
|
+
from geovisio.utils.reports import Report, ReportType, get_report, list_reports, is_picture_owner
|
|
12
|
+
from geovisio.utils.params import validation_error
|
|
13
|
+
from geovisio.errors import InvalidAPIUsage, InternalError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
bp = Blueprint("reports", __name__, url_prefix="/api")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ReportCreationParameter(BaseModel):
|
|
20
|
+
"""Parameters used to create a Report"""
|
|
21
|
+
|
|
22
|
+
issue: ReportType
|
|
23
|
+
"""Nature of the issue you want to report"""
|
|
24
|
+
|
|
25
|
+
picture_id: Optional[UUID] = None
|
|
26
|
+
"""The ID of the picture concerned by this report. You should either set picture_id or sequence_id."""
|
|
27
|
+
|
|
28
|
+
sequence_id: Optional[UUID] = None
|
|
29
|
+
"""The ID of the sequence concerned by this report. You should either set picture_id or sequence_id. If no picture_id is set, report will concern the whole sequence."""
|
|
30
|
+
|
|
31
|
+
reporter_email: Optional[str] = None
|
|
32
|
+
"""The reporter email, optional but can be useful to get an answer or if precisions are necessary."""
|
|
33
|
+
|
|
34
|
+
reporter_comments: Optional[str] = None
|
|
35
|
+
"""Optional details about the issue."""
|
|
36
|
+
|
|
37
|
+
model_config = ConfigDict(use_attribute_docstrings=True)
|
|
38
|
+
|
|
39
|
+
@model_validator(mode="after")
|
|
40
|
+
def check_ids(self) -> Self:
|
|
41
|
+
if self.picture_id is None and self.sequence_id is None:
|
|
42
|
+
raise ValueError("At least one ID between picture_id and sequence_id must be set")
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def create_report(params: ReportCreationParameter, accountId: Optional[UUID]) -> Report:
|
|
47
|
+
params_as_dict = params.model_dump(exclude_none=True) | {"reporter_account_id": accountId}
|
|
48
|
+
|
|
49
|
+
fields = [SQL(f) for f in params_as_dict.keys()] # type: ignore (we can ignore psycopg types there as we control those keys since they are the attributes of UploadSetCreationParameter)
|
|
50
|
+
values = [SQL(f"%({f})s") for f in params_as_dict.keys()] # type: ignore
|
|
51
|
+
|
|
52
|
+
return db.fetchone(
|
|
53
|
+
current_app,
|
|
54
|
+
SQL("INSERT INTO reports({fields}) VALUES({values}) RETURNING *").format(
|
|
55
|
+
fields=SQL(", ").join(fields), values=SQL(", ").join(values)
|
|
56
|
+
),
|
|
57
|
+
params_as_dict,
|
|
58
|
+
row_factory=class_row(Report),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@bp.route("/reports", methods=["POST"])
|
|
63
|
+
def postReport():
|
|
64
|
+
"""
|
|
65
|
+
Create a new report
|
|
66
|
+
|
|
67
|
+
Note that this call can be authenticated to make report associated to your account.
|
|
68
|
+
---
|
|
69
|
+
tags:
|
|
70
|
+
- Reports
|
|
71
|
+
requestBody:
|
|
72
|
+
content:
|
|
73
|
+
application/json:
|
|
74
|
+
schema:
|
|
75
|
+
$ref: '#/components/schemas/GeoVisioPostReport'
|
|
76
|
+
responses:
|
|
77
|
+
200:
|
|
78
|
+
description: the Report metadata
|
|
79
|
+
content:
|
|
80
|
+
application/json:
|
|
81
|
+
schema:
|
|
82
|
+
$ref: '#/components/schemas/GeoVisioReport'
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
if request.is_json and request.json is not None:
|
|
86
|
+
try:
|
|
87
|
+
params = ReportCreationParameter(**request.json)
|
|
88
|
+
except ValidationError as ve:
|
|
89
|
+
raise InvalidAPIUsage(_("Impossible to create a Report"), payload=validation_error(ve))
|
|
90
|
+
else:
|
|
91
|
+
raise InvalidAPIUsage(_("Parameter for creating a Report should be a valid JSON"), status_code=415)
|
|
92
|
+
|
|
93
|
+
account = auth.get_current_account()
|
|
94
|
+
account_id = UUID(account.id) if account is not None else None
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
report = create_report(params, account_id)
|
|
98
|
+
except Exception as e:
|
|
99
|
+
raise InternalError(_("Impossible to create a Report"), status_code=500, payload={"details": str(e)})
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
report.for_public().model_dump_json(exclude_none=True),
|
|
103
|
+
200,
|
|
104
|
+
{
|
|
105
|
+
"Content-Type": "application/json",
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@bp.route("/reports/<uuid:report_id>", methods=["GET"])
|
|
111
|
+
def getReport(report_id):
|
|
112
|
+
"""Get an existing Report
|
|
113
|
+
|
|
114
|
+
Note that you can only retrieve reports related to your account:
|
|
115
|
+
- Reports you created
|
|
116
|
+
- Reports made by others on your pictures/sequences
|
|
117
|
+
|
|
118
|
+
Accounts with admin role can retrieve any report.
|
|
119
|
+
---
|
|
120
|
+
tags:
|
|
121
|
+
- Reports
|
|
122
|
+
parameters:
|
|
123
|
+
- name: report_id
|
|
124
|
+
in: path
|
|
125
|
+
description: ID of the Report to retrieve
|
|
126
|
+
required: true
|
|
127
|
+
schema:
|
|
128
|
+
type: string
|
|
129
|
+
security:
|
|
130
|
+
- bearerToken: []
|
|
131
|
+
- cookieAuth: []
|
|
132
|
+
responses:
|
|
133
|
+
200:
|
|
134
|
+
description: the Report metadata
|
|
135
|
+
content:
|
|
136
|
+
application/json:
|
|
137
|
+
schema:
|
|
138
|
+
$ref: '#/components/schemas/GeoVisioReport'
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
account = auth.get_current_account()
|
|
142
|
+
|
|
143
|
+
if account is None:
|
|
144
|
+
raise InvalidAPIUsage(_("Only authenticated users can access reports"), status_code=401)
|
|
145
|
+
|
|
146
|
+
report = get_report(report_id)
|
|
147
|
+
if report is None:
|
|
148
|
+
raise InvalidAPIUsage(_("Report doesn't exist"), status_code=404)
|
|
149
|
+
|
|
150
|
+
# Check if user is legimitate to access report
|
|
151
|
+
if not account.can_check_reports(): # Is admin ?
|
|
152
|
+
if str(report.reporter_account_id) == account.id: # Is reporter ?
|
|
153
|
+
report = report.for_public()
|
|
154
|
+
elif is_picture_owner(report, account.id): # Is owner of concerned picture/sequence ?
|
|
155
|
+
report = report.for_public()
|
|
156
|
+
else: # Is going home 😂
|
|
157
|
+
raise InvalidAPIUsage(_("You're not authorized to access this report"), status_code=403)
|
|
158
|
+
|
|
159
|
+
return report.model_dump_json(exclude_none=True), 200, {"Content-Type": "application/json"}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class ReportStatusEdit(Enum):
|
|
163
|
+
waiting = "waiting"
|
|
164
|
+
closed_solved = "closed_solved"
|
|
165
|
+
closed_ignored = "closed_ignored"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class UserReportRole(Enum):
|
|
169
|
+
reporter = "reporter"
|
|
170
|
+
owner = "owner"
|
|
171
|
+
admin = "admin"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class EditReportParameter(BaseModel):
|
|
175
|
+
"""Parameters to edit a report details"""
|
|
176
|
+
|
|
177
|
+
issue: Optional[ReportType] = None
|
|
178
|
+
"""Nature of the issue"""
|
|
179
|
+
status: Optional[ReportStatusEdit] = None
|
|
180
|
+
"""New report status"""
|
|
181
|
+
reporter_email: Optional[str] = None
|
|
182
|
+
"""Email of the person who created the issue"""
|
|
183
|
+
resolver_comments: Optional[str] = None
|
|
184
|
+
|
|
185
|
+
# Context for validation
|
|
186
|
+
editor_role: Optional[UserReportRole] = Field(None, exclude=True)
|
|
187
|
+
|
|
188
|
+
@model_validator(mode="before")
|
|
189
|
+
def check_rights(cls, values):
|
|
190
|
+
status = values.get("status")
|
|
191
|
+
editor_role = UserReportRole(values.get("editor_role"))
|
|
192
|
+
issue = values.get("issue")
|
|
193
|
+
reporter_email = values.get("reporter_email")
|
|
194
|
+
resolver_comments = values.get("resolver_comments")
|
|
195
|
+
|
|
196
|
+
if status:
|
|
197
|
+
if editor_role is None:
|
|
198
|
+
raise ValueError("status can't be changed by anonymous role")
|
|
199
|
+
elif editor_role == UserReportRole.reporter and status == ReportStatusEdit.closed_ignored:
|
|
200
|
+
raise ValueError("status can't be 'closed_ignored' for reporter")
|
|
201
|
+
|
|
202
|
+
if issue and editor_role != UserReportRole.admin:
|
|
203
|
+
raise ValueError("issue type can't be changed by non-admin role")
|
|
204
|
+
|
|
205
|
+
if reporter_email and editor_role != UserReportRole.admin:
|
|
206
|
+
raise ValueError("reporter email can't be changed by non-admin role")
|
|
207
|
+
|
|
208
|
+
if resolver_comments and editor_role not in [UserReportRole.owner, UserReportRole.admin]:
|
|
209
|
+
raise ValueError("resolver comments can't be changed by reporter")
|
|
210
|
+
|
|
211
|
+
return values
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def edit_report(report: Report, params: EditReportParameter, accountId: Optional[UUID]) -> Report:
|
|
215
|
+
params_as_dict = params.model_dump(exclude=["editor_role"], exclude_none=True)
|
|
216
|
+
if params.status in [ReportStatusEdit.closed_ignored, ReportStatusEdit.closed_solved]:
|
|
217
|
+
params_as_dict["resolver_account_id"] = accountId
|
|
218
|
+
|
|
219
|
+
changes = SQL(", ").join([SQL("{c} = {v}").format(c=Identifier(c), v=Literal(v)) for c, v in params_as_dict.items()])
|
|
220
|
+
return db.fetchone(
|
|
221
|
+
current_app,
|
|
222
|
+
SQL("UPDATE reports SET {changes} WHERE id = %(id)s RETURNING *").format(changes=changes),
|
|
223
|
+
{"id": report.id},
|
|
224
|
+
row_factory=class_row(Report),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@bp.route("/reports/<uuid:report_id>", methods=["PATCH"])
|
|
229
|
+
@auth.login_required_with_redirect()
|
|
230
|
+
def editReport(account, report_id):
|
|
231
|
+
"""Edit an existing Report
|
|
232
|
+
|
|
233
|
+
Only a limited set of edits are available:
|
|
234
|
+
- Reports you created: set "status" to waiting/closed_solved
|
|
235
|
+
- Reports on your pictures: set "status" to waiting/closed_solved/closed_ignored, edit "resolver_comments"
|
|
236
|
+
- If you're admin: you can do anything you like 😄
|
|
237
|
+
---
|
|
238
|
+
tags:
|
|
239
|
+
- Reports
|
|
240
|
+
parameters:
|
|
241
|
+
- name: report_id
|
|
242
|
+
in: path
|
|
243
|
+
description: ID of the Report
|
|
244
|
+
required: true
|
|
245
|
+
schema:
|
|
246
|
+
type: string
|
|
247
|
+
requestBody:
|
|
248
|
+
content:
|
|
249
|
+
application/json:
|
|
250
|
+
schema:
|
|
251
|
+
$ref: '#/components/schemas/GeoVisioPatchReport'
|
|
252
|
+
security:
|
|
253
|
+
- bearerToken: []
|
|
254
|
+
- cookieAuth: []
|
|
255
|
+
responses:
|
|
256
|
+
200:
|
|
257
|
+
description: the Report metadata
|
|
258
|
+
content:
|
|
259
|
+
application/json:
|
|
260
|
+
schema:
|
|
261
|
+
$ref: '#/components/schemas/GeoVisioReport'
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
report = get_report(report_id)
|
|
265
|
+
if report is None:
|
|
266
|
+
raise InvalidAPIUsage(_("Report doesn't exist"), status_code=404)
|
|
267
|
+
|
|
268
|
+
# Who is trying to edit ?
|
|
269
|
+
who = None
|
|
270
|
+
if account.can_check_reports():
|
|
271
|
+
who = UserReportRole.admin
|
|
272
|
+
elif str(report.reporter_account_id) == account.id:
|
|
273
|
+
who = UserReportRole.reporter
|
|
274
|
+
elif is_picture_owner(report, account.id):
|
|
275
|
+
who = UserReportRole.owner
|
|
276
|
+
else:
|
|
277
|
+
raise InvalidAPIUsage(_("You're not authorized to edit this Report"), status_code=403)
|
|
278
|
+
|
|
279
|
+
# Parse parameters
|
|
280
|
+
if request.is_json and request.json is not None:
|
|
281
|
+
try:
|
|
282
|
+
params = EditReportParameter(**request.json, editor_role=who.value)
|
|
283
|
+
except ValidationError as ve:
|
|
284
|
+
raise InvalidAPIUsage(_("Impossible to edit the Report"), payload=validation_error(ve))
|
|
285
|
+
else:
|
|
286
|
+
raise InvalidAPIUsage(_("Parameter for editing the Report should be a valid JSON"), status_code=415)
|
|
287
|
+
|
|
288
|
+
# Edit
|
|
289
|
+
report = edit_report(report, params, account.id)
|
|
290
|
+
if who != UserReportRole.admin:
|
|
291
|
+
report = report.for_public()
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
report.model_dump_json(exclude_none=True),
|
|
295
|
+
200,
|
|
296
|
+
{
|
|
297
|
+
"Content-Type": "application/json",
|
|
298
|
+
"Access-Control-Expose-Headers": "Location", # Needed for allowing web browsers access Location header
|
|
299
|
+
"Location": url_for("reports.getReport", _external=True, report_id=report.id),
|
|
300
|
+
},
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class ListReportsParameter(BaseModel):
|
|
305
|
+
"""Parameters used to list user's reports"""
|
|
306
|
+
|
|
307
|
+
account_id: UUID
|
|
308
|
+
limit: int = Field(default=100, ge=0, le=1000)
|
|
309
|
+
filter: Optional[str] = "status IN ('open', 'open_autofix', 'waiting') AND (reporter = 'me' OR owner = 'me')"
|
|
310
|
+
"""Filter to apply to the list of reports. The filter should be a valid SQL WHERE clause"""
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@bp.route("/reports", methods=["GET"])
|
|
314
|
+
@auth.login_required_with_redirect()
|
|
315
|
+
def listReports(account):
|
|
316
|
+
"""List reports
|
|
317
|
+
|
|
318
|
+
This route is only available for admins, to see your own reports, use /api/users/me/reports route instead.
|
|
319
|
+
---
|
|
320
|
+
tags:
|
|
321
|
+
- Reports
|
|
322
|
+
parameters:
|
|
323
|
+
- $ref: '#/components/parameters/GeoVisioReports_filter'
|
|
324
|
+
- name: limit
|
|
325
|
+
in: query
|
|
326
|
+
description: limit to the number of reports to retrieve
|
|
327
|
+
required: true
|
|
328
|
+
schema:
|
|
329
|
+
type: integer
|
|
330
|
+
minimum: 1
|
|
331
|
+
maximum: 100
|
|
332
|
+
security:
|
|
333
|
+
- bearerToken: []
|
|
334
|
+
- cookieAuth: []
|
|
335
|
+
responses:
|
|
336
|
+
200:
|
|
337
|
+
description: the Report metadata
|
|
338
|
+
content:
|
|
339
|
+
application/json:
|
|
340
|
+
schema:
|
|
341
|
+
$ref: '#/components/schemas/GeoVisioReports'
|
|
342
|
+
"""
|
|
343
|
+
try:
|
|
344
|
+
params = request.args.copy()
|
|
345
|
+
if "filter" not in params:
|
|
346
|
+
params["filter"] = "status IN ('open', 'open_autofix', 'waiting')"
|
|
347
|
+
params = ListReportsParameter(account_id=UUID(account.id), **params)
|
|
348
|
+
except ValidationError as ve:
|
|
349
|
+
raise InvalidAPIUsage(_("Impossible to parse parameters"), payload=validation_error(ve))
|
|
350
|
+
|
|
351
|
+
if not account.can_check_reports():
|
|
352
|
+
raise InvalidAPIUsage(_("You're not authorized to list reports"), status_code=403)
|
|
353
|
+
|
|
354
|
+
reports = list_reports(account_id=params.account_id, limit=params.limit, filter=params.filter, forceAccount=False)
|
|
355
|
+
|
|
356
|
+
return reports.model_dump_json(exclude_none=True), 200, {"Content-Type": "application/json"}
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
@bp.route("/users/me/reports", methods=["GET"])
|
|
360
|
+
@auth.login_required_with_redirect()
|
|
361
|
+
def listUserReports(account):
|
|
362
|
+
"""List reports associated to current user
|
|
363
|
+
|
|
364
|
+
This concerns reports you created, as long as reports on your pictures or sequences.
|
|
365
|
+
---
|
|
366
|
+
tags:
|
|
367
|
+
- Reports
|
|
368
|
+
parameters:
|
|
369
|
+
- $ref: '#/components/parameters/GeoVisioUserReports_filter'
|
|
370
|
+
- name: limit
|
|
371
|
+
in: query
|
|
372
|
+
description: limit to the number of reports to retrieve
|
|
373
|
+
required: true
|
|
374
|
+
schema:
|
|
375
|
+
type: integer
|
|
376
|
+
minimum: 1
|
|
377
|
+
maximum: 100
|
|
378
|
+
security:
|
|
379
|
+
- bearerToken: []
|
|
380
|
+
- cookieAuth: []
|
|
381
|
+
responses:
|
|
382
|
+
200:
|
|
383
|
+
description: the Report metadata
|
|
384
|
+
content:
|
|
385
|
+
application/json:
|
|
386
|
+
schema:
|
|
387
|
+
$ref: '#/components/schemas/GeoVisioReports'
|
|
388
|
+
"""
|
|
389
|
+
try:
|
|
390
|
+
params = ListReportsParameter(account_id=UUID(account.id), **request.args)
|
|
391
|
+
except ValidationError as ve:
|
|
392
|
+
raise InvalidAPIUsage(_("Impossible to parse parameters"), payload=validation_error(ve))
|
|
393
|
+
|
|
394
|
+
reports = list_reports(account_id=params.account_id, limit=params.limit, filter=params.filter)
|
|
395
|
+
|
|
396
|
+
if not account.can_check_reports():
|
|
397
|
+
reports.reports = [r.for_public() for r in reports.reports]
|
|
398
|
+
|
|
399
|
+
return reports.model_dump_json(exclude_none=True), 200, {"Content-Type": "application/json"}
|
geovisio/web/rss.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from rfeed import *
|
|
2
2
|
from flask import url_for
|
|
3
|
+
from flask_babel import gettext as _, get_locale
|
|
3
4
|
import datetime
|
|
4
5
|
from . import utils
|
|
5
6
|
|
|
@@ -14,12 +15,17 @@ def dbSequencesToGeoRSS(dbSequences):
|
|
|
14
15
|
url = utils.get_viewerpage_url() + f"#focus=map&map=18/{dbSeq['y1']}/{dbSeq['x1']}"
|
|
15
16
|
urlJson = url_for("stac_collections.getCollection", _external=True, collectionId=dbSeq["id"])
|
|
16
17
|
urlThumb = url_for("stac_collections.getCollectionThumbnail", _external=True, collectionId=dbSeq["id"])
|
|
17
|
-
|
|
18
|
+
desc = _(
|
|
19
|
+
"""Sequence "%(name)s" by "%(user)s" was captured on %(date)s.""",
|
|
20
|
+
name=dbSeq["name"],
|
|
21
|
+
user=dbSeq["account_name"],
|
|
22
|
+
date=str(dbSeq["mints"]),
|
|
23
|
+
)
|
|
18
24
|
items.append(
|
|
19
25
|
Item(
|
|
20
26
|
title=dbSeq["name"],
|
|
21
27
|
link=url,
|
|
22
|
-
description=
|
|
28
|
+
description=desc,
|
|
23
29
|
author=dbSeq["account_name"],
|
|
24
30
|
guid=Guid(urlJson),
|
|
25
31
|
pubDate=dbSeq["created"],
|
|
@@ -30,8 +36,8 @@ def dbSequencesToGeoRSS(dbSequences):
|
|
|
30
36
|
f"""
|
|
31
37
|
<p>
|
|
32
38
|
<img src="{urlThumb}" /><br />
|
|
33
|
-
|
|
34
|
-
<a href="{url}">View on the map</a> - <a href="{urlJson}">JSON metadata</a>
|
|
39
|
+
{desc}<br />
|
|
40
|
+
<a href="{url}">{_('View on the map')}</a> - <a href="{urlJson}">{_('JSON metadata')}</a>
|
|
35
41
|
</p>
|
|
36
42
|
"""
|
|
37
43
|
),
|
|
@@ -40,10 +46,10 @@ def dbSequencesToGeoRSS(dbSequences):
|
|
|
40
46
|
)
|
|
41
47
|
|
|
42
48
|
return Feed(
|
|
43
|
-
title="GeoVisio collections",
|
|
49
|
+
title=_("GeoVisio collections"),
|
|
44
50
|
link=urlHome,
|
|
45
|
-
description="List of collections from this GeoVisio server",
|
|
46
|
-
language=
|
|
51
|
+
description=_("List of collections from this GeoVisio server"),
|
|
52
|
+
language=get_locale().language,
|
|
47
53
|
lastBuildDate=datetime.datetime.now(),
|
|
48
54
|
items=items,
|
|
49
55
|
docs="https://cyber.harvard.edu/rss/rss.html",
|