Make creating order and adding items work

This commit is contained in:
Midgard 2020-01-27 02:31:02 +01:00
parent 0e7675cec6
commit 6f24b52855
Signed by: midgard
GPG key ID: 511C112F1331BBB4
10 changed files with 110 additions and 76 deletions

View file

@ -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)

View file

@ -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):

View file

@ -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

View file

@ -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"

View file

@ -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 "<!channel|@channel> {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 "<!channel|@channel> 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"])

View file

@ -43,15 +43,15 @@
<a class="btn btn-primary" onclick="chooseRandom()">Choose for me</a>
</span>
{{ form.csrf_token }}
<div class="form-group select2-container select2 {{ 'has-errors' if form.product_id.errors}}">
{{ form.product_id.label(class='control-label') }}<br>
{{ form.product_id(class='form-control select') }}
{{ util.render_form_field_errors(form.product_id) }}
<div class="form-group select2-container select2 {{ 'has-errors' if form.dish_id.errors}}">
{{ form.dish_id.label(class='control-label') }}<br>
{{ form.dish_id(class='form-control select') }}
{{ util.render_form_field_errors(form.dish_id) }}
</div>
<div class="form-group {{ 'has-errors' if form.product_id.errors }}">
{{ form.extra.label(class='control-label') }}<br>
{{ form.extra(class='form-control', placeholder='Fill in extras, when applicable') }}
{{ util.render_form_field_errors(form.extra) }}
<div class="form-group {{ 'has-errors' if form.dish_id.errors }}">
{{ form.comment.label(class='control-label') }}<br>
{{ form.comment(class='form-control', placeholder='Fill in comment, when applicable') }}
{{ util.render_form_field_errors(form.comment) }}
</div>
{% if current_user.is_anonymous() %}
<div class="form-group{{ ' has-error' if form.name.errors }}{{ ' required' if form.name.flags.required }}">
@ -78,8 +78,8 @@
{% for item in order.items -%}
<tr>
<td>{{ item.get_name() }}</td>
<td><span title="{{ item.extra if item.extra }}">{{ item.product.name }}{{ "*" if item.extra }}</span></td>
<td>{{ item.product.price|euro }}</td>
<td><span title="{{ item.comment if item.comment }}">{{ item.dish_name }}{{ "*" if item.comment }}</span></td>
<td>{{ item.price|euro }}</td>
{% if courier_or_admin %}<td>{% if not item.paid %}
<form action="{{ url_for('order_bp.item_paid', order_id=order.id, item_id=item.id) }}" method="post" style="display:inline">
<input type="submit" class="btn btn-xs btn-primary" value="Pay"></input>
@ -98,15 +98,15 @@
</table>
</div>
<div class="col-md-push-2 col-md-4 darker box order_ordered" id="items-ordered">
<h3>Ordered products: {{ order.items.count() }}</h3>
<h3>Ordered dishes: {{ order.items.count() }}</h3>
<a class="divLink" href="{{ url_for('order_bp.items_showcase', order_id=order.id) }}"></a>
{% for key, value in order.group_by_product().items() -%}
{% for key, value in order.group_by_dish().items() -%}
<div class="product">
{{ key }}: {{ value["count"] }}
{% if value["extras"] -%}
<div class="extras">
{% for extra in value["extras"] -%}
<div>- {{ extra }}</div>
{% if value["comments"] -%}
<div class="comments">
{% for comment in value["comments"] -%}
<div>- {{ comment }}</div>
{% endfor %}
</div>
{%- endif %}

View file

@ -22,8 +22,8 @@ Haldis - Order {{ order.id }}
<div class="row">
<div class="col-sm-6 col-sm-offset-3 darker showcase" id="items-ordered">
<h3 class="text-center">Ordered products: {{ order.items.count() }}</h3>
{% for key, value in order.group_by_product().items() -%}
<h3 class="text-center">Ordered dishes: {{ order.items.count() }}</h3>
{% for key, value in order.group_by_dish().items() -%}
<div class="product text-center">
<p>
{{ key }}: {{ value["count"] }}

View file

@ -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

View file

@ -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/<location_id>")
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)

View file

@ -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)