Merge pull request #187 from ZeusWPI/feature/invitelink-token

Feature/invitelink token
This commit is contained in:
Maxime 2022-05-20 19:15:20 +02:00 committed by GitHub
commit f87f3c5446
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 129 additions and 94 deletions

View file

@ -1,7 +1,9 @@
"Script for everything Order related in the database" """Script for everything Order related in the database"""
import typing import typing
from collections import defaultdict from collections import defaultdict
from datetime import datetime from datetime import datetime
import secrets
import string
from hlds.definitions import location_definitions from hlds.definitions import location_definitions
from utils import first from utils import first
@ -10,8 +12,13 @@ from .database import db
from .user import User from .user import User
def generate_slug():
alphabet = string.ascii_letters + string.digits
return ''.join(secrets.choice(alphabet) for i in range(7))
class Order(db.Model): class Order(db.Model):
"Class used for configuring the Order model in the database" """Class used for configuring the Order model in the database"""
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
courier_id = db.Column(db.Integer, nullable=True) courier_id = db.Column(db.Integer, nullable=True)
location_id = db.Column(db.String(64)) location_id = db.Column(db.String(64))
@ -19,6 +26,7 @@ class Order(db.Model):
starttime = db.Column(db.DateTime) starttime = db.Column(db.DateTime)
stoptime = db.Column(db.DateTime) stoptime = db.Column(db.DateTime)
public = db.Column(db.Boolean, default=True) public = db.Column(db.Boolean, default=True)
slug = db.Column(db.String(7), default=generate_slug, unique=True)
items = db.relationship("OrderItem", backref="order", lazy="dynamic") items = db.relationship("OrderItem", backref="order", lazy="dynamic")
@ -47,7 +55,7 @@ class Order(db.Model):
self.location_name = self.location.name self.location_name = self.location.name
def for_user(self, anon=None, user=None) -> typing.List: def for_user(self, anon=None, user=None) -> typing.List:
"Get the items for a certain user" """Get the items for a certain user"""
return list( return list(
filter( filter(
(lambda i: i.user == user) (lambda i: i.user == user)
@ -58,7 +66,7 @@ class Order(db.Model):
) )
def group_by_user(self) -> typing.List[typing.Tuple[str, typing.List]]: def group_by_user(self) -> typing.List[typing.Tuple[str, typing.List]]:
"Group items of an Order by user" """Group items of an Order by user"""
group: typing.Dict[str, typing.List] = {} group: typing.Dict[str, typing.List] = {}
# pylint: disable=E1133 # pylint: disable=E1133
@ -78,7 +86,7 @@ class Order(db.Model):
) -> typing.List[ ) -> typing.List[
typing.Tuple[str, int, typing.List[typing.Tuple[str, typing.List]]] typing.Tuple[str, int, typing.List[typing.Tuple[str, typing.List]]]
]: ]:
"Group items of an Order by dish" """Group items of an Order by dish"""
group: typing.Dict[str, typing.Dict[str, typing.List]] = defaultdict( group: typing.Dict[str, typing.Dict[str, typing.List]] = defaultdict(
lambda: defaultdict(list) lambda: defaultdict(list)
) )
@ -101,11 +109,11 @@ class Order(db.Model):
) )
def is_closed(self) -> bool: def is_closed(self) -> bool:
"Return whether or not the order is closed" """Return whether the order is closed"""
return self.stoptime and datetime.now() > self.stoptime return self.stoptime and datetime.now() > self.stoptime
def can_close(self, user_id: int) -> bool: def can_close(self, user_id: int) -> bool:
"Check if a user can close the Order" """Check if a user can close the Order"""
if self.stoptime and self.stoptime < datetime.now(): if self.stoptime and self.stoptime < datetime.now():
return False return False
user = None user = None

View file

@ -10,7 +10,7 @@ from .user import User
class OrderItem(db.Model): class OrderItem(db.Model):
"Class used for configuring the OrderItem model in the database" """Class used for configuring the OrderItem model in the database"""
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
order_id = db.Column(db.Integer, db.ForeignKey("order.id"), nullable=False) order_id = db.Column(db.Integer, db.ForeignKey("order.id"), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey("user.id")) user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
@ -61,7 +61,7 @@ class OrderItem(db.Model):
# pylint: disable=W0613 # pylint: disable=W0613
def can_delete(self, order_id: int, user_id: int, name: str) -> bool: def can_delete(self, order_id: int, user_id: int, name: str) -> bool:
"Check if a user can delete an item" """Check if a user can delete an item"""
if int(self.order_id) != int(order_id): if int(self.order_id) != int(order_id):
return False return False
if self.order.is_closed(): if self.order.is_closed():

View file

@ -11,14 +11,14 @@ from models.order import Order
def webhook_text(order: Order) -> typing.Optional[str]: def webhook_text(order: Order) -> typing.Optional[str]:
"Function that makes the text for the notification" """Function that makes the text for the notification"""
if order.location_id == "test": if order.location_id == "test":
return None return None
if order.courier is not None: if order.courier is not None:
# pylint: disable=C0301, C0209 # pylint: disable=C0301, C0209
return "<!channel|@channel> {3} is going to {1}, order <{0}|here>! Deadline in {2} minutes!".format( 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.id, _external=True), url_for("order_bp.order_from_slug", order_slug=order.slug, _external=True),
order.location_name, order.location_name,
remaining_minutes(order.stoptime), remaining_minutes(order.stoptime),
order.courier.username.title(), order.courier.username.title(),
@ -28,12 +28,12 @@ def webhook_text(order: Order) -> typing.Optional[str]:
return "<!channel|@channel> New order for {}. Deadline in {} minutes. <{}|Open here.>".format( return "<!channel|@channel> New order for {}. Deadline in {} minutes. <{}|Open here.>".format(
order.location_name, order.location_name,
remaining_minutes(order.stoptime), remaining_minutes(order.stoptime),
url_for("order_bp.order_from_id", order_id=order.id, _external=True), url_for("order_bp.order_from_slug", order_slug=order.slug, _external=True),
) )
def post_order_to_webhook(order: Order) -> None: def post_order_to_webhook(order: Order) -> None:
"Function that sends the notification for the order" """Function that sends the notification for the order"""
message = webhook_text(order) message = webhook_text(order)
if message: if message:
webhookthread = WebhookSenderThread(message, app.config["SLACK_WEBHOOK"]) webhookthread = WebhookSenderThread(message, app.config["SLACK_WEBHOOK"])
@ -41,7 +41,7 @@ def post_order_to_webhook(order: Order) -> None:
class WebhookSenderThread(Thread): class WebhookSenderThread(Thread):
"Extension of the Thread class, which sends a webhook for the notification" """Extension of the Thread class, which sends a webhook for the notification"""
def __init__(self, message: str, url: str) -> None: def __init__(self, message: str, url: str) -> None:
super().__init__() super().__init__()
@ -52,7 +52,7 @@ class WebhookSenderThread(Thread):
self.slack_webhook() self.slack_webhook()
def slack_webhook(self) -> None: def slack_webhook(self) -> None:
"The webhook for the specified chat platform" """The webhook for the specified chat platform"""
if self.url: if self.url:
requests.post(self.url, json={"text": self.message}) requests.post(self.url, json={"text": self.message})
else: else:
@ -60,7 +60,7 @@ class WebhookSenderThread(Thread):
def remaining_minutes(value) -> str: def remaining_minutes(value) -> str:
"Return the remaining minutes until the deadline of and order" """Return the remaining minutes until the deadline of and order"""
delta = value - datetime.now() delta = value - datetime.now()
if delta.total_seconds() < 0: if delta.total_seconds() < 0:
return "0" return "0"

2
app/static/js/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
app/static/js/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -12,10 +12,27 @@
{% block metas %} {% block metas %}
{{ super() }} {{ super() }}
<meta name="robots" content="noindex, nofollow"> <meta name="robots" content="noindex, nofollow">
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/qrcode.min.js') }}"></script>
{% endblock %} {% endblock %}
{% block container %} {% block container %}
<header> <header class="row">
<div class="col-md-2" style="padding-top: 2em">
<div id="qrcode"></div>
<script type="text/javascript">
var qrcode = new QRCode(document.getElementById("qrcode"), {
text: "{{ url_for("order_bp.order_from_slug", order_slug=order.slug, _external=True) }}",
width: 128,
height: 128,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.H
});
</script>
</div>
<div class="col-md-10">
<h2 id="order-title">Order {{ order.id }}</h2> <h2 id="order-title">Order {{ order.id }}</h2>
<div class="location"> <div class="location">
@ -25,6 +42,10 @@
{{ order.location_name }} {{ order.location_name }}
{% endif %} {% endif %}
</div> </div>
<div>
Unique order link: <code>{{ url_for("order_bp.order_from_slug", order_slug=order.slug, _external=True) }}</code>
</div>
</div>
</header> </header>
<section> <section>
@ -36,7 +57,7 @@
{% for item in my_items %} {% for item in my_items %}
<li class="spacecake"> <li class="spacecake">
{% if item.can_delete(order.id, current_user.id, session.get('anon_name', '')) -%} {% if item.can_delete(order.id, current_user.id, session.get('anon_name', '')) -%}
<form action="{{ url_for('order_bp.delete_item', order_id=order.id, item_id=item.id) }}" method="post" style="display:inline"> <form action="{{ url_for('order_bp.delete_item', order_slug=order.slug, item_id=item.id) }}" method="post" style="display:inline">
<button class="btn btn-link btn-sm" type="submit" style="padding: 0 0.5em;"><span class="glyphicon glyphicon-remove"></span></button> <button class="btn btn-link btn-sm" type="submit" style="padding: 0 0.5em;"><span class="glyphicon glyphicon-remove"></span></button>
</form> </form>
{%- endif %} {%- endif %}
@ -65,7 +86,7 @@
<h3>Add item to order</h3> <h3>Add item to order</h3>
{% for dish in order.location.dishes %} {% for dish in order.location.dishes %}
<form method="post" action="{{ url_for('order_bp.order_item_create', order_id=order.id) }}" id="dish_{{ dish.id }}"> <form method="post" action="{{ url_for('order_bp.order_item_create', order_slug=order.slug) }}" id="dish_{{ dish.id }}">
{{ form.csrf_token }} {{ form.csrf_token }}
<input type="hidden" name="dish_id" value="{{ dish.id }}" /> <input type="hidden" name="dish_id" value="{{ dish.id }}" />
@ -167,7 +188,7 @@
<dd> <dd>
{% if order.courier == None %} {% if order.courier == None %}
{% if not current_user.is_anonymous() %} {% if not current_user.is_anonymous() %}
<form action="{{ url_for('order_bp.volunteer', order_id=order.id) }}" method="post" style="display:inline"> <form action="{{ url_for('order_bp.volunteer', order_slug=order.slug) }}" method="post" style="display:inline">
<input type="submit" class="btn btn-primary btn-sm" value="Volunteer"></input> <input type="submit" class="btn btn-primary btn-sm" value="Volunteer"></input>
</form> </form>
{% else %}No-one yet{% endif %} {% else %}No-one yet{% endif %}
@ -180,12 +201,12 @@
<div> <div>
{% if order.can_close(current_user.id) -%} {% if order.can_close(current_user.id) -%}
<form action="{{ url_for('order_bp.close_order', order_id=order.id) }}" method="post" style="display:inline"> <form action="{{ url_for('order_bp.close_order', order_slug=order.slug) }}" method="post" style="display:inline">
<input type="submit" class="btn btn-danger" value="Close"></input> <input type="submit" class="btn btn-danger" value="Close"></input>
</form> </form>
{% endif %} {% endif %}
{% if courier_or_admin %} {% if courier_or_admin %}
<a class="btn" href="{{ url_for('order_bp.order_edit', order_id=order.id) }}">Edit</a> <a class="btn" href="{{ url_for('order_bp.order_edit', order_slug=order.slug) }}">Edit</a>
{%- endif %} {%- endif %}
</div> </div>
</div> </div>
@ -258,7 +279,7 @@
<div class="footer"> <div class="footer">
Total {{ order.items.count() }} items — {{ total_price|euro }} Total {{ order.items.count() }} items — {{ total_price|euro }}
&nbsp; &nbsp;
<a class="btn btn-sm" href="{{ url_for('order_bp.items_shop_view', order_id=order.id) }}">Shop view</a> <a class="btn btn-sm" href="{{ url_for('order_bp.items_shop_view', order_slug=order.slug) }}">Shop view</a>
</div> </div>
</div> </div>
</div> </div>
@ -267,7 +288,7 @@
<section class="single_column"> <section class="single_column">
<div class="box" id="per_person"> <div class="box" id="per_person">
<h3>Items per person</h3> <h3>Items per person</h3>
<form action="{{ url_for('order_bp.modify_items', order_id=order.id) }}" method="post"> <form action="{{ url_for('order_bp.modify_items', order_slug=order.slug) }}" method="post">
<table class="table table-condensed"> <table class="table table-condensed">
<thead> <thead>
<tr><th>Total</th><th>Name</th><th>Items</th></tr> <tr><th>Total</th><th>Name</th><th>Items</th></tr>
@ -293,7 +314,9 @@
<li class="{{ 'paid' if item.paid }}"> <li class="{{ 'paid' if item.paid }}">
<div class="actions"> <div class="actions">
{% if item.can_delete(order.id, current_user.id, session.get('anon_name', '')) -%} {% if item.can_delete(order.id, current_user.id, session.get('anon_name', '')) -%}
<button class="btn btn-link btn-sm" type="submit" name="delete_item" value="{{ item.id }}" style="padding: 0 0.5em;"><span class="glyphicon glyphicon-remove"></span></button> <form action="{{ url_for('order_bp.delete_item', order_slug=order.slug, item_id=item.id) }}" method="post" style="display:inline">
<button class="btn btn-link btn-sm" type="submit" style="padding: 0 0.5em;"><span class="glyphicon glyphicon-remove"></span></button>
</form>
{% else %} {% else %}
<span class="glyphicon glyphicon-remove" style="color: var(--gray3); padding: 0 0.5em; cursor: not-allowed"></span> <span class="glyphicon glyphicon-remove" style="color: var(--gray3); padding: 0 0.5em; cursor: not-allowed"></span>
{%- endif %} {%- endif %}

View file

@ -11,7 +11,7 @@
<h3>Edit order</h3> <h3>Edit order</h3>
<div class="row darker"> <div class="row darker">
<div class="col-sm-12"> <div class="col-sm-12">
<form method="post" action="{{ url_for('.order_edit', order_id=order_id) }}"> <form method="post" action="{{ url_for('.order_edit', order_slug=order_slug) }}">
{{ form.csrf_token }} {{ form.csrf_token }}
<div class="form-group select2 {{ 'has-errors' if form.courier_id.errors else ''}}"> <div class="form-group select2 {{ 'has-errors' if form.courier_id.errors else ''}}">
{{ form.courier_id.label(class='control-label') }}<br> {{ form.courier_id.label(class='control-label') }}<br>

View file

@ -9,7 +9,7 @@
{% else %}open{% endif %}<br/> {% else %}open{% endif %}<br/>
</div> </div>
<div class="col-md-4 col-lg-3 expand_button_wrapper"> <div class="col-md-4 col-lg-3 expand_button_wrapper">
<a class="btn btn-primary btn-block align-bottom expand_button" href="{{ url_for('order_bp.order_from_id', order_id=order.id) }}">Expand</a> <a class="btn btn-primary btn-block align-bottom expand_button" href="{{ url_for('order_bp.order_from_slug', order_slug=order.slug) }}">Expand</a>
</div> </div>
</div> </div>
{%- endmacro %} {%- endmacro %}

View file

@ -1,4 +1,4 @@
"Script to generate the order related views of Haldis" """Script to generate the order related views of Haldis"""
import random import random
import re import re
import typing import typing
@ -20,7 +20,7 @@ order_bp = Blueprint("order_bp", "order")
@order_bp.route("/") @order_bp.route("/")
def orders(form: OrderForm = None) -> str: def orders(form: OrderForm = None) -> str:
"Generate general order view" """Generate general order view"""
if form is None and not current_user.is_anonymous(): if form is None and not current_user.is_anonymous():
form = OrderForm() form = OrderForm()
location_id = request.args.get("location_id") location_id = request.args.get("location_id")
@ -33,7 +33,7 @@ def orders(form: OrderForm = None) -> str:
@order_bp.route("/create", methods=["POST"]) @order_bp.route("/create", methods=["POST"])
@login_required @login_required
def order_create() -> typing.Union[str, Response]: def order_create() -> typing.Union[str, Response]:
"Generate order create view" """Generate order create view"""
orderForm = OrderForm() orderForm = OrderForm()
orderForm.populate() orderForm.populate()
if orderForm.validate_on_submit(): if orderForm.validate_on_submit():
@ -43,14 +43,14 @@ def order_create() -> typing.Union[str, Response]:
db.session.add(order) db.session.add(order)
db.session.commit() db.session.commit()
post_order_to_webhook(order) post_order_to_webhook(order)
return redirect(url_for("order_bp.order_from_id", order_id=order.id)) return redirect(url_for("order_bp.order_from_slug", order_slug=order.slug))
return orders(form=orderForm) return orders(form=orderForm)
@order_bp.route("/<order_id>") @order_bp.route("/<order_slug>")
def order_from_id(order_id: int, form: OrderForm = None, dish_id=None) -> str: def order_from_slug(order_slug: str, form: OrderForm = None, dish_id=None) -> str:
"Generate order view from id" """Generate order view from id"""
order = Order.query.filter(Order.id == order_id).first() order = Order.query.filter(Order.slug == order_slug).first()
if order is None: if order is None:
abort(404) abort(404)
if current_user.is_anonymous() and not order.public: if current_user.is_anonymous() and not order.public:
@ -77,10 +77,10 @@ def order_from_id(order_id: int, form: OrderForm = None, dish_id=None) -> str:
) )
@order_bp.route("/<order_id>/items") @order_bp.route("/<order_slug>/items")
def items_shop_view(order_id: int) -> str: def items_shop_view(order_slug: int) -> str:
"Generate order items view from id" """Generate order items view from id"""
order = Order.query.filter(Order.id == order_id).first() order = Order.query.filter(Order.slug == order_slug).first()
if order is None: if order is None:
abort(404) abort(404)
if current_user.is_anonymous() and not order.public: if current_user.is_anonymous() and not order.public:
@ -90,31 +90,31 @@ def items_shop_view(order_id: int) -> str:
return render_template("order_items.html", order=order, total_price=total_price) return render_template("order_items.html", order=order, total_price=total_price)
@order_bp.route("/<order_id>/edit", methods=["GET", "POST"]) @order_bp.route("/<order_slug>/edit", methods=["GET", "POST"])
@login_required @login_required
def order_edit(order_id: int) -> typing.Union[str, Response]: def order_edit(order_slug: str) -> typing.Union[str, Response]:
"Generate order edit view from id" """Generate order edit view from id"""
order = Order.query.filter(Order.id == order_id).first() order = Order.query.filter(Order.slug == order_slug).first()
if current_user.id is not order.courier_id and not current_user.is_admin(): if current_user.id is not order.courier_id and not current_user.is_admin():
abort(401) abort(401)
if order is None: if order is None:
abort(404) abort(404)
orderForm = OrderForm(obj=order) order_form = OrderForm(obj=order)
orderForm.populate() order_form.populate()
if orderForm.validate_on_submit(): if order_form.validate_on_submit():
orderForm.populate_obj(order) order_form.populate_obj(order)
order.update_from_hlds() order.update_from_hlds()
db.session.commit() db.session.commit()
return redirect(url_for("order_bp.order_from_id", order_id=order.id)) return redirect(url_for("order_bp.order_from_slug", order_slug=order.slug))
return render_template("order_edit.html", form=orderForm, order_id=order_id) return render_template("order_edit.html", form=order_form, order_slug=order.slug)
@order_bp.route("/<order_id>/create", methods=["GET", "POST"]) @order_bp.route("/<order_slug>/create", methods=["GET", "POST"])
def order_item_create(order_id: int) -> typing.Any: def order_item_create(order_slug: str) -> 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
"Add item to order from id" """Add item to order from slug"""
current_order = Order.query.filter(Order.id == order_id).first() current_order = Order.query.filter(Order.slug == order_slug).first()
if current_order is None: if current_order is None:
abort(404) abort(404)
if current_order.is_closed(): if current_order.is_closed():
@ -171,7 +171,7 @@ def order_item_create(order_id: int) -> typing.Any:
return redirect( return redirect(
url_for( url_for(
"order_bp.order_item_create", "order_bp.order_item_create",
order_id=order_id, order_slug=current_order.slug,
dish=form.dish_id.data, dish=form.dish_id.data,
user_name=user_name, user_name=user_name,
comment=comment, comment=comment,
@ -180,14 +180,13 @@ def order_item_create(order_id: int) -> typing.Any:
# If the form was not submitted (GET request) or the form had errors: show form again # If the form was not submitted (GET request) or the form had errors: show form again
if not form.validate_on_submit(): if not form.validate_on_submit():
return order_from_id(order_id, form=form, dish_id=dish_id) return order_from_slug(current_order.slug, form=form, dish_id=dish_id)
# Form was submitted and is valid # Form was submitted and is valid
item = OrderItem() item = OrderItem()
form.populate_obj(item) form.populate_obj(item)
item.hlds_data_version = location_definition_version item.hlds_data_version = location_definition_version
item.order_id = order_id item.order_id = current_order.id
if not current_user.is_anonymous(): if not current_user.is_anonymous():
item.user_id = current_user.id item.user_id = current_user.id
else: else:
@ -222,16 +221,16 @@ def order_item_create(order_id: int) -> typing.Any:
db.session.add(item) db.session.add(item)
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_slug", order_slug=order_slug))
@order_bp.route("/<order_id>/modify_items", methods=["POST"]) @order_bp.route("/<order_slug>/modify_items", methods=["POST"])
@login_required @login_required
# pylint: disable=R1710 # pylint: disable=R1710
def modify_items(order_id: int) -> typing.Optional[Response]: def modify_items(order_slug: str) -> typing.Optional[Response]:
if "delete_item" in request.form: if "delete_item" in request.form:
return delete_item(order_id, int(request.form["delete_item"])) return delete_item(order_slug, int(request.form["delete_item"]))
user_names = request.form.getlist("user_names") user_names = request.form.getlist("user_names")
if request.form.get("action") == "mark_paid": if request.form.get("action") == "mark_paid":
return set_items_paid(order_id, user_names, True) return set_items_paid(order_id, user_names, True)
@ -241,7 +240,8 @@ def modify_items(order_id: int) -> typing.Optional[Response]:
abort(404) abort(404)
return None return None
def set_items_paid(order_id: int, user_names: typing.Iterable[str], paid: bool): def set_items_paid(order_slug: str, user_names: typing.Iterable[str], paid: bool):
order = Order.query.filter(Order.slug == order_slug).first()
total_paid_items = 0 total_paid_items = 0
total_failed_items = 0 total_failed_items = 0
for user_name in user_names: for user_name in user_names:
@ -249,15 +249,15 @@ def set_items_paid(order_id: int, user_names: typing.Iterable[str], paid: bool):
items: typing.List[OrderItem] = [] items: typing.List[OrderItem] = []
if user: if user:
items = OrderItem.query.filter( items = OrderItem.query.filter(
(OrderItem.user_id == user.id) & (OrderItem.order_id == order_id) (OrderItem.user_id == user.id) & (OrderItem.order_id == order.id)
).all() ).all()
else: else:
items = OrderItem.query.filter( items = OrderItem.query.filter(
(OrderItem.user_name == user_name) & (OrderItem.order_id == order_id) (OrderItem.user_name == user_name) & (OrderItem.order_id == order.id)
).all() ).all()
for item in items: for item in items:
if item.can_modify_payment(order_id, current_user.id): if item.can_modify_payment(order.id, current_user.id):
if item.paid != paid: if item.paid != paid:
item.paid = paid item.paid = paid
total_paid_items += 1 total_paid_items += 1
@ -269,33 +269,34 @@ def set_items_paid(order_id: int, user_names: typing.Iterable[str], paid: bool):
flash("Marked %d items as paid" % (total_paid_items,), "success") flash("Marked %d items as paid" % (total_paid_items,), "success")
else: else:
flash("Failed to mark %d items as paid (succeeded in marking %d items as paid)" % (total_failed_items, total_paid_items), "error") flash("Failed to mark %d items as paid (succeeded in marking %d items as paid)" % (total_failed_items, total_paid_items), "error")
return redirect(url_for("order_bp.order_from_id", order_id=order_id)) return redirect(url_for("order_bp.order_from_slug", order_slug=order_slug))
@order_bp.route("/<order_id>/<item_id>/delete", methods=["POST"]) @order_bp.route("/<order_slug>/<item_id>/delete", methods=["POST"])
# pylint: disable=R1710 # pylint: disable=R1710
def delete_item(order_id: int, item_id: int) -> typing.Any: def delete_item(order_slug: str, item_id: int) -> typing.Any:
# type is 'typing.Optional[Response]', but this errors due to # type is 'typing.Optional[Response]', but this errors due to
# https://github.com/python/mypy/issues/7187 # https://github.com/python/mypy/issues/7187
"Delete an item from an order" """Delete an item from an order"""
item = OrderItem.query.filter(OrderItem.id == item_id).first() item: OrderItem = OrderItem.query.filter(OrderItem.id == item_id).first()
order: Order = Order.query.filter(Order.slug == order_slug).first()
user_id = None user_id = None
if not current_user.is_anonymous(): if not current_user.is_anonymous():
user_id = current_user.id user_id = current_user.id
if item.can_delete(order_id, user_id, session.get("anon_name", "")): if item.can_delete(order.id, user_id, session.get("anon_name", "")):
dish_name = item.dish_name dish_name = item.dish_name
db.session.delete(item) db.session.delete(item)
db.session.commit() db.session.commit()
flash("Deleted %s" % (dish_name), "success") flash("Deleted %s" % dish_name, "success")
return redirect(url_for("order_bp.order_from_id", order_id=order_id)) return redirect(url_for("order_bp.order_from_slug", order_slug=order_slug))
abort(404) abort(404)
@order_bp.route("/<order_id>/volunteer", methods=["POST"]) @order_bp.route("/<order_slug>/volunteer", methods=["POST"])
@login_required @login_required
def volunteer(order_id: int) -> Response: def volunteer(order_slug: str) -> Response:
"Add a volunteer to an order" """Add a volunteer to an order"""
order = Order.query.filter(Order.id == order_id).first() order = Order.query.filter(Order.slug == order_slug).first()
if order is None: if order is None:
abort(404) abort(404)
if order.courier_id is None or order.courier_id == 0: if order.courier_id is None or order.courier_id == 0:
@ -304,14 +305,14 @@ def volunteer(order_id: int) -> Response:
flash("Thank you for volunteering!") flash("Thank you for volunteering!")
else: else:
flash("Volunteering not possible!") flash("Volunteering not possible!")
return redirect(url_for("order_bp.order_from_id", order_id=order_id)) return redirect(url_for("order_bp.order_from_slug", order_slug=order.slug))
@order_bp.route("/<order_id>/close", methods=["POST"]) @order_bp.route("/<order_slug>/close", methods=["POST"])
@login_required @login_required
def close_order(order_id: int) -> typing.Optional[Response]: def close_order(order_slug: str) -> typing.Optional[Response]:
"Close an order" """Close an order"""
order = Order.query.filter(Order.id == order_id).first() order = Order.query.filter(Order.slug == order_slug).first()
if order is None: if order is None:
abort(404) abort(404)
if ( if (
@ -323,7 +324,7 @@ def close_order(order_id: int) -> typing.Optional[Response]:
if courier is not None: if courier is not None:
order.courier_id = courier.id order.courier_id = courier.id
db.session.commit() db.session.commit()
return redirect(url_for("order_bp.order_from_id", order_id=order_id)) return redirect(url_for("order_bp.order_from_slug", order_slug=order_slug))
return None return None
@ -370,7 +371,7 @@ def prices(order_id: int) -> typing.Optional[Response]:
def select_user(items) -> typing.Optional[User]: def select_user(items) -> typing.Optional[User]:
"Select a random user from those who are signed up for the order" """Select a random user from those who are signed up for the order"""
user = None user = None
# remove non users # remove non users
items = [i for i in items if i.user_id] items = [i for i in items if i.user_id]
@ -389,7 +390,7 @@ def select_user(items) -> typing.Optional[User]:
def get_orders(expression=None) -> typing.List[Order]: def get_orders(expression=None) -> typing.List[Order]:
"Give the list of all currently open and public Orders" """Give the list of all currently open and public Orders"""
order_list: typing.List[OrderForm] = [] order_list: typing.List[OrderForm] = []
if expression is None: if expression is None:
expression = (datetime.now() > Order.starttime) & ( expression = (datetime.now() > Order.starttime) & (