Merge branch 'pluginify' of groeger-clan.duckdns.org:newgeruecht into pluginify

This commit is contained in:
Ferdinand Thiessen 2021-03-19 02:04:04 +01:00
commit d6475604e9
15 changed files with 567 additions and 168 deletions

View File

@ -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):

View File

@ -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

View File

@ -1,3 +1,5 @@
from __future__ import annotations # TODO: Remove if python requirement is >= 3.10
from ..database import db

View File

@ -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(

View File

@ -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:

View File

@ -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"):

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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]"
)

View File

@ -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):

View File

@ -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/

View File

@ -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]

View File

@ -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={