[controller] Add controller for handling uploading images
This commit is contained in:
parent
42e304cf5f
commit
a6fe921920
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
4
setup.py
4
setup.py
|
@ -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={
|
||||||
|
|
Loading…
Reference in New Issue