diff --git a/flaschengeist/plugins/pricelist/__init__.py b/flaschengeist/plugins/pricelist/__init__.py index afe21e4..7423d1e 100644 --- a/flaschengeist/plugins/pricelist/__init__.py +++ b/flaschengeist/plugins/pricelist/__init__.py @@ -2,10 +2,11 @@ from flask import Blueprint, jsonify, request, current_app from werkzeug.local import LocalProxy -from werkzeug.exceptions import BadRequest, Forbidden, Unauthorized +from werkzeug.exceptions import BadRequest, Forbidden, NotFound, Unauthorized from flaschengeist import logger from flaschengeist.controller import userController +from flaschengeist.controller.imageController import send_image, send_thumbnail from flaschengeist.plugins import Plugin from flaschengeist.utils.decorators import login_required, extract_session from flaschengeist.utils.HTTP import no_content @@ -709,7 +710,7 @@ def get_priclist_setting(userid, current_session): return no_content() -@PriceListPlugin.blueprint.route("/drinks//picture", methods=["POST", "GET", "DELETE"]) +@PriceListPlugin.blueprint.route("/drinks//picture", methods=["POST" "DELETE"]) @login_required(permission=permissions.EDIT) def set_picture(identifier, current_session): """Get, Create, Delete Drink Picture @@ -731,25 +732,24 @@ def set_picture(identifier, current_session): file = request.files.get("file") if file: - picture = models._Picture() - picture.mimetype = file.content_type - picture.binary = bytearray(file.stream.read()) - return jsonify(pricelist_controller.save_drink_picture(identifier, picture)) + return jsonify(pricelist_controller.save_drink_picture(identifier, file)) else: raise BadRequest -@PriceListPlugin.blueprint.route("/picture/", methods=["GET"]) +@PriceListPlugin.blueprint.route("/drinks//picture", methods=["GET"]) def _get_picture(identifier): """Get Picture Args: - identifier: Identifier of Picture + identifier: Identifier of Drink Returns: Picture or HTTP-error """ - if request.method == "GET": - size = request.args.get("size") - response = pricelist_controller.get_drink_picture(identifier, size) - return response.make_conditional(request) + drink = pricelist_controller.get_drink(identifier) + if drink.has_image: + if request.args.get("thumbnail"): + return send_thumbnail(image=drink.image_) + return send_image(image=drink.image_) + raise NotFound diff --git a/flaschengeist/plugins/pricelist/models.py b/flaschengeist/plugins/pricelist/models.py index 43cf792..be36247 100644 --- a/flaschengeist/plugins/pricelist/models.py +++ b/flaschengeist/plugins/pricelist/models.py @@ -1,7 +1,7 @@ from __future__ import annotations # TODO: Remove if python requirement is >= 3.10 from flaschengeist.database import db -from flaschengeist.models import ModelSerializeMixin +from flaschengeist.models.image import Image from typing import Optional @@ -150,11 +150,14 @@ class Drink(db.Model, ModelSerializeMixin): volume: Optional[float] = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False)) cost_per_volume: Optional[float] = db.Column(db.Numeric(precision=5, scale=3, asdecimal=False)) cost_per_package: Optional[float] = db.Column(db.Numeric(precision=5, scale=3, asdecimal=False)) + has_image: bool = False uuid: str = db.Column(db.String(36)) receipt: Optional[list[str]] = db.Column(db.PickleType(protocol=4)) - _type_id = db.Column("type_id", db.Integer, db.ForeignKey("drink_type.id")) + _image_id = db.Column("image_id", Serial, db.ForeignKey("image.id")) + + image_: Image = db.relationship("Image", cascade="all, delete", foreign_keys=[_image_id]) tags: Optional[list[Tag]] = db.relationship("Tag", secondary=drink_tag_association, cascade="save-update, merge") type: Optional[DrinkType] = db.relationship("DrinkType", foreign_keys=[_type_id]) @@ -166,9 +169,6 @@ class Drink(db.Model, ModelSerializeMixin): def __repr__(self): return f"Drink({self.id},{self.name},{self.volumes})" - -class _Picture: - """Wrapper class for pictures binaries""" - - mimetype = "" - binary = bytearray() + @property + def has_image(self): + return self.image_ is not None diff --git a/flaschengeist/plugins/pricelist/pricelist_controller.py b/flaschengeist/plugins/pricelist/pricelist_controller.py index 136711c..69b011c 100644 --- a/flaschengeist/plugins/pricelist/pricelist_controller.py +++ b/flaschengeist/plugins/pricelist/pricelist_controller.py @@ -5,12 +5,12 @@ from uuid import uuid4 from flaschengeist import logger from flaschengeist.config import config from flaschengeist.database import db -from flaschengeist.utils.picture import save_picture, get_picture, delete_picture from flaschengeist.utils.decorators import extract_session from .models import Drink, DrinkPrice, Ingredient, Tag, DrinkType, DrinkPriceVolume, DrinkIngredient, ExtraIngredient from .permissions import EDIT_VOLUME, EDIT_PRICE, EDIT_INGREDIENTS_DRINK +import flaschengeist.controller.imageController as image_controller def update(): db.session.commit() @@ -333,9 +333,6 @@ def set_volumes(volumes): def delete_drink(identifier): drink = get_drink(identifier) - if drink.uuid: - path = config["pricelist"]["path"] - delete_picture(f"{path}/{drink.uuid}") db.session.delete(drink) db.session.commit() @@ -534,33 +531,13 @@ def delete_extra_ingredient(identifier): def save_drink_picture(identifier, file): drink = get_drink(identifier) - old_uuid = None - if drink.uuid: - old_uuid = drink.uuid - drink.uuid = str(uuid4()) + drink.image = image_controller.upload_image(file) db.session.commit() - path = config["pricelist"]["path"] - save_picture(file, f"{path}/{drink.uuid}") - if old_uuid: - delete_picture(f"{path}/{old_uuid}") return drink -def get_drink_picture(identifier, size=None): - path = config["pricelist"]["path"] - drink = None - if isinstance(identifier, int): - drink = get_drink(identifier) - if isinstance(identifier, str): - drink = Drink.query.filter(Drink.uuid == identifier).one_or_none() - if drink: - return get_picture(f"{path}/{drink.uuid}", size) - raise FileNotFoundError - - def delete_drink_picture(identifier): drink = get_drink(identifier) - if drink.uuid: - delete_picture(f"{config['pricelist']['path']}/{drink.uuid}") - drink.uuid = None - db.session.commit() + drink.image = None + db.session.commit() + return drink diff --git a/flaschengeist/utils/no-image.png b/flaschengeist/utils/no-image.png deleted file mode 100644 index 240507b..0000000 Binary files a/flaschengeist/utils/no-image.png and /dev/null differ diff --git a/flaschengeist/utils/picture.py b/flaschengeist/utils/picture.py deleted file mode 100644 index 3f2dc40..0000000 --- a/flaschengeist/utils/picture.py +++ /dev/null @@ -1,55 +0,0 @@ -import os, sys, shutil, io -from PIL import Image -from flask import Response -from werkzeug.exceptions import BadRequest -from ..utils.HTTP import no_content - -thumbnail_sizes = ((32, 32), (64, 64), (128, 128), (256, 256), (512, 512)) - - -def save_picture(picture, path): - - if not picture.mimetype.startswith("image/"): - raise BadRequest - os.makedirs(path, exist_ok=True) - file_type = picture.mimetype.replace("image/", "") - filename = f"{path}/drink" - with open(f"{filename}.{file_type}", "wb") as file: - file.write(picture.binary) - image = Image.open(f"{filename}.{file_type}") - if file_type != "png": - image.save(f"{filename}.png", "PNG") - os.remove(f"{filename}.{file_type}") - for thumbnail_size in thumbnail_sizes: - work_image = image.copy() - work_image.thumbnail(thumbnail_size) - work_image.save(f"{filename}-{thumbnail_size[0]}.png", "PNG") - - -def get_picture(path, size=None): - try: - if size: - if os.path.isfile(f"{path}/drink-{size}.png"): - with open(f"{path}/drink-{size}.png", "rb") as file: - image = file.read() - else: - _image = Image.open(f"{path}/drink.png") - _image.thumbnail((int(size), int(size))) - with io.BytesIO() as file: - _image.save(file, format="PNG") - image = file.getvalue() - else: - with open(f"{path}/drink.png", "rb") as file: - image = file.read() - response = Response(image, mimetype="image/png") - response.add_etag() - return response - except: - raise FileNotFoundError - - -def delete_picture(path): - try: - shutil.rmtree(path) - except FileNotFoundError: - pass