From 1025ade7588b4a098d30bea4f3d4c1e905c95dd6 Mon Sep 17 00:00:00 2001 From: Midgard Date: Mon, 24 Feb 2020 00:31:14 +0100 Subject: [PATCH] Fix validation and saving for multi_choice, add price range --- app/forms.py | 11 +++++++++-- app/hlds/models.py | 13 ++++++++++++- app/hlds/parser.py | 15 ++++++++++++--- app/utils.py | 3 +++ app/views/order.py | 22 +++++++++++++++++----- 5 files changed, 53 insertions(+), 11 deletions(-) diff --git a/app/forms.py b/app/forms.py index 08f42f3..f57dc0c 100644 --- a/app/forms.py +++ b/app/forms.py @@ -53,10 +53,17 @@ class OrderItemForm(Form): comment = StringField("Comment") submit_button = SubmitField("Submit") + @staticmethod + def format_price_range(price_range): + if price_range[0] == price_range[1]: + return euro_string(price_range[0]) + else: + return "from {}".format(euro_string(price_range[0])) + def populate(self, location: Location, dish_id: Optional[str]) -> None: self.dish_id.choices = [ - (i.id, (i.name + ": " + euro_string(i.price))) - for i in location.dishes + (dish.id, (dish.name + ": " + self.format_price_range(dish.price_range()))) + for dish in location.dishes ] diff --git a/app/hlds/models.py b/app/hlds/models.py index 43cac53..6d83abb 100644 --- a/app/hlds/models.py +++ b/app/hlds/models.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # pylint: disable=too-few-public-methods -from typing import Iterable, List, Mapping, Any, Optional +from typing import Iterable, List, Tuple, Mapping, Any, Optional from utils import euro_string, first @@ -78,6 +78,17 @@ class Dish: "\n\t".join(map(_format_type_and_choice, self.choices)) ) + def price_range(self) -> Tuple[int, int]: + return (self.price + self._sum_f_option_prices(min), + self.price + self._sum_f_option_prices(max)) + + def _sum_f_option_prices(self, f): + return sum( + f(option.price for option in choice.options) + for (choice_type, choice) in self.choices + if choice_type == "single_choice" + ) + class Location: def __init__(self, id_, *, name, dishes, osm=None, address=None, telephone=None, website=None): diff --git a/app/hlds/parser.py b/app/hlds/parser.py index f8761db..9fa689a 100644 --- a/app/hlds/parser.py +++ b/app/hlds/parser.py @@ -8,6 +8,7 @@ from tatsu import parse as tatsu_parse from tatsu.ast import AST from tatsu.exceptions import SemanticError from .models import Location, Choice, Option, Dish +from utils import first # TODO Use proper way to get resources, see https://stackoverflow.com/a/10935674 @@ -29,8 +30,16 @@ class HldsSemanticActions: if not isinstance(choice[1], Choice): dish.choices[i] = (dish.choices[i][0], choices[choice[1]]) + # Move the base price to the first single choice if there is any + first_single_choice = first(c[1] for c in dish.choices if c[0] == "single_choice") + if dish.price and first_single_choice: + for option in first_single_choice.options: + option.price += dish.price + dish.price = 0 + attributes = {att["key"]: att["value"] for att in ast["attributes"]} + return Location( ast["id"], name=ast["name"], @@ -46,8 +55,8 @@ class HldsSemanticActions: ast["id"], name=ast["name"], description=ast["description"], - price=ast["price"] if ast["price"] else 0, - tags=ast["tags"] if ast["tags"] else [], + price=ast["price"] or 0, + tags=ast["tags"] or [], choices=ast["choices"], ) @@ -74,7 +83,7 @@ class HldsSemanticActions: ast["id"], name=ast["name"], description=ast["description"], - price=ast["price"] if ast["price"] else 0, + price=ast["price"] or 0, tags=ast["tags"], ) diff --git a/app/utils.py b/app/utils.py index 3e79cdd..f0b4245 100644 --- a/app/utils.py +++ b/app/utils.py @@ -19,3 +19,6 @@ def first(iterable: Iterable, default=None): except StopIteration: return default + +def ignore_none(iterable: Iterable): + return filter(lambda x: x is not None, iterable) diff --git a/app/views/order.py b/app/views/order.py index 4e0658a..dbc79f5 100644 --- a/app/views/order.py +++ b/app/views/order.py @@ -13,6 +13,7 @@ from forms import AnonOrderItemForm, OrderForm, OrderItemForm from models import Order, OrderItem, User, db from hlds.definitions import location_definitions from notification import post_order_to_webhook +from utils import ignore_none order_bp = Blueprint("order_bp", "order") @@ -104,6 +105,11 @@ def order_edit(order_id: int) -> typing.Union[str, Response]: return render_template("order_edit.html", form=orderForm, order_id=order_id) +def _name(option): + try: + return option.name + except AttributeError: + return ", ".join(o.name for o in option) @order_bp.route("//create", methods=["GET", "POST"]) def order_item_create(order_id: int) -> typing.Any: @@ -141,10 +147,14 @@ def order_item_create(order_id: int) -> typing.Any: # The form's validation tests that dish_id is valid and gives a friendly error if it's not choices = location.dish_by_id(form.dish_id.data).choices chosen = [ - choice.option_by_id(request.form.get("choice_" + choice.id)) - for (_choice_type, choice) in choices + ( + choice.option_by_id(request.form.get("choice_" + choice.id)) + if choice_type == "single_choice" else + list(ignore_none(request.form.getlist("choice_" + choice.id, type=choice.option_by_id))) + ) + for (choice_type, choice) in choices ] - all_choices_present = all(chosen) + all_choices_present = all(x is not None for x in chosen) if not all_choices_present: return redirect(url_for("order_bp.order_item_create", order_id=order_id, dish=form.dish_id.data)) @@ -158,8 +168,10 @@ def order_item_create(order_id: int) -> typing.Any: session["anon_name"] = item.name # XXX Temporary - chosen_text = "; ".join(option.name for option in chosen) - item.comment = chosen_text + "; Comment: " + item.comment if item.comment else chosen_text + comments = [_name(option) for option in chosen if option] + if item.comment: + comments.append("Comment: " + item.comment) + item.comment = "; ".join(comments) item.update_from_hlds() db.session.add(item)