From 6f24b52855f100599b3d3781254373ddf5c42296 Mon Sep 17 00:00:00 2001 From: Midgard Date: Mon, 27 Jan 2020 02:31:02 +0100 Subject: [PATCH] Make creating order and adding items work --- app/forms.py | 16 +++++++------- app/hlds/models.py | 3 ++- app/models/order.py | 39 +++++++++++++++++++++------------- app/models/orderitem.py | 28 ++++++++++++++++++------ app/notification.py | 24 ++++++++++----------- app/templates/order.html | 32 ++++++++++++++-------------- app/templates/order_items.html | 4 ++-- app/utils.py | 16 ++++++++++++-- app/views/general.py | 10 ++------- app/views/order.py | 14 ++++++------ 10 files changed, 110 insertions(+), 76 deletions(-) diff --git a/app/forms.py b/app/forms.py index 5a1520e..e5c6981 100644 --- a/app/forms.py +++ b/app/forms.py @@ -7,10 +7,10 @@ from flask_wtf import FlaskForm as Form from wtforms import (DateTimeField, SelectField, StringField, SubmitField, validators) +from utils import euro_string from hlds.definitions import location_definitions from hlds.models import Location from models import User -from utils import euro_string class OrderForm(Form): @@ -18,7 +18,7 @@ class OrderForm(Form): # pylint: disable=R0903 courier_id = SelectField("Courier", coerce=int) location_id = SelectField( - "Location", coerce=int, validators=[validators.required()] + "Location", coerce=str, validators=[validators.required()] ) starttime = DateTimeField( "Starttime", default=datetime.now, format="%d-%m-%Y %H:%M" @@ -47,15 +47,15 @@ class OrderForm(Form): class OrderItemForm(Form): "Class which defines the form for a new Item in an Order" # pylint: disable=R0903 - product_id = SelectField("Item", coerce=int) - extra = StringField("Extra") + dish_id = SelectField("Dish") + comment = StringField("Comment") submit_button = SubmitField("Submit") def populate(self, location: Location) -> None: - "Fill in all the product options from the location" - self.product_id.choices = [ + "Fill in all the dish options from the location" + self.dish_id.choices = [ (i.id, (i.name + ": " + euro_string(i.price))) - for i in location.products + for i in location.dishes ] @@ -68,7 +68,7 @@ class AnonOrderItemForm(OrderItemForm): def populate(self, location: Location) -> None: """ - Fill in all the product options from the location and + Fill in all the dish options from the location and the name of the anon user """ OrderItemForm.populate(self, location) diff --git a/app/hlds/models.py b/app/hlds/models.py index dc1ec81..68160f1 100644 --- a/app/hlds/models.py +++ b/app/hlds/models.py @@ -2,6 +2,7 @@ # pylint: disable=too-few-public-methods from typing import Iterable, List, Mapping, Any, Optional +from utils import euro_string def _format_tags(tags: Iterable[str]) -> str: @@ -13,7 +14,7 @@ def _format_tags(tags: Iterable[str]) -> str: def _format_price(price: int) -> str: - return " € {}.{:02}".format(*divmod(price, 100)) if price else "" + return " {}".format(euro_string(price)) if price else "" def _format_type_and_choice(type_and_choice): diff --git a/app/models/order.py b/app/models/order.py index cf27ae2..4cdb185 100644 --- a/app/models/order.py +++ b/app/models/order.py @@ -2,6 +2,8 @@ import typing from datetime import datetime +from utils import first +from hlds.definitions import location_definitions from .database import db from .user import User @@ -18,13 +20,12 @@ class Order(db.Model): items = db.relationship("OrderItem", backref="order", lazy="dynamic") - def configure(self, courier: User, - starttime: db.DateTime, stoptime: db.DateTime,) -> None: - "Configure the Order" - # pylint: disable=W0201 - self.courier = courier - self.starttime = starttime - self.stoptime = stoptime + def __getattr__(self, name): + if name == "location": + location = first(filter(lambda l: l.id == self.location_id, location_definitions)) + if location: + return location + raise AttributeError() def __repr__(self) -> str: # pylint: disable=R1705 @@ -33,6 +34,14 @@ class Order(db.Model): else: return "Order %d" % (self.id) + def update_from_hlds(self) -> None: + """ + Update the location name from the HLDS definition. + User should commit after running this to make the change persistent. + """ + assert self.location_id, "location_id must be configured before updating from HLDS" + self.location_name = self.location.name + def group_by_user(self) -> typing.Dict[str, typing.Any]: "Group items of an Order by user" group: typing.Dict[str, typing.Any] = dict() @@ -44,20 +53,20 @@ class Order(db.Model): item.price if not item.paid else 0 ) user["paid"] = user.get("paid", True) and item.paid - user["products"] = user.get("products", []) + [item.dish_name] + user["dishes"] = user.get("dishes", []) + [item.dish_name] group[item.get_name()] = user return group - def group_by_product(self) -> typing.Dict[str, typing.Any]: - "Group items of an Order by product" + def group_by_dish(self) -> typing.Dict[str, typing.Any]: + "Group items of an Order by dish" group: typing.Dict[str, typing.Any] = dict() for item in self.items: - product = group.get(item.dish_name, dict()) - product["count"] = product.get("count", 0) + 1 - if item.extra: - product["extras"] = product.get("extras", []) + [item.comment] - group[item.dish_name] = product + dish = group.get(item.dish_name, dict()) + dish["count"] = dish.get("count", 0) + 1 + if item.comment: + dish["comments"] = dish.get("comments", []) + [item.comment] + group[item.dish_name] = dish return group diff --git a/app/models/orderitem.py b/app/models/orderitem.py index f597163..ec2cc88 100644 --- a/app/models/orderitem.py +++ b/app/models/orderitem.py @@ -1,6 +1,8 @@ "Script for everything OrderItem related in the database" from datetime import datetime +from utils import first +from hlds.definitions import location_definitions from .database import db from .order import Order from .user import User @@ -23,17 +25,21 @@ class OrderItem(db.Model): choices = db.relationship("OrderItemChoice", backref="order_item", lazy="dynamic") - def configure(self, user: User, order: Order) -> None: - "Configure the OrderItem" - # pylint: disable=W0201 - self.user = user - self.order = order + def __getattr__(self, name): + if name == "dish": + location_id = Order.query.filter(Order.id == self.order_id).first().location_id + location = first(filter(lambda l: l.id == location_id, location_definitions)) + if location: + return first(filter(lambda d: d.id == self.dish_id, location.dishes)) + else: + raise ValueError("No Location found with id: " + location_id) + raise AttributeError() def get_name(self) -> str: "Get the name of the user which 'owns' the item" if self.user_id is not None and self.user_id > 0: return self.user.username - return self.name + return self.user_name def __repr__(self) -> str: return "Order %d: %s wants %s" % ( @@ -42,6 +48,16 @@ class OrderItem(db.Model): self.dish_name or "None", ) + def update_from_hlds(self) -> None: + """ + Update the dish name and price from the HLDS definition. + User should commit after running this to make the change persistent. + """ + assert self.order_id, "order_id must be configured before updating from HLDS" + assert self.dish_id, "dish_id must be configured before updating from HLDS" + self.dish_name = self.dish.name + self.price = self.dish.price + # pylint: disable=W0613 def can_delete(self, order_id: int, user_id: int, name: str) -> bool: "Check if a user can delete an item" diff --git a/app/notification.py b/app/notification.py index 023b346..1218aa9 100644 --- a/app/notification.py +++ b/app/notification.py @@ -10,30 +10,30 @@ from flask import url_for from models.order import Order -def webhook_text(order_item: Order) -> typing.Optional[str]: +def webhook_text(order: Order) -> typing.Optional[str]: "Function that makes the text for the notification" - if "Testlocation" in order_item.location.name: + if order.location_id == "test": return None - if order_item.courier is not None: + if order.courier is not None: # pylint: disable=C0301 return " {3} is going to {1}, order <{0}|here>! Deadline in {2} minutes!".format( - url_for("order_bp.order_from_id", order_id=order_item.id, _external=True), - order_item.location.name, - remaining_minutes(order_item.stoptime), - order_item.courier.username.title(), + url_for("order_bp.order_from_id", order_id=order.id, _external=True), + order.location_name, + remaining_minutes(order.stoptime), + order.courier.username.title(), ) return " New order for {}. Deadline in {} minutes. <{}|Open here.>".format( - order_item.location.name, - remaining_minutes(order_item.stoptime), - url_for("order_bp.order_from_id", order_id=order_item.id, _external=True), + order.location_name, + remaining_minutes(order.stoptime), + url_for("order_bp.order_from_id", order_id=order.id, _external=True), ) -def post_order_to_webhook(order_item: Order) -> None: +def post_order_to_webhook(order: Order) -> None: "Function that sends the notification for the order" - message = webhook_text(order_item) + message = webhook_text(order) if message: webhookthread = WebhookSenderThread( message, app.config["SLACK_WEBHOOK"]) diff --git a/app/templates/order.html b/app/templates/order.html index 6783119..1fc3b78 100644 --- a/app/templates/order.html +++ b/app/templates/order.html @@ -43,15 +43,15 @@ Choose for me {{ form.csrf_token }} -
- {{ form.product_id.label(class='control-label') }}
- {{ form.product_id(class='form-control select') }} - {{ util.render_form_field_errors(form.product_id) }} +
+ {{ form.dish_id.label(class='control-label') }}
+ {{ form.dish_id(class='form-control select') }} + {{ util.render_form_field_errors(form.dish_id) }}
-
- {{ form.extra.label(class='control-label') }}
- {{ form.extra(class='form-control', placeholder='Fill in extras, when applicable') }} - {{ util.render_form_field_errors(form.extra) }} +
+ {{ form.comment.label(class='control-label') }}
+ {{ form.comment(class='form-control', placeholder='Fill in comment, when applicable') }} + {{ util.render_form_field_errors(form.comment) }}
{% if current_user.is_anonymous() %}
@@ -78,8 +78,8 @@ {% for item in order.items -%} {{ item.get_name() }} - {{ item.product.name }}{{ "*" if item.extra }} - {{ item.product.price|euro }} + {{ item.dish_name }}{{ "*" if item.comment }} + {{ item.price|euro }} {% if courier_or_admin %}{% if not item.paid %}
@@ -98,15 +98,15 @@
-

Ordered products: {{ order.items.count() }}

+

Ordered dishes: {{ order.items.count() }}

- {% for key, value in order.group_by_product().items() -%} + {% for key, value in order.group_by_dish().items() -%}
{{ key }}: {{ value["count"] }} - {% if value["extras"] -%} -
- {% for extra in value["extras"] -%} -
- {{ extra }}
+ {% if value["comments"] -%} +
+ {% for comment in value["comments"] -%} +
- {{ comment }}
{% endfor %}
{%- endif %} diff --git a/app/templates/order_items.html b/app/templates/order_items.html index 70fc2d3..4b94ba5 100644 --- a/app/templates/order_items.html +++ b/app/templates/order_items.html @@ -22,8 +22,8 @@ Haldis - Order {{ order.id }}
-

Ordered products: {{ order.items.count() }}

- {% for key, value in order.group_by_product().items() -%} +

Ordered dishes: {{ order.items.count() }}

+ {% for key, value in order.group_by_dish().items() -%}

{{ key }}: {{ value["count"] }} diff --git a/app/utils.py b/app/utils.py index d9b0f38..3e79cdd 100644 --- a/app/utils.py +++ b/app/utils.py @@ -1,9 +1,21 @@ "Script which contains several utils for Haldis" +from typing import Iterable + def euro_string(value: int) -> str: """ Convert cents to string formatted euro """ - result = "€ {:.2f}".format(round(value / 100, 2)) - return result + return "€ {}.{:02}".format(*divmod(value, 100)) + + +def first(iterable: Iterable, default=None): + """ + Return first element of iterable + """ + try: + return next(iter(iterable)) + except StopIteration: + return default + diff --git a/app/views/general.py b/app/views/general.py index fe36cf9..146809d 100644 --- a/app/views/general.py +++ b/app/views/general.py @@ -12,6 +12,7 @@ from flask import current_app as app from flask import send_from_directory, url_for from flask_login import login_required +from utils import first from hlds.definitions import location_definitions from hlds.models import Location from models import Order @@ -21,13 +22,6 @@ from views.order import get_orders general_bp = Blueprint("general_bp", __name__) -def _first(iterable: typing.Iterable, default=None): - try: - return next(iter(iterable)) - except StopIteration: - return default - - @general_bp.route("/") def home() -> str: "Generate the home view" @@ -131,7 +125,7 @@ def locations() -> str: @general_bp.route("/location/") def location(location_id) -> str: "Generate the location view given an id" - loc = _first(filter(lambda l: l.id == location_id, location_definitions)) + loc = first(filter(lambda l: l.id == location_id, location_definitions)) if loc is None: abort(404) return render_template("location.html", location=loc, title=loc.name) diff --git a/app/views/order.py b/app/views/order.py index bb88093..bdc9776 100644 --- a/app/views/order.py +++ b/app/views/order.py @@ -37,6 +37,7 @@ def order_create() -> typing.Union[str, Response]: if orderForm.validate_on_submit(): order = Order() orderForm.populate_obj(order) + order.update_from_hlds() db.session.add(order) db.session.commit() post_order_to_webhook(order) @@ -59,8 +60,8 @@ def order_from_id(order_id: int, form: OrderForm = None) -> str: form.populate(order.location) if order.stoptime and order.stoptime < datetime.now(): form = None - total_price = sum([o.product.price for o in order.items]) - debts = sum([o.product.price for o in order.items if not o.paid]) + total_price = sum([o.price for o in order.items]) + debts = sum([o.price for o in order.items if not o.paid]) return render_template("order.html", order=order, form=form, total_price=total_price, debts=debts) @@ -121,9 +122,10 @@ def order_item_create(order_id: int) -> typing.Any: 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.product.name), "success") + 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) @@ -138,7 +140,7 @@ def item_paid(order_id: int, item_id: int) -> typing.Optional[Response]: if item.order.courier_id == user_id or current_user.admin: item.paid = True db.session.commit() - flash("Paid %s by %s" % (item.product.name, item.get_name()), + flash("Paid %s by %s" % (item.dish_name, item.get_name()), "success") return redirect(url_for("order_bp.order_from_id", order_id=order_id)) abort(404) @@ -184,10 +186,10 @@ def delete_item(order_id: int, item_id: int) -> typing.Any: print("%s tries to delete orders" % (current_user.username)) user_id = current_user.id if item.can_delete(order_id, user_id, session.get("anon_name", "")): - product_name = item.product.name + dish_name = item.dish_name db.session.delete(item) db.session.commit() - flash("Deleted %s" % (product_name), "success") + flash("Deleted %s" % (dish_name), "success") return redirect(url_for("order_bp.order_from_id", order_id=order_id)) abort(404)