Compare commits
2 Commits
a6fe921920
...
e3d0014e62
Author | SHA1 | Date |
---|---|---|
Ferdinand Thiessen | e3d0014e62 | |
Ferdinand Thiessen | a43441e0c5 |
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
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, Unauthorized
|
from werkzeug.exceptions import BadRequest, Forbidden, NotFound, 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
|
||||||
|
@ -709,7 +710,7 @@ def get_priclist_setting(userid, current_session):
|
||||||
return no_content()
|
return no_content()
|
||||||
|
|
||||||
|
|
||||||
@PriceListPlugin.blueprint.route("/drinks/<int:identifier>/picture", methods=["POST", "GET", "DELETE"])
|
@PriceListPlugin.blueprint.route("/drinks/<int:identifier>/picture", methods=["POST" "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
|
||||||
|
@ -731,25 +732,24 @@ def set_picture(identifier, current_session):
|
||||||
|
|
||||||
file = request.files.get("file")
|
file = request.files.get("file")
|
||||||
if file:
|
if file:
|
||||||
picture = models._Picture()
|
return jsonify(pricelist_controller.save_drink_picture(identifier, file))
|
||||||
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("/picture/<identifier>", methods=["GET"])
|
@PriceListPlugin.blueprint.route("/drinks/<int:identifier>/picture", methods=["GET"])
|
||||||
def _get_picture(identifier):
|
def _get_picture(identifier):
|
||||||
"""Get Picture
|
"""Get Picture
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
identifier: Identifier of Picture
|
identifier: Identifier of Drink
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Picture or HTTP-error
|
Picture or HTTP-error
|
||||||
"""
|
"""
|
||||||
if request.method == "GET":
|
drink = pricelist_controller.get_drink(identifier)
|
||||||
size = request.args.get("size")
|
if drink.has_image:
|
||||||
response = pricelist_controller.get_drink_picture(identifier, size)
|
if request.args.get("thumbnail"):
|
||||||
return response.make_conditional(request)
|
return send_thumbnail(image=drink.image_)
|
||||||
|
return send_image(image=drink.image_)
|
||||||
|
raise NotFound
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
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
|
from flaschengeist.models import ModelSerializeMixin, Serial
|
||||||
|
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", db.Integer, db.ForeignKey("drink.id")),
|
db.Column("drink_id", Serial, db.ForeignKey("drink.id")),
|
||||||
db.Column("tag_id", db.Integer, db.ForeignKey("drink_tag.id")),
|
db.Column("tag_id", Serial, 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", db.Integer, db.ForeignKey("drink.id")),
|
db.Column("drink_id", Serial, db.ForeignKey("drink.id")),
|
||||||
db.Column("type_id", db.Integer, db.ForeignKey("drink_type.id")),
|
db.Column("type_id", Serial, db.ForeignKey("drink_type.id")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ class Tag(db.Model, ModelSerializeMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "drink_tag"
|
__tablename__ = "drink_tag"
|
||||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
id: int = db.Column("id", Serial, 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)
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ class DrinkType(db.Model, ModelSerializeMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "drink_type"
|
__tablename__ = "drink_type"
|
||||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
id: int = db.Column("id", Serial, 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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,9 +46,9 @@ class DrinkPrice(db.Model, ModelSerializeMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "drink_price"
|
__tablename__ = "drink_price"
|
||||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
id: int = db.Column("id", Serial, 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", db.Integer, db.ForeignKey("drink_price_volume.id"))
|
volume_id_ = db.Column("volume_id", Serial, 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)
|
||||||
|
@ -62,8 +63,8 @@ class ExtraIngredient(db.Model, ModelSerializeMixin):
|
||||||
ExtraIngredient
|
ExtraIngredient
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "extra_ingredient"
|
__tablename__ = "drink_extra_ingredient"
|
||||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
id: int = db.Column("id", Serial, 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))
|
||||||
|
|
||||||
|
@ -74,9 +75,9 @@ class DrinkIngredient(db.Model, ModelSerializeMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "drink_ingredient"
|
__tablename__ = "drink_ingredient"
|
||||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
id: int = db.Column("id", Serial, 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(db.Integer, db.ForeignKey("drink.id"))
|
ingredient_id: int = db.Column(Serial, 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")
|
||||||
|
@ -95,14 +96,14 @@ class Ingredient(db.Model, ModelSerializeMixin):
|
||||||
Ingredient Associationtable
|
Ingredient Associationtable
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "ingredient_association"
|
__tablename__ = "drink_ingredient_association"
|
||||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
id: int = db.Column("id", Serial, primary_key=True)
|
||||||
volume_id = db.Column(db.Integer, db.ForeignKey("drink_price_volume.id"))
|
volume_id = db.Column(Serial, 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(db.Integer, db.ForeignKey("drink_ingredient.id"))
|
_drink_ingredient_id = db.Column(Serial, db.ForeignKey("drink_ingredient.id"))
|
||||||
_extra_ingredient_id = db.Column(db.Integer, db.ForeignKey("extra_ingredient.id"))
|
_extra_ingredient_id = db.Column(Serial, db.ForeignKey("drink_extra_ingredient.id"))
|
||||||
|
|
||||||
|
|
||||||
class MinPrices(ModelSerializeMixin):
|
class MinPrices(ModelSerializeMixin):
|
||||||
|
@ -120,8 +121,8 @@ class DrinkPriceVolume(db.Model, ModelSerializeMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "drink_price_volume"
|
__tablename__ = "drink_price_volume"
|
||||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
id: int = db.Column("id", Serial, primary_key=True)
|
||||||
drink_id = db.Column(db.Integer, db.ForeignKey("drink.id"))
|
drink_id = db.Column(Serial, 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))
|
||||||
|
@ -143,18 +144,21 @@ class Drink(db.Model, ModelSerializeMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "drink"
|
__tablename__ = "drink"
|
||||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
id: int = db.Column("id", Serial, 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", db.Integer, db.ForeignKey("drink_type.id"))
|
_type_id = db.Column("type_id", Serial, 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])
|
||||||
|
@ -166,9 +170,6 @@ 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
|
||||||
class _Picture:
|
def has_image(self):
|
||||||
"""Wrapper class for pictures binaries"""
|
return self.image_ is not None
|
||||||
|
|
||||||
mimetype = ""
|
|
||||||
binary = bytearray()
|
|
||||||
|
|
|
@ -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,9 +333,6 @@ 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()
|
||||||
|
|
||||||
|
@ -534,33 +531,13 @@ 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)
|
||||||
old_uuid = None
|
drink.image = image_controller.upload_image(file)
|
||||||
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)
|
||||||
if drink.uuid:
|
drink.image = None
|
||||||
delete_picture(f"{config['pricelist']['path']}/{drink.uuid}")
|
db.session.commit()
|
||||||
drink.uuid = None
|
return drink
|
||||||
db.session.commit()
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 46 KiB |
|
@ -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
|
|
Loading…
Reference in New Issue