Web platform for sharing free image data for ML and research

Homepage: https://datasets.roundabout-host.com

Important information: Google announced that, from September 2026, Android devices will require ALL apps to be signed by Google, effectively leading to an iOS situation. Value your right to a computer that does what you want; do not tolerate this monopolistic practice! Contact me if you don't understand why it is bad. Click to learn more.

 app.py

View raw Download
text/x-script.python • 10.16 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
14
import config
15
import markdown
16
17
18
app = flask.Flask(__name__)
19
bcrypt = Bcrypt(app)
20
21
22
app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI
23
app.config["SECRET_KEY"] = config.DB_PASSWORD
24
25
26
db = SQLAlchemy(app)
27
migrate = Migrate(app, db)
28
29
30
@app.template_filter("split")
31
def split(value, separator=None, maxsplit=-1):
32
return value.split(separator, maxsplit)
33
34
35
36
with app.app_context():
37
class User(db.Model):
38
username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True)
39
password_hashed = db.Column(db.String(60), nullable=False)
40
admin = db.Column(db.Boolean, nullable=False, default=False, server_default="false")
41
42
def __init__(self, username, password):
43
self.username = username
44
self.password_hashed = bcrypt.generate_password_hash(password).decode("utf-8")
45
46
47
class Licence(db.Model):
48
id = db.Column(db.String(32), primary_key=True) # SPDX identifier
49
title = db.Column(db.UnicodeText, nullable=False) # the official name of the licence
50
description = db.Column(db.UnicodeText, nullable=False) # brief description of its permissions and restrictions
51
legal_text = db.Column(db.UnicodeText, nullable=False) # the full legal text of the licence
52
url = db.Column(db.String(2048), nullable=True) # the URL to a page with the full text of the licence and more information
53
pictures = db.relationship("PictureLicence", back_populates="licence")
54
55
def __init__(self, id, title, description, legal_text, url):
56
self.id = id
57
self.title = title
58
self.description = description
59
self.legal_text = legal_text
60
self.url = url
61
62
63
class PictureLicence(db.Model):
64
resource_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"), primary_key=True)
65
licence_id = db.Column(db.String(32), db.ForeignKey("licence.id"), primary_key=True)
66
67
resource = db.relationship("PictureResource", back_populates="licences")
68
licence = db.relationship("Licence", back_populates="pictures")
69
70
def __init__(self, resource_id, licence_id):
71
self.resource_id = resource_id
72
self.licence_id = licence_id
73
74
75
class Resource(db.Model):
76
__abstract__ = True
77
78
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
79
title = db.Column(db.UnicodeText, nullable=False)
80
description = db.Column(db.UnicodeText, nullable=False)
81
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
82
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
83
84
class PictureNature(db.Model):
85
# Examples:
86
# "photo", "paper-scan", "2d-art-photo", "sculpture-photo", "computer-3d", "computer-painting",
87
# "computer-line-art", "diagram", "infographic", "text", "map", "chart-graph", "screen-capture",
88
# "screen-photo", "pattern", "collage", "ai", and so on
89
id = db.Column(db.String(64), primary_key=True)
90
description = db.Column(db.UnicodeText, nullable=False)
91
resources = db.relationship("PictureResource", backref="nature")
92
93
def __init__(self, id, description):
94
self.id = id
95
self.description = description
96
97
98
class PictureObjectInheritance(db.Model):
99
parent_id = db.Column(db.String(64), db.ForeignKey("picture_object.id"),
100
primary_key=True)
101
child_id = db.Column(db.String(64), db.ForeignKey("picture_object.id"),
102
primary_key=True)
103
104
parent = db.relationship("PictureObject", foreign_keys=[parent_id],
105
backref="child_links")
106
child = db.relationship("PictureObject", foreign_keys=[child_id],
107
backref="parent_links")
108
109
def __init__(self, parent, child):
110
self.parent = parent
111
self.child = child
112
113
114
class PictureObject(db.Model):
115
id = db.Column(db.String(64), primary_key=True)
116
description = db.Column(db.UnicodeText, nullable=False)
117
118
child_links = db.relationship("PictureObjectInheritance",
119
foreign_keys=[PictureObjectInheritance.parent_id],
120
backref="parent")
121
parent_links = db.relationship("PictureObjectInheritance",
122
foreign_keys=[PictureObjectInheritance.child_id],
123
backref="child")
124
125
def __init__(self, id, description):
126
self.id = id
127
self.description = description
128
129
class PictureRegion(db.Model):
130
# This is for picture region annotations
131
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
132
json = db.Column(sqlalchemy.dialects.postgresql.JSONB, nullable=False)
133
134
resource_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"), nullable=False)
135
object_id = db.Column(db.String(64), db.ForeignKey("picture_object.id"), nullable=False)
136
137
resource = db.relationship("PictureResource", backref="regions")
138
object = db.relationship("PictureObject", backref="regions")
139
140
def __init__(self, json, resource, object):
141
self.json = json
142
self.resource = resource
143
self.object = object
144
145
146
class PictureResource(Resource):
147
# This is only for bitmap pictures. Vectors will be stored under a different model
148
# File name is the ID in the picture directory under data, without an extension
149
file_format = db.Column(db.String(64), nullable=False) # MIME type
150
width = db.Column(db.Integer, nullable=False)
151
height = db.Column(db.Integer, nullable=False)
152
nature_id = db.Column(db.String(32), db.ForeignKey("picture_nature.id"), nullable=False)
153
154
nature = db.relationship("PictureNature", back_populates="resources")
155
156
replaces_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"), nullable=True)
157
replaced_by_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"), nullable=True) # if this is set, this resource is obsolete and hidden from search results, only available if asked for
158
159
replaces = db.relationship("PictureResource", foreign_keys=[replaces_id], backref="replaced_by")
160
replaced_by = db.relationship("PictureResource", foreign_keys=[replaced_by_id], backref="replaces")
161
162
163
licences = db.relationship("ResourceLicence", # a work can be under multiple licences; at least one must be free, but others can be proprietary
164
back_populates="resource")
165
166
167
def __init__(self, title, description, origin_url, licence_ids, mime, size, nature, replaces=None):
168
self.title = title
169
self.description = description
170
self.origin_url = origin_url
171
self.file_format = mime
172
self.width, self.height = size
173
self.nature = nature
174
db.session.add(self)
175
db.session.commit()
176
for licence_id in licence_ids:
177
joiner = PictureLicence(self.id, licence_id)
178
db.session.add(joiner)
179
if replaces is not None:
180
self.replaces = replaces
181
replaces.replaced_by = self
182
183
184
@app.route("/")
185
def index():
186
return flask.render_template("home.html")
187
188
189
@app.route("/accounts")
190
def accounts():
191
return flask.render_template("login.html")
192
193
194
@app.route("/login", methods=["POST"])
195
def login():
196
username = flask.request.form["username"]
197
password = flask.request.form["password"]
198
199
user = db.session.get(User, username)
200
201
if user is None:
202
flask.flash("This username is not registered.")
203
return flask.redirect("/accounts")
204
205
if not bcrypt.check_password_hash(user.password_hashed, password):
206
flask.flash("Incorrect password.")
207
return flask.redirect("/accounts")
208
209
flask.flash("You have been logged in.")
210
211
flask.session["username"] = username
212
return flask.redirect("/")
213
214
215
@app.route("/logout")
216
def logout():
217
flask.session.pop("username", None)
218
flask.flash("You have been logged out.")
219
return flask.redirect("/")
220
221
222
@app.route("/signup", methods=["POST"])
223
def signup():
224
username = flask.request.form["username"]
225
password = flask.request.form["password"]
226
227
if db.session.get(User, username) is not None:
228
flask.flash("This username is already taken.")
229
return flask.redirect("/accounts")
230
231
if set(username) > set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"):
232
flask.flash("Usernames can only contain the Latin alphabet, digits, hyphens, and underscores.")
233
return flask.redirect("/accounts")
234
235
if len(username) < 3 or len(username) > 32:
236
flask.flash("Usernames must be between 3 and 32 characters long.")
237
return flask.redirect("/accounts")
238
239
if len(password) < 6:
240
flask.flash("Passwords must be at least 6 characters long.")
241
return flask.redirect("/accounts")
242
243
user = User(username, None, password)
244
db.session.add(user)
245
db.session.commit()
246
247
flask.session["username"] = username
248
249
flask.flash("You have been registered and logged in.")
250
251
return flask.redirect("/")
252
253
254
@app.route("/profile", defaults={"username": None})
255
@app.route("/profile/<username>")
256
def profile(username):
257
if username is None:
258
if "username" in flask.session:
259
return flask.redirect("/profile/" + flask.session["username"])
260
else:
261
flask.flash("Please log in to perform this action.")
262
return flask.redirect("/accounts")
263
264
user = db.session.get(User, username)
265
if user is None:
266
return flask.abort(404)
267
268
return flask.render_template("profile.html", user=user)
269