Add form for choices, submitting fails
This commit is contained in:
parent
91e76c2b45
commit
f900c85931
4 changed files with 93 additions and 24 deletions
38
app/forms.py
38
app/forms.py
|
@ -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):
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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') }}
|
||||||
|
|
|
@ -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"])
|
||||||
|
|
Loading…
Reference in a new issue