Compare commits

..

8 Commits

Author SHA1 Message Date
Ferdinand Thiessen 44a7abab82 fix(app): Skip plugins with not satisfied dependencies.
ci/woodpecker/push/lint Pipeline was successful Details
ci/woodpecker/push/test Pipeline was successful Details
ci/woodpecker/pr/lint Pipeline was successful Details
ci/woodpecker/pr/test Pipeline was successful Details
2022-02-22 23:48:15 +01:00
Ferdinand Thiessen c161541b46 fix(plugins): Fix plugin version for plugin list API endpoint 2022-02-22 23:46:13 +01:00
Ferdinand Thiessen b03fe136be feat(cli): Added CLI command for handling plugins
* Install / Uninstall plugins
* List plugins
2022-02-22 23:46:13 +01:00
Ferdinand Thiessen 5c1fc19ca8 feat(plugins): Identify plugins by id, migrations must be provided at defined location, add utils for plugin functions 2022-02-22 23:46:13 +01:00
Ferdinand Thiessen 216b88c529 feat(docs): Add documentation on how to install tables
Also various documentation fixed and improvments
2022-02-22 23:45:59 +01:00
Ferdinand Thiessen cfb4776a3c feat(db): Add migrations support to plugins
* Add initial migrations for core Flaschengeist
* Add migrations to balance
* Add migrations to pricelist
2022-02-22 23:45:23 +01:00
Ferdinand Thiessen f1df5076ed fix(db): Add __repr__ to custom column types, same as done by SQLAlchemy 2022-02-22 23:24:14 +01:00
Ferdinand Thiessen 35a2f25e71 feat(db): Add database migration support, implements #19
Migrations allow us to keep track of database changes and upgrading databases if needed.
2022-02-22 23:24:14 +01:00
11 changed files with 133 additions and 129 deletions

View File

@ -4,13 +4,14 @@ from flask import Flask
from flask_cors import CORS from flask_cors import CORS
from datetime import datetime, date from datetime import datetime, date
from flask.json import JSONEncoder, jsonify from flask.json import JSONEncoder, jsonify
from importlib_metadata import entry_points
from sqlalchemy.exc import OperationalError from sqlalchemy.exc import OperationalError
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
from flaschengeist import logger from flaschengeist import logger
from flaschengeist.utils.hook import Hook from flaschengeist.utils.hook import Hook
from flaschengeist.plugins import AuthPlugin from flaschengeist.plugins import AuthPlugin, Plugin
from flaschengeist.utils.plugin import get_plugins
from flaschengeist.controller import roleController
from flaschengeist.config import config, configure_app from flaschengeist.config import config, configure_app
@ -37,38 +38,29 @@ class CustomJSONEncoder(JSONEncoder):
@Hook("plugins.loaded") @Hook("plugins.loaded")
def load_plugins(app: Flask): def load_plugins(app: Flask):
app.config["FG_PLUGINS"] = {} def load_plugin(cls: type[Plugin]):
logger.debug(f"Load plugin {cls.id}")
for entry_point in entry_points(group="flaschengeist.plugins"): # Initialize plugin with config section
logger.debug(f"Found plugin: {entry_point.name} ({entry_point.dist.version})") plugin = cls(config.get(plugin_class.id, config.get(plugin_class.id.split(".")[-1], {})))
# Register blueprint if provided
if entry_point.name == config["FLASCHENGEIST"]["auth"] or ( if plugin.blueprint is not None:
entry_point.name in config and config[entry_point.name].get("enabled", False)
):
logger.debug(f"Load plugin {entry_point.name}")
try:
plugin = entry_point.load()(entry_point, config=config.get(entry_point.name, {}))
if hasattr(plugin, "blueprint") and plugin.blueprint is not None:
app.register_blueprint(plugin.blueprint) app.register_blueprint(plugin.blueprint)
except: # Save plugin application context
logger.error( app.config.setdefault("FG_PLUGINS", {})[plugin.id] = plugin
f"Plugin {entry_point.name} was enabled, but could not be loaded due to an error.", return plugin
exc_info=True,
) for plugin_class in get_plugins():
continue names = [plugin_class.id, plugin_class.id.split(".")[-1]]
if isinstance(plugin, AuthPlugin): if config["FLASCHENGEIST"]["auth"] in names:
if entry_point.name != config["FLASCHENGEIST"]["auth"]: # Load authentification plugin
logger.debug(f"Unload not configured AuthPlugin {entry_point.name}") app.config["FG_AUTH_BACKEND"] = load_plugin(plugin_class)
del plugin logger.info(f"Using authentication plugin: {plugin_class.id}")
continue elif any([i in config and config[i].get("enabled", False) for i in names]):
# Load all other enabled plugins
load_plugin(plugin_class)
logger.info(f"Using plugin: {plugin_class.id}")
else: else:
logger.info(f"Using authentication plugin: {entry_point.name}") logger.debug(f"Skip disabled plugin {plugin_class.id}")
app.config["FG_AUTH_BACKEND"] = plugin
else:
logger.info(f"Using plugin: {entry_point.name}")
app.config["FG_PLUGINS"][entry_point.name] = plugin
else:
logger.debug(f"Skip disabled plugin {entry_point.name}")
if "FG_AUTH_BACKEND" not in app.config: if "FG_AUTH_BACKEND" not in app.config:
logger.fatal("No authentication plugin configured or authentication plugin not found") logger.fatal("No authentication plugin configured or authentication plugin not found")
raise RuntimeError("No authentication plugin configured or authentication plugin not found") raise RuntimeError("No authentication plugin configured or authentication plugin not found")

View File

@ -24,11 +24,7 @@ For more information, please refer to
- `flaschengeist.utils.hook.HookAfter` - `flaschengeist.utils.hook.HookAfter`
""" """
from importlib_metadata import Distribution, EntryPoint
from werkzeug.exceptions import MethodNotAllowed, NotFound from werkzeug.exceptions import MethodNotAllowed, NotFound
from werkzeug.datastructures import FileStorage
from flaschengeist.models.user import _Avatar, User
from flaschengeist.utils.hook import HookBefore, HookAfter from flaschengeist.utils.hook import HookBefore, HookAfter
__all__ = [ __all__ = [
@ -92,47 +88,54 @@ Passed args:
class Plugin: class Plugin:
"""Base class for all Plugins """Base class for all Plugins
All plugins must derived from this class. All plugins must be derived from this class.
There are some static properties a plugin must provide,
and some properties a plugin can provide if you might want
to use more functionality.
Required:
- *id*: Unique identifier of your plugin
Optional: Optional:
- *blueprint*: `flask.Blueprint` providing your routes - *blueprint*: `flask.Blueprint` providing your routes
- *permissions*: List of your custom permissions - *permissions*: List of your custom permissions
- *models*: Your models, used for API export - *models*: Your models, used for API export
- *version*: Version of your plugin, can also be guessed by Flaschengeist
""" """
name: str id: str = None
"""Name of the plugin, loaded from EntryPoint""" """Override with the unique ID of the plugin
version: str Hint: Use a fully qualified name like "dev.flaschengeist.plugin"
"""Version of the plugin, loaded from Distribution""" """
dist: Distribution
"""Distribution of this plugin"""
blueprint = None blueprint = None
"""Optional `flask.blueprint` if the plugin uses custom routes""" """Override with a `flask.blueprint` if the plugin uses custom routes"""
permissions: list[str] = [] permissions: list[str] = [] # You have to override
"""Optional list of custom permissions used by the plugin """Override to add custom permissions used by the plugin
A good style is to name the permissions with a prefix related to the plugin name, A good style is to name the permissions with a prefix related to the plugin name,
to prevent clashes with other plugins. E. g. instead of *delete* use *plugin_delete*. to prevent clashes with other plugins. E. g. instead of *delete* use *plugin_delete*.
""" """
models = None models = None
"""Optional module containing the SQLAlchemy models used by the plugin""" """Override with models module
migrations_path = None Used for API export, has to be a static property
"""Optional location of the path to migration files, required if custome db tables are used""" """
def __init__(self, entry_point: EntryPoint, config=None): version = None
"""Override with a custom version, optional
If not set, the version is guessed from the package / distribution
"""
def __init__(self, config=None):
"""Constructor called by create_app """Constructor called by create_app
Args: Args:
config: Dict configuration containing the plugin section config: Dict configuration containing the plugin section
""" """
self.version = entry_point.dist.version
self.name = entry_point.name
self.dist = entry_point.dist
def install(self): def install(self):
"""Installation routine """Installation routine
@ -165,7 +168,7 @@ class Plugin:
""" """
from ..controller import pluginController from ..controller import pluginController
return pluginController.get_setting(self.id, name, **kwargs) return pluginController.get_setting(self.id)
def set_setting(self, name: str, value): def set_setting(self, name: str, value):
"""Save setting in database """Save setting in database
@ -287,7 +290,7 @@ class AuthPlugin(Plugin):
""" """
raise NotFound raise NotFound
def set_avatar(self, user, file: FileStorage): def set_avatar(self, user, file):
"""Set the avatar for given user (if supported by auth backend) """Set the avatar for given user (if supported by auth backend)
Default behavior is to use native Image objects stored on the Flaschengeist server Default behavior is to use native Image objects stored on the Flaschengeist server

View File

@ -17,8 +17,10 @@ from flaschengeist.plugins import AuthPlugin, before_role_updated
class AuthLDAP(AuthPlugin): class AuthLDAP(AuthPlugin):
def __init__(self, entry_point, config): id = "auth_ldap"
super().__init__(entry_point, config)
def __init__(self, config):
super().__init__()
app.config.update( app.config.update(
LDAP_SERVER=config.get("host", "localhost"), LDAP_SERVER=config.get("host", "localhost"),
LDAP_PORT=config.get("port", 389), LDAP_PORT=config.get("port", 389),

View File

@ -57,15 +57,17 @@ def service_debit():
class BalancePlugin(Plugin): class BalancePlugin(Plugin):
"""Balance Plugin"""
id = "dev.flaschengeist.balance"
blueprint = Blueprint("balance", __name__)
permissions = permissions.permissions permissions = permissions.permissions
plugin: "BalancePlugin" = LocalProxy(lambda: current_app.config["FG_PLUGINS"][BalancePlugin.name]) plugin: "BalancePlugin" = LocalProxy(lambda: current_app.config["FG_PLUGINS"][BalancePlugin.name])
models = models models = models
def __init__(self, entry_point, config): def __init__(self, config):
super(BalancePlugin, self).__init__(entry_point, config) super(BalancePlugin, self).__init__(config)
from .routes import blueprint from . import routes
self.blueprint = blueprint
self.migrations_path = (pathlib.Path(__file__).parent / "migrations").resolve() self.migrations_path = (pathlib.Path(__file__).parent / "migrations").resolve()

View File

@ -1,6 +1,6 @@
from datetime import datetime, timezone from datetime import datetime, timezone
from werkzeug.exceptions import Forbidden, BadRequest from werkzeug.exceptions import Forbidden, BadRequest
from flask import Blueprint, request, jsonify from flask import request, jsonify
from flaschengeist.utils import HTTP from flaschengeist.utils import HTTP
from flaschengeist.models.session import Session from flaschengeist.models.session import Session
@ -18,10 +18,7 @@ def str2bool(string: str):
raise ValueError raise ValueError
blueprint = Blueprint("balance", __package__) @BalancePlugin.blueprint.route("/users/<userid>/balance/shortcuts", methods=["GET", "PUT"])
@blueprint.route("/users/<userid>/balance/shortcuts", methods=["GET", "PUT"])
@login_required() @login_required()
def get_shortcuts(userid, current_session: Session): def get_shortcuts(userid, current_session: Session):
"""Get balance shortcuts of an user """Get balance shortcuts of an user
@ -53,7 +50,7 @@ def get_shortcuts(userid, current_session: Session):
return HTTP.no_content() return HTTP.no_content()
@blueprint.route("/users/<userid>/balance/limit", methods=["GET"]) @BalancePlugin.blueprint.route("/users/<userid>/balance/limit", methods=["GET"])
@login_required() @login_required()
def get_limit(userid, current_session: Session): def get_limit(userid, current_session: Session):
"""Get limit of an user """Get limit of an user
@ -76,7 +73,7 @@ def get_limit(userid, current_session: Session):
return {"limit": balance_controller.get_limit(user)} return {"limit": balance_controller.get_limit(user)}
@blueprint.route("/users/<userid>/balance/limit", methods=["PUT"]) @BalancePlugin.blueprint.route("/users/<userid>/balance/limit", methods=["PUT"])
@login_required(permissions.SET_LIMIT) @login_required(permissions.SET_LIMIT)
def set_limit(userid, current_session: Session): def set_limit(userid, current_session: Session):
"""Set the limit of an user """Set the limit of an user
@ -102,7 +99,7 @@ def set_limit(userid, current_session: Session):
return HTTP.no_content() return HTTP.no_content()
@blueprint.route("/users/balance/limit", methods=["GET", "PUT"]) @BalancePlugin.blueprint.route("/users/balance/limit", methods=["GET", "PUT"])
@login_required(permission=permissions.SET_LIMIT) @login_required(permission=permissions.SET_LIMIT)
def limits(current_session: Session): def limits(current_session: Session):
"""Get, Modify limit of all users """Get, Modify limit of all users
@ -127,7 +124,7 @@ def limits(current_session: Session):
return HTTP.no_content() return HTTP.no_content()
@blueprint.route("/users/<userid>/balance", methods=["GET"]) @BalancePlugin.blueprint.route("/users/<userid>/balance", methods=["GET"])
@login_required(permission=permissions.SHOW) @login_required(permission=permissions.SHOW)
def get_balance(userid, current_session: Session): def get_balance(userid, current_session: Session):
"""Get balance of user, optionally filtered """Get balance of user, optionally filtered
@ -165,7 +162,7 @@ def get_balance(userid, current_session: Session):
return {"credit": balance[0], "debit": balance[1], "balance": balance[2]} return {"credit": balance[0], "debit": balance[1], "balance": balance[2]}
@blueprint.route("/users/<userid>/balance/transactions", methods=["GET"]) @BalancePlugin.blueprint.route("/users/<userid>/balance/transactions", methods=["GET"])
@login_required(permission=permissions.SHOW) @login_required(permission=permissions.SHOW)
def get_transactions(userid, current_session: Session): def get_transactions(userid, current_session: Session):
"""Get transactions of user, optionally filtered """Get transactions of user, optionally filtered
@ -226,7 +223,7 @@ def get_transactions(userid, current_session: Session):
return {"transactions": transactions, "count": count} return {"transactions": transactions, "count": count}
@blueprint.route("/users/<userid>/balance", methods=["PUT"]) @BalancePlugin.blueprint.route("/users/<userid>/balance", methods=["PUT"])
@login_required() @login_required()
def change_balance(userid, current_session: Session): def change_balance(userid, current_session: Session):
"""Change balance of an user """Change balance of an user
@ -275,7 +272,7 @@ def change_balance(userid, current_session: Session):
raise Forbidden raise Forbidden
@blueprint.route("/balance/<int:transaction_id>", methods=["DELETE"]) @BalancePlugin.blueprint.route("/balance/<int:transaction_id>", methods=["DELETE"])
@login_required() @login_required()
def reverse_transaction(transaction_id, current_session: Session): def reverse_transaction(transaction_id, current_session: Session):
"""Reverse a transaction """Reverse a transaction
@ -300,7 +297,7 @@ def reverse_transaction(transaction_id, current_session: Session):
raise Forbidden raise Forbidden
@blueprint.route("/balance", methods=["GET"]) @BalancePlugin.blueprint.route("/balance", methods=["GET"])
@login_required(permission=permissions.SHOW_OTHER) @login_required(permission=permissions.SHOW_OTHER)
def get_balances(current_session: Session): def get_balances(current_session: Session):
"""Get all balances """Get all balances

View File

@ -12,8 +12,10 @@ from . import Plugin
class MailMessagePlugin(Plugin): class MailMessagePlugin(Plugin):
def __init__(self, entry_point, config): id = "dev.flaschengeist.mail_plugin"
super().__init__(entry_point, config)
def __init__(self, config):
super().__init__()
self.server = config["SERVER"] self.server = config["SERVER"]
self.port = config["PORT"] self.port = config["PORT"]
self.user = config["USER"] self.user = config["USER"]

View File

@ -16,24 +16,22 @@ from . import models
from . import pricelist_controller, permissions from . import pricelist_controller, permissions
blueprint = Blueprint("pricelist", __name__, url_prefix="/pricelist")
class PriceListPlugin(Plugin): class PriceListPlugin(Plugin):
id = "dev.flaschengeist.pricelist"
permissions = permissions.permissions permissions = permissions.permissions
blueprint = Blueprint("pricelist", __name__, url_prefix="/pricelist")
plugin = LocalProxy(lambda: current_app.config["FG_PLUGINS"][PriceListPlugin.name]) plugin = LocalProxy(lambda: current_app.config["FG_PLUGINS"][PriceListPlugin.name])
models = models models = models
def __init__(self, entry_point, config=None): def __init__(self, cfg):
super().__init__(entry_point, config) super().__init__(cfg)
self.blueprint = blueprint
self.migrations_path = (pathlib.Path(__file__).parent / "migrations").resolve() self.migrations_path = (pathlib.Path(__file__).parent / "migrations").resolve()
config = {"discount": 0} config = {"discount": 0}
config.update(config) config.update(cfg)
@blueprint.route("/drink-types", methods=["GET"]) @PriceListPlugin.blueprint.route("/drink-types", methods=["GET"])
@blueprint.route("/drink-types/<int:identifier>", methods=["GET"]) @PriceListPlugin.blueprint.route("/drink-types/<int:identifier>", methods=["GET"])
def get_drink_types(identifier=None): def get_drink_types(identifier=None):
"""Get DrinkType(s) """Get DrinkType(s)
@ -53,7 +51,7 @@ def get_drink_types(identifier=None):
return jsonify(result) return jsonify(result)
@blueprint.route("/drink-types", methods=["POST"]) @PriceListPlugin.blueprint.route("/drink-types", methods=["POST"])
@login_required(permission=permissions.CREATE_TYPE) @login_required(permission=permissions.CREATE_TYPE)
def new_drink_type(current_session): def new_drink_type(current_session):
"""Create new DrinkType """Create new DrinkType
@ -75,7 +73,7 @@ def new_drink_type(current_session):
return jsonify(drink_type) return jsonify(drink_type)
@blueprint.route("/drink-types/<int:identifier>", methods=["PUT"]) @PriceListPlugin.blueprint.route("/drink-types/<int:identifier>", methods=["PUT"])
@login_required(permission=permissions.EDIT_TYPE) @login_required(permission=permissions.EDIT_TYPE)
def update_drink_type(identifier, current_session): def update_drink_type(identifier, current_session):
"""Modify DrinkType """Modify DrinkType
@ -98,7 +96,7 @@ def update_drink_type(identifier, current_session):
return jsonify(drink_type) return jsonify(drink_type)
@blueprint.route("/drink-types/<int:identifier>", methods=["DELETE"]) @PriceListPlugin.blueprint.route("/drink-types/<int:identifier>", methods=["DELETE"])
@login_required(permission=permissions.DELETE_TYPE) @login_required(permission=permissions.DELETE_TYPE)
def delete_drink_type(identifier, current_session): def delete_drink_type(identifier, current_session):
"""Delete DrinkType """Delete DrinkType
@ -116,8 +114,8 @@ def delete_drink_type(identifier, current_session):
return no_content() return no_content()
@blueprint.route("/tags", methods=["GET"]) @PriceListPlugin.blueprint.route("/tags", methods=["GET"])
@blueprint.route("/tags/<int:identifier>", methods=["GET"]) @PriceListPlugin.blueprint.route("/tags/<int:identifier>", methods=["GET"])
def get_tags(identifier=None): def get_tags(identifier=None):
"""Get Tag(s) """Get Tag(s)
@ -137,7 +135,7 @@ def get_tags(identifier=None):
return jsonify(result) return jsonify(result)
@blueprint.route("/tags", methods=["POST"]) @PriceListPlugin.blueprint.route("/tags", methods=["POST"])
@login_required(permission=permissions.CREATE_TAG) @login_required(permission=permissions.CREATE_TAG)
def new_tag(current_session): def new_tag(current_session):
"""Create Tag """Create Tag
@ -157,7 +155,7 @@ def new_tag(current_session):
return jsonify(drink_type) return jsonify(drink_type)
@blueprint.route("/tags/<int:identifier>", methods=["PUT"]) @PriceListPlugin.blueprint.route("/tags/<int:identifier>", methods=["PUT"])
@login_required(permission=permissions.EDIT_TAG) @login_required(permission=permissions.EDIT_TAG)
def update_tag(identifier, current_session): def update_tag(identifier, current_session):
"""Modify Tag """Modify Tag
@ -178,7 +176,7 @@ def update_tag(identifier, current_session):
return jsonify(tag) return jsonify(tag)
@blueprint.route("/tags/<int:identifier>", methods=["DELETE"]) @PriceListPlugin.blueprint.route("/tags/<int:identifier>", methods=["DELETE"])
@login_required(permission=permissions.DELETE_TAG) @login_required(permission=permissions.DELETE_TAG)
def delete_tag(identifier, current_session): def delete_tag(identifier, current_session):
"""Delete Tag """Delete Tag
@ -196,8 +194,8 @@ def delete_tag(identifier, current_session):
return no_content() return no_content()
@blueprint.route("/drinks", methods=["GET"]) @PriceListPlugin.blueprint.route("/drinks", methods=["GET"])
@blueprint.route("/drinks/<int:identifier>", methods=["GET"]) @PriceListPlugin.blueprint.route("/drinks/<int:identifier>", methods=["GET"])
def get_drinks(identifier=None): def get_drinks(identifier=None):
"""Get Drink(s) """Get Drink(s)
@ -253,7 +251,7 @@ def get_drinks(identifier=None):
return jsonify({"drinks": drinks, "count": count}) return jsonify({"drinks": drinks, "count": count})
@blueprint.route("/list", methods=["GET"]) @PriceListPlugin.blueprint.route("/list", methods=["GET"])
def get_pricelist(): def get_pricelist():
"""Get Priclist """Get Priclist
Route: ``/pricelist/list`` | Method: ``GET`` Route: ``/pricelist/list`` | Method: ``GET``
@ -302,7 +300,7 @@ def get_pricelist():
return jsonify({"pricelist": pricelist, "count": count}) return jsonify({"pricelist": pricelist, "count": count})
@blueprint.route("/drinks/search/<string:name>", methods=["GET"]) @PriceListPlugin.blueprint.route("/drinks/search/<string:name>", methods=["GET"])
def search_drinks(name): def search_drinks(name):
"""Search Drink """Search Drink
@ -323,7 +321,7 @@ def search_drinks(name):
return jsonify(pricelist_controller.get_drinks(name, public=public)) return jsonify(pricelist_controller.get_drinks(name, public=public))
@blueprint.route("/drinks", methods=["POST"]) @PriceListPlugin.blueprint.route("/drinks", methods=["POST"])
@login_required(permission=permissions.CREATE) @login_required(permission=permissions.CREATE)
def create_drink(current_session): def create_drink(current_session):
"""Create Drink """Create Drink
@ -375,7 +373,7 @@ def create_drink(current_session):
return jsonify(pricelist_controller.set_drink(data)) return jsonify(pricelist_controller.set_drink(data))
@blueprint.route("/drinks/<int:identifier>", methods=["PUT"]) @PriceListPlugin.blueprint.route("/drinks/<int:identifier>", methods=["PUT"])
@login_required(permission=permissions.EDIT) @login_required(permission=permissions.EDIT)
def update_drink(identifier, current_session): def update_drink(identifier, current_session):
"""Modify Drink """Modify Drink
@ -429,7 +427,7 @@ def update_drink(identifier, current_session):
return jsonify(pricelist_controller.update_drink(identifier, data)) return jsonify(pricelist_controller.update_drink(identifier, data))
@blueprint.route("/drinks/<int:identifier>", methods=["DELETE"]) @PriceListPlugin.blueprint.route("/drinks/<int:identifier>", methods=["DELETE"])
@login_required(permission=permissions.DELETE) @login_required(permission=permissions.DELETE)
def delete_drink(identifier, current_session): def delete_drink(identifier, current_session):
"""Delete Drink """Delete Drink
@ -447,7 +445,7 @@ def delete_drink(identifier, current_session):
return no_content() return no_content()
@blueprint.route("/prices/<int:identifier>", methods=["DELETE"]) @PriceListPlugin.blueprint.route("/prices/<int:identifier>", methods=["DELETE"])
@login_required(permission=permissions.DELETE_PRICE) @login_required(permission=permissions.DELETE_PRICE)
def delete_price(identifier, current_session): def delete_price(identifier, current_session):
"""Delete Price """Delete Price
@ -465,7 +463,7 @@ def delete_price(identifier, current_session):
return no_content() return no_content()
@blueprint.route("/volumes/<int:identifier>", methods=["DELETE"]) @PriceListPlugin.blueprint.route("/volumes/<int:identifier>", methods=["DELETE"])
@login_required(permission=permissions.DELETE_VOLUME) @login_required(permission=permissions.DELETE_VOLUME)
def delete_volume(identifier, current_session): def delete_volume(identifier, current_session):
"""Delete DrinkPriceVolume """Delete DrinkPriceVolume
@ -483,7 +481,7 @@ def delete_volume(identifier, current_session):
return no_content() return no_content()
@blueprint.route("/ingredients/extraIngredients", methods=["GET"]) @PriceListPlugin.blueprint.route("/ingredients/extraIngredients", methods=["GET"])
@login_required() @login_required()
def get_extra_ingredients(current_session): def get_extra_ingredients(current_session):
"""Get ExtraIngredients """Get ExtraIngredients
@ -499,7 +497,7 @@ def get_extra_ingredients(current_session):
return jsonify(pricelist_controller.get_extra_ingredients()) return jsonify(pricelist_controller.get_extra_ingredients())
@blueprint.route("/ingredients/<int:identifier>", methods=["DELETE"]) @PriceListPlugin.blueprint.route("/ingredients/<int:identifier>", methods=["DELETE"])
@login_required(permission=permissions.DELETE_INGREDIENTS_DRINK) @login_required(permission=permissions.DELETE_INGREDIENTS_DRINK)
def delete_ingredient(identifier, current_session): def delete_ingredient(identifier, current_session):
"""Delete Ingredient """Delete Ingredient
@ -517,7 +515,7 @@ def delete_ingredient(identifier, current_session):
return no_content() return no_content()
@blueprint.route("/ingredients/extraIngredients", methods=["POST"]) @PriceListPlugin.blueprint.route("/ingredients/extraIngredients", methods=["POST"])
@login_required(permission=permissions.EDIT_INGREDIENTS) @login_required(permission=permissions.EDIT_INGREDIENTS)
def set_extra_ingredient(current_session): def set_extra_ingredient(current_session):
"""Create ExtraIngredient """Create ExtraIngredient
@ -536,7 +534,7 @@ def set_extra_ingredient(current_session):
return jsonify(pricelist_controller.set_extra_ingredient(data)) return jsonify(pricelist_controller.set_extra_ingredient(data))
@blueprint.route("/ingredients/extraIngredients/<int:identifier>", methods=["PUT"]) @PriceListPlugin.blueprint.route("/ingredients/extraIngredients/<int:identifier>", methods=["PUT"])
@login_required(permission=permissions.EDIT_INGREDIENTS) @login_required(permission=permissions.EDIT_INGREDIENTS)
def update_extra_ingredient(identifier, current_session): def update_extra_ingredient(identifier, current_session):
"""Modify ExtraIngredient """Modify ExtraIngredient
@ -556,7 +554,7 @@ def update_extra_ingredient(identifier, current_session):
return jsonify(pricelist_controller.update_extra_ingredient(identifier, data)) return jsonify(pricelist_controller.update_extra_ingredient(identifier, data))
@blueprint.route("/ingredients/extraIngredients/<int:identifier>", methods=["DELETE"]) @PriceListPlugin.blueprint.route("/ingredients/extraIngredients/<int:identifier>", methods=["DELETE"])
@login_required(permission=permissions.DELETE_INGREDIENTS) @login_required(permission=permissions.DELETE_INGREDIENTS)
def delete_extra_ingredient(identifier, current_session): def delete_extra_ingredient(identifier, current_session):
"""Delete ExtraIngredient """Delete ExtraIngredient
@ -574,7 +572,7 @@ def delete_extra_ingredient(identifier, current_session):
return no_content() return no_content()
@blueprint.route("/settings/min_prices", methods=["GET"]) @PriceListPlugin.blueprint.route("/settings/min_prices", methods=["GET"])
@login_required() @login_required()
def get_pricelist_settings_min_prices(current_session): def get_pricelist_settings_min_prices(current_session):
"""Get MinPrices """Get MinPrices
@ -595,7 +593,7 @@ def get_pricelist_settings_min_prices(current_session):
return jsonify(min_prices) return jsonify(min_prices)
@blueprint.route("/settings/min_prices", methods=["POST"]) @PriceListPlugin.blueprint.route("/settings/min_prices", methods=["POST"])
@login_required(permission=permissions.EDIT_MIN_PRICES) @login_required(permission=permissions.EDIT_MIN_PRICES)
def post_pricelist_settings_min_prices(current_session): def post_pricelist_settings_min_prices(current_session):
"""Create MinPrices """Create MinPrices
@ -618,7 +616,7 @@ def post_pricelist_settings_min_prices(current_session):
return no_content() return no_content()
@blueprint.route("/users/<userid>/pricecalc_columns", methods=["GET", "PUT"]) @PriceListPlugin.blueprint.route("/users/<userid>/pricecalc_columns", methods=["GET", "PUT"])
@login_required() @login_required()
def get_columns(userid, current_session): def get_columns(userid, current_session):
"""Get pricecalc_columns of an user """Get pricecalc_columns of an user
@ -650,7 +648,7 @@ def get_columns(userid, current_session):
return no_content() return no_content()
@blueprint.route("/users/<userid>/pricecalc_columns_order", methods=["GET", "PUT"]) @PriceListPlugin.blueprint.route("/users/<userid>/pricecalc_columns_order", methods=["GET", "PUT"])
@login_required() @login_required()
def get_columns_order(userid, current_session): def get_columns_order(userid, current_session):
"""Get pricecalc_columns_order of an user """Get pricecalc_columns_order of an user
@ -681,7 +679,7 @@ def get_columns_order(userid, current_session):
return no_content() return no_content()
@blueprint.route("/users/<userid>/pricelist", methods=["GET", "PUT"]) @PriceListPlugin.blueprint.route("/users/<userid>/pricelist", methods=["GET", "PUT"])
@login_required() @login_required()
def get_priclist_setting(userid, current_session): def get_priclist_setting(userid, current_session):
"""Get pricelistsetting of an user """Get pricelistsetting of an user
@ -714,7 +712,7 @@ def get_priclist_setting(userid, current_session):
return no_content() return no_content()
@blueprint.route("/drinks/<int:identifier>/picture", methods=["POST", "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
@ -741,7 +739,7 @@ def set_picture(identifier, current_session):
raise BadRequest raise BadRequest
@blueprint.route("/drinks/<int:identifier>/picture", methods=["GET"]) @PriceListPlugin.blueprint.route("/drinks/<int:identifier>/picture", methods=["GET"])
# @headers({"Cache-Control": "private, must-revalidate"}) # @headers({"Cache-Control": "private, must-revalidate"})
def _get_picture(identifier): def _get_picture(identifier):
"""Get Picture """Get Picture

View File

@ -16,6 +16,7 @@ from . import permissions
class RolesPlugin(Plugin): class RolesPlugin(Plugin):
id = "dev.flaschengeist.roles"
blueprint = Blueprint("roles", __name__) blueprint = Blueprint("roles", __name__)
permissions = permissions.permissions permissions = permissions.permissions

View File

@ -1,5 +1,6 @@
from flask import Blueprint import pkg_resources
from datetime import datetime, timedelta from datetime import datetime, timedelta
from flask import Blueprint
from flaschengeist import logger from flaschengeist import logger
from flaschengeist.utils.HTTP import no_content from flaschengeist.utils.HTTP import no_content
@ -39,9 +40,15 @@ def scheduled(id: str, replace=False, **kwargs):
class SchedulerPlugin(Plugin): class SchedulerPlugin(Plugin):
def __init__(self, entry_point, config=None): id = "dev.flaschengeist.scheduler"
super().__init__(entry_point, config) name = "scheduler"
self.blueprint = Blueprint(self.name, __name__) blueprint = Blueprint(name, __name__)
def __init__(self, config=None):
"""Constructor called by create_app
Args:
config: Dict configuration containing the plugin section
"""
def __view_func(): def __view_func():
self.run_tasks() self.run_tasks()
@ -53,6 +60,7 @@ class SchedulerPlugin(Plugin):
except: except:
logger.error("Error while executing scheduled tasks!", exc_info=True) logger.error("Error while executing scheduled tasks!", exc_info=True)
self.version = pkg_resources.get_distribution(self.__module__.split(".")[0]).version
cron = None if config is None else config.get("cron", "passive_web").lower() cron = None if config is None else config.get("cron", "passive_web").lower()
if cron is None or cron == "passive_web": if cron is None or cron == "passive_web":

View File

@ -18,6 +18,7 @@ from flaschengeist.utils.datetime import from_iso_format
class UsersPlugin(Plugin): class UsersPlugin(Plugin):
id = "dev.flaschengeist.users"
blueprint = Blueprint("users", __name__) blueprint = Blueprint("users", __name__)
permissions = permissions.permissions permissions = permissions.permissions

View File

@ -23,13 +23,11 @@ python_requires = >=3.9
packages = find: packages = find:
install_requires = install_requires =
Flask >= 2.0 Flask >= 2.0
Flask-Cors >= 3.0
Flask-Migrate >= 3.1.0
Flask-SQLAlchemy >= 2.5
Pillow >= 8.4.0 Pillow >= 8.4.0
flask_cors SQLAlchemy >= 1.4.28
flask_migrate>=3.1.0
flask_sqlalchemy>=2.5
# Importlib requirement can be dropped when python requirement is >= 3.10
importlib_metadata>=4.3
sqlalchemy>=1.4.26
toml toml
werkzeug >= 2.0 werkzeug >= 2.0