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 • 12.22 kiB
Python script, ASCII text executable
        
            
1
from datetime import datetime
2
from email.policy import default
3
4
import flask
5
from flask_sqlalchemy import SQLAlchemy
6
from flask_bcrypt import Bcrypt
7
from flask_httpauth import HTTPBasicAuth
8
from markupsafe import escape, Markup
9
from flask_migrate import Migrate
10
from jinja2_fragments.flask import render_block
11
from sqlalchemy.orm import backref
12
import sqlalchemy.dialects.postgresql
13
from os import path
14
import mimetypes
15
16
import config
17
import markdown
18
19
20
app = flask.Flask(__name__)
21
bcrypt = Bcrypt(app)
22
23
24
app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI
25
app.config["SECRET_KEY"] = config.DB_PASSWORD
26
27
28
db = SQLAlchemy(app)
29
migrate = Migrate(app, db)
30
31
32
@app.template_filter("split")
33
def split(value, separator=None, maxsplit=-1):
34
return value.split(separator, maxsplit)
35
36
37
38
with app.app_context():
39
class User(db.Model):
40
username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True)
41
password_hashed = db.Column(db.String(60), nullable=False)
42
admin = db.Column(db.Boolean, nullable=False, default=False, server_default="false")
43
44
def __init__(self, username, password):
45
self.username = username
46
self.password_hashed = bcrypt.generate_password_hash(password).decode("utf-8")
47
48
49
class Licence(db.Model):
50
id = db.Column(db.String(64), primary_key=True) # SPDX identifier
51
title = db.Column(db.UnicodeText, nullable=False) # the official name of the licence
52
description = db.Column(db.UnicodeText, nullable=False) # brief description of its permissions and restrictions
53
legal_text = db.Column(db.UnicodeText, nullable=False) # the full legal text of the licence
54
url = db.Column(db.String(2048), nullable=True) # the URL to a page with the full text of the licence and more information
55
pictures = db.relationship("PictureLicence", back_populates="licence")
56
free = db.Column(db.Boolean, nullable=False, default=False) # whether the licence is free or not
57
58
def __init__(self, id, title, description, legal_text, url, free):
59
self.id = id
60
self.title = title
61
self.description = description
62
self.legal_text = legal_text
63
self.url = url
64
self.free = free
65
66
67
class PictureLicence(db.Model):
68
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
69
70
resource_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"))
71
licence_id = db.Column(db.String(32), db.ForeignKey("licence.id"))
72
73
resource = db.relationship("PictureResource", back_populates="licences")
74
licence = db.relationship("Licence", back_populates="pictures")
75
76
def __init__(self, resource_id, licence_id):
77
self.resource_id = resource_id
78
self.licence_id = licence_id
79
80
81
class Resource(db.Model):
82
__abstract__ = True
83
84
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
85
title = db.Column(db.UnicodeText, nullable=False)
86
description = db.Column(db.UnicodeText, nullable=False)
87
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
88
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
89
90
91
class PictureNature(db.Model):
92
# Examples:
93
# "photo", "paper-scan", "2d-art-photo", "sculpture-photo", "computer-3d", "computer-painting",
94
# "computer-line-art", "diagram", "infographic", "text", "map", "chart-graph", "screen-capture",
95
# "screen-photo", "pattern", "collage", "ai", and so on
96
id = db.Column(db.String(64), primary_key=True)
97
description = db.Column(db.UnicodeText, nullable=False)
98
resources = db.relationship("PictureResource", back_populates="nature")
99
100
def __init__(self, id, description):
101
self.id = id
102
self.description = description
103
104
105
class PictureObjectInheritance(db.Model):
106
parent_id = db.Column(db.String(64), db.ForeignKey("picture_object.id"),
107
primary_key=True)
108
child_id = db.Column(db.String(64), db.ForeignKey("picture_object.id"),
109
primary_key=True)
110
111
parent = db.relationship("PictureObject", foreign_keys=[parent_id],
112
back_populates="child_links")
113
child = db.relationship("PictureObject", foreign_keys=[child_id],
114
back_populates="parent_links")
115
116
def __init__(self, parent, child):
117
self.parent = parent
118
self.child = child
119
120
121
class PictureObject(db.Model):
122
id = db.Column(db.String(64), primary_key=True)
123
description = db.Column(db.UnicodeText, nullable=False)
124
125
child_links = db.relationship("PictureObjectInheritance",
126
foreign_keys=[PictureObjectInheritance.parent_id],
127
back_populates="parent")
128
parent_links = db.relationship("PictureObjectInheritance",
129
foreign_keys=[PictureObjectInheritance.child_id],
130
back_populates="child")
131
132
def __init__(self, id, description):
133
self.id = id
134
self.description = description
135
136
137
class PictureRegion(db.Model):
138
# This is for picture region annotations
139
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
140
json = db.Column(sqlalchemy.dialects.postgresql.JSONB, nullable=False)
141
142
resource_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"), nullable=False)
143
object_id = db.Column(db.String(64), db.ForeignKey("picture_object.id"), nullable=False)
144
145
resource = db.relationship("PictureResource", backref="regions")
146
object = db.relationship("PictureObject", backref="regions")
147
148
def __init__(self, json, resource, object):
149
self.json = json
150
self.resource = resource
151
self.object = object
152
153
154
class PictureResource(Resource):
155
# This is only for bitmap pictures. Vectors will be stored under a different model
156
# File name is the ID in the picture directory under data, without an extension
157
file_format = db.Column(db.String(64), nullable=False) # MIME type
158
width = db.Column(db.Integer, nullable=False)
159
height = db.Column(db.Integer, nullable=False)
160
nature_id = db.Column(db.String(32), db.ForeignKey("picture_nature.id"), nullable=True)
161
162
nature = db.relationship("PictureNature", back_populates="resources")
163
164
replaces_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"), nullable=True)
165
replaced_by_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"),
166
nullable=True)
167
168
replaces = db.relationship("PictureResource", remote_side="PictureResource.id",
169
foreign_keys=[replaces_id], back_populates="replaced_by")
170
replaced_by = db.relationship("PictureResource", remote_side="PictureResource.id",
171
foreign_keys=[replaced_by_id])
172
173
licences = db.relationship("PictureLicence", back_populates="resource")
174
175
def __init__(self, title, description, origin_url, licence_ids, mime, nature=None,
176
replaces=None):
177
self.title = title
178
self.description = description
179
self.origin_url = origin_url
180
self.file_format = mime
181
self.width = self.height = 0
182
self.nature = nature
183
db.session.add(self)
184
db.session.commit()
185
for licence_id in licence_ids:
186
joiner = PictureLicence(self.id, licence_id)
187
db.session.add(joiner)
188
if replaces is not None:
189
self.replaces = replaces
190
replaces.replaced_by = self
191
192
193
@app.route("/")
194
def index():
195
return flask.render_template("home.html")
196
197
198
@app.route("/accounts/")
199
def accounts():
200
return flask.render_template("login.html")
201
202
203
@app.route("/login", methods=["POST"])
204
def login():
205
username = flask.request.form["username"]
206
password = flask.request.form["password"]
207
208
user = db.session.get(User, username)
209
210
if user is None:
211
flask.flash("This username is not registered.")
212
return flask.redirect("/accounts")
213
214
if not bcrypt.check_password_hash(user.password_hashed, password):
215
flask.flash("Incorrect password.")
216
return flask.redirect("/accounts")
217
218
flask.flash("You have been logged in.")
219
220
flask.session["username"] = username
221
return flask.redirect("/")
222
223
224
@app.route("/logout")
225
def logout():
226
flask.session.pop("username", None)
227
flask.flash("You have been logged out.")
228
return flask.redirect("/")
229
230
231
@app.route("/signup", methods=["POST"])
232
def signup():
233
username = flask.request.form["username"]
234
password = flask.request.form["password"]
235
236
if db.session.get(User, username) is not None:
237
flask.flash("This username is already taken.")
238
return flask.redirect("/accounts")
239
240
if set(username) > set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"):
241
flask.flash("Usernames can only contain the Latin alphabet, digits, hyphens, and underscores.")
242
return flask.redirect("/accounts")
243
244
if len(username) < 3 or len(username) > 32:
245
flask.flash("Usernames must be between 3 and 32 characters long.")
246
return flask.redirect("/accounts")
247
248
if len(password) < 6:
249
flask.flash("Passwords must be at least 6 characters long.")
250
return flask.redirect("/accounts")
251
252
user = User(username, password)
253
db.session.add(user)
254
db.session.commit()
255
256
flask.session["username"] = username
257
258
flask.flash("You have been registered and logged in.")
259
260
return flask.redirect("/")
261
262
263
@app.route("/profile", defaults={"username": None})
264
@app.route("/profile/<username>")
265
def profile(username):
266
if username is None:
267
if "username" in flask.session:
268
return flask.redirect("/profile/" + flask.session["username"])
269
else:
270
flask.flash("Please log in to perform this action.")
271
return flask.redirect("/accounts")
272
273
user = db.session.get(User, username)
274
if user is None:
275
return flask.abort(404)
276
277
return flask.render_template("profile.html", user=user)
278
279
280
@app.route("/upload")
281
def upload():
282
return flask.render_template("upload.html")
283
284
285
@app.route("/upload", methods=["POST"])
286
def upload_post():
287
title = flask.request.form["title"]
288
description = flask.request.form["description"]
289
origin_url = flask.request.form["origin_url"]
290
291
file = flask.request.files["file"]
292
293
if not file or not file.filename:
294
flask.flash("No selected file")
295
return flask.redirect(flask.request.url)
296
297
resource = PictureResource(title, description, origin_url, ["CC0-1.0"], file.mimetype)
298
db.session.add(resource)
299
db.session.commit()
300
file.save(path.join(config.DATA_PATH, "pictures", str(resource.id)))
301
302
return flask.redirect("/picture/" + str(resource.id))
303
304
305
@app.route("/picture/<int:id>/")
306
def picture(id):
307
resource = db.session.get(PictureResource, id)
308
if resource is None:
309
return flask.abort(404)
310
311
return flask.render_template("picture.html", resource=resource,
312
file_extension=mimetypes.guess_extension(resource.file_format))
313
314
315
316
@app.route("/picture/<int:id>/annotate")
317
def annotate_picture(id):
318
resource = db.session.get(PictureResource, id)
319
if resource is None:
320
return flask.abort(404)
321
322
return flask.render_template("picture-annotation.html", resource=resource,
323
file_extension=mimetypes.guess_extension(resource.file_format))
324
325
326
@app.route("/raw/picture/<int:id>")
327
def raw_picture(id):
328
resource = db.session.get(PictureResource, id)
329
if resource is None:
330
return flask.abort(404)
331
332
response = flask.send_from_directory(path.join(config.DATA_PATH, "pictures"), str(resource.id))
333
response.mimetype = resource.file_format
334
335
return response
336
337
338
@app.route("/api/object-types")
339
def object_types():
340
objects = db.session.query(PictureObject).all()
341
return flask.jsonify({object.id: object.description for object in objects})
342