[pricelist] add more permissions

This commit is contained in:
Tim Gröger 2021-04-14 22:42:57 +02:00
parent 2d45c0dab9
commit 1d36c3ef6c
3 changed files with 406 additions and 33 deletions

View File

@ -8,8 +8,6 @@ from flaschengeist import logger
from flaschengeist.plugins import Plugin from flaschengeist.plugins import Plugin
from flaschengeist.utils.decorators import login_required, extract_session from flaschengeist.utils.decorators import login_required, extract_session
from flaschengeist.utils.HTTP import no_content from flaschengeist.utils.HTTP import no_content
from flaschengeist.models.session import Session
from flaschengeist.controller import userController
from . import models from . import models
from . import pricelist_controller, permissions 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"])
@PriceListPlugin.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)
Route: ``/pricelist/drink-types`` | Method: ``GET``
Route: ``/pricelist/drink-types/<identifier>`` | Method: ``GET``
Args:
identifier: If querying a spicific DrinkType
Returns:
JSON encoded (list of) DrinkType(s) or HTTP-error
"""
if identifier is None: if identifier is None:
result = pricelist_controller.get_drink_types() result = pricelist_controller.get_drink_types()
else: else:
@ -41,6 +50,18 @@ def get_drink_types(identifier=None):
@PriceListPlugin.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
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() data = request.get_json()
if "name" not in data: if "name" not in data:
raise BadRequest raise BadRequest
@ -51,6 +72,19 @@ def new_drink_type(current_session):
@PriceListPlugin.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
Route ``/pricelist/drink-types/<identifier>`` | 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() data = request.get_json()
if "name" not in data: if "name" not in data:
raise BadRequest raise BadRequest
@ -61,6 +95,17 @@ def update_drink_type(identifier, current_session):
@PriceListPlugin.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
Route: ``/pricelist/drink-types/<identifier>`` | 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) pricelist_controller.delete_drink_type(identifier)
return no_content() 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"])
@PriceListPlugin.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)
Route: ``/pricelist/tags`` | Method: ``GET``
Route: ``/pricelist/tags/<identifier>`` | Method: ``GET``
Args:
identifier: Identifier of Tag
Returns:
JSON encoded (list of) Tag(s) or HTTP-error
"""
if identifier: if identifier:
result = pricelist_controller.get_tag(identifier) result = pricelist_controller.get_tag(identifier)
else: else:
@ -78,6 +134,18 @@ def get_tags(identifier=None):
@PriceListPlugin.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
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() data = request.get_json()
drink_type = pricelist_controller.create_tag(data) drink_type = pricelist_controller.create_tag(data)
return jsonify(drink_type) return jsonify(drink_type)
@ -86,6 +154,19 @@ def new_tag(current_session):
@PriceListPlugin.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
Route: ``/pricelist/tags/<identifier>`` | 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() data = request.get_json()
tag = pricelist_controller.update_tag(identifier, data) tag = pricelist_controller.update_tag(identifier, data)
return jsonify(tag) return jsonify(tag)
@ -94,6 +175,17 @@ def update_tag(identifier, current_session):
@PriceListPlugin.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
Route: ``/pricelist/tags/<identifier>`` | 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) pricelist_controller.delete_tag(identifier)
return no_content() 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"])
@PriceListPlugin.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)
Route: ``/pricelist/drinks`` | Method: ``GET``
Route: ``/pricelist/drinks/<identifier>`` | Method: ``GET``
Args:
identifier: Identifier of Drink
Returns:
JSON encoded (list of) Drink(s) or HTTP-error
"""
public = True public = True
try: try:
extract_session() extract_session()
@ -118,80 +221,311 @@ def get_drinks(identifier=None):
@PriceListPlugin.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):
return jsonify(pricelist_controller.get_drinks(name)) """Search Drink
Route: ``/pricelist/drinks/search/<name>`` | 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"]) @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
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() data = request.get_json()
return jsonify(pricelist_controller.set_drink(data)) return jsonify(pricelist_controller.set_drink(data))
@PriceListPlugin.blueprint.route("/drinks/<int:identifier>", methods=["PUT"]) @PriceListPlugin.blueprint.route("/drinks/<int:identifier>", methods=["PUT"])
def update_drink(identifier): @login_required(permission=permissions.EDIT)
def update_drink(identifier, current_session):
"""Modify Drink
Route: ``/pricelist/drinks/<identifier>`` | 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() data = request.get_json()
logger.debug(f"update drink {data}") logger.debug(f"update drink {data}")
return jsonify(pricelist_controller.update_drink(identifier, data)) return jsonify(pricelist_controller.update_drink(identifier, data))
@PriceListPlugin.blueprint.route("/drinks/<int:identifier>", methods=["DELETE"]) @PriceListPlugin.blueprint.route("/drinks/<int:identifier>", methods=["DELETE"])
def delete_drink(identifier): @login_required(permission=permissions.DELETE)
def delete_drink(identifier, current_session):
"""Delete Drink
Route: ``/pricelist/drinks/<identifier>`` | 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) pricelist_controller.delete_drink(identifier)
return no_content() return no_content()
@PriceListPlugin.blueprint.route("/prices/<int:identifier>", methods=["DELETE"]) @PriceListPlugin.blueprint.route("/prices/<int:identifier>", methods=["DELETE"])
def delete_price(identifier): @login_required(permission=permissions.DELETE_PRICE)
def delete_price(identifier, current_session):
"""Delete Price
Route: ``/pricelist/prices/<identifier>`` | 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) pricelist_controller.delete_price(identifier)
return no_content() return no_content()
@PriceListPlugin.blueprint.route("/volumes/<int:identifier>", methods=["DELETE"]) @PriceListPlugin.blueprint.route("/volumes/<int:identifier>", methods=["DELETE"])
def delete_volume(identifier): @login_required(permission=permissions.DELETE_VOLUME)
def delete_volume(identifier, current_session):
"""Delete DrinkPriceVolume
Route: ``/pricelist/volumes/<identifier>`` | 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) pricelist_controller.delete_volume(identifier)
return no_content() return no_content()
@PriceListPlugin.blueprint.route("/ingredients/extraIngredients", methods=["GET"]) @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()) return jsonify(pricelist_controller.get_extra_ingredients())
@PriceListPlugin.blueprint.route("/ingredients/<int:identifier>", methods=["DELETE"]) @PriceListPlugin.blueprint.route("/ingredients/<int:identifier>", methods=["DELETE"])
def delete_ingredient(identifier): @login_required(permission=permissions.DELETE_INGREDIENTS_DRINK)
def delete_ingredient(identifier, current_session):
"""Delete Ingredient
Route: ``/pricelist/ingredients/<identifier>`` | 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) pricelist_controller.delete_ingredient(identifier)
return no_content() return no_content()
@PriceListPlugin.blueprint.route("/ingredients/extraIngredients", methods=["POST"]) @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() data = request.get_json()
return jsonify(pricelist_controller.set_extra_ingredient(data)) return jsonify(pricelist_controller.set_extra_ingredient(data))
@PriceListPlugin.blueprint.route("/ingredients/extraIngredients/<int:identifier>", methods=["PUT"]) @PriceListPlugin.blueprint.route("/ingredients/extraIngredients/<int:identifier>", 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() data = request.get_json()
return jsonify(pricelist_controller.update_extra_ingredient(identifier, data)) return jsonify(pricelist_controller.update_extra_ingredient(identifier, data))
@PriceListPlugin.blueprint.route("/ingredients/extraIngredients/<int:identifier>", methods=["DELETE"]) @PriceListPlugin.blueprint.route("/ingredients/extraIngredients/<int:identifier>", 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) pricelist_controller.delete_extra_ingredient(identifier)
return no_content() return no_content()
@PriceListPlugin.blueprint.route("/settings/min_prices", methods=["POST", "GET"]) @PriceListPlugin.blueprint.route("/settings/min_prices", methods=["GET"])
def pricelist_settings_min_prices(): @login_required()
if request.method == "GET": 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! # TODO: Handle if no prices are set!
try: try:
min_prices = PriceListPlugin.plugin.get_setting("min_prices") min_prices = PriceListPlugin.plugin.get_setting("min_prices")
except KeyError: except KeyError:
min_prices = [] min_prices = []
return jsonify(min_prices) return jsonify(min_prices)
else:
@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() data = request.get_json()
if not isinstance(data, list) or not all(isinstance(n, int) for n in data): if not isinstance(data, list) or not all(isinstance(n, int) for n in data):
raise BadRequest raise BadRequest
@ -201,8 +535,21 @@ def pricelist_settings_min_prices():
@PriceListPlugin.blueprint.route("/drinks/<int:identifier>/picture", methods=["POST", "GET", "DELETE"]) @PriceListPlugin.blueprint.route("/drinks/<int:identifier>/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/<identifier>/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": if request.method == "DELETE":
pricelist_controller.delete_drink_picture(identifier) pricelist_controller.delete_drink_picture(identifier)
return no_content() return no_content()
@ -219,6 +566,14 @@ def set_picture(identifier):
@PriceListPlugin.blueprint.route("/picture/<identifier>", methods=["GET"]) @PriceListPlugin.blueprint.route("/picture/<identifier>", methods=["GET"])
def _get_picture(identifier): def _get_picture(identifier):
"""Get Picture
Args:
identifier: Identifier of Picture
Returns:
Picture or HTTP-error
"""
if request.method == "GET": if request.method == "GET":
size = request.args.get("size") size = request.args.get("size")
response = pricelist_controller.get_drink_picture(identifier, size) response = pricelist_controller.get_drink_picture(identifier, size)

View File

@ -10,6 +10,18 @@ DELETE = "drink_delete"
CREATE_TAG = "drink_tag_create" CREATE_TAG = "drink_tag_create"
"""Can create and edit Tags""" """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" EDIT_TAG = "drink_tag_edit"
DELETE_TAG = "drink_tag_delete" DELETE_TAG = "drink_tag_delete"
@ -20,4 +32,6 @@ EDIT_TYPE = "drink_type_edit"
DELETE_TYPE = "drink_type_delete" DELETE_TYPE = "drink_type_delete"
EDIT_MIN_PRICES = "edit_min_prices"
permissions = [value for key, value in globals().items() if not key.startswith("_")] permissions = [value for key, value in globals().items() if not key.startswith("_")]

View File

@ -6,8 +6,10 @@ from flaschengeist import logger
from flaschengeist.config import config from flaschengeist.config import config
from flaschengeist.database import db from flaschengeist.database import db
from flaschengeist.utils.picture import save_picture, get_picture, delete_picture 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 .models import Drink, DrinkPrice, Ingredient, Tag, DrinkType, DrinkPriceVolume, DrinkIngredient, ExtraIngredient
from .permissions import EDIT_VOLUME, EDIT_PRICE, EDIT_INGREDIENTS_DRINK
def update(): def update():
@ -159,6 +161,7 @@ def set_drink(data):
def update_drink(identifier, data): def update_drink(identifier, data):
try: try:
session = extract_session()
if "id" in data: if "id" in data:
data.pop("id") data.pop("id")
volumes = data.pop("volumes") if "volumes" in data else None volumes = data.pop("volumes") if "volumes" in data else None
@ -184,7 +187,7 @@ def update_drink(identifier, data):
if drink_type: if drink_type:
drink.type = 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) set_volumes(volumes, drink)
if len(tags) > 0: if len(tags) > 0:
drink.tags = tags drink.tags = tags
@ -218,6 +221,7 @@ def get_volumes(drink_id=None):
def set_volume(data): def set_volume(data):
session = extract_session()
allowed_keys = DrinkPriceVolume().serialize().keys() allowed_keys = DrinkPriceVolume().serialize().keys()
values = {key: value for key, value in data.items() if key in allowed_keys} values = {key: value for key, value in data.items() if key in allowed_keys}
prices = None prices = None
@ -237,9 +241,9 @@ def set_volume(data):
for key, value in values.items(): for key, value in values.items():
setattr(volume, key, value if value != "" else None) setattr(volume, key, value if value != "" else None)
if prices: if prices and session.user_.has_permission(EDIT_PRICE):
set_prices(prices, volume) set_prices(prices, volume)
if ingredients: if ingredients and session.user_.has_permission(EDIT_INGREDIENTS_DRINK):
set_ingredients(ingredients, volume) set_ingredients(ingredients, volume)
return volume return volume