Merge pull request #187 from ZeusWPI/feature/invitelink-token
Feature/invitelink token
This commit is contained in:
commit
f87f3c5446
9 changed files with 129 additions and 94 deletions
|
@ -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
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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
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
1
app/static/js/qrcode.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -12,18 +12,39 @@
|
||||||
{% 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">
|
||||||
<h2 id="order-title">Order {{ order.id }}</h2>
|
<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>
|
||||||
|
|
||||||
<div class="location">
|
<div class="location">
|
||||||
{% if order.location %}
|
{% if order.location %}
|
||||||
<a href="{{ url_for('general_bp.location', location_id=order.location_id) }}">{{ order.location_name }}</a>
|
<a href="{{ url_for('general_bp.location', location_id=order.location_id) }}">{{ order.location_name }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ order.location_name }}
|
{{ order.location_name }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Unique order link: <code>{{ url_for("order_bp.order_from_slug", order_slug=order.slug, _external=True) }}</code>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -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 }}
|
||||||
|
|
||||||
<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 %}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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():
|
||||||
|
@ -123,7 +123,7 @@ def order_item_create(order_id: int) -> typing.Any:
|
||||||
flash("Please login to see this order.", "info")
|
flash("Please login to see this order.", "info")
|
||||||
abort(401)
|
abort(401)
|
||||||
location = current_order.location
|
location = current_order.location
|
||||||
# If location doesn't exist any more, adding items is nonsensical
|
# If location doesn't exist anymore, adding items is nonsensical
|
||||||
if not location:
|
if not location:
|
||||||
abort(404)
|
abort(404)
|
||||||
form = AnonOrderItemForm() if current_user.is_anonymous() else OrderItemForm()
|
form = AnonOrderItemForm() if current_user.is_anonymous() else OrderItemForm()
|
||||||
|
@ -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) & (
|
||||||
|
|
Loading…
Reference in a new issue