Compare commits
	
		
			2 Commits
		
	
	
		
			a6fe921920
			...
			e3d0014e62
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | e3d0014e62 | |
|  | a43441e0c5 | 
|  | @ -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/<int:identifier>/picture", methods=["POST", "GET", "DELETE"]) | ||||
| @PriceListPlugin.blueprint.route("/drinks/<int:identifier>/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/<identifier>", methods=["GET"]) | ||||
| @PriceListPlugin.blueprint.route("/drinks/<int:identifier>/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 | ||||
|  |  | |||
|  | @ -1,20 +1,21 @@ | |||
| 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 import ModelSerializeMixin, Serial | ||||
| from flaschengeist.models.image import Image | ||||
| 
 | ||||
| from typing import Optional | ||||
| 
 | ||||
| drink_tag_association = db.Table( | ||||
|     "drink_x_tag", | ||||
|     db.Column("drink_id", db.Integer, db.ForeignKey("drink.id")), | ||||
|     db.Column("tag_id", db.Integer, db.ForeignKey("drink_tag.id")), | ||||
|     db.Column("drink_id", Serial, db.ForeignKey("drink.id")), | ||||
|     db.Column("tag_id", Serial, db.ForeignKey("drink_tag.id")), | ||||
| ) | ||||
| 
 | ||||
| drink_type_association = db.Table( | ||||
|     "drink_x_type", | ||||
|     db.Column("drink_id", db.Integer, db.ForeignKey("drink.id")), | ||||
|     db.Column("type_id", db.Integer, db.ForeignKey("drink_type.id")), | ||||
|     db.Column("drink_id", Serial, db.ForeignKey("drink.id")), | ||||
|     db.Column("type_id", Serial, db.ForeignKey("drink_type.id")), | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -24,7 +25,7 @@ class Tag(db.Model, ModelSerializeMixin): | |||
|     """ | ||||
| 
 | ||||
|     __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) | ||||
|     color: str = db.Column(db.String(7), nullable=False) | ||||
| 
 | ||||
|  | @ -35,7 +36,7 @@ class DrinkType(db.Model, ModelSerializeMixin): | |||
|     """ | ||||
| 
 | ||||
|     __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) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -45,9 +46,9 @@ class DrinkPrice(db.Model, ModelSerializeMixin): | |||
|     """ | ||||
| 
 | ||||
|     __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)) | ||||
|     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" = db.relationship("DrinkPriceVolume", back_populates="_prices", join_depth=1) | ||||
|     public: bool = db.Column(db.Boolean, default=True) | ||||
|  | @ -62,8 +63,8 @@ class ExtraIngredient(db.Model, ModelSerializeMixin): | |||
|     ExtraIngredient | ||||
|     """ | ||||
| 
 | ||||
|     __tablename__ = "extra_ingredient" | ||||
|     id: int = db.Column("id", db.Integer, primary_key=True) | ||||
|     __tablename__ = "drink_extra_ingredient" | ||||
|     id: int = db.Column("id", Serial, primary_key=True) | ||||
|     name: str = db.Column(db.String(30), unique=True, nullable=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" | ||||
|     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) | ||||
|     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 | ||||
|     name: str | ||||
|     _drink_ingredient: Drink = db.relationship("Drink") | ||||
|  | @ -95,14 +96,14 @@ class Ingredient(db.Model, ModelSerializeMixin): | |||
|     Ingredient Associationtable | ||||
|     """ | ||||
| 
 | ||||
|     __tablename__ = "ingredient_association" | ||||
|     id: int = db.Column("id", db.Integer, primary_key=True) | ||||
|     volume_id = db.Column(db.Integer, db.ForeignKey("drink_price_volume.id")) | ||||
|     __tablename__ = "drink_ingredient_association" | ||||
|     id: int = db.Column("id", Serial, primary_key=True) | ||||
|     volume_id = db.Column(Serial, db.ForeignKey("drink_price_volume.id")) | ||||
|     drink_ingredient: Optional[DrinkIngredient] = db.relationship(DrinkIngredient) | ||||
|     extra_ingredient: Optional[ExtraIngredient] = db.relationship(ExtraIngredient) | ||||
| 
 | ||||
|     _drink_ingredient_id = db.Column(db.Integer, db.ForeignKey("drink_ingredient.id")) | ||||
|     _extra_ingredient_id = db.Column(db.Integer, db.ForeignKey("extra_ingredient.id")) | ||||
|     _drink_ingredient_id = db.Column(Serial, db.ForeignKey("drink_ingredient.id")) | ||||
|     _extra_ingredient_id = db.Column(Serial, db.ForeignKey("drink_extra_ingredient.id")) | ||||
| 
 | ||||
| 
 | ||||
| class MinPrices(ModelSerializeMixin): | ||||
|  | @ -120,8 +121,8 @@ class DrinkPriceVolume(db.Model, ModelSerializeMixin): | |||
|     """ | ||||
| 
 | ||||
|     __tablename__ = "drink_price_volume" | ||||
|     id: int = db.Column("id", db.Integer, primary_key=True) | ||||
|     drink_id = db.Column(db.Integer, db.ForeignKey("drink.id")) | ||||
|     id: int = db.Column("id", Serial, primary_key=True) | ||||
|     drink_id = db.Column(Serial, db.ForeignKey("drink.id")) | ||||
|     drink: "Drink" = None | ||||
|     _drink: "Drink" = db.relationship("Drink", back_populates="_volumes") | ||||
|     volume: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False)) | ||||
|  | @ -143,18 +144,21 @@ class Drink(db.Model, ModelSerializeMixin): | |||
|     """ | ||||
| 
 | ||||
|     __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)) | ||||
|     package_size: Optional[int] = db.Column(db.Integer) | ||||
|     name: str = db.Column(db.String(60), nullable=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_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")) | ||||
|     _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") | ||||
|     type: Optional[DrinkType] = db.relationship("DrinkType", foreign_keys=[_type_id]) | ||||
|  | @ -166,9 +170,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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
										
											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