Merge branch 'pluginify' of groeger-clan.duckdns.org:newgeruecht into pluginify
This commit is contained in:
commit
d6475604e9
|
@ -14,8 +14,10 @@ class ModelSerializeMixin:
|
|||
return False
|
||||
|
||||
import typing
|
||||
if typing.get_origin(self.__class__.__annotations__[param]) is typing.Union and \
|
||||
typing.get_args(self.__class__.__annotations__[param])[1] is None:
|
||||
|
||||
if typing.get_origin(self.__class__.__annotations__[param]) is typing.Union and typing.get_args(
|
||||
self.__class__.__annotations__[param]
|
||||
)[1] is type(None):
|
||||
return getattr(self, param) is None
|
||||
|
||||
def serialize(self):
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations # TODO: Remove if python requirement is >= 3.10
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from . import ModelSerializeMixin, UtcDateTime
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations # TODO: Remove if python requirement is >= 3.10
|
||||
|
||||
from ..database import db
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations # TODO: Remove if python requirement is >= 3.10
|
||||
|
||||
from flask import url_for
|
||||
from typing import Optional
|
||||
from datetime import date, datetime
|
||||
|
@ -6,6 +8,7 @@ from sqlalchemy.orm.collections import attribute_mapped_collection
|
|||
from ..database import db
|
||||
from . import ModelSerializeMixin, UtcDateTime
|
||||
|
||||
|
||||
association_table = db.Table(
|
||||
"user_x_role",
|
||||
db.Column("user_id", db.Integer, db.ForeignKey("user.id")),
|
||||
|
@ -30,7 +33,7 @@ class Role(db.Model, ModelSerializeMixin):
|
|||
__tablename__ = "role"
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
name: str = db.Column(db.String(30), unique=True)
|
||||
permissions: [Permission] = db.relationship("Permission", secondary=role_permission_association_table)
|
||||
permissions: list[Permission] = db.relationship("Permission", secondary=role_permission_association_table)
|
||||
|
||||
|
||||
class User(db.Model, ModelSerializeMixin):
|
||||
|
@ -55,11 +58,11 @@ class User(db.Model, ModelSerializeMixin):
|
|||
lastname: str = db.Column(db.String(50), nullable=False)
|
||||
mail: str = db.Column(db.String(60), nullable=False)
|
||||
birthday: Optional[date] = db.Column(db.Date)
|
||||
roles: [str] = []
|
||||
permissions: Optional[type([str])] = None
|
||||
roles: list[str] = []
|
||||
permissions: Optional[list[str]] = None
|
||||
avatar_url: Optional[str] = ""
|
||||
|
||||
roles_: [Role] = db.relationship("Role", secondary=association_table, cascade="save-update, merge")
|
||||
roles_: list[Role] = db.relationship("Role", secondary=association_table, cascade="save-update, merge")
|
||||
_id = db.Column("id", db.Integer, primary_key=True)
|
||||
_sessions = db.relationship("Session", back_populates="_user")
|
||||
_attributes = db.relationship(
|
||||
|
|
|
@ -62,9 +62,11 @@ class Plugin:
|
|||
Value stored in database (native python)
|
||||
"""
|
||||
try:
|
||||
setting = _PluginSetting.query\
|
||||
.filter(_PluginSetting.plugin == self._plugin_name)\
|
||||
.filter(_PluginSetting.name == name).one()
|
||||
setting = (
|
||||
_PluginSetting.query.filter(_PluginSetting.plugin == self._plugin_name)
|
||||
.filter(_PluginSetting.name == name)
|
||||
.one()
|
||||
)
|
||||
return setting.value
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
if "default" in kwargs:
|
||||
|
@ -78,9 +80,11 @@ class Plugin:
|
|||
name: String identifying the setting
|
||||
value: Value to be stored
|
||||
"""
|
||||
setting = _PluginSetting.query \
|
||||
.filter(_PluginSetting.plugin == self._plugin_name) \
|
||||
.filter(_PluginSetting.name == name).one_or_none()
|
||||
setting = (
|
||||
_PluginSetting.query.filter(_PluginSetting.plugin == self._plugin_name)
|
||||
.filter(_PluginSetting.name == name)
|
||||
.one_or_none()
|
||||
)
|
||||
if setting is not None:
|
||||
setting.value = value
|
||||
else:
|
||||
|
|
|
@ -23,10 +23,12 @@ class AuthPlain(AuthPlugin):
|
|||
self.modify_user(admin, None, "admin")
|
||||
db.session.add(admin)
|
||||
db.session.commit()
|
||||
logger.warning("New administrator user was added, please change the password or remove it before going into"
|
||||
"production mode. Initial credentials:\n"
|
||||
"name: admin\n"
|
||||
"password: admin")
|
||||
logger.warning(
|
||||
"New administrator user was added, please change the password or remove it before going into"
|
||||
"production mode. Initial credentials:\n"
|
||||
"name: admin\n"
|
||||
"password: admin"
|
||||
)
|
||||
|
||||
def login(self, user: User, password: str):
|
||||
if user.has_attribute("password"):
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations # TODO: Remove if python requirement is >= 3.10
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
|
||||
from flaschengeist.database import db
|
||||
|
|
|
@ -5,10 +5,13 @@ from http.client import NO_CONTENT
|
|||
|
||||
from flaschengeist.plugins import Plugin
|
||||
from flaschengeist.utils.decorators import login_required
|
||||
from werkzeug.exceptions import BadRequest
|
||||
from werkzeug.exceptions import BadRequest, Forbidden
|
||||
|
||||
from . import models
|
||||
from . import pricelist_controller, permissions
|
||||
from ...controller import userController
|
||||
from ...models.session import Session
|
||||
from ...utils.HTTP import no_content
|
||||
|
||||
pricelist_bp = Blueprint("pricelist", __name__, url_prefix="/pricelist")
|
||||
|
||||
|
@ -120,8 +123,135 @@ def search_drinks(name):
|
|||
@login_required(permission=permissions.CREATE)
|
||||
def create_drink(current_session):
|
||||
data = request.get_json()
|
||||
if not all(item in data for item in ["name", "volume", "cost_price"]) or not all(
|
||||
item in data for item in ["name", "ingredients"]
|
||||
):
|
||||
raise BadRequest("No correct Keys to create drink")
|
||||
return jsonify(pricelist_controller.create_drink(data))
|
||||
return jsonify(pricelist_controller.set_drink(data))
|
||||
|
||||
|
||||
@pricelist_bp.route("/drinks/<int:identifier>", methods=["PUT"])
|
||||
def update_drink(identifier):
|
||||
data = request.get_json()
|
||||
return jsonify(pricelist_controller.update_drink(identifier, data))
|
||||
|
||||
|
||||
@pricelist_bp.route("/drinks/<int:identifier>", methods=["DELETE"])
|
||||
def delete_drink(identifier):
|
||||
pricelist_controller.delete_drink(identifier)
|
||||
return "", NO_CONTENT
|
||||
|
||||
|
||||
@pricelist_bp.route("/prices", methods=["GET"])
|
||||
@pricelist_bp.route("/prices/<int:identifier>", methods=["GET"])
|
||||
def get_prices(identifier=None):
|
||||
if identifier:
|
||||
result = pricelist_controller.get_price(identifier)
|
||||
else:
|
||||
result = pricelist_controller.get_prices()
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@pricelist_bp.route("/volumes/<int:identifier>/prices", methods=["POST"])
|
||||
def create_price(identifier):
|
||||
data = request.get_json()
|
||||
return jsonify(pricelist_controller.set_price(identifier, data))
|
||||
|
||||
|
||||
@pricelist_bp.route("/prices/<int:identifier>", methods=["PUT"])
|
||||
def modify_price(identifier):
|
||||
data = request.get_json()
|
||||
return jsonify(pricelist_controller.update_price(identifier, data))
|
||||
|
||||
|
||||
@pricelist_bp.route("/prices/<int:identifier>", methods=["DELETE"])
|
||||
def delete_price(identifier):
|
||||
pricelist_controller.delete_price(identifier)
|
||||
return "", NO_CONTENT
|
||||
|
||||
|
||||
@pricelist_bp.route("/drinks/<int:identifier>/volumes", methods=["POST"])
|
||||
def set_volume(identifier):
|
||||
data = request.get_json()
|
||||
return jsonify(pricelist_controller.set_volume(identifier, data))
|
||||
|
||||
|
||||
@pricelist_bp.route("/volumes/<int:identifier>/ingredients", methods=["POST"])
|
||||
def set_ingredient(identifier):
|
||||
data = request.get_json()
|
||||
return jsonify(pricelist_controller.set_ingredient(data, identifier))
|
||||
|
||||
|
||||
@pricelist_bp.route("/volumes/<int:identifier>", methods=["PUT"])
|
||||
def update_volume(identifier):
|
||||
data = request.get_json()
|
||||
return jsonify(pricelist_controller.update_volume(identifier, data))
|
||||
|
||||
|
||||
@pricelist_bp.route("/volumes/<int:identifier>", methods=["DELETE"])
|
||||
def delete_volume(identifier):
|
||||
pricelist_controller.delete_volume(identifier)
|
||||
return "", NO_CONTENT
|
||||
|
||||
|
||||
@pricelist_bp.route("/ingredients/extraIngredients", methods=["GET"])
|
||||
def get_extraIngredients():
|
||||
return jsonify(pricelist_controller.get_extra_ingredients())
|
||||
|
||||
|
||||
@pricelist_bp.route("/ingredients/<int:identifier>", methods=["PUT"])
|
||||
def update_ingredient(identifier):
|
||||
data = request.get_json()
|
||||
return jsonify(pricelist_controller.update_ingredient(identifier, data))
|
||||
|
||||
|
||||
@pricelist_bp.route("/ingredients/<int:identifier>", methods=["DELETE"])
|
||||
def delete_ingredient(identifier):
|
||||
pricelist_controller.delete_ingredient(identifier)
|
||||
return "", NO_CONTENT
|
||||
|
||||
|
||||
@pricelist_bp.route("/ingredients/extraIngredients", methods=["POST"])
|
||||
def set_extra_ingredient():
|
||||
data = request.get_json()
|
||||
return jsonify(pricelist_controller.set_extra_ingredient(data))
|
||||
|
||||
|
||||
@pricelist_bp.route("/ingredients/extraIngredients/<int:identifier>", methods=["PUT"])
|
||||
def update_extra_ingredient(identifier):
|
||||
data = request.get_json()
|
||||
return jsonify(pricelist_controller.update_extra_ingredient(identifier, data))
|
||||
|
||||
|
||||
@pricelist_bp.route("/ingredients/extraIngredients/<int:identifier>", methods=["DELETE"])
|
||||
def delete_extra_ingredient(identifier):
|
||||
pricelist_controller.delete_extra_ingredient(identifier)
|
||||
return "", NO_CONTENT
|
||||
|
||||
|
||||
@pricelist_bp.route("/users/<userid>/pricecalc_columns", methods=["GET", "PUT"])
|
||||
@login_required()
|
||||
def get_columns(userid, current_session: Session):
|
||||
"""Get pricecalc_columns of an user
|
||||
|
||||
Route: ``/users/<userid>/pricelist/pricecac_columns`` | Method: ``GET`` or ``PUT``
|
||||
POST-data: On ``PUT`` json encoded array of floats
|
||||
|
||||
Args:
|
||||
userid: Userid identifying the user
|
||||
current_session: Session sent with Authorization Header
|
||||
|
||||
Returns:
|
||||
GET: JSON object containing the shortcuts as float array or HTTP error
|
||||
PUT: HTTP-created or HTTP error
|
||||
"""
|
||||
if userid != current_session._user.userid:
|
||||
raise Forbidden
|
||||
|
||||
user = userController.get_user(userid)
|
||||
if request.method == "GET":
|
||||
return jsonify(user.get_attribute("pricecalc_columns", []))
|
||||
else:
|
||||
data = request.get_json()
|
||||
if not isinstance(data, list) or not all(isinstance(n, str) for n in data):
|
||||
raise BadRequest
|
||||
data.sort(reverse=True)
|
||||
user.set_attribute("pricecalc_columns", data)
|
||||
userController.persist()
|
||||
return no_content()
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
from __future__ import annotations # TODO: Remove if python requirement is >= 3.10
|
||||
|
||||
from flaschengeist.database import db
|
||||
from flaschengeist.models import ModelSerializeMixin
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Union
|
||||
|
||||
drink_tag_association = db.Table(
|
||||
"drink_x_tag",
|
||||
|
@ -43,28 +45,82 @@ class DrinkPrice(db.Model, ModelSerializeMixin):
|
|||
|
||||
__tablename__ = "drink_price"
|
||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
||||
volume: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False), nullable=False)
|
||||
price: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False))
|
||||
drink_id = db.Column("drink_id", db.Integer, db.ForeignKey("drink.id"))
|
||||
drink = db.relationship("Drink", back_populates="prices")
|
||||
no_auto: bool = db.Column(db.Boolean, default=False)
|
||||
volume_id_ = db.Column("volume_id", db.Integer, db.ForeignKey("drink_price_volume.id"))
|
||||
volume = db.relationship("DrinkPriceVolume", back_populates="prices")
|
||||
public: bool = db.Column(db.Boolean, default=True)
|
||||
description: Optional[str] = db.Column(db.String(30))
|
||||
round_step: float = db.Column(db.Numeric(precision=3, scale=2, asdecimal=False), nullable=False, default=0.5)
|
||||
|
||||
|
||||
class Ingredient(db.Model, ModelSerializeMixin):
|
||||
class ExtraIngredient(db.Model, ModelSerializeMixin):
|
||||
"""
|
||||
Drink Build
|
||||
ExtraIngredient
|
||||
"""
|
||||
|
||||
__tablename__ = "extra_ingredient"
|
||||
id: int = db.Column("id", db.Integer, 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))
|
||||
|
||||
|
||||
class DrinkIngredient(db.Model, ModelSerializeMixin):
|
||||
"""
|
||||
Drink Ingredient
|
||||
"""
|
||||
|
||||
__tablename__ = "drink_ingredient"
|
||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
||||
volume: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False), nullable=False)
|
||||
drink_parent_id: int = db.Column("drink_parent_id", db.Integer, db.ForeignKey("drink.id"))
|
||||
drink_parent = db.relationship("Drink", foreign_keys=drink_parent_id)
|
||||
drink_ingredient_id: int = db.Column("drink_ingredient_id", db.Integer, db.ForeignKey("drink.id"))
|
||||
drink_ingredient: "Drink" = db.relationship("Drink", foreign_keys=drink_ingredient_id)
|
||||
# drink_ingredient: Drink = db.relationship("Drink")
|
||||
# price: float = 0
|
||||
|
||||
|
||||
# @property
|
||||
# def price(self):
|
||||
# try:
|
||||
# return self.drink_ingredient.cost_price_pro_volume * self.volume
|
||||
# except AttributeError:
|
||||
# pass
|
||||
|
||||
|
||||
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"))
|
||||
drink_ingredient_id = db.Column(db.Integer, db.ForeignKey("drink_ingredient.id"))
|
||||
drink_ingredient: Optional[DrinkIngredient] = db.relationship(DrinkIngredient)
|
||||
extra_ingredient_id = db.Column(db.Integer, db.ForeignKey("extra_ingredient.id"))
|
||||
extra_ingredient: Optional[ExtraIngredient] = db.relationship(ExtraIngredient)
|
||||
|
||||
|
||||
class MinPrices(ModelSerializeMixin):
|
||||
"""
|
||||
MinPrices
|
||||
"""
|
||||
|
||||
percentage: float
|
||||
price: float
|
||||
|
||||
|
||||
class DrinkPriceVolume(db.Model, ModelSerializeMixin):
|
||||
"""
|
||||
Drink Volumes and Prices
|
||||
"""
|
||||
|
||||
__tablename__ = "drink_price_volume"
|
||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
||||
drink_id = db.Column(db.Integer, db.ForeignKey("drink.id"), nullable=False)
|
||||
volume: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False))
|
||||
min_prices: list[MinPrices] = []
|
||||
# ingredients: list[Ingredient] = []
|
||||
|
||||
prices: list[DrinkPrice] = db.relationship(DrinkPrice, back_populates="volume", cascade="all,delete,delete-orphan")
|
||||
ingredients: list[Ingredient] = db.relationship("Ingredient", foreign_keys=Ingredient.volume_id)
|
||||
|
||||
|
||||
class Drink(db.Model, ModelSerializeMixin):
|
||||
|
@ -74,17 +130,15 @@ class Drink(db.Model, ModelSerializeMixin):
|
|||
|
||||
__tablename__ = "drink"
|
||||
id: int = db.Column("id", db.Integer, 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: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False))
|
||||
cost_price: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False))
|
||||
discount: float = db.Column(db.Numeric(precision=3, scale=2, asdecimal=False), nullable=False)
|
||||
extra_charge: Optional[float] = db.Column(db.Numeric(precision=3, scale=2, asdecimal=False), default=0)
|
||||
prices: [DrinkPrice] = db.relationship(
|
||||
"DrinkPrice", back_populates="drink", cascade="all,delete,delete-orphan", order_by=[DrinkPrice.volume]
|
||||
)
|
||||
ingredients: [Ingredient] = db.relationship(
|
||||
"Ingredient", back_populates="drink_parent", foreign_keys=Ingredient.drink_parent_id
|
||||
)
|
||||
tags: [Optional[Tag]] = db.relationship("Tag", secondary=drink_tag_association, cascade="save-update, merge")
|
||||
type_id_ = db.Column("type_id", db.Integer, db.ForeignKey("drink_type.id"))
|
||||
type = db.relationship("DrinkType")
|
||||
volume: Optional[float] = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False))
|
||||
cost_price_pro_volume: Optional[float] = db.Column(db.Numeric(precision=5, scale=3, asdecimal=False))
|
||||
cost_price_package_netto: Optional[float] = db.Column(db.Numeric(precision=5, scale=3, asdecimal=False))
|
||||
|
||||
_type_id = db.Column("type_id", db.Integer, db.ForeignKey("drink_type.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])
|
||||
volumes: list[DrinkPriceVolume] = db.relationship(DrinkPriceVolume)
|
||||
|
|
|
@ -3,7 +3,7 @@ from sqlalchemy.exc import IntegrityError
|
|||
|
||||
from flaschengeist import logger
|
||||
from flaschengeist.database import db
|
||||
from .models import Drink, DrinkPrice, Ingredient, Tag, DrinkType
|
||||
from .models import Drink, DrinkPrice, Ingredient, Tag, DrinkType, DrinkPriceVolume, DrinkIngredient, ExtraIngredient
|
||||
|
||||
from math import ceil
|
||||
|
||||
|
@ -103,56 +103,6 @@ def delete_drink_type(identifier):
|
|||
raise BadRequest("DrinkType still in use")
|
||||
|
||||
|
||||
def round_price(price, round_step):
|
||||
return round(ceil(float(price) / round_step) * round_step * 100) / 100
|
||||
|
||||
|
||||
def calc_prices(drink, prices):
|
||||
retVal = []
|
||||
if len(drink.ingredients) > 0:
|
||||
return calc_price_by_ingredients(drink, prices)
|
||||
allowed_keys = DrinkPrice().serialize().keys()
|
||||
for price in prices:
|
||||
values = {key: value for key, value in price.items() if key in allowed_keys}
|
||||
if values.get("no_auto"):
|
||||
retVal.append(DrinkPrice(**values))
|
||||
else:
|
||||
volume = float(values.pop("volume"))
|
||||
if "price" in values:
|
||||
values.pop("price")
|
||||
_price = float(drink.cost_price) / float(drink.volume) * volume
|
||||
_price += _price * float(drink.discount)
|
||||
if drink.extra_charge:
|
||||
_price += float(drink.extra_charge)
|
||||
_price = round_price(_price, float(price.get("round_step")))
|
||||
retVal.append(DrinkPrice(volume=volume, price=_price, **values))
|
||||
return retVal
|
||||
|
||||
|
||||
def calc_price_by_ingredients(drink, prices):
|
||||
allowed_keys = DrinkPrice().serialize().keys()
|
||||
retVal = []
|
||||
for price in prices:
|
||||
values = {key: value for key, value in price.items() if key in allowed_keys}
|
||||
if values.get("no_auto"):
|
||||
retVal.append(DrinkPrice(**values))
|
||||
else:
|
||||
volume = float(values.pop("volume"))
|
||||
if "price" in values:
|
||||
values.pop("price")
|
||||
_price = 0
|
||||
for ingredient in drink.ingredients:
|
||||
_price = (
|
||||
float(ingredient.drink_ingredient.cost_price)
|
||||
/ float(ingredient.drink_ingredient.volume)
|
||||
* float(ingredient.volume)
|
||||
)
|
||||
_price += _price * float(drink.discount) + float(drink.extra_charge)
|
||||
_price = round_price(_price, price.get("round_step"))
|
||||
retVal.append(DrinkPrice(volume=volume, price=_price, **values))
|
||||
return retVal
|
||||
|
||||
|
||||
def get_drinks(name=None):
|
||||
if name:
|
||||
return Drink.query.filter(Drink.name.contains(name)).all()
|
||||
|
@ -161,49 +111,264 @@ def get_drinks(name=None):
|
|||
|
||||
def get_drink(identifier):
|
||||
if isinstance(identifier, int):
|
||||
retVal = Drink.query.get(identifier)
|
||||
return Drink.query.get(identifier)
|
||||
elif isinstance(identifier, str):
|
||||
retVal = Drink.query.filter(Tag.name == identifier).one_or_none()
|
||||
return Drink.query.filter(Tag.name == identifier).one_or_none()
|
||||
else:
|
||||
logger.debug("Invalid identifier type for Drink")
|
||||
raise BadRequest
|
||||
if not retVal:
|
||||
raise NotFound
|
||||
return retVal
|
||||
raise NotFound
|
||||
|
||||
|
||||
def add_prices(drink, prices):
|
||||
for price in prices:
|
||||
drink.prices.append(price)
|
||||
|
||||
|
||||
def add_ingredients(drink, ingredients):
|
||||
for identifier, volume in ingredients:
|
||||
ingredient = Ingredient(volume=volume, drink_ingredient=get_drink(identifier))
|
||||
drink.ingredients.append(ingredient)
|
||||
|
||||
|
||||
def create_drink(data):
|
||||
allowed_keys = Drink().serialize().keys()
|
||||
values = {key: value for key, value in data.items() if key in allowed_keys}
|
||||
prices = values.pop("prices", [])
|
||||
ingredients = values.pop("ingredients", [])
|
||||
if "id" in values:
|
||||
values.pop("id")
|
||||
|
||||
def set_drink(data):
|
||||
allowedKeys = Drink().serialize().keys()
|
||||
if "id" in data:
|
||||
data.pop("id")
|
||||
values = {key: value if value != "" else None for key, value in data.items() if key in allowedKeys}
|
||||
if "volumes" in values:
|
||||
values.pop("volumes")
|
||||
if "tags" in values:
|
||||
values.pop("tags")
|
||||
if "type" in values:
|
||||
_type = values.pop("type")
|
||||
if isinstance(_type, dict) and "id" in _type:
|
||||
type = get_drink_type(_type.get("id"))
|
||||
drink = Drink(**values)
|
||||
add_ingredients(drink, ingredients)
|
||||
drink.prices = calc_prices(drink, prices)
|
||||
if type:
|
||||
drink.type = type
|
||||
db.session.add(drink)
|
||||
update()
|
||||
db.session.commit()
|
||||
return drink
|
||||
|
||||
|
||||
def update_drink(identifier, data):
|
||||
allowedKeys = Drink().serialize().keys()
|
||||
if "id" in data:
|
||||
data.pop("id")
|
||||
values = {key: value for key, value in data.items() if key in allowedKeys}
|
||||
if "volumes" in values:
|
||||
values.pop("volumes")
|
||||
if "tags" in values:
|
||||
values.pop("tags")
|
||||
if "type" in values:
|
||||
_type = values.pop("type")
|
||||
if isinstance(_type, dict) and "id" in _type:
|
||||
type = get_drink_type(_type.get("id"))
|
||||
drink = get_drink(identifier)
|
||||
for key, value in values.items():
|
||||
setattr(drink, key, value if value != "" else None)
|
||||
if type:
|
||||
drink.type = type
|
||||
db.session.commit()
|
||||
return drink
|
||||
|
||||
|
||||
def delete_drink(identifier):
|
||||
drink = get_drink(identifier)
|
||||
for price in drink.prices:
|
||||
db.session.delete(price)
|
||||
for ingredient in drink.ingredients:
|
||||
db.session.delete(ingredient)
|
||||
db.session.delete(drink)
|
||||
update()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def get_volume(identifier):
|
||||
return DrinkPriceVolume.query.get(identifier)
|
||||
|
||||
|
||||
def get_volumes(drink_id=None):
|
||||
if drink_id:
|
||||
return DrinkPriceVolume.query.filter(DrinkPriceVolume.drink_id == drink_id).all()
|
||||
return DrinkPriceVolume.query.all()
|
||||
|
||||
|
||||
def set_volume(identifier, data):
|
||||
allowed_keys = DrinkPriceVolume().serialize().keys()
|
||||
if "id" in data:
|
||||
data.pop("id")
|
||||
values = {key: value for key, value in data.items() if key in allowed_keys}
|
||||
if "prices" in values:
|
||||
prices = values.pop("prices")
|
||||
if "ingredients" in values:
|
||||
ingredients = values.pop("ingredients")
|
||||
volume = DrinkPriceVolume(**values)
|
||||
drink = get_drink(identifier)
|
||||
if not drink:
|
||||
raise BadRequest
|
||||
drink.volumes.append(volume)
|
||||
db.session.add(volume)
|
||||
db.session.commit()
|
||||
return volume
|
||||
|
||||
|
||||
def update_volume(identifier, data):
|
||||
allowed_keys = DrinkPriceVolume().serialize().keys()
|
||||
if "id" in data:
|
||||
data.pop("id")
|
||||
values = {key: value for key, value in data.items() if key in allowed_keys}
|
||||
if "prices" in values:
|
||||
prices = values.pop("prices")
|
||||
if "ingredients" in values:
|
||||
ingredients = values.pop("ingredients")
|
||||
volume = get_volume(identifier)
|
||||
for key, value in values.items():
|
||||
setattr(volume, key, value if value != "" else None)
|
||||
db.session.commit()
|
||||
return volume
|
||||
|
||||
|
||||
def delete_volume(identifier):
|
||||
volume = get_volume(identifier)
|
||||
db.session.delete(volume)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def get_price(identifier):
|
||||
if isinstance(identifier, int):
|
||||
return DrinkPrice.query.get(identifier)
|
||||
raise NotFound
|
||||
|
||||
|
||||
def get_prices(volume_id=None):
|
||||
if volume_id:
|
||||
return DrinkPrice.query.filter(DrinkPrice.volume_id_ == volume_id).all()
|
||||
return DrinkPrice.query.all()
|
||||
|
||||
|
||||
def set_price(identifier, data):
|
||||
allowed_keys = DrinkPrice().serialize().keys()
|
||||
if "id" in data:
|
||||
data.pop("id")
|
||||
values = {key: value for key, value in data.items() if key in allowed_keys}
|
||||
price = DrinkPrice(**values)
|
||||
volume = get_volume(identifier)
|
||||
if not volume:
|
||||
raise BadRequest
|
||||
volume.prices.append(price)
|
||||
db.session.add(price)
|
||||
db.session.commit()
|
||||
return price
|
||||
|
||||
|
||||
def update_price(identifier, data):
|
||||
allowed_keys = DrinkPrice().serialize().keys()
|
||||
if "id" in data:
|
||||
data.pop("id")
|
||||
values = {key: value for key, value in data.items() if key in allowed_keys}
|
||||
price = get_price(identifier)
|
||||
for key, value in values.items():
|
||||
setattr(price, key, value)
|
||||
db.session.commit()
|
||||
return price
|
||||
|
||||
|
||||
def delete_price(identifier):
|
||||
price = get_price(identifier)
|
||||
db.session.delete(price)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def set_drink_ingredient(data):
|
||||
allowedKeys = DrinkIngredient().serialize().keys()
|
||||
if "id" in data:
|
||||
data.pop("id")
|
||||
drink = None
|
||||
if "drink_ingredient" in data:
|
||||
drink_ingredient_ = data.pop("drink_ingredient")
|
||||
if "id" in drink_ingredient_:
|
||||
drink = get_drink(drink_ingredient_.get("id"))
|
||||
values = {key: value for key, value in data.items() if key in allowedKeys}
|
||||
if "price" in values:
|
||||
values.pop("price")
|
||||
drink_ingredient = DrinkIngredient(**values)
|
||||
if drink:
|
||||
drink_ingredient.drink_ingredient = drink
|
||||
db.session.add(drink_ingredient)
|
||||
db.session.commit()
|
||||
return drink_ingredient
|
||||
|
||||
|
||||
def get_ingredient(identifier):
|
||||
return Ingredient.query.get(identifier)
|
||||
|
||||
|
||||
def set_ingredient(data, volume_id):
|
||||
allowedKeys = Ingredient().serialize().keys()
|
||||
if "id" in data:
|
||||
data.pop("id")
|
||||
values = {key: value for key, value in data.items() if key in allowedKeys}
|
||||
drink_ingredient_value = None
|
||||
extra_ingredient_value = None
|
||||
if "drink_ingredient" in values:
|
||||
drink_ingredient_value = values.pop("drink_ingredient")
|
||||
if "extra_ingredient" in values:
|
||||
extra_ingredient_value = values.pop("extra_ingredient")
|
||||
ingredient = Ingredient(**values)
|
||||
volume = get_volume(volume_id)
|
||||
if drink_ingredient_value:
|
||||
ingredient.drink_ingredient = set_drink_ingredient(drink_ingredient_value)
|
||||
if extra_ingredient_value:
|
||||
if "id" in extra_ingredient_value:
|
||||
ingredient.extra_ingredient = get_extra_ingredient(extra_ingredient_value.get("id"))
|
||||
|
||||
volume.ingredients.append(ingredient)
|
||||
db.session.add(ingredient)
|
||||
db.session.commit()
|
||||
return ingredient
|
||||
|
||||
|
||||
def update_ingredient(identifier, data):
|
||||
ingredient = get_ingredient(identifier)
|
||||
if "extra_ingredient" in data and isinstance(data.get("extra_ingredient"), dict):
|
||||
if "id" in data.get("extra_ingredient"):
|
||||
ingredient.extra_ingredient = get_extra_ingredient(data.get("extra_ingredient").get("id"))
|
||||
if "drink_ingredient" in data and ingredient.drink_ingredient:
|
||||
if data.get("drink_ingredient").get("drink_ingredient_id") > -1:
|
||||
ingredient.drink_ingredient.drink_ingredient_id = data.get("drink_ingredient").get("drink_ingredient_id")
|
||||
if "volume" in data.get("drink_ingredient"):
|
||||
ingredient.drink_ingredient.volume = data.get("drink_ingredient").get("volume")
|
||||
db.session.commit()
|
||||
return ingredient
|
||||
|
||||
|
||||
def delete_ingredient(identifier):
|
||||
ingredient = get_ingredient(identifier)
|
||||
if ingredient.drink_ingredient:
|
||||
db.session.delete(ingredient.drink_ingredient)
|
||||
db.session.delete(ingredient)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def get_extra_ingredients():
|
||||
return ExtraIngredient.query.all()
|
||||
|
||||
|
||||
def get_extra_ingredient(identifier):
|
||||
return ExtraIngredient.query.get(identifier)
|
||||
|
||||
|
||||
def set_extra_ingredient(data):
|
||||
allowedKeys = ExtraIngredient().serialize().keys()
|
||||
if "id" in data:
|
||||
data.pop("id")
|
||||
values = {key: value for key, value in data.items() if key in allowedKeys}
|
||||
extra_ingredient = ExtraIngredient(**values)
|
||||
db.session.add(extra_ingredient)
|
||||
db.session.commit()
|
||||
return extra_ingredient
|
||||
|
||||
|
||||
def update_extra_ingredient(identifier, data):
|
||||
allowedKeys = ExtraIngredient().serialize().keys()
|
||||
if "id" in data:
|
||||
data.pop("id")
|
||||
values = {key: value for key, value in data.items() if key in allowedKeys}
|
||||
extra_ingredient = get_extra_ingredient(identifier)
|
||||
if extra_ingredient:
|
||||
for key, value in data.items:
|
||||
setattr(extra_ingredient, key, value)
|
||||
db.session.commit()
|
||||
return extra_ingredient
|
||||
|
||||
|
||||
def delete_extra_ingredient(identifier):
|
||||
extra_ingredient = get_extra_ingredient(identifier)
|
||||
db.session.delete(extra_ingredient)
|
||||
db.session.commit()
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations # TODO: Remove if python requirement is >= 3.10
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
|
@ -55,7 +57,7 @@ class Job(db.Model, ModelSerializeMixin):
|
|||
end: Optional[datetime] = db.Column(UtcDateTime)
|
||||
comment: str = db.Column(db.String(256))
|
||||
type: JobType = db.relationship("JobType")
|
||||
services: [Service] = db.relationship("Service", back_populates="job_")
|
||||
services: list[Service] = db.relationship("Service", back_populates="job_")
|
||||
required_services: float = db.Column(db.Numeric(precision=4, scale=2, asdecimal=False), nullable=False)
|
||||
|
||||
event_ = db.relationship("Event", back_populates="jobs")
|
||||
|
@ -81,6 +83,6 @@ class Event(db.Model, ModelSerializeMixin):
|
|||
end: datetime = db.Column(UtcDateTime)
|
||||
description: Optional[str] = db.Column(db.String(255))
|
||||
type: EventType = db.relationship("EventType")
|
||||
jobs: [Job] = db.relationship(
|
||||
jobs: list[Job] = db.relationship(
|
||||
"Job", back_populates="event_", cascade="all,delete,delete-orphan", order_by="[Job.start, Job.end]"
|
||||
)
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import datetime
|
||||
from backports.datetime_fromisoformat import MonkeyPatch
|
||||
import sys
|
||||
|
||||
MonkeyPatch.patch_fromisoformat()
|
||||
if sys.version_info < (3, 7):
|
||||
from backports.datetime_fromisoformat import MonkeyPatch
|
||||
|
||||
MonkeyPatch.patch_fromisoformat()
|
||||
|
||||
|
||||
def from_iso_format(date_str):
|
||||
|
|
|
@ -17,8 +17,10 @@ You will also need a MySQL driver, recommended drivers are
|
|||
- `mysqlclient`
|
||||
- `PyMySQL`
|
||||
|
||||
`setup.py` will try to install a matching driver.
|
||||
|
||||
#### Windows
|
||||
Same as above, but for mysql you have to follow this guide:
|
||||
Same as above, but if you want to use `mysqlclient` instead of `PyMySQL` (performance?) you have to follow this guide:
|
||||
|
||||
https://www.radishlogic.com/coding/python-3/installing-mysqldb-for-python-3-in-windows/
|
||||
|
||||
|
|
|
@ -1,33 +1,42 @@
|
|||
#!/usr/bin/python3
|
||||
from __future__ import annotations # TODO: Remove if python requirement is >= 3.10
|
||||
import inspect
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
import pkg_resources
|
||||
|
||||
from flaschengeist.config import config
|
||||
|
||||
|
||||
class PrefixMiddleware(object):
|
||||
|
||||
def __init__(self, app, prefix=''):
|
||||
def __init__(self, app, prefix=""):
|
||||
self.app = app
|
||||
self.prefix = prefix
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
|
||||
if environ['PATH_INFO'].startswith(self.prefix):
|
||||
environ['PATH_INFO'] = environ['PATH_INFO'][len(self.prefix):]
|
||||
environ['SCRIPT_NAME'] = self.prefix
|
||||
if environ["PATH_INFO"].startswith(self.prefix):
|
||||
environ["PATH_INFO"] = environ["PATH_INFO"][len(self.prefix) :]
|
||||
environ["SCRIPT_NAME"] = self.prefix
|
||||
return self.app(environ, start_response)
|
||||
else:
|
||||
start_response('404', [('Content-Type', 'text/plain')])
|
||||
start_response("404", [("Content-Type", "text/plain")])
|
||||
return ["This url does not belong to the app.".encode()]
|
||||
|
||||
|
||||
class InterfaceGenerator:
|
||||
known = []
|
||||
classes = {}
|
||||
mapper = {"str": "string", "int": "number", "float": "number", "date": "Date", "datetime": "Date", "NoneType": "null"}
|
||||
mapper = {
|
||||
"str": "string",
|
||||
"int": "number",
|
||||
"float": "number",
|
||||
"date": "Date",
|
||||
"datetime": "Date",
|
||||
"NoneType": "null",
|
||||
"bool": "boolean",
|
||||
}
|
||||
|
||||
def __init__(self, namespace, filename):
|
||||
self.basename = ""
|
||||
|
@ -36,30 +45,43 @@ class InterfaceGenerator:
|
|||
self.this_type = None
|
||||
|
||||
def pytype(self, cls):
|
||||
if isinstance(cls, list):
|
||||
return "", "Array<{}>".format(self.pytype(cls[0])[1])
|
||||
if sys.version_info >= (3, 8):
|
||||
import typing
|
||||
a = self._pytype(cls)
|
||||
print(f"{cls} -> {a}")
|
||||
return a
|
||||
|
||||
if isinstance(cls, typing.ForwardRef):
|
||||
return "", "this" if cls.__forward_arg__ == self.this_type else cls.__forward_arg__
|
||||
if typing.get_origin(cls) == typing.Union:
|
||||
types = typing.get_args(cls)
|
||||
if len(types) == 2 and types[-1] is type(None):
|
||||
return "?", self.pytype(types[0])[1]
|
||||
else:
|
||||
return "", "|".join([self.pytype(pt)[1] for pt in types])
|
||||
if hasattr(cls, "__name__"):
|
||||
if cls.__name__ in self.mapper:
|
||||
return "", self.mapper[cls.__name__]
|
||||
def _pytype(self, cls):
|
||||
import typing
|
||||
|
||||
origin = typing.get_origin(cls)
|
||||
arguments = typing.get_args(cls)
|
||||
|
||||
if origin is typing.ForwardRef: # isinstance(cls, typing.ForwardRef):
|
||||
return "", "this" if cls.__forward_arg__ == self.this_type else cls.__forward_arg__
|
||||
if origin is typing.Union:
|
||||
print(f"A1: {arguments[1]}")
|
||||
if len(arguments) == 2 and arguments[1] is type(None):
|
||||
return "?", self.pytype(arguments[0])[1]
|
||||
else:
|
||||
return "", cls.__name__
|
||||
return "", "|".join([self.pytype(pt)[1] for pt in arguments])
|
||||
if origin is list:
|
||||
return "", "Array<{}>".format("|".join([self.pytype(a_type)[1] for a_type in arguments]))
|
||||
|
||||
name = cls.__name__ if hasattr(cls, "__name__") else cls if isinstance(cls, str) else None
|
||||
if name is not None:
|
||||
if name in self.mapper:
|
||||
return "", self.mapper[name]
|
||||
else:
|
||||
return "", name
|
||||
print(
|
||||
"WARNING: This python version might not detect all types (try >= 3.8). Could not identify >{}<".format(cls)
|
||||
"WARNING: This python version might not detect all types (try >= 3.9). Could not identify >{}<".format(cls)
|
||||
)
|
||||
return "?", "any"
|
||||
|
||||
def walker(self, module):
|
||||
if sys.version_info < (3, 9):
|
||||
raise RuntimeError("Python >= 3.9 is required to export API")
|
||||
import typing
|
||||
|
||||
if (
|
||||
inspect.ismodule(module[1])
|
||||
and module[1].__name__.startswith(self.basename)
|
||||
|
@ -76,11 +98,13 @@ class InterfaceGenerator:
|
|||
and hasattr(module[1], "__annotations__")
|
||||
):
|
||||
self.this_type = module[0]
|
||||
d = {
|
||||
param: self.pytype(ptype)
|
||||
for param, ptype in module[1].__annotations__.items()
|
||||
if not param.startswith("_") and not param.endswith("_")
|
||||
}
|
||||
print("\n\n" + module[0] + "\n")
|
||||
d = {}
|
||||
for param, ptype in typing.get_type_hints(module[1], globalns=None, localns=None).items():
|
||||
if not param.startswith("_") and not param.endswith("_"):
|
||||
print(f"{param} ::: {ptype}")
|
||||
d[param] = self.pytype(ptype)
|
||||
|
||||
if len(d) == 1:
|
||||
key, value = d.popitem()
|
||||
self.classes[module[0]] = value[1]
|
||||
|
|
11
setup.py
11
setup.py
|
@ -1,4 +1,7 @@
|
|||
from setuptools import setup, find_packages
|
||||
import os
|
||||
|
||||
mysql_driver = "PyMySQL" if os.name == "nt" else "mysqlclient"
|
||||
|
||||
setup(
|
||||
name="flaschengeist",
|
||||
|
@ -10,16 +13,16 @@ setup(
|
|||
packages=find_packages(),
|
||||
package_data={"": ["*.toml"]},
|
||||
scripts=["run_flaschengeist"],
|
||||
python_requires=">=3.6",
|
||||
python_requires=">=3.7",
|
||||
install_requires=[
|
||||
"Flask >= 1.1",
|
||||
"toml",
|
||||
"sqlalchemy>=1.3",
|
||||
# < 1.4: https://github.com/pallets/flask-sqlalchemy/issues/885
|
||||
"sqlalchemy>=1.3,<1.4",
|
||||
"flask_sqlalchemy",
|
||||
"flask_cors",
|
||||
"werkzeug",
|
||||
# Needed for python < 3.7
|
||||
"backports-datetime-fromisoformat",
|
||||
mysql_driver,
|
||||
],
|
||||
extras_require={"ldap": ["flask_ldapconn", "ldap3"], "test": ["pytest", "coverage"]},
|
||||
entry_points={
|
||||
|
|
Loading…
Reference in New Issue