Compare commits

..

No commits in common. "e3d0014e62e2b24871299b77c417cec3160a426a" and "a6fe921920c4d0e17938f62509f5d1fa9e905af3" have entirely different histories.

5 changed files with 124 additions and 47 deletions

View File

@ -2,11 +2,10 @@
from flask import Blueprint, jsonify, request, current_app from flask import Blueprint, jsonify, request, current_app
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
from werkzeug.exceptions import BadRequest, Forbidden, NotFound, Unauthorized from werkzeug.exceptions import BadRequest, Forbidden, Unauthorized
from flaschengeist import logger from flaschengeist import logger
from flaschengeist.controller import userController from flaschengeist.controller import userController
from flaschengeist.controller.imageController import send_image, send_thumbnail
from flaschengeist.plugins import Plugin from flaschengeist.plugins import Plugin
from flaschengeist.utils.decorators import login_required, extract_session from flaschengeist.utils.decorators import login_required, extract_session
from flaschengeist.utils.HTTP import no_content from flaschengeist.utils.HTTP import no_content
@ -710,7 +709,7 @@ def get_priclist_setting(userid, current_session):
return no_content() return no_content()
@PriceListPlugin.blueprint.route("/drinks/<int:identifier>/picture", methods=["POST" "DELETE"]) @PriceListPlugin.blueprint.route("/drinks/<int:identifier>/picture", methods=["POST", "GET", "DELETE"])
@login_required(permission=permissions.EDIT) @login_required(permission=permissions.EDIT)
def set_picture(identifier, current_session): def set_picture(identifier, current_session):
"""Get, Create, Delete Drink Picture """Get, Create, Delete Drink Picture
@ -732,24 +731,25 @@ def set_picture(identifier, current_session):
file = request.files.get("file") file = request.files.get("file")
if file: if file:
return jsonify(pricelist_controller.save_drink_picture(identifier, file)) picture = models._Picture()
picture.mimetype = file.content_type
picture.binary = bytearray(file.stream.read())
return jsonify(pricelist_controller.save_drink_picture(identifier, picture))
else: else:
raise BadRequest raise BadRequest
@PriceListPlugin.blueprint.route("/drinks/<int:identifier>/picture", methods=["GET"]) @PriceListPlugin.blueprint.route("/picture/<identifier>", methods=["GET"])
def _get_picture(identifier): def _get_picture(identifier):
"""Get Picture """Get Picture
Args: Args:
identifier: Identifier of Drink identifier: Identifier of Picture
Returns: Returns:
Picture or HTTP-error Picture or HTTP-error
""" """
drink = pricelist_controller.get_drink(identifier) if request.method == "GET":
if drink.has_image: size = request.args.get("size")
if request.args.get("thumbnail"): response = pricelist_controller.get_drink_picture(identifier, size)
return send_thumbnail(image=drink.image_) return response.make_conditional(request)
return send_image(image=drink.image_)
raise NotFound

View File

@ -1,21 +1,20 @@
from __future__ import annotations # TODO: Remove if python requirement is >= 3.10 from __future__ import annotations # TODO: Remove if python requirement is >= 3.10
from flaschengeist.database import db from flaschengeist.database import db
from flaschengeist.models import ModelSerializeMixin, Serial from flaschengeist.models import ModelSerializeMixin
from flaschengeist.models.image import Image
from typing import Optional from typing import Optional
drink_tag_association = db.Table( drink_tag_association = db.Table(
"drink_x_tag", "drink_x_tag",
db.Column("drink_id", Serial, db.ForeignKey("drink.id")), db.Column("drink_id", db.Integer, db.ForeignKey("drink.id")),
db.Column("tag_id", Serial, db.ForeignKey("drink_tag.id")), db.Column("tag_id", db.Integer, db.ForeignKey("drink_tag.id")),
) )
drink_type_association = db.Table( drink_type_association = db.Table(
"drink_x_type", "drink_x_type",
db.Column("drink_id", Serial, db.ForeignKey("drink.id")), db.Column("drink_id", db.Integer, db.ForeignKey("drink.id")),
db.Column("type_id", Serial, db.ForeignKey("drink_type.id")), db.Column("type_id", db.Integer, db.ForeignKey("drink_type.id")),
) )
@ -25,7 +24,7 @@ class Tag(db.Model, ModelSerializeMixin):
""" """
__tablename__ = "drink_tag" __tablename__ = "drink_tag"
id: int = db.Column("id", Serial, primary_key=True) id: int = db.Column("id", db.Integer, primary_key=True)
name: str = db.Column(db.String(30), nullable=False, unique=True) name: str = db.Column(db.String(30), nullable=False, unique=True)
color: str = db.Column(db.String(7), nullable=False) color: str = db.Column(db.String(7), nullable=False)
@ -36,7 +35,7 @@ class DrinkType(db.Model, ModelSerializeMixin):
""" """
__tablename__ = "drink_type" __tablename__ = "drink_type"
id: int = db.Column("id", Serial, primary_key=True) id: int = db.Column("id", db.Integer, primary_key=True)
name: str = db.Column(db.String(30), nullable=False, unique=True) name: str = db.Column(db.String(30), nullable=False, unique=True)
@ -46,9 +45,9 @@ class DrinkPrice(db.Model, ModelSerializeMixin):
""" """
__tablename__ = "drink_price" __tablename__ = "drink_price"
id: int = db.Column("id", Serial, primary_key=True) id: int = db.Column("id", db.Integer, primary_key=True)
price: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False)) price: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False))
volume_id_ = db.Column("volume_id", Serial, db.ForeignKey("drink_price_volume.id")) volume_id_ = db.Column("volume_id", db.Integer, db.ForeignKey("drink_price_volume.id"))
volume: "DrinkPriceVolume" = None volume: "DrinkPriceVolume" = None
_volume: "DrinkPriceVolume" = db.relationship("DrinkPriceVolume", back_populates="_prices", join_depth=1) _volume: "DrinkPriceVolume" = db.relationship("DrinkPriceVolume", back_populates="_prices", join_depth=1)
public: bool = db.Column(db.Boolean, default=True) public: bool = db.Column(db.Boolean, default=True)
@ -63,8 +62,8 @@ class ExtraIngredient(db.Model, ModelSerializeMixin):
ExtraIngredient ExtraIngredient
""" """
__tablename__ = "drink_extra_ingredient" __tablename__ = "extra_ingredient"
id: int = db.Column("id", Serial, primary_key=True) id: int = db.Column("id", db.Integer, primary_key=True)
name: str = db.Column(db.String(30), unique=True, nullable=False) name: str = db.Column(db.String(30), unique=True, nullable=False)
price: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False)) price: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False))
@ -75,9 +74,9 @@ class DrinkIngredient(db.Model, ModelSerializeMixin):
""" """
__tablename__ = "drink_ingredient" __tablename__ = "drink_ingredient"
id: int = db.Column("id", Serial, primary_key=True) id: int = db.Column("id", db.Integer, primary_key=True)
volume: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False), nullable=False) volume: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False), nullable=False)
ingredient_id: int = db.Column(Serial, db.ForeignKey("drink.id")) ingredient_id: int = db.Column(db.Integer, db.ForeignKey("drink.id"))
cost_per_volume: float cost_per_volume: float
name: str name: str
_drink_ingredient: Drink = db.relationship("Drink") _drink_ingredient: Drink = db.relationship("Drink")
@ -96,14 +95,14 @@ class Ingredient(db.Model, ModelSerializeMixin):
Ingredient Associationtable Ingredient Associationtable
""" """
__tablename__ = "drink_ingredient_association" __tablename__ = "ingredient_association"
id: int = db.Column("id", Serial, primary_key=True) id: int = db.Column("id", db.Integer, primary_key=True)
volume_id = db.Column(Serial, db.ForeignKey("drink_price_volume.id")) volume_id = db.Column(db.Integer, db.ForeignKey("drink_price_volume.id"))
drink_ingredient: Optional[DrinkIngredient] = db.relationship(DrinkIngredient) drink_ingredient: Optional[DrinkIngredient] = db.relationship(DrinkIngredient)
extra_ingredient: Optional[ExtraIngredient] = db.relationship(ExtraIngredient) extra_ingredient: Optional[ExtraIngredient] = db.relationship(ExtraIngredient)
_drink_ingredient_id = db.Column(Serial, db.ForeignKey("drink_ingredient.id")) _drink_ingredient_id = db.Column(db.Integer, db.ForeignKey("drink_ingredient.id"))
_extra_ingredient_id = db.Column(Serial, db.ForeignKey("drink_extra_ingredient.id")) _extra_ingredient_id = db.Column(db.Integer, db.ForeignKey("extra_ingredient.id"))
class MinPrices(ModelSerializeMixin): class MinPrices(ModelSerializeMixin):
@ -121,8 +120,8 @@ class DrinkPriceVolume(db.Model, ModelSerializeMixin):
""" """
__tablename__ = "drink_price_volume" __tablename__ = "drink_price_volume"
id: int = db.Column("id", Serial, primary_key=True) id: int = db.Column("id", db.Integer, primary_key=True)
drink_id = db.Column(Serial, db.ForeignKey("drink.id")) drink_id = db.Column(db.Integer, db.ForeignKey("drink.id"))
drink: "Drink" = None drink: "Drink" = None
_drink: "Drink" = db.relationship("Drink", back_populates="_volumes") _drink: "Drink" = db.relationship("Drink", back_populates="_volumes")
volume: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False)) volume: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False))
@ -144,21 +143,18 @@ class Drink(db.Model, ModelSerializeMixin):
""" """
__tablename__ = "drink" __tablename__ = "drink"
id: int = db.Column("id", Serial, primary_key=True) id: int = db.Column("id", db.Integer, primary_key=True)
article_id: Optional[str] = db.Column(db.String(64)) article_id: Optional[str] = db.Column(db.String(64))
package_size: Optional[int] = db.Column(db.Integer) package_size: Optional[int] = db.Column(db.Integer)
name: str = db.Column(db.String(60), nullable=False) name: str = db.Column(db.String(60), nullable=False)
volume: Optional[float] = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False)) 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_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)) 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)) receipt: Optional[list[str]] = db.Column(db.PickleType(protocol=4))
_type_id = db.Column("type_id", Serial, db.ForeignKey("drink_type.id")) _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") 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]) type: Optional[DrinkType] = db.relationship("DrinkType", foreign_keys=[_type_id])
@ -170,6 +166,9 @@ class Drink(db.Model, ModelSerializeMixin):
def __repr__(self): def __repr__(self):
return f"Drink({self.id},{self.name},{self.volumes})" return f"Drink({self.id},{self.name},{self.volumes})"
@property
def has_image(self): class _Picture:
return self.image_ is not None """Wrapper class for pictures binaries"""
mimetype = ""
binary = bytearray()

View File

@ -5,12 +5,12 @@ from uuid import uuid4
from flaschengeist import logger from flaschengeist import logger
from flaschengeist.config import config from flaschengeist.config import config
from flaschengeist.database import db from flaschengeist.database import db
from flaschengeist.utils.picture import save_picture, get_picture, delete_picture
from flaschengeist.utils.decorators import extract_session from flaschengeist.utils.decorators import extract_session
from .models import Drink, DrinkPrice, Ingredient, Tag, DrinkType, DrinkPriceVolume, DrinkIngredient, ExtraIngredient from .models import Drink, DrinkPrice, Ingredient, Tag, DrinkType, DrinkPriceVolume, DrinkIngredient, ExtraIngredient
from .permissions import EDIT_VOLUME, EDIT_PRICE, EDIT_INGREDIENTS_DRINK from .permissions import EDIT_VOLUME, EDIT_PRICE, EDIT_INGREDIENTS_DRINK
import flaschengeist.controller.imageController as image_controller
def update(): def update():
db.session.commit() db.session.commit()
@ -333,6 +333,9 @@ def set_volumes(volumes):
def delete_drink(identifier): def delete_drink(identifier):
drink = get_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.delete(drink)
db.session.commit() db.session.commit()
@ -531,13 +534,33 @@ def delete_extra_ingredient(identifier):
def save_drink_picture(identifier, file): def save_drink_picture(identifier, file):
drink = get_drink(identifier) drink = get_drink(identifier)
drink.image = image_controller.upload_image(file) old_uuid = None
if drink.uuid:
old_uuid = drink.uuid
drink.uuid = str(uuid4())
db.session.commit() 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 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): def delete_drink_picture(identifier):
drink = get_drink(identifier) drink = get_drink(identifier)
drink.image = None if drink.uuid:
db.session.commit() delete_picture(f"{config['pricelist']['path']}/{drink.uuid}")
return drink drink.uuid = None
db.session.commit()

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,55 @@
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