[controller] Add controller for handling uploading images

This commit is contained in:
Ferdinand Thiessen 2021-11-15 16:32:24 +01:00
parent 42e304cf5f
commit a6fe921920
4 changed files with 111 additions and 7 deletions

View File

@ -0,0 +1,65 @@
from datetime import date
from flask import send_file
from pathlib import Path
from PIL import Image as PImage
from werkzeug.exceptions import NotFound, UnprocessableEntity
from werkzeug.datastructures import FileStorage
from werkzeug.utils import secure_filename
from flaschengeist.models.image import Image
from flaschengeist.database import db
from flaschengeist.config import config
def check_mimetype(mime: str):
return mime in config["FILES"].get('allowed_mimetypes', [])
def send_image(id: int = None, image: Image = None):
if image is None:
image = Image.query.get(id)
if not image:
raise NotFound
return send_file(image.path_, mimetype=image.mimetype_, download_name=image.filename_)
def send_thumbnail(id: int = None, image: Image = None):
if image is None:
image = Image.query.get(id)
if not image:
raise NotFound
if not image.thumbnail_:
with PImage.open(image.open()) as im:
im.thumbnail(tuple(config["FILES"].get("thumbnail_size")))
s = image.path_.split('.')
s.insert(len(s)-1, 'thumbnail')
im.save('.'.join(s))
image.thumbnail_ = '.'.join(s)
db.session.commit()
return send_file(image.thumbnail_, mimetype=image.mimetype_, download_name=image.filename_)
def upload_image(file: FileStorage):
if not check_mimetype(file.mimetype):
raise UnprocessableEntity
path = Path(config["FILES"].get("data_path")) / str(date.today().year)
path.mkdir(mode=int('0700', 8), parents=True, exist_ok=True)
if file.filename.count('.') < 1:
name = secure_filename(file.filename + '.' + file.mimetype.split('/')[-1])
else:
name = secure_filename(file.filename)
img = Image(mimetype_=file.mimetype, filename_=name)
db.session.add(img)
db.session.flush()
try:
img.path_ = str((path / f"{img.id}.{img.filename_.split('.')[-1]}").resolve())
file.save(img.path_)
except:
db.session.delete(img)
raise
finally:
db.session.commit()
return img

View File

@ -7,7 +7,7 @@ auth = "auth_plain"
# Enable if you run flaschengeist behind a proxy, e.g. nginx + gunicorn # Enable if you run flaschengeist behind a proxy, e.g. nginx + gunicorn
#proxy = false #proxy = false
# Set root path, prefixes all routes # Set root path, prefixes all routes
#root = /api root = "/api"
# Set secret key # Set secret key
secret_key = "V3ryS3cr3t" secret_key = "V3ryS3cr3t"
# Domain used by frontend # Domain used by frontend
@ -21,10 +21,19 @@ level = "WARNING"
[DATABASE] [DATABASE]
# engine = "mysql" (default) # engine = "mysql" (default)
# user = "user"
# host = "127.0.0.1" [FILES]
# password = "password" # Path for file / image uploads
# database = "database" data_path = "./data"
# Thumbnail size
thumbnail_size = [192, 192]
# Accepted mimetypes
allowed_mimetypes = [
"image/avif",
"image/jpeg",
"image/png",
"image/webp"
]
[auth_plain] [auth_plain]
enabled = true enabled = true

View File

@ -0,0 +1,30 @@
from __future__ import annotations # TODO: Remove if python requirement is >= 3.10
from sqlalchemy import event
from pathlib import Path
from . import ModelSerializeMixin, Serial
from ..database import db
class Image(db.Model, ModelSerializeMixin):
__tablename__ = "image"
id: int = db.Column("id", Serial, primary_key=True)
filename_: str = db.Column(db.String(30), nullable=False)
mimetype_: str = db.Column(db.String(30), nullable=False)
thumbnail_: str = db.Column(db.String(127))
path_: str = db.Column(db.String(127))
def open(self):
return open(self.path_, "rb")
@event.listens_for(Image, 'before_delete')
def clear_file(mapper, connection, target: Image):
if target.path_:
p = Path(target.path_)
if p.exists():
p.unlink()
if target.thumbnail_:
p = Path(target.thumbnail_)
if p.exists():
p.unlink()

View File

@ -38,13 +38,13 @@ setup(
"sqlalchemy>=1.4", "sqlalchemy>=1.4",
"flask_sqlalchemy>=2.5", "flask_sqlalchemy>=2.5",
"flask_cors", "flask_cors",
"Pillow>=8.4.0",
"werkzeug", "werkzeug",
mysql_driver, mysql_driver,
], ],
extras_require={ extras_require={
"ldap": ["flask_ldapconn", "ldap3"], "ldap": ["flask_ldapconn", "ldap3"],
"argon": ["argon2-cffi"], "argon": ["argon2-cffi"],
"pricelist": ["pillow"],
"test": ["pytest", "coverage"], "test": ["pytest", "coverage"],
}, },
entry_points={ entry_points={
@ -59,7 +59,7 @@ setup(
"balance = flaschengeist.plugins.balance:BalancePlugin", "balance = flaschengeist.plugins.balance:BalancePlugin",
"events = flaschengeist.plugins.events:EventPlugin", "events = flaschengeist.plugins.events:EventPlugin",
"mail = flaschengeist.plugins.message_mail:MailMessagePlugin", "mail = flaschengeist.plugins.message_mail:MailMessagePlugin",
"pricelist = flaschengeist.plugins.pricelist:PriceListPlugin [pricelist]", "pricelist = flaschengeist.plugins.pricelist:PriceListPlugin",
], ],
}, },
cmdclass={ cmdclass={