diff --git a/flaschengeist/plugins/pricelist/__init__.py b/flaschengeist/plugins/pricelist/__init__.py index 0f56111..998e046 100644 --- a/flaschengeist/plugins/pricelist/__init__.py +++ b/flaschengeist/plugins/pricelist/__init__.py @@ -8,8 +8,6 @@ from flaschengeist import logger from flaschengeist.plugins import Plugin from flaschengeist.utils.decorators import login_required, extract_session from flaschengeist.utils.HTTP import no_content -from flaschengeist.models.session import Session -from flaschengeist.controller import userController from . import models from . import pricelist_controller, permissions @@ -31,6 +29,17 @@ class PriceListPlugin(Plugin): @PriceListPlugin.blueprint.route("/drink-types", methods=["GET"]) @PriceListPlugin.blueprint.route("/drink-types/", methods=["GET"]) def get_drink_types(identifier=None): + """Get DrinkType(s) + + Route: ``/pricelist/drink-types`` | Method: ``GET`` + Route: ``/pricelist/drink-types/`` | Method: ``GET`` + + Args: + identifier: If querying a spicific DrinkType + + Returns: + JSON encoded (list of) DrinkType(s) or HTTP-error + """ if identifier is None: result = pricelist_controller.get_drink_types() else: @@ -41,6 +50,18 @@ def get_drink_types(identifier=None): @PriceListPlugin.blueprint.route("/drink-types", methods=["POST"]) @login_required(permission=permissions.CREATE_TYPE) def new_drink_type(current_session): + """Create new DrinkType + + Route ``/pricelist/drink-types`` | Method: ``POST`` + + POST-data: ``{name: string}`` + + Args: + current_session: Session sent with Authorization Header + + Returns: + JSON encoded DrinkType or HTTP-error + """ data = request.get_json() if "name" not in data: raise BadRequest @@ -51,6 +72,19 @@ def new_drink_type(current_session): @PriceListPlugin.blueprint.route("/drink-types/", methods=["PUT"]) @login_required(permission=permissions.EDIT_TYPE) def update_drink_type(identifier, current_session): + """Modify DrinkType + + Route ``/pricelist/drink-types/`` | METHOD ``PUT`` + + POST-data: ``{name: string}`` + + Args: + identifier: Identifier of DrinkType + current_session: Session sent with Authorization Header + + Returns: + JSON encoded DrinkType or HTTP-error + """ data = request.get_json() if "name" not in data: raise BadRequest @@ -61,6 +95,17 @@ def update_drink_type(identifier, current_session): @PriceListPlugin.blueprint.route("/drink-types/", methods=["DELETE"]) @login_required(permission=permissions.DELETE_TYPE) def delete_drink_type(identifier, current_session): + """Delete DrinkType + + Route: ``/pricelist/drink-types/`` | Method: ``DELETE`` + + Args: + identifier: Identifier of DrinkType + current_session: Session sent with Authorization Header + + Returns: + HTTP-NoContent or HTTP-error + """ pricelist_controller.delete_drink_type(identifier) return no_content() @@ -68,6 +113,17 @@ def delete_drink_type(identifier, current_session): @PriceListPlugin.blueprint.route("/tags", methods=["GET"]) @PriceListPlugin.blueprint.route("/tags/", methods=["GET"]) def get_tags(identifier=None): + """Get Tag(s) + + Route: ``/pricelist/tags`` | Method: ``GET`` + Route: ``/pricelist/tags/`` | Method: ``GET`` + + Args: + identifier: Identifier of Tag + + Returns: + JSON encoded (list of) Tag(s) or HTTP-error + """ if identifier: result = pricelist_controller.get_tag(identifier) else: @@ -78,6 +134,18 @@ def get_tags(identifier=None): @PriceListPlugin.blueprint.route("/tags", methods=["POST"]) @login_required(permission=permissions.CREATE_TAG) def new_tag(current_session): + """Create Tag + + Route: ``/pricelist/tags`` | Method: ``POST`` + + POST-data: ``{name: string, color: string}`` + + Args: + current_session: Session sent with Authorization Header + + Returns: + JSON encoded Tag or HTTP-error + """ data = request.get_json() drink_type = pricelist_controller.create_tag(data) return jsonify(drink_type) @@ -86,6 +154,19 @@ def new_tag(current_session): @PriceListPlugin.blueprint.route("/tags/", methods=["PUT"]) @login_required(permission=permissions.EDIT_TAG) def update_tag(identifier, current_session): + """Modify Tag + + Route: ``/pricelist/tags/`` | Methods: ``PUT`` + + POST-data: ``{name: string, color: string}`` + + Args: + identifier: Identifier of Tag + current_session: Session sent with Authorization Header + + Returns: + JSON encoded Tag or HTTP-error + """ data = request.get_json() tag = pricelist_controller.update_tag(identifier, data) return jsonify(tag) @@ -94,6 +175,17 @@ def update_tag(identifier, current_session): @PriceListPlugin.blueprint.route("/tags/", methods=["DELETE"]) @login_required(permission=permissions.DELETE_TAG) def delete_tag(identifier, current_session): + """Delete Tag + + Route: ``/pricelist/tags/`` | Methods: ``DELETE`` + + Args: + identifier: Identifier of Tag + current_session: Session sent with Authorization Header + + Returns: + HTTP-NoContent or HTTP-error + """ pricelist_controller.delete_tag(identifier) return no_content() @@ -101,6 +193,17 @@ def delete_tag(identifier, current_session): @PriceListPlugin.blueprint.route("/drinks", methods=["GET"]) @PriceListPlugin.blueprint.route("/drinks/", methods=["GET"]) def get_drinks(identifier=None): + """Get Drink(s) + + Route: ``/pricelist/drinks`` | Method: ``GET`` + Route: ``/pricelist/drinks/`` | Method: ``GET`` + + Args: + identifier: Identifier of Drink + + Returns: + JSON encoded (list of) Drink(s) or HTTP-error + """ public = True try: extract_session() @@ -118,91 +221,335 @@ def get_drinks(identifier=None): @PriceListPlugin.blueprint.route("/drinks/search/", methods=["GET"]) def search_drinks(name): - return jsonify(pricelist_controller.get_drinks(name)) + """Search Drink + + Route: ``/pricelist/drinks/search/`` | Method: ``GET`` + + Args: + name: Name to search + + Returns: + JSON encoded list of Drinks or HTTP-error + """ + public = True + try: + extract_session() + public = False + except Unauthorized: + public = True + return jsonify(pricelist_controller.get_drinks(name, public=public)) @PriceListPlugin.blueprint.route("/drinks", methods=["POST"]) @login_required(permission=permissions.CREATE) def create_drink(current_session): + """Create Drink + + Route: ``/pricelist/drinks`` | Method: ``POST`` + + POST-data : +``{ + article_id?: string + cost_per_package?: float, + cost_per_volume?: float, + name: string, + package_size?: number, + receipt?: list[string], + tags?: list[Tag], + type: DrinkType, + uuid?: string, + volume?: float, + volumes?: list[ + { + ingredients?: list[{ + id: int + drink_ingredient?: { + ingredient_id: int, + volume: float + }, + extra_ingredient?: { + id: number, + } + }], + prices?: list[ + { + price: float + public: boolean + } + ], + volume: float + } + ] +}`` + + Args: + current_session: Session sent with Authorization Header + + Returns: + JSON encoded Drink or HTTP-error + """ data = request.get_json() return jsonify(pricelist_controller.set_drink(data)) @PriceListPlugin.blueprint.route("/drinks/", methods=["PUT"]) -def update_drink(identifier): +@login_required(permission=permissions.EDIT) +def update_drink(identifier, current_session): + """Modify Drink + + Route: ``/pricelist/drinks/`` | Method: ``PUT`` + + POST-data : +``{ + article_id?: string + cost_per_package?: float, + cost_per_volume?: float, + name: string, + package_size?: number, + receipt?: list[string], + tags?: list[Tag], + type: DrinkType, + uuid?: string, + volume?: float, + volumes?: list[ + { + ingredients?: list[{ + id: int + drink_ingredient?: { + ingredient_id: int, + volume: float + }, + extra_ingredient?: { + id: number, + } + }], + prices?: list[ + { + price: float + public: boolean + } + ], + volume: float + } + ] +}`` + + Args: + identifier: Identifier of Drink + current_session: Session sent with Authorization Header + + Returns: + JSON encoded Drink or HTTP-error + """ data = request.get_json() logger.debug(f"update drink {data}") return jsonify(pricelist_controller.update_drink(identifier, data)) @PriceListPlugin.blueprint.route("/drinks/", methods=["DELETE"]) -def delete_drink(identifier): +@login_required(permission=permissions.DELETE) +def delete_drink(identifier, current_session): + """Delete Drink + + Route: ``/pricelist/drinks/`` | Method: ``DELETE`` + + Args: + identifier: Identifier of Drink + current_session: Session sent with Authorization Header + + Returns: + HTTP-NoContent or HTTP-error + """ pricelist_controller.delete_drink(identifier) return no_content() @PriceListPlugin.blueprint.route("/prices/", methods=["DELETE"]) -def delete_price(identifier): +@login_required(permission=permissions.DELETE_PRICE) +def delete_price(identifier, current_session): + """Delete Price + + Route: ``/pricelist/prices/`` | Methods: ``DELETE`` + + Args: + identifier: Identiefer of Price + current_session: Session sent with Authorization Header + + Returns: + HTTP-NoContent or HTTP-error + """ pricelist_controller.delete_price(identifier) return no_content() @PriceListPlugin.blueprint.route("/volumes/", methods=["DELETE"]) -def delete_volume(identifier): +@login_required(permission=permissions.DELETE_VOLUME) +def delete_volume(identifier, current_session): + """Delete DrinkPriceVolume + + Route: ``/pricelist/volumes/`` | Method: ``DELETE`` + + Args: + identifier: Identifier of DrinkPriceVolume + current_session: Session sent with Authorization Header + + Returns: + HTTP-NoContent or HTTP-error + """ pricelist_controller.delete_volume(identifier) return no_content() @PriceListPlugin.blueprint.route("/ingredients/extraIngredients", methods=["GET"]) -def get_extra_ingredients(): +@login_required() +def get_extra_ingredients(current_session): + """Get ExtraIngredients + + Route: ``/pricelist/ingredients/extraIngredients`` | Method: ``GET`` + + Args: + current_session: Session sent with Authorization Header + + Returns: + JSON encoded list of ExtraIngredients or HTTP-error + """ return jsonify(pricelist_controller.get_extra_ingredients()) @PriceListPlugin.blueprint.route("/ingredients/", methods=["DELETE"]) -def delete_ingredient(identifier): +@login_required(permission=permissions.DELETE_INGREDIENTS_DRINK) +def delete_ingredient(identifier, current_session): + """Delete Ingredient + + Route: ``/pricelist/ingredients/`` | Method: ``DELETE`` + + Args: + identifier: Identifier of Ingredient + current_session: Session sent with Authorization Header + + Returns: + HTTP-NoContent or HTTP-error + """ pricelist_controller.delete_ingredient(identifier) return no_content() @PriceListPlugin.blueprint.route("/ingredients/extraIngredients", methods=["POST"]) -def set_extra_ingredient(): +@login_required(permission=permissions.EDIT_INGREDIENTS) +def set_extra_ingredient(current_session): + """Create ExtraIngredient + + Route: ``/pricelist/ingredients/extraIngredients`` | Method: ``POST`` + + POST-data: ``{ name: string, price: float }`` + + Args: + current_session: Session sent with Authorization Header + + Returns: + JSON encoded ExtraIngredient or HTTP-error + """ data = request.get_json() return jsonify(pricelist_controller.set_extra_ingredient(data)) @PriceListPlugin.blueprint.route("/ingredients/extraIngredients/", methods=["PUT"]) -def update_extra_ingredient(identifier): +@login_required(permission=permissions.EDIT_INGREDIENTS) +def update_extra_ingredient(identifier, current_session): + """Modify ExtraIngredient + + Route: ``/pricelist/ingredients/extraIngredients`` | Method: ``PUT`` + + POST-data: ``{ name: string, price: float }`` + + Args: + identifier: Identifier of ExtraIngredient + current_session: Session sent with Authorization Header + + Returns: + JSON encoded ExtraIngredient or HTTP-error + """ data = request.get_json() return jsonify(pricelist_controller.update_extra_ingredient(identifier, data)) @PriceListPlugin.blueprint.route("/ingredients/extraIngredients/", methods=["DELETE"]) -def delete_extra_ingredient(identifier): +@login_required(permission=permissions.DELETE_INGREDIENTS) +def delete_extra_ingredient(identifier, current_session): + """Delete ExtraIngredient + + Route: ``/pricelist/ingredients/extraIngredients`` | Method: ``DELETE`` + + Args: + identifier: Identifier of ExtraIngredient + current_session: Session sent with Authorization Header + + Returns: + HTTP-NoContent or HTTP-error + """ pricelist_controller.delete_extra_ingredient(identifier) return no_content() -@PriceListPlugin.blueprint.route("/settings/min_prices", methods=["POST", "GET"]) -def pricelist_settings_min_prices(): - if request.method == "GET": - # TODO: Handle if no prices are set! - try: - min_prices = PriceListPlugin.plugin.get_setting("min_prices") - except KeyError: - min_prices = [] - return jsonify(min_prices) - else: - data = request.get_json() - if not isinstance(data, list) or not all(isinstance(n, int) for n in data): - raise BadRequest - data.sort() - PriceListPlugin.plugin.set_setting("min_prices", data) - return no_content() - +@PriceListPlugin.blueprint.route("/settings/min_prices", methods=["GET"]) +@login_required() +def get_pricelist_settings_min_prices(current_session): + """Get MinPrices + + Route: ``/pricelist/settings/min_prices`` | Method: ``GET`` + + Args: + current_session: Session sent with Authorization Header + + Returns: + JSON encoded list of MinPrices + """ + # TODO: Handle if no prices are set! + try: + min_prices = PriceListPlugin.plugin.get_setting("min_prices") + except KeyError: + min_prices = [] + return jsonify(min_prices) + +@PriceListPlugin.blueprint.route("/settings/min_prices", methods=["POST"]) +@login_required(permission=permissions.EDIT_MIN_PRICES) +def post_pricelist_settings_min_prices(current_session): + """Create MinPrices + + Route: ``/pricelist/settings/min_prices`` | Method: ``POST`` + + POST-data: ``list[int]`` + + Args: + current_session: Session sent with Authorization Header + + Returns: + HTTP-NoContent or HTTP-error + """ + data = request.get_json() + if not isinstance(data, list) or not all(isinstance(n, int) for n in data): + raise BadRequest + data.sort() + PriceListPlugin.plugin.set_setting("min_prices", data) + return no_content() + @PriceListPlugin.blueprint.route("/drinks//picture", methods=["POST", "GET", "DELETE"]) -def set_picture(identifier): +@login_required(permission=permissions.EDIT) +def set_picture(identifier, current_session): + """Get, Create, Delete Drink Picture + Route: ``/pricelist//picture`` | Method: ``GET,POST,DELETE`` + + POST-data: (if remaining) ``Form-Data: mime: 'image/*'`` + + Args: + identifier: Identifier of Drink + current_session: Session sent with Authorization + + Returns: + Picture or HTTP-error + """ if request.method == "DELETE": pricelist_controller.delete_drink_picture(identifier) return no_content() @@ -219,6 +566,14 @@ def set_picture(identifier): @PriceListPlugin.blueprint.route("/picture/", methods=["GET"]) def _get_picture(identifier): + """Get Picture + + Args: + identifier: Identifier of Picture + + Returns: + Picture or HTTP-error + """ if request.method == "GET": size = request.args.get("size") response = pricelist_controller.get_drink_picture(identifier, size) diff --git a/flaschengeist/plugins/pricelist/permissions.py b/flaschengeist/plugins/pricelist/permissions.py index b92ab9a..a94b62b 100644 --- a/flaschengeist/plugins/pricelist/permissions.py +++ b/flaschengeist/plugins/pricelist/permissions.py @@ -10,6 +10,18 @@ DELETE = "drink_delete" CREATE_TAG = "drink_tag_create" """Can create and edit Tags""" +EDIT_PRICE = "edit_price" +DELETE_PRICE = "delete_price" + +EDIT_VOLUME = "edit_volume" +DELETE_VOLUME = "delete_volume" + +EDIT_INGREDIENTS_DRINK = "edit_ingredients_drink" +DELETE_INGREDIENTS_DRINK = "delete_ingredients_drink" + +EDIT_INGREDIENTS = "edit_ingredients" +DELETE_INGREDIENTS = "delete_ingredients" + EDIT_TAG = "drink_tag_edit" DELETE_TAG = "drink_tag_delete" @@ -20,4 +32,6 @@ EDIT_TYPE = "drink_type_edit" DELETE_TYPE = "drink_type_delete" +EDIT_MIN_PRICES = "edit_min_prices" + permissions = [value for key, value in globals().items() if not key.startswith("_")] diff --git a/flaschengeist/plugins/pricelist/pricelist_controller.py b/flaschengeist/plugins/pricelist/pricelist_controller.py index 32d2c33..ca18aaf 100644 --- a/flaschengeist/plugins/pricelist/pricelist_controller.py +++ b/flaschengeist/plugins/pricelist/pricelist_controller.py @@ -6,8 +6,10 @@ from flaschengeist import logger from flaschengeist.config import config from flaschengeist.database import db from flaschengeist.utils.picture import save_picture, get_picture, delete_picture +from flaschengeist.utils.decorators import extract_session from .models import Drink, DrinkPrice, Ingredient, Tag, DrinkType, DrinkPriceVolume, DrinkIngredient, ExtraIngredient +from .permissions import EDIT_VOLUME, EDIT_PRICE, EDIT_INGREDIENTS_DRINK def update(): @@ -159,6 +161,7 @@ def set_drink(data): def update_drink(identifier, data): try: + session = extract_session() if "id" in data: data.pop("id") volumes = data.pop("volumes") if "volumes" in data else None @@ -184,7 +187,7 @@ def update_drink(identifier, data): if drink_type: drink.type = drink_type - if volumes is not None: + if volumes is not None and session.user_.has_permission(EDIT_VOLUME): set_volumes(volumes, drink) if len(tags) > 0: drink.tags = tags @@ -218,6 +221,7 @@ def get_volumes(drink_id=None): def set_volume(data): + session = extract_session() allowed_keys = DrinkPriceVolume().serialize().keys() values = {key: value for key, value in data.items() if key in allowed_keys} prices = None @@ -237,9 +241,9 @@ def set_volume(data): for key, value in values.items(): setattr(volume, key, value if value != "" else None) - if prices: + if prices and session.user_.has_permission(EDIT_PRICE): set_prices(prices, volume) - if ingredients: + if ingredients and session.user_.has_permission(EDIT_INGREDIENTS_DRINK): set_ingredients(ingredients, volume) return volume