Add form for choices, submitting fails

This commit is contained in:
Midgard 2020-02-21 18:38:30 +01:00
parent 91e76c2b45
commit f900c85931
Signed by: midgard
GPG key ID: 511C112F1331BBB4
4 changed files with 93 additions and 24 deletions

View file

@ -1,15 +1,17 @@
"Script for everything form related in Haldis" "Script for everything form related in Haldis"
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Optional
from flask import session from flask import session
from flask_login import current_user from flask_login import current_user
from flask_wtf import FlaskForm as Form from flask_wtf import FlaskForm as Form
from wtforms import (DateTimeField, SelectField, StringField, SubmitField, from wtforms import (DateTimeField, SelectField, SelectMultipleField, StringField, SubmitField,
validators) FieldList, validators)
from utils import euro_string from utils import euro_string
from hlds.definitions import location_definitions from hlds.definitions import location_definitions
from hlds.models import Location from hlds.models import Location, Dish, Choice
from models import User from models import User
@ -45,18 +47,42 @@ class OrderForm(Form):
class OrderItemForm(Form): class OrderItemForm(Form):
"Class which defines the form for a new Item in an Order" "New Item in an Order"
# pylint: disable=R0903 # pylint: disable=R0903
dish_id = SelectField("Dish") dish_id = SelectField("Dish")
single_choices = FieldList(SelectField())
multi_choices = FieldList(SelectMultipleField())
comment = StringField("Comment") comment = StringField("Comment")
submit_button = SubmitField("Submit") submit_button = SubmitField("Submit")
def populate(self, location: Location) -> None: def populate(self, location: Location, dish_id: Optional[str]) -> None:
"Fill in all the dish options from the location"
self.dish_id.choices = [ self.dish_id.choices = [
(i.id, (i.name + ": " + euro_string(i.price))) (i.id, (i.name + ": " + euro_string(i.price)))
for i in location.dishes 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): class AnonOrderItemForm(OrderItemForm):

View file

@ -2,7 +2,7 @@
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
from typing import Iterable, List, Mapping, Any, Optional 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: def _format_tags(tags: Iterable[str]) -> str:
@ -63,6 +63,7 @@ class Dish:
self.price: int = price self.price: int = price
self.tags: List[str] = tags 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 self.choices: List[(str, Choice)] = choices
def __str__(self): def __str__(self):
@ -86,6 +87,9 @@ class Location:
self.dishes: List[Dish] = dishes 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): def __str__(self):
return ( return (
"============================\n" "============================\n"

View file

@ -57,6 +57,21 @@
{{ form.comment(class='form-control', placeholder='Fill in comment, when applicable') }} {{ form.comment(class='form-control', placeholder='Fill in comment, when applicable') }}
{{ util.render_form_field_errors(form.comment) }} {{ util.render_form_field_errors(form.comment) }}
</div> </div>
{% for choice_field in form.single_choices %}
<div class="form-group {{ 'has-errors' if choice_field }}">
{{ choice_field.label(class='control-label') }}<br>
{{ choice_field(class='form-control') }}
{{ util.render_form_field_errors(choice_field) }}
</div>
{% endfor %}
{% for choice_field in form.multi_choices %}
<div class="form-group {{ 'has-errors' if choice_field }}">
{{ choice_field.label(class='control-label') }}<br>
{{ choice_field(class='form-control') }}
{{ util.render_form_field_errors(choice_field) }}
</div>
{% endfor %}
{% if current_user.is_anonymous() %} {% if current_user.is_anonymous() %}
<div class="form-group{{ ' has-error' if form.name.errors }}{{ ' required' if form.name.flags.required }}"> <div class="form-group{{ ' has-error' if form.name.errors }}{{ ' required' if form.name.flags.required }}">
{{ form.name.label(class='control-label') }} {{ form.name.label(class='control-label') }}

View file

@ -11,6 +11,7 @@ from flask_login import current_user, login_required
from forms import AnonOrderItemForm, OrderForm, OrderItemForm from forms import AnonOrderItemForm, OrderForm, OrderItemForm
from models import Order, OrderItem, User, db from models import Order, OrderItem, User, db
from hlds.definitions import location_definitions
from notification import post_order_to_webhook from notification import post_order_to_webhook
order_bp = Blueprint("order_bp", "order") 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() \ form = AnonOrderItemForm() if current_user.is_anonymous() \
else OrderItemForm() else OrderItemForm()
if order.location: if order.location:
form.populate(order.location) form.populate(order.location, None)
if order.is_closed(): if order.is_closed():
form = None form = None
total_price = sum([o.price for o in order.items]) 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_id=order_id)
@order_bp.route("/<order_id>/create", methods=["POST"]) @order_bp.route("/<order_id>/create", methods=["GET", "POST"])
def order_item_create(order_id: int) -> typing.Any: def order_item_create(order_id: int) -> typing.Any:
# type is 'typing.Union[str, Response]', but this errors due to # type is 'typing.Union[str, Response]', but this errors due to
# https://github.com/python/mypy/issues/7187 # https://github.com/python/mypy/issues/7187
@ -114,10 +115,34 @@ def order_item_create(order_id: int) -> typing.Any:
if current_user.is_anonymous() and not current_order.public: if current_user.is_anonymous() and not current_order.public:
flash("Please login to see this order.", "info") flash("Please login to see this order.", "info")
abort(401) 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() \ form = AnonOrderItemForm() if current_user.is_anonymous() \
else OrderItemForm() else OrderItemForm()
form.populate(current_order.location)
if form.validate_on_submit(): 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() item = OrderItem()
form.populate_obj(item) form.populate_obj(item)
item.order_id = order_id item.order_id = order_id
@ -130,7 +155,6 @@ def order_item_create(order_id: int) -> typing.Any:
db.session.commit() db.session.commit()
flash("Ordered %s" % (item.dish_name), "success") flash("Ordered %s" % (item.dish_name), "success")
return redirect(url_for("order_bp.order_from_id", order_id=order_id)) return redirect(url_for("order_bp.order_from_id", order_id=order_id))
return order_from_id(order_id, form=form)
@order_bp.route("/<order_id>/<item_id>/paid", methods=["POST"]) @order_bp.route("/<order_id>/<item_id>/paid", methods=["POST"])