diff --git a/app/forms.py b/app/forms.py
index e5c6981..86bb9c1 100644
--- a/app/forms.py
+++ b/app/forms.py
@@ -1,15 +1,17 @@
"Script for everything form related in Haldis"
from datetime import datetime, timedelta
+from typing import Optional
+
from flask import session
from flask_login import current_user
from flask_wtf import FlaskForm as Form
-from wtforms import (DateTimeField, SelectField, StringField, SubmitField,
- validators)
+from wtforms import (DateTimeField, SelectField, SelectMultipleField, StringField, SubmitField,
+ FieldList, validators)
from utils import euro_string
from hlds.definitions import location_definitions
-from hlds.models import Location
+from hlds.models import Location, Dish, Choice
from models import User
@@ -45,18 +47,42 @@ class OrderForm(Form):
class OrderItemForm(Form):
- "Class which defines the form for a new Item in an Order"
+ "New Item in an Order"
# pylint: disable=R0903
dish_id = SelectField("Dish")
+ single_choices = FieldList(SelectField())
+ multi_choices = FieldList(SelectMultipleField())
comment = StringField("Comment")
submit_button = SubmitField("Submit")
- def populate(self, location: Location) -> None:
- "Fill in all the dish options from the location"
+ 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 = location.dish_by_id(dish_id) if dish_id else None
+ if dish:
+ self.add_choices_for(dish)
+
+ def add_choices_for(self, dish: Dish):
+ for (choice_type, choice) in dish.choices:
+ if choice_type == "single_choice":
+ field = self.single_choices.append_entry(choice.name)
+ elif choice_type == "multi_choice":
+ field = self.multi_choices.append_entry(choice.name)
+ else:
+ assert False, "Unsupported choice type"
+ field.label.text = choice.name
+ field.choices = self.options_for(choice)
+
+ @staticmethod
+ def options_for(choice: Choice):
+ return [
+ (c.id, (c.name +
+ (" (" + c.description + ")" if c.description else "") +
+ (": +" + euro_string(c.price) if c.price else "")))
+ for c in choice.options
+ ]
class AnonOrderItemForm(OrderItemForm):
diff --git a/app/hlds/models.py b/app/hlds/models.py
index 68160f1..2a1161d 100644
--- a/app/hlds/models.py
+++ b/app/hlds/models.py
@@ -2,7 +2,7 @@
# pylint: disable=too-few-public-methods
from typing import Iterable, List, Mapping, Any, Optional
-from utils import euro_string
+from utils import euro_string, first
def _format_tags(tags: Iterable[str]) -> str:
@@ -63,6 +63,7 @@ class Dish:
self.price: int = price
self.tags: List[str] = tags
+ # The str in (str, Choice) is the type of choice: single_choice or multi_choice
self.choices: List[(str, Choice)] = choices
def __str__(self):
@@ -86,6 +87,9 @@ class Location:
self.dishes: List[Dish] = dishes
+ def dish_by_id(self, dish_id: str) -> Optional[Dish]:
+ return first(filter(lambda d: d.id == dish_id, self.dishes))
+
def __str__(self):
return (
"============================\n"
diff --git a/app/templates/order.html b/app/templates/order.html
index 51a617b..37a6f88 100644
--- a/app/templates/order.html
+++ b/app/templates/order.html
@@ -57,6 +57,21 @@
{{ form.comment(class='form-control', placeholder='Fill in comment, when applicable') }}
{{ util.render_form_field_errors(form.comment) }}
+
+ {% for choice_field in form.single_choices %}
+
+ {{ choice_field.label(class='control-label') }}
+ {{ choice_field(class='form-control') }}
+ {{ util.render_form_field_errors(choice_field) }}
+
+ {% endfor %}
+ {% for choice_field in form.multi_choices %}
+
+ {{ choice_field.label(class='control-label') }}
+ {{ choice_field(class='form-control') }}
+ {{ util.render_form_field_errors(choice_field) }}
+
+ {% endfor %}
{% if current_user.is_anonymous() %}
{{ form.name.label(class='control-label') }}
diff --git a/app/views/order.py b/app/views/order.py
index 2bff4c5..a36b5c7 100644
--- a/app/views/order.py
+++ b/app/views/order.py
@@ -11,6 +11,7 @@ from flask_login import current_user, login_required
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
order_bp = Blueprint("order_bp", "order")
@@ -58,7 +59,7 @@ def order_from_id(order_id: int, form: OrderForm = None) -> str:
form = AnonOrderItemForm() if current_user.is_anonymous() \
else OrderItemForm()
if order.location:
- form.populate(order.location)
+ form.populate(order.location, None)
if order.is_closed():
form = None
total_price = sum([o.price for o in order.items])
@@ -101,7 +102,7 @@ def order_edit(order_id: int) -> typing.Union[str, Response]:
order_id=order_id)
-@order_bp.route("//create", methods=["POST"])
+@order_bp.route("//create", methods=["GET", "POST"])
def order_item_create(order_id: int) -> typing.Any:
# type is 'typing.Union[str, Response]', but this errors due to
# https://github.com/python/mypy/issues/7187
@@ -114,23 +115,46 @@ def order_item_create(order_id: int) -> typing.Any:
if current_user.is_anonymous() and not current_order.public:
flash("Please login to see this order.", "info")
abort(401)
+ location = current_order.location
+ # If location doesn't exist any more, adding items is nonsensical
+ if not location:
+ abort(404)
form = AnonOrderItemForm() if current_user.is_anonymous() \
else OrderItemForm()
- form.populate(current_order.location)
- if form.validate_on_submit():
- item = OrderItem()
- form.populate_obj(item)
- item.order_id = order_id
- if not current_user.is_anonymous():
- item.user_id = current_user.id
- else:
- session["anon_name"] = item.name
- item.update_from_hlds()
- db.session.add(item)
- db.session.commit()
- flash("Ordered %s" % (item.dish_name), "success")
- return redirect(url_for("order_bp.order_from_id", order_id=order_id))
- return order_from_id(order_id, form=form)
+
+ dish_id = form.dish_id.data if form.is_submitted() else request.args.get("dish")
+ if dish_id and not location.dish_by_id(dish_id):
+ abort(404)
+ form.populate(current_order.location, dish_id)
+
+ if not form.validate_on_submit():
+ return order_from_id(order_id, form=form)
+
+ # Form was submitted and is valid
+
+ # The form's validation tests that dish_id is valid and gives a friendly error if it's not
+ form_data = form.data
+ choices = location.dish_by_id(form_data["dish_id"]).choices
+ all_choices_present = all(
+ ("choice_" + choice.id) in form_data
+ for (_choice_type, choice) in choices
+ )
+ if not all_choices_present:
+ return redirect(url_for("order_bp.order_item_create",
+ order_id=order_id, dish=form_data["dish_id"]))
+
+ item = OrderItem()
+ form.populate_obj(item)
+ item.order_id = order_id
+ if not current_user.is_anonymous():
+ item.user_id = current_user.id
+ else:
+ session["anon_name"] = item.name
+ item.update_from_hlds()
+ db.session.add(item)
+ db.session.commit()
+ flash("Ordered %s" % (item.dish_name), "success")
+ return redirect(url_for("order_bp.order_from_id", order_id=order_id))
@order_bp.route("///paid", methods=["POST"])