Web platform for sharing free data for ML and research

By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 app.py

View raw Download
text/x-script.python • 27.82 kiB
Python script, ASCII text executable
        
            
1
import json
2
from datetime import datetime
3
from email.policy import default
4
from time import perf_counter
5
6
import flask
7
from flask_sqlalchemy import SQLAlchemy
8
from flask_bcrypt import Bcrypt
9
from flask_httpauth import HTTPBasicAuth
10
from markupsafe import escape, Markup
11
from flask_migrate import Migrate, current
12
from jinja2_fragments.flask import render_block
13
from sqlalchemy.orm import backref
14
import sqlalchemy.dialects.postgresql
15
from os import path
16
import os
17
from urllib.parse import urlencode
18
import mimetypes
19
import ruamel.yaml as yaml
20
21
from PIL import Image
22
from sqlalchemy.orm.persistence import post_update
23
24
import config
25
import markdown
26
27
28
app = flask.Flask(__name__)
29
bcrypt = Bcrypt(app)
30
31
32
app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI
33
app.config["SECRET_KEY"] = config.DB_PASSWORD
34
35
36
db = SQLAlchemy(app)
37
migrate = Migrate(app, db)
38
39
40
@app.template_filter("split")
41
def split(value, separator=None, maxsplit=-1):
42
return value.split(separator, maxsplit)
43
44
45
@app.template_filter("median")
46
def median(value):
47
value = list(value) # prevent generators
48
return sorted(value)[len(value) // 2]
49
50
51
@app.template_filter("set")
52
def set_filter(value):
53
return set(value)
54
55
56
@app.template_global()
57
def modify_query(**new_values):
58
args = flask.request.args.copy()
59
for key, value in new_values.items():
60
args[key] = value
61
62
return f"{flask.request.path}?{urlencode(args)}"
63
64
65
@app.context_processor
66
def default_variables():
67
return {
68
"current_user": db.session.get(User, flask.session.get("username")),
69
}
70
71
72
with app.app_context():
73
class User(db.Model):
74
username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True)
75
password_hashed = db.Column(db.String(60), nullable=False)
76
admin = db.Column(db.Boolean, nullable=False, default=False, server_default="false")
77
pictures = db.relationship("PictureResource", back_populates="author")
78
79
def __init__(self, username, password):
80
self.username = username
81
self.password_hashed = bcrypt.generate_password_hash(password).decode("utf-8")
82
83
84
class Licence(db.Model):
85
id = db.Column(db.String(64), primary_key=True) # SPDX identifier
86
title = db.Column(db.UnicodeText, nullable=False) # the official name of the licence
87
description = db.Column(db.UnicodeText, nullable=False) # brief description of its permissions and restrictions
88
info_url = db.Column(db.String(1024), nullable=False) # the URL to a page with general information about the licence
89
url = db.Column(db.String(1024), nullable=True) # the URL to a page with the full text of the licence and more information
90
pictures = db.relationship("PictureLicence", back_populates="licence")
91
free = db.Column(db.Boolean, nullable=False, default=False) # whether the licence is free or not
92
logo_url = db.Column(db.String(1024), nullable=True) # URL to the logo of the licence
93
pinned = db.Column(db.Boolean, nullable=False, default=False) # whether the licence should be shown at the top of the list
94
95
def __init__(self, id, title, description, info_url, url, free, logo_url=None, pinned=False):
96
self.id = id
97
self.title = title
98
self.description = description
99
self.info_url = info_url
100
self.url = url
101
self.free = free
102
self.logo_url = logo_url
103
self.pinned = pinned
104
105
106
class PictureLicence(db.Model):
107
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
108
109
resource_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"))
110
licence_id = db.Column(db.String(64), db.ForeignKey("licence.id"))
111
112
resource = db.relationship("PictureResource", back_populates="licences")
113
licence = db.relationship("Licence", back_populates="pictures")
114
115
def __init__(self, resource, licence):
116
self.resource = resource
117
self.licence = licence
118
119
120
class Resource(db.Model):
121
__abstract__ = True
122
123
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
124
title = db.Column(db.UnicodeText, nullable=False)
125
description = db.Column(db.UnicodeText, nullable=False)
126
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
127
origin_url = db.Column(db.String(2048), nullable=True) # should be left empty if it's original or the source is unknown but public domain
128
129
130
class PictureNature(db.Model):
131
# Examples:
132
# "photo", "paper-scan", "2d-art-photo", "sculpture-photo", "computer-3d", "computer-painting",
133
# "computer-line-art", "diagram", "infographic", "text", "map", "chart-graph", "screen-capture",
134
# "screen-photo", "pattern", "collage", "ai", and so on
135
id = db.Column(db.String(64), primary_key=True)
136
description = db.Column(db.UnicodeText, nullable=False)
137
resources = db.relationship("PictureResource", back_populates="nature")
138
139
def __init__(self, id, description):
140
self.id = id
141
self.description = description
142
143
144
class PictureObjectInheritance(db.Model):
145
parent_id = db.Column(db.String(64), db.ForeignKey("picture_object.id"),
146
primary_key=True)
147
child_id = db.Column(db.String(64), db.ForeignKey("picture_object.id"),
148
primary_key=True)
149
150
parent = db.relationship("PictureObject", foreign_keys=[parent_id],
151
back_populates="child_links")
152
child = db.relationship("PictureObject", foreign_keys=[child_id],
153
back_populates="parent_links")
154
155
def __init__(self, parent, child):
156
self.parent = parent
157
self.child = child
158
159
160
class PictureObject(db.Model):
161
id = db.Column(db.String(64), primary_key=True)
162
description = db.Column(db.UnicodeText, nullable=False)
163
164
child_links = db.relationship("PictureObjectInheritance",
165
foreign_keys=[PictureObjectInheritance.parent_id],
166
back_populates="parent")
167
parent_links = db.relationship("PictureObjectInheritance",
168
foreign_keys=[PictureObjectInheritance.child_id],
169
back_populates="child")
170
171
def __init__(self, id, description):
172
self.id = id
173
self.description = description
174
175
176
class PictureRegion(db.Model):
177
# This is for picture region annotations
178
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
179
json = db.Column(sqlalchemy.dialects.postgresql.JSONB, nullable=False)
180
181
resource_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"), nullable=False)
182
object_id = db.Column(db.String(64), db.ForeignKey("picture_object.id"), nullable=True)
183
184
resource = db.relationship("PictureResource", backref="regions")
185
object = db.relationship("PictureObject", backref="regions")
186
187
def __init__(self, json, resource, object):
188
self.json = json
189
self.resource = resource
190
self.object = object
191
192
193
class PictureResource(Resource):
194
# This is only for bitmap pictures. Vectors will be stored under a different model
195
# File name is the ID in the picture directory under data, without an extension
196
file_format = db.Column(db.String(64), nullable=False) # MIME type
197
width = db.Column(db.Integer, nullable=False)
198
height = db.Column(db.Integer, nullable=False)
199
nature_id = db.Column(db.String(32), db.ForeignKey("picture_nature.id"), nullable=True)
200
author_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
201
author = db.relationship("User", back_populates="pictures")
202
203
nature = db.relationship("PictureNature", back_populates="resources")
204
205
replaces_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"), nullable=True)
206
replaced_by_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"),
207
nullable=True)
208
209
replaces = db.relationship("PictureResource", remote_side="PictureResource.id",
210
foreign_keys=[replaces_id], back_populates="replaced_by",
211
post_update=True)
212
replaced_by = db.relationship("PictureResource", remote_side="PictureResource.id",
213
foreign_keys=[replaced_by_id], post_update=True)
214
215
copied_from_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"), nullable=True)
216
copied_from = db.relationship("PictureResource", remote_side="PictureResource.id",
217
backref="copies", foreign_keys=[copied_from_id])
218
219
licences = db.relationship("PictureLicence", back_populates="resource")
220
221
def __init__(self, title, author, description, origin_url, licence_ids, mime, nature=None):
222
self.title = title
223
self.author = author
224
self.description = description
225
self.origin_url = origin_url
226
self.file_format = mime
227
self.width = self.height = 0
228
self.nature = nature
229
db.session.add(self)
230
db.session.commit()
231
for licence_id in licence_ids:
232
joiner = PictureLicence(self, db.session.get(Licence, licence_id))
233
db.session.add(joiner)
234
235
def put_annotations(self, json):
236
# Delete all previous annotations
237
db.session.query(PictureRegion).filter_by(resource_id=self.id).delete()
238
239
for region in json:
240
object_id = region["object"]
241
picture_object = db.session.get(PictureObject, object_id)
242
243
region_data = {
244
"type": region["type"],
245
"shape": region["shape"],
246
}
247
248
region_row = PictureRegion(region_data, self, picture_object)
249
db.session.add(region_row)
250
251
252
@app.route("/")
253
def index():
254
return flask.render_template("home.html", resources=PictureResource.query.order_by(db.func.random()).limit(10).all())
255
256
257
@app.route("/accounts/")
258
def accounts():
259
return flask.render_template("login.html")
260
261
262
@app.route("/login", methods=["POST"])
263
def login():
264
username = flask.request.form["username"]
265
password = flask.request.form["password"]
266
267
user = db.session.get(User, username)
268
269
if user is None:
270
flask.flash("This username is not registered.")
271
return flask.redirect("/accounts")
272
273
if not bcrypt.check_password_hash(user.password_hashed, password):
274
flask.flash("Incorrect password.")
275
return flask.redirect("/accounts")
276
277
flask.flash("You have been logged in.")
278
279
flask.session["username"] = username
280
return flask.redirect("/")
281
282
283
@app.route("/logout")
284
def logout():
285
flask.session.pop("username", None)
286
flask.flash("You have been logged out.")
287
return flask.redirect("/")
288
289
290
@app.route("/signup", methods=["POST"])
291
def signup():
292
username = flask.request.form["username"]
293
password = flask.request.form["password"]
294
295
if db.session.get(User, username) is not None:
296
flask.flash("This username is already taken.")
297
return flask.redirect("/accounts")
298
299
if set(username) > set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"):
300
flask.flash("Usernames can only contain the Latin alphabet, digits, hyphens, and underscores.")
301
return flask.redirect("/accounts")
302
303
if len(username) < 3 or len(username) > 32:
304
flask.flash("Usernames must be between 3 and 32 characters long.")
305
return flask.redirect("/accounts")
306
307
if len(password) < 6:
308
flask.flash("Passwords must be at least 6 characters long.")
309
return flask.redirect("/accounts")
310
311
user = User(username, password)
312
db.session.add(user)
313
db.session.commit()
314
315
flask.session["username"] = username
316
317
flask.flash("You have been registered and logged in.")
318
319
return flask.redirect("/")
320
321
322
@app.route("/profile", defaults={"username": None})
323
@app.route("/profile/<username>")
324
def profile(username):
325
if username is None:
326
if "username" in flask.session:
327
return flask.redirect("/profile/" + flask.session["username"])
328
else:
329
flask.flash("Please log in to perform this action.")
330
return flask.redirect("/accounts")
331
332
user = db.session.get(User, username)
333
if user is None:
334
flask.abort(404)
335
336
return flask.render_template("profile.html", user=user)
337
338
339
@app.route("/object/<id>")
340
def has_object(id):
341
object_ = db.session.get(PictureObject, id)
342
if object_ is None:
343
flask.abort(404)
344
345
query = db.session.query(PictureResource).join(PictureRegion).filter(PictureRegion.object_id == id)
346
347
page = int(flask.request.args.get("page", 1))
348
per_page = int(flask.request.args.get("per_page", 16))
349
350
resources = query.paginate(page=page, per_page=per_page)
351
352
return flask.render_template("object.html", object=object_, resources=resources, page_number=page,
353
page_length=per_page, num_pages=resources.pages, prev_page=resources.prev_num,
354
next_page=resources.next_num, PictureRegion=PictureRegion)
355
356
357
@app.route("/upload")
358
def upload():
359
if "username" not in flask.session:
360
flask.flash("Log in to upload pictures.")
361
return flask.redirect("/accounts")
362
363
licences = Licence.query.order_by(Licence.free.desc(), Licence.pinned.desc(), Licence.title).all()
364
365
types = PictureNature.query.all()
366
367
return flask.render_template("upload.html", licences=licences, types=types)
368
369
370
@app.route("/upload", methods=["POST"])
371
def upload_post():
372
title = flask.request.form["title"]
373
description = flask.request.form["description"]
374
origin_url = flask.request.form["origin_url"]
375
author = db.session.get(User, flask.session.get("username"))
376
licence_ids = flask.request.form.getlist("licence")
377
nature_id = flask.request.form["nature"]
378
379
if author is None:
380
flask.abort(401)
381
382
file = flask.request.files["file"]
383
384
if not file or not file.filename:
385
flask.flash("Select a file")
386
return flask.redirect(flask.request.url)
387
388
if not file.mimetype.startswith("image/"):
389
flask.flash("Only images are supported")
390
return flask.redirect(flask.request.url)
391
392
if not title:
393
flask.flash("Enter a title")
394
return flask.redirect(flask.request.url)
395
396
if not description:
397
description = ""
398
399
if not nature_id:
400
flask.flash("Select a picture type")
401
return flask.redirect(flask.request.url)
402
403
if not licence_ids:
404
flask.flash("Select licences")
405
return flask.redirect(flask.request.url)
406
407
licences = [db.session.get(Licence, licence_id) for licence_id in licence_ids]
408
if not any(licence.free for licence in licences):
409
flask.flash("Select at least one free licence")
410
return flask.redirect(flask.request.url)
411
412
resource = PictureResource(title, author, description, origin_url, licence_ids, file.mimetype,
413
db.session.get(PictureNature, nature_id))
414
db.session.add(resource)
415
db.session.commit()
416
file.save(path.join(config.DATA_PATH, "pictures", str(resource.id)))
417
pil_image = Image.open(path.join(config.DATA_PATH, "pictures", str(resource.id)))
418
resource.width, resource.height = pil_image.size
419
db.session.commit()
420
421
if flask.request.form.get("annotations"):
422
try:
423
resource.put_annotations(json.loads(flask.request.form.get("annotations")))
424
db.session.commit()
425
except json.JSONDecodeError:
426
flask.flash("Invalid annotations")
427
428
flask.flash("Picture uploaded successfully")
429
430
return flask.redirect("/picture/" + str(resource.id))
431
432
433
@app.route("/picture/<int:id>/")
434
def picture(id):
435
resource = db.session.get(PictureResource, id)
436
if resource is None:
437
flask.abort(404)
438
439
image = Image.open(path.join(config.DATA_PATH, "pictures", str(resource.id)))
440
441
return flask.render_template("picture.html", resource=resource,
442
file_extension=mimetypes.guess_extension(resource.file_format),
443
size=image.size, copies=resource.copies)
444
445
446
447
@app.route("/picture/<int:id>/annotate")
448
def annotate_picture(id):
449
resource = db.session.get(PictureResource, id)
450
if resource is None:
451
flask.abort(404)
452
453
current_user = db.session.get(User, flask.session.get("username"))
454
if current_user is None:
455
flask.abort(401)
456
if resource.author != current_user and not current_user.admin:
457
flask.abort(403)
458
459
return flask.render_template("picture-annotation.html", resource=resource,
460
file_extension=mimetypes.guess_extension(resource.file_format))
461
462
463
@app.route("/picture/<int:id>/put-annotations-form")
464
def put_annotations_form(id):
465
resource = db.session.get(PictureResource, id)
466
if resource is None:
467
flask.abort(404)
468
469
current_user = db.session.get(User, flask.session.get("username"))
470
if current_user is None:
471
flask.abort(401)
472
473
if resource.author != current_user and not current_user.admin:
474
flask.abort(403)
475
476
return flask.render_template("put-annotations-form.html", resource=resource)
477
478
479
@app.route("/picture/<int:id>/put-annotations-form", methods=["POST"])
480
def put_annotations_form_post(id):
481
resource = db.session.get(PictureResource, id)
482
if resource is None:
483
flask.abort(404)
484
485
current_user = db.session.get(User, flask.session.get("username"))
486
if current_user is None:
487
flask.abort(401)
488
489
if resource.author != current_user and not current_user.admin:
490
flask.abort(403)
491
492
resource.put_annotations(json.loads(flask.request.form["annotations"]))
493
494
db.session.commit()
495
496
return flask.redirect("/picture/" + str(resource.id))
497
498
499
500
@app.route("/picture/<int:id>/save-annotations", methods=["POST"])
501
def save_annotations(id):
502
resource = db.session.get(PictureResource, id)
503
if resource is None:
504
flask.abort(404)
505
506
current_user = db.session.get(User, flask.session.get("username"))
507
if resource.author != current_user and not current_user.admin:
508
flask.abort(403)
509
510
resource.put_annotations(flask.request.json)
511
512
db.session.commit()
513
514
response = flask.make_response()
515
response.status_code = 204
516
return response
517
518
519
@app.route("/picture/<int:id>/get-annotations")
520
def get_annotations(id):
521
resource = db.session.get(PictureResource, id)
522
if resource is None:
523
flask.abort(404)
524
525
regions = db.session.query(PictureRegion).filter_by(resource_id=id).all()
526
527
regions_json = []
528
529
for region in regions:
530
regions_json.append({
531
"object": region.object_id,
532
"type": region.json["type"],
533
"shape": region.json["shape"],
534
})
535
536
return flask.jsonify(regions_json)
537
538
539
@app.route("/picture/<int:id>/delete")
540
def delete_picture(id):
541
resource = db.session.get(PictureResource, id)
542
if resource is None:
543
flask.abort(404)
544
545
current_user = db.session.get(User, flask.session.get("username"))
546
if current_user is None:
547
flask.abort(401)
548
549
if resource.author != current_user and not current_user.admin:
550
flask.abort(403)
551
552
PictureLicence.query.filter_by(resource=resource).delete()
553
PictureRegion.query.filter_by(resource=resource).delete()
554
db.session.delete(resource)
555
db.session.commit()
556
557
return flask.redirect("/")
558
559
560
@app.route("/picture/<int:id>/edit-metadata")
561
def edit_picture(id):
562
resource = db.session.get(PictureResource, id)
563
if resource is None:
564
flask.abort(404)
565
566
current_user = db.session.get(User, flask.session.get("username"))
567
if current_user is None:
568
flask.abort(401)
569
570
if resource.author != current_user and not current_user.admin:
571
flask.abort(403)
572
573
licences = Licence.query.order_by(Licence.free.desc(), Licence.pinned.desc(), Licence.title).all()
574
575
types = PictureNature.query.all()
576
577
return flask.render_template("edit-picture.html", resource=resource, licences=licences, types=types,
578
PictureLicence=PictureLicence)
579
580
581
@app.route("/picture/<int:id>/edit-metadata", methods=["POST"])
582
def edit_picture_post(id):
583
resource = db.session.get(PictureResource, id)
584
if resource is None:
585
flask.abort(404)
586
587
current_user = db.session.get(User, flask.session.get("username"))
588
if current_user is None:
589
flask.abort(401)
590
591
if resource.author != current_user and not current_user.admin:
592
flask.abort(403)
593
594
title = flask.request.form["title"]
595
description = flask.request.form["description"]
596
origin_url = flask.request.form["origin_url"]
597
licence_ids = flask.request.form.getlist("licence")
598
nature_id = flask.request.form["nature"]
599
600
if not title:
601
flask.flash("Enter a title")
602
return flask.redirect(flask.request.url)
603
604
if not description:
605
description = ""
606
607
if not nature_id:
608
flask.flash("Select a picture type")
609
return flask.redirect(flask.request.url)
610
611
if not licence_ids:
612
flask.flash("Select licences")
613
return flask.redirect(flask.request.url)
614
615
licences = [db.session.get(Licence, licence_id) for licence_id in licence_ids]
616
if not any(licence.free for licence in licences):
617
flask.flash("Select at least one free licence")
618
return flask.redirect(flask.request.url)
619
620
resource.title = title
621
resource.description = description
622
resource.origin_url = origin_url
623
for licence_id in licence_ids:
624
joiner = PictureLicence(resource, db.session.get(Licence, licence_id))
625
db.session.add(joiner)
626
resource.nature = db.session.get(PictureNature, nature_id)
627
628
db.session.commit()
629
630
return flask.redirect("/picture/" + str(resource.id))
631
632
633
@app.route("/picture/<int:id>/copy")
634
def copy_picture(id):
635
resource = db.session.get(PictureResource, id)
636
if resource is None:
637
flask.abort(404)
638
639
current_user = db.session.get(User, flask.session.get("username"))
640
if current_user is None:
641
flask.abort(401)
642
643
new_resource = PictureResource(resource.title, current_user, resource.description, resource.origin_url,
644
[licence.licence_id for licence in resource.licences], resource.file_format,
645
resource.nature)
646
647
for region in resource.regions:
648
db.session.add(PictureRegion(region.json, new_resource, region.object))
649
650
db.session.commit()
651
652
# Create a hard link for the new picture
653
old_path = path.join(config.DATA_PATH, "pictures", str(resource.id))
654
new_path = path.join(config.DATA_PATH, "pictures", str(new_resource.id))
655
os.link(old_path, new_path)
656
657
new_resource.width = resource.width
658
new_resource.height = resource.height
659
new_resource.copied_from = resource
660
661
db.session.commit()
662
663
return flask.redirect("/picture/" + str(new_resource.id))
664
665
666
@app.route("/query-pictures", methods=["POST"]) # sadly GET can't have a body
667
def query_pictures():
668
offset = int(flask.request.args.get("offset", 0))
669
limit = int(flask.request.args.get("limit", 16))
670
ordering = flask.request.args.get("ordering", "date-desc")
671
672
yaml_parser = yaml.YAML()
673
query_data = yaml_parser.load(flask.request.data) or {}
674
675
query = db.session.query(PictureResource)
676
677
requirement_conditions = {
678
"has_object": lambda value: PictureResource.regions.any(
679
PictureRegion.object_id.in_(value)),
680
"nature": lambda value: PictureResource.nature_id.in_(value),
681
"licence": lambda value: PictureResource.licences.any(
682
PictureLicence.licence_id.in_(value)),
683
"author": lambda value: PictureResource.author_name.in_(value),
684
"title": lambda value: PictureResource.title.ilike(value),
685
"description": lambda value: PictureResource.description.ilike(value),
686
"origin_url": lambda value: db.func.lower(db.func.substr(
687
PictureResource.origin_url,
688
db.func.length(db.func.split_part(PictureResource.origin_url, "://", 1)) + 4
689
)).in_(value),
690
"above_width": lambda value: PictureResource.width >= value,
691
"below_width": lambda value: PictureResource.width <= value,
692
"above_height": lambda value: PictureResource.height >= value,
693
"below_height": lambda value: PictureResource.height <= value,
694
"before_date": lambda value: PictureResource.timestamp <= datetime.utcfromtimestamp(
695
value),
696
"after_date": lambda value: PictureResource.timestamp >= datetime.utcfromtimestamp(
697
value)
698
}
699
if "want" in query_data:
700
for i in query_data["want"]:
701
requirement, value = list(i.items())[0]
702
condition = requirement_conditions.get(requirement)
703
if condition:
704
query = query.filter(condition(value))
705
if "exclude" in query_data:
706
for i in query_data["exclude"]:
707
requirement, value = list(i.items())[0]
708
condition = requirement_conditions.get(requirement)
709
if condition:
710
query = query.filter(~condition(value))
711
if not query_data.get("include_obsolete", False):
712
query = query.filter(PictureResource.replaced_by_id.is_(None))
713
714
match ordering:
715
case "date-desc":
716
query = query.order_by(PictureResource.timestamp.desc())
717
case "date-asc":
718
query = query.order_by(PictureResource.timestamp.asc())
719
case "title-asc":
720
query = query.order_by(PictureResource.title.asc())
721
case "title-desc":
722
query = query.order_by(PictureResource.title.desc())
723
case "random":
724
query = query.order_by(db.func.random())
725
case "number-regions-desc":
726
query = query.order_by(db.func.count(PictureResource.regions).desc())
727
case "number-regions-asc":
728
query = query.order_by(db.func.count(PictureResource.regions).asc())
729
730
query = query.offset(offset).limit(limit)
731
resources = query.all()
732
733
json_response = {
734
"date_generated": datetime.utcnow().timestamp(),
735
"resources": [],
736
"offset": offset,
737
"limit": limit,
738
}
739
740
json_resources = json_response["resources"]
741
742
for resource in resources:
743
json_resource = {
744
"id": resource.id,
745
"title": resource.title,
746
"description": resource.description,
747
"timestamp": resource.timestamp.timestamp(),
748
"origin_url": resource.origin_url,
749
"author": resource.author_name,
750
"file_format": resource.file_format,
751
"width": resource.width,
752
"height": resource.height,
753
"nature": resource.nature_id,
754
"licences": [licence.licence_id for licence in resource.licences],
755
"replaces": resource.replaces_id,
756
"replaced_by": resource.replaced_by_id,
757
"regions": [],
758
"download": config.ROOT_URL + flask.url_for("raw_picture", id=resource.id),
759
}
760
for region in resource.regions:
761
json_resource["regions"].append({
762
"object": region.object_id,
763
"type": region.json["type"],
764
"shape": region.json["shape"],
765
})
766
767
json_resources.append(json_resource)
768
769
response = flask.jsonify(json_response)
770
response.headers["Content-Type"] = "application/json"
771
return response
772
773
774
@app.route("/raw/picture/<int:id>")
775
def raw_picture(id):
776
resource = db.session.get(PictureResource, id)
777
if resource is None:
778
flask.abort(404)
779
780
response = flask.send_from_directory(path.join(config.DATA_PATH, "pictures"), str(resource.id))
781
response.mimetype = resource.file_format
782
783
return response
784
785
786
@app.route("/api/object-types")
787
def object_types():
788
objects = db.session.query(PictureObject).all()
789
return flask.jsonify({object.id: object.description for object in objects})
790