commit
461664f629
28 changed files with 278 additions and 233 deletions
|
@ -5,6 +5,8 @@
|
||||||
# run arbitrary code.
|
# run arbitrary code.
|
||||||
extension-pkg-whitelist=
|
extension-pkg-whitelist=
|
||||||
|
|
||||||
|
fail-under=9
|
||||||
|
|
||||||
# Add files or directories to the blacklist. They should be base names, not
|
# Add files or directories to the blacklist. They should be base names, not
|
||||||
# paths.
|
# paths.
|
||||||
ignore=CVS
|
ignore=CVS
|
||||||
|
@ -28,7 +30,7 @@ limit-inference-results=100
|
||||||
|
|
||||||
# List of plugins (as comma separated values of python modules names) to load,
|
# List of plugins (as comma separated values of python modules names) to load,
|
||||||
# usually to register additional checkers.
|
# usually to register additional checkers.
|
||||||
load-plugins=
|
load-plugins=pylint_flask_sqlalchemy,pylint_flask
|
||||||
|
|
||||||
# Pickle collected data for later comparisons.
|
# Pickle collected data for later comparisons.
|
||||||
persistent=yes
|
persistent=yes
|
||||||
|
@ -60,7 +62,7 @@ confidence=
|
||||||
# --enable=similarities". If you want to run only the classes checker, but have
|
# --enable=similarities". If you want to run only the classes checker, but have
|
||||||
# no Warning level messages displayed, use "--disable=all --enable=classes
|
# no Warning level messages displayed, use "--disable=all --enable=classes
|
||||||
# --disable=W".
|
# --disable=W".
|
||||||
disable=E0401,E0611,C0103,W0511,W0611
|
disable=E0401,E0611,C0103,W0511,W0611,C0415
|
||||||
|
|
||||||
# Enable the message, report, category or checker with the given id(s). You can
|
# Enable the message, report, category or checker with the given id(s). You can
|
||||||
# either give multiple identifier separated by comma (,) or put this option
|
# either give multiple identifier separated by comma (,) or put this option
|
||||||
|
|
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
|
@ -1,7 +1,7 @@
|
||||||
"""Script for adding users as admin to Haldis."""
|
"""Script for adding users as admin to Haldis."""
|
||||||
|
from models import User
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from models import User
|
|
||||||
|
|
||||||
|
|
||||||
def add() -> None:
|
def add() -> None:
|
||||||
|
|
29
app/admin.py
29
app/admin.py
|
@ -3,7 +3,6 @@ from flask import Flask
|
||||||
from flask_admin import Admin
|
from flask_admin import Admin
|
||||||
from flask_admin.contrib.sqla import ModelView
|
from flask_admin.contrib.sqla import ModelView
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
from models import Order, OrderItem, OrderItemChoice, User
|
from models import Order, OrderItem, OrderItemChoice, User
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,8 +25,10 @@ class OrderAdminModel(ModelBaseView):
|
||||||
column_default_sort = ("starttime", True)
|
column_default_sort = ("starttime", True)
|
||||||
column_list = ["starttime", "stoptime", "location_name", "location_id", "courier"]
|
column_list = ["starttime", "stoptime", "location_name", "location_id", "courier"]
|
||||||
column_labels = {
|
column_labels = {
|
||||||
"starttime": "Start Time", "stoptime": "Closing Time",
|
"starttime": "Start Time",
|
||||||
"location_id": "HLDS Location ID"}
|
"stoptime": "Closing Time",
|
||||||
|
"location_id": "HLDS Location ID",
|
||||||
|
}
|
||||||
form_excluded_columns = ["items", "courier_id"]
|
form_excluded_columns = ["items", "courier_id"]
|
||||||
can_delete = False
|
can_delete = False
|
||||||
|
|
||||||
|
@ -36,13 +37,25 @@ class OrderItemAdminModel(ModelBaseView):
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
column_default_sort = ("order_id", True)
|
column_default_sort = ("order_id", True)
|
||||||
column_list = [
|
column_list = [
|
||||||
"order_id", "order.location_name", "user_name", "user", "dish_name", "dish_id", "comment", "price", "paid",
|
"order_id",
|
||||||
"hlds_data_version"
|
"order.location_name",
|
||||||
|
"user_name",
|
||||||
|
"user",
|
||||||
|
"dish_name",
|
||||||
|
"dish_id",
|
||||||
|
"comment",
|
||||||
|
"price",
|
||||||
|
"paid",
|
||||||
|
"hlds_data_version",
|
||||||
]
|
]
|
||||||
column_labels = {
|
column_labels = {
|
||||||
"order_id": "Order", "order.location_name": "Order's Location",
|
"order_id": "Order",
|
||||||
"user_name": "Anon. User", "user_id": "Registered User",
|
"order.location_name": "Order's Location",
|
||||||
"hlds_data_version": "HLDS Data Version", "dish_id": "HLDS Dish ID"}
|
"user_name": "Anon. User",
|
||||||
|
"user_id": "Registered User",
|
||||||
|
"hlds_data_version": "HLDS Data Version",
|
||||||
|
"dish_id": "HLDS Dish ID",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def init_admin(app: Flask, database: SQLAlchemy) -> None:
|
def init_admin(app: Flask, database: SQLAlchemy) -> None:
|
||||||
|
|
18
app/app.py
18
app/app.py
|
@ -3,10 +3,11 @@
|
||||||
"""Main Haldis script"""
|
"""Main Haldis script"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from logging.handlers import TimedRotatingFileHandler
|
|
||||||
import typing
|
import typing
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
|
|
||||||
|
from admin import init_admin
|
||||||
from flask import Flask, render_template
|
from flask import Flask, render_template
|
||||||
from flask_bootstrap import Bootstrap, StaticCDN
|
from flask_bootstrap import Bootstrap, StaticCDN
|
||||||
from flask_debugtoolbar import DebugToolbarExtension
|
from flask_debugtoolbar import DebugToolbarExtension
|
||||||
|
@ -14,10 +15,8 @@ from flask_login import LoginManager
|
||||||
from flask_migrate import Migrate, MigrateCommand
|
from flask_migrate import Migrate, MigrateCommand
|
||||||
from flask_oauthlib.client import OAuth, OAuthException
|
from flask_oauthlib.client import OAuth, OAuthException
|
||||||
from flask_script import Manager, Server
|
from flask_script import Manager, Server
|
||||||
from markupsafe import Markup
|
|
||||||
|
|
||||||
from admin import init_admin
|
|
||||||
from login import init_login
|
from login import init_login
|
||||||
|
from markupsafe import Markup
|
||||||
from models import db
|
from models import db
|
||||||
from models.anonymous_user import AnonymouseUser
|
from models.anonymous_user import AnonymouseUser
|
||||||
from utils import euro_string, price_range_string
|
from utils import euro_string, price_range_string
|
||||||
|
@ -69,7 +68,8 @@ def register_plugins(app: Flask) -> Manager:
|
||||||
|
|
||||||
# Make cookies more secure
|
# Make cookies more secure
|
||||||
app.config.update(
|
app.config.update(
|
||||||
SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE="Lax",
|
SESSION_COOKIE_HTTPONLY=True,
|
||||||
|
SESSION_COOKIE_SAMESITE="Lax",
|
||||||
)
|
)
|
||||||
|
|
||||||
if not app.debug:
|
if not app.debug:
|
||||||
|
@ -95,11 +95,11 @@ def add_routes(application: Flask) -> None:
|
||||||
# import views # TODO convert to blueprint
|
# import views # TODO convert to blueprint
|
||||||
# import views.stats # TODO convert to blueprint
|
# import views.stats # TODO convert to blueprint
|
||||||
|
|
||||||
from views.order import order_bp
|
|
||||||
from views.general import general_bp
|
|
||||||
from views.stats import stats_blueprint
|
|
||||||
from views.debug import debug_bp
|
|
||||||
from login import auth_bp
|
from login import auth_bp
|
||||||
|
from views.debug import debug_bp
|
||||||
|
from views.general import general_bp
|
||||||
|
from views.order import order_bp
|
||||||
|
from views.stats import stats_blueprint
|
||||||
from zeus import oauth_bp
|
from zeus import oauth_bp
|
||||||
|
|
||||||
application.register_blueprint(general_bp, url_prefix="/")
|
application.register_blueprint(general_bp, url_prefix="/")
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import add_admins
|
import add_admins
|
||||||
|
|
||||||
from app import db, app_manager
|
from app import app_manager, db
|
||||||
|
|
||||||
entry_sets = {
|
entry_sets = {
|
||||||
"admins": add_admins.add,
|
"admins": add_admins.add,
|
||||||
|
@ -27,7 +27,7 @@ def check_if_overwrite() -> bool:
|
||||||
def add_all() -> None:
|
def add_all() -> None:
|
||||||
"Add all possible entries in the entry_sets to the database"
|
"Add all possible entries in the entry_sets to the database"
|
||||||
for entry_set, function in entry_sets.items():
|
for entry_set, function in entry_sets.items():
|
||||||
print("Adding {}.".format(entry_set))
|
print(f"Adding {entry_set}.")
|
||||||
function()
|
function()
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,14 +45,14 @@ def add_to_current() -> None:
|
||||||
|
|
||||||
def add_numbers() -> str:
|
def add_numbers() -> str:
|
||||||
return " ".join(
|
return " ".join(
|
||||||
["{}({}), ".format(loc, i) for i, loc in enumerate(available)]
|
[f"{loc}({i}), " for i, loc in enumerate(available)]
|
||||||
).rstrip(", ")
|
).rstrip(", ")
|
||||||
|
|
||||||
while input("Do you still want to add something? (Y/n) ").lower() not in no:
|
while input("Do you still want to add something? (Y/n) ").lower() not in no:
|
||||||
print(
|
print(
|
||||||
"What do you want to add? (Use numbers, or A for all, or C for cancel) "
|
"What do you want to add? (Use numbers, or A for all, or C for cancel) "
|
||||||
)
|
)
|
||||||
answer = input("Available: {} : ".format(add_numbers()))
|
answer = input(f"Available: {add_numbers()} : ")
|
||||||
if answer.lower() == "a":
|
if answer.lower() == "a":
|
||||||
add_all()
|
add_all()
|
||||||
available = []
|
available = []
|
||||||
|
@ -60,7 +60,7 @@ def add_to_current() -> None:
|
||||||
pass
|
pass
|
||||||
elif answer.isnumeric() and answer in [str(x) for x in range(len(available))]:
|
elif answer.isnumeric() and answer in [str(x) for x in range(len(available))]:
|
||||||
answer_index = int(answer)
|
answer_index = int(answer)
|
||||||
print("Adding {}.".format(available[answer_index]))
|
print(f"Adding {available[answer_index]}.")
|
||||||
entry_sets[str(available[answer_index])]()
|
entry_sets[str(available[answer_index])]()
|
||||||
del available[answer_index]
|
del available[answer_index]
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from sqlalchemy.sql import desc, func
|
|
||||||
|
|
||||||
from hlds.definitions import location_definitions
|
from hlds.definitions import location_definitions
|
||||||
from hlds.models import Location, Dish
|
from hlds.models import Dish, Location
|
||||||
from models import Order, OrderItem, User
|
from models import Order, OrderItem, User
|
||||||
|
from sqlalchemy.sql import desc, func
|
||||||
|
|
||||||
|
|
||||||
class FatModel:
|
class FatModel:
|
||||||
|
|
19
app/forms.py
19
app/forms.py
|
@ -1,25 +1,16 @@
|
||||||
"Script for everything form related in Haldis"
|
"Script for everything form related in Haldis"
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from flask import session, request
|
from flask import request, session
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_wtf import FlaskForm as Form
|
from flask_wtf import FlaskForm as Form
|
||||||
from wtforms import (
|
|
||||||
DateTimeField,
|
|
||||||
SelectField,
|
|
||||||
SelectMultipleField,
|
|
||||||
StringField,
|
|
||||||
SubmitField,
|
|
||||||
FieldList,
|
|
||||||
validators,
|
|
||||||
)
|
|
||||||
|
|
||||||
from utils import euro_string, price_range_string
|
|
||||||
from hlds.definitions import location_definitions
|
from hlds.definitions import location_definitions
|
||||||
from hlds.models import Location, Dish, Choice
|
from hlds.models import Choice, Dish, Location
|
||||||
from models import User
|
from models import User
|
||||||
|
from utils import euro_string, price_range_string
|
||||||
|
from wtforms import (DateTimeField, FieldList, SelectField,
|
||||||
|
SelectMultipleField, StringField, SubmitField, validators)
|
||||||
|
|
||||||
|
|
||||||
class OrderForm(Form):
|
class OrderForm(Form):
|
||||||
|
|
|
@ -6,4 +6,4 @@ These are not imported in this module's init, to avoid opening the definition fi
|
||||||
parser on them when testing other code in this module, or when testing the parser on other files.
|
parser on them when testing other code in this module, or when testing the parser on other files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .models import Location, Choice, Option
|
from .models import Choice, Location, Option
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# Import this class to load the standard HLDS definitions
|
# Import this class to load the standard HLDS definitions
|
||||||
|
|
||||||
|
import subprocess
|
||||||
from os import path
|
from os import path
|
||||||
from typing import List
|
from typing import List
|
||||||
import subprocess
|
|
||||||
from .parser import parse_all_directory
|
|
||||||
from .models import Location
|
|
||||||
|
|
||||||
|
from .models import Location
|
||||||
|
from .parser import parse_all_directory
|
||||||
|
|
||||||
__all__ = ["location_definitions", "location_definition_version"]
|
__all__ = ["location_definitions", "location_definition_version"]
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
|
|
||||||
from typing import Iterable, List, Tuple, Mapping, Any, Optional
|
from typing import Any, Iterable, List, Mapping, Optional, Tuple
|
||||||
|
|
||||||
from utils import euro_string, first
|
from utils import euro_string, first
|
||||||
|
|
||||||
|
|
||||||
def _format_tags(tags: Iterable[str]) -> str:
|
def _format_tags(tags: Iterable[str]) -> str:
|
||||||
return " :: {}".format(" ".join(["{" + tag + "}" for tag in tags])) \
|
# pylint: disable=consider-using-f-string
|
||||||
if tags \
|
return " :: {}".format(" ".join(["{" + tag + "}"
|
||||||
else ""
|
for tag in tags])) if tags else ""
|
||||||
|
|
||||||
|
|
||||||
def _format_price(price: int) -> str:
|
def _format_price(price: int) -> str:
|
||||||
return " {}".format(euro_string(price)) if price else ""
|
return f" {euro_string(price)}" if price else ""
|
||||||
|
|
||||||
|
|
||||||
def _format_type_and_choice(type_and_choice):
|
def _format_type_and_choice(type_and_choice):
|
||||||
type_, choice = type_and_choice
|
type_, choice = type_and_choice
|
||||||
return "{} {}".format(type_, choice)
|
return f"{type_} {choice}"
|
||||||
|
|
||||||
|
|
||||||
class Option:
|
class Option:
|
||||||
|
|
||||||
def __init__(self, id_, *, name, description, price, tags):
|
def __init__(self, id_, *, name, description, price, tags):
|
||||||
self.id: str = id_
|
self.id: str = id_
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
|
@ -29,15 +31,17 @@ class Option:
|
||||||
self.tags: List[str] = tags
|
self.tags: List[str] = tags
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
# pylint: disable=consider-using-f-string
|
||||||
return "{0.id}: {0.name}{1}{2}{3}".format(
|
return "{0.id}: {0.name}{1}{2}{3}".format(
|
||||||
self,
|
self,
|
||||||
" -- {}".format(self.description) if self.description else "",
|
f" -- {self.description}" if self.description else "",
|
||||||
_format_tags(self.tags),
|
_format_tags(self.tags),
|
||||||
_format_price(self.price),
|
_format_price(self.price),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Choice:
|
class Choice:
|
||||||
|
|
||||||
def __init__(self, id_, *, name, description, options):
|
def __init__(self, id_, *, name, description, options):
|
||||||
self.id: str = id_
|
self.id: str = id_
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
|
@ -48,7 +52,7 @@ class Choice:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{0.id}: {0.name}{1}\n\t\t{2}".format(
|
return "{0.id}: {0.name}{1}\n\t\t{2}".format(
|
||||||
self,
|
self,
|
||||||
" -- {}".format(self.description) if self.description else "",
|
f" -- {self.description}" if self.description else "",
|
||||||
"\n\t\t".join(map(str, self.options)),
|
"\n\t\t".join(map(str, self.options)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,6 +61,7 @@ class Choice:
|
||||||
|
|
||||||
|
|
||||||
class Dish:
|
class Dish:
|
||||||
|
|
||||||
def __init__(self, id_, *, name, description, price, tags, choices):
|
def __init__(self, id_, *, name, description, price, tags, choices):
|
||||||
self.id: str = id_
|
self.id: str = id_
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
|
@ -70,7 +75,7 @@ class Dish:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "dish {0.id}: {0.name}{1}{2}{3}\n\t{4}".format(
|
return "dish {0.id}: {0.name}{1}{2}{3}\n\t{4}".format(
|
||||||
self,
|
self,
|
||||||
" -- {}".format(self.description) if self.description else "",
|
f" -- {self.description}" if self.description else "",
|
||||||
_format_tags(self.tags),
|
_format_tags(self.tags),
|
||||||
_format_price(self.price),
|
_format_price(self.price),
|
||||||
"\n\t".join(map(_format_type_and_choice, self.choices)),
|
"\n\t".join(map(_format_type_and_choice, self.choices)),
|
||||||
|
@ -86,14 +91,20 @@ class Dish:
|
||||||
return sum(
|
return sum(
|
||||||
f(option.price for option in choice.options)
|
f(option.price for option in choice.options)
|
||||||
for (choice_type, choice) in self.choices
|
for (choice_type, choice) in self.choices
|
||||||
if choice_type == "single_choice"
|
if choice_type == "single_choice")
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Location:
|
class Location:
|
||||||
def __init__(
|
|
||||||
self, id_, *, name, dishes, osm=None, address=None, telephone=None, website=None
|
def __init__(self,
|
||||||
):
|
id_,
|
||||||
|
*,
|
||||||
|
name,
|
||||||
|
dishes,
|
||||||
|
osm=None,
|
||||||
|
address=None,
|
||||||
|
telephone=None,
|
||||||
|
website=None):
|
||||||
self.id: str = id_
|
self.id: str = id_
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
self.osm: Optional[str] = osm
|
self.osm: Optional[str] = osm
|
||||||
|
@ -107,24 +118,18 @@ class Location:
|
||||||
return first(filter(lambda d: d.id == dish_id, self.dishes))
|
return first(filter(lambda d: d.id == dish_id, self.dishes))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return (
|
return ("============================\n"
|
||||||
"============================\n"
|
|
||||||
"{0.id}: {0.name}"
|
"{0.id}: {0.name}"
|
||||||
"{1}\n"
|
"{1}\n"
|
||||||
"============================\n"
|
"============================\n"
|
||||||
"\n"
|
"\n"
|
||||||
"{2}"
|
"{2}").format(
|
||||||
).format(
|
|
||||||
self,
|
self,
|
||||||
"".join(
|
"".join(f"\n\t{k} {v}" for k, v in (
|
||||||
"\n\t{} {}".format(k, v)
|
|
||||||
for k, v in (
|
|
||||||
("osm", self.osm),
|
("osm", self.osm),
|
||||||
("address", self.address),
|
("address", self.address),
|
||||||
("telephone", self.telephone),
|
("telephone", self.telephone),
|
||||||
("website", self.website),
|
("website", self.website),
|
||||||
)
|
) if v is not None),
|
||||||
if v is not None
|
|
||||||
),
|
|
||||||
"\n".join(map(str, self.dishes)),
|
"\n".join(map(str, self.dishes)),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from glob import glob
|
|
||||||
from os import path
|
|
||||||
import itertools
|
import itertools
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import Iterable, List, Union, Tuple
|
from glob import glob
|
||||||
|
from os import path
|
||||||
|
from typing import Iterable, List, Tuple, Union
|
||||||
|
|
||||||
from tatsu import parse as tatsu_parse
|
from tatsu import parse as tatsu_parse
|
||||||
from tatsu.ast import AST
|
from tatsu.ast import AST
|
||||||
from tatsu.exceptions import SemanticError
|
from tatsu.exceptions import SemanticError
|
||||||
from .models import Location, Choice, Option, Dish
|
|
||||||
from utils import first
|
from utils import first
|
||||||
|
|
||||||
|
from .models import Choice, Dish, Location, Option
|
||||||
|
|
||||||
# TODO Use proper way to get resources, see https://stackoverflow.com/a/10935674
|
# TODO Use proper way to get resources, see https://stackoverflow.com/a/10935674
|
||||||
with open(path.join(path.dirname(__file__), "hlds.tatsu")) as fh:
|
with open(path.join(path.dirname(__file__), "hlds.tatsu")) as fh:
|
||||||
|
@ -58,14 +59,16 @@ class HldsSemanticActions:
|
||||||
option.price += dish.price
|
option.price += dish.price
|
||||||
dish.price = 0
|
dish.price = 0
|
||||||
dishes = list(dishes)
|
dishes = list(dishes)
|
||||||
dishes.append(Dish(
|
dishes.append(
|
||||||
|
Dish(
|
||||||
"custom",
|
"custom",
|
||||||
name="Vrije keuze",
|
name="Vrije keuze",
|
||||||
description="Zet wat je wil in comment",
|
description="Zet wat je wil in comment",
|
||||||
price=0,
|
price=0,
|
||||||
tags=[],
|
tags=[],
|
||||||
choices=[],
|
choices=[],
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
attributes = {att["key"]: att["value"] for att in ast["attributes"]}
|
attributes = {att["key"]: att["value"] for att in ast["attributes"]}
|
||||||
|
|
||||||
|
@ -145,7 +148,7 @@ def parse(menu: str) -> List[Location]:
|
||||||
|
|
||||||
|
|
||||||
def parse_file(filename: str) -> List[Location]:
|
def parse_file(filename: str) -> List[Location]:
|
||||||
with open(filename, "r") as file_handle:
|
with open(filename) as file_handle:
|
||||||
return parse(file_handle.read())
|
return parse(file_handle.read())
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
"Script for everything related to logging in and out"
|
"Script for everything related to logging in and out"
|
||||||
from flask import Blueprint, abort, redirect, session, url_for
|
from flask import Blueprint, abort, redirect, session, url_for
|
||||||
from flask_login import current_user, logout_user
|
from flask_login import current_user, logout_user
|
||||||
from werkzeug.wrappers import Response
|
|
||||||
|
|
||||||
from models import User
|
from models import User
|
||||||
|
from werkzeug.wrappers import Response
|
||||||
from zeus import zeus_login
|
from zeus import zeus_login
|
||||||
|
|
||||||
auth_bp = Blueprint("auth_bp", __name__)
|
auth_bp = Blueprint("auth_bp", __name__)
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
"Script that runs migrations online or offline"
|
"Script that runs migrations online or offline"
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
from logging.config import fileConfig
|
from logging.config import fileConfig
|
||||||
|
|
||||||
from alembic import context
|
from alembic import context
|
||||||
|
|
||||||
# add your model's MetaData object here
|
# add your model's MetaData object here
|
||||||
# for 'autogenerate' support
|
# for 'autogenerate' support
|
||||||
# from myapp import mymodel
|
# from myapp import mymodel
|
||||||
|
|
|
@ -12,11 +12,11 @@ revision = "9159a6fed021"
|
||||||
down_revision = "150252c1cdb1"
|
down_revision = "150252c1cdb1"
|
||||||
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from sqlalchemy.sql import table, column, text
|
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
from hlds.definitions import location_definitions
|
from hlds.definitions import location_definitions
|
||||||
|
from sqlalchemy.sql import column, table, text
|
||||||
|
|
||||||
LOCATION_LEGACY_TO_HLDS = {
|
LOCATION_LEGACY_TO_HLDS = {
|
||||||
2: "blauw_kotje",
|
2: "blauw_kotje",
|
||||||
|
@ -50,43 +50,67 @@ LOCATION_LEGACY_TO_HLDS = {
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# First the simple actions
|
# First the simple actions
|
||||||
op.create_table("order_item_choice",
|
op.create_table(
|
||||||
|
"order_item_choice",
|
||||||
sa.Column("id", sa.Integer, nullable=False),
|
sa.Column("id", sa.Integer, nullable=False),
|
||||||
sa.Column("choice_id", sa.String(length=64), nullable=True),
|
sa.Column("choice_id", sa.String(length=64), nullable=True),
|
||||||
sa.Column("order_item_id", sa.Integer, nullable=False),
|
sa.Column("order_item_id", sa.Integer, nullable=False),
|
||||||
sa.Column("kind", sa.String(length=1), nullable=False),
|
sa.Column("kind", sa.String(length=1), nullable=False),
|
||||||
sa.Column("name", sa.String(length=120), nullable=True),
|
sa.Column("name", sa.String(length=120), nullable=True),
|
||||||
sa.Column("value", sa.String(length=120), nullable=True),
|
sa.Column("value", sa.String(length=120), nullable=True),
|
||||||
sa.ForeignKeyConstraint(["order_item_id"], ["order_item.id"], ),
|
sa.ForeignKeyConstraint(
|
||||||
sa.PrimaryKeyConstraint("id")
|
["order_item_id"],
|
||||||
|
["order_item.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"order_item",
|
||||||
|
sa.Column("hlds_data_version", sa.String(length=40), nullable=True),
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"order", "courrier_id", new_column_name="courier_id", type_=sa.Integer
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"order_item",
|
||||||
|
"extra",
|
||||||
|
new_column_name="comment",
|
||||||
|
existing_type=sa.String(254),
|
||||||
|
type_=sa.Text,
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"order_item", "name", new_column_name="user_name", type_=sa.String(120)
|
||||||
)
|
)
|
||||||
op.add_column("order_item", sa.Column("hlds_data_version", sa.String(length=40), nullable=True))
|
|
||||||
op.alter_column("order", "courrier_id", new_column_name="courier_id", type_=sa.Integer)
|
|
||||||
op.alter_column("order_item", "extra", new_column_name="comment",
|
|
||||||
existing_type=sa.String(254), type_=sa.Text)
|
|
||||||
op.alter_column("order_item", "name", new_column_name="user_name", type_=sa.String(120))
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------------
|
||||||
# Migrate historical product data to order items
|
# Migrate historical product data to order items
|
||||||
|
|
||||||
# First create the new columns we will populate
|
# First create the new columns we will populate
|
||||||
op.add_column("order_item", sa.Column("dish_id", sa.String(length=64), nullable=True))
|
op.add_column(
|
||||||
op.add_column("order_item", sa.Column("dish_name", sa.String(length=120), nullable=True))
|
"order_item", sa.Column("dish_id", sa.String(length=64), nullable=True)
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"order_item", sa.Column("dish_name", sa.String(length=120), nullable=True)
|
||||||
|
)
|
||||||
op.add_column("order_item", sa.Column("price", sa.Integer(), nullable=True))
|
op.add_column("order_item", sa.Column("price", sa.Integer(), nullable=True))
|
||||||
# Brief, ad-hoc table constructs just for our UPDATE statement, see
|
# Brief, ad-hoc table constructs just for our UPDATE statement, see
|
||||||
# https://alembic.sqlalchemy.org/en/latest/ops.html#alembic.operations.Operations.execute
|
# https://alembic.sqlalchemy.org/en/latest/ops.html#alembic.operations.Operations.execute
|
||||||
order_item = table("order_item",
|
order_item = table(
|
||||||
|
"order_item",
|
||||||
column("product_id", sa.Integer),
|
column("product_id", sa.Integer),
|
||||||
column("dish_id", sa.String),
|
column("dish_id", sa.String),
|
||||||
column("dish_name", sa.String),
|
column("dish_name", sa.String),
|
||||||
column("price", sa.Integer)
|
column("price", sa.Integer),
|
||||||
)
|
)
|
||||||
# Construct and execute queries
|
# Construct and execute queries
|
||||||
op.execute(text("""
|
op.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
UPDATE order_item
|
UPDATE order_item
|
||||||
SET dish_name = (SELECT product.name FROM product WHERE product.id = order_item.product_id),
|
SET dish_name = (SELECT product.name FROM product WHERE product.id = order_item.product_id),
|
||||||
price = (SELECT product.price FROM product WHERE product.id = order_item.product_id)"""
|
price = (SELECT product.price FROM product WHERE product.id = order_item.product_id)"""
|
||||||
))
|
)
|
||||||
|
)
|
||||||
# Historical product data migrated, drop obsolete column and table
|
# Historical product data migrated, drop obsolete column and table
|
||||||
op.execute(text("ALTER TABLE order_item DROP FOREIGN KEY order_item_ibfk_3"))
|
op.execute(text("ALTER TABLE order_item DROP FOREIGN KEY order_item_ibfk_3"))
|
||||||
op.drop_column("order_item", "product_id")
|
op.drop_column("order_item", "product_id")
|
||||||
|
@ -96,16 +120,26 @@ def upgrade():
|
||||||
# Migrate historical location data to orders
|
# Migrate historical location data to orders
|
||||||
|
|
||||||
op.execute(text("ALTER TABLE `order` DROP FOREIGN KEY order_ibfk_2"))
|
op.execute(text("ALTER TABLE `order` DROP FOREIGN KEY order_ibfk_2"))
|
||||||
op.alter_column("order", "location_id", new_column_name="legacy_location_id",
|
op.alter_column(
|
||||||
type_=sa.Integer, nullable=True)
|
"order",
|
||||||
op.add_column("order", sa.Column("location_id", sa.String(length=64), nullable=True))
|
"location_id",
|
||||||
op.add_column("order", sa.Column("location_name", sa.String(length=128), nullable=True))
|
new_column_name="legacy_location_id",
|
||||||
|
type_=sa.Integer,
|
||||||
|
nullable=True,
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"order", sa.Column("location_id", sa.String(length=64), nullable=True)
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"order", sa.Column("location_name", sa.String(length=128), nullable=True)
|
||||||
|
)
|
||||||
# Brief, ad-hoc table constructs just for our UPDATE statement, see
|
# Brief, ad-hoc table constructs just for our UPDATE statement, see
|
||||||
# https://alembic.sqlalchemy.org/en/latest/ops.html#alembic.operations.Operations.execute
|
# https://alembic.sqlalchemy.org/en/latest/ops.html#alembic.operations.Operations.execute
|
||||||
order = table("order",
|
order = table(
|
||||||
|
"order",
|
||||||
column("legacy_location_id", sa.Integer),
|
column("legacy_location_id", sa.Integer),
|
||||||
column("location_id", sa.String),
|
column("location_id", sa.String),
|
||||||
column("location_name", sa.String)
|
column("location_name", sa.String),
|
||||||
)
|
)
|
||||||
# Construct and execute queries
|
# Construct and execute queries
|
||||||
new_location_id = [
|
new_location_id = [
|
||||||
|
@ -114,7 +148,8 @@ def upgrade():
|
||||||
.values(location_id=new_id)
|
.values(location_id=new_id)
|
||||||
for old_id, new_id in LOCATION_LEGACY_TO_HLDS.items()
|
for old_id, new_id in LOCATION_LEGACY_TO_HLDS.items()
|
||||||
]
|
]
|
||||||
location_name_from_location = text("""
|
location_name_from_location = text(
|
||||||
|
"""
|
||||||
UPDATE `order`
|
UPDATE `order`
|
||||||
SET location_name = (SELECT location.name FROM location
|
SET location_name = (SELECT location.name FROM location
|
||||||
WHERE location.id = `order`.legacy_location_id)"""
|
WHERE location.id = `order`.legacy_location_id)"""
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
"Script for everything Order related in the database"
|
"Script for everything Order related in the database"
|
||||||
import typing
|
import typing
|
||||||
from datetime import datetime
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from utils import first
|
|
||||||
from hlds.definitions import location_definitions
|
from hlds.definitions import location_definitions
|
||||||
|
from utils import first
|
||||||
|
|
||||||
from .database import db
|
from .database import db
|
||||||
from .user import User
|
from .user import User
|
||||||
|
|
||||||
|
@ -31,9 +32,9 @@ class Order(db.Model):
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
# pylint: disable=R1705
|
# pylint: disable=R1705
|
||||||
if self.location:
|
if self.location:
|
||||||
return "Order %d @ %s" % (self.id, self.location.name or "None")
|
return f"Order {self.id} @ {self.location.name or 'None'}"
|
||||||
else:
|
else:
|
||||||
return "Order %d" % (self.id)
|
return f"Order {self.id}"
|
||||||
|
|
||||||
def update_from_hlds(self) -> None:
|
def update_from_hlds(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -46,19 +47,21 @@ 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"
|
||||||
return list(
|
return list(
|
||||||
filter(
|
filter(
|
||||||
(lambda i: i.user == user)
|
(lambda i: i.user == user)
|
||||||
if user is not None
|
if user is not None
|
||||||
else (lambda i: i.user_name == anon),
|
else (lambda i: i.user_name == anon),
|
||||||
self.items
|
self.items,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
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] = dict()
|
group: typing.Dict[str, typing.List] = {}
|
||||||
|
|
||||||
|
# pylint: disable=E1133
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
if item.for_name not in group:
|
if item.for_name not in group:
|
||||||
group[item.for_name] = []
|
group[item.for_name] = []
|
||||||
|
@ -70,12 +73,17 @@ class Order(db.Model):
|
||||||
|
|
||||||
return list(sorted(group.items(), key=lambda t: (t[0] or "", t[1] or "")))
|
return list(sorted(group.items(), key=lambda t: (t[0] or "", t[1] or "")))
|
||||||
|
|
||||||
def group_by_dish(self) \
|
def group_by_dish(
|
||||||
-> typing.List[typing.Tuple[str, int, typing.List[typing.Tuple[str, typing.List]]]]:
|
self,
|
||||||
|
) -> 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]] = \
|
group: typing.Dict[str, typing.Dict[str, typing.List]] = defaultdict(
|
||||||
defaultdict(lambda: defaultdict(list))
|
lambda: defaultdict(list)
|
||||||
|
)
|
||||||
|
|
||||||
|
# pylint: disable=E1133
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
group[item.dish_name][item.comment].append(item)
|
group[item.dish_name][item.comment].append(item)
|
||||||
|
|
||||||
|
@ -87,12 +95,13 @@ class Order(db.Model):
|
||||||
sorted(
|
sorted(
|
||||||
(comment, sorted(items, key=lambda x: (x.for_name or "")))
|
(comment, sorted(items, key=lambda x: (x.for_name or "")))
|
||||||
for comment, items in comment_group.items()
|
for comment, items in comment_group.items()
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
for dish_name, comment_group in group.items()
|
for dish_name, comment_group in group.items()
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_closed(self) -> bool:
|
def is_closed(self) -> bool:
|
||||||
|
"Return whether or not 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:
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
"Script for everything OrderItem related in the database"
|
"Script for everything OrderItem related in the database"
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from utils import first
|
|
||||||
from hlds.definitions import location_definitions
|
from hlds.definitions import location_definitions
|
||||||
|
from utils import first
|
||||||
|
|
||||||
from .database import db
|
from .database import db
|
||||||
from .order import Order
|
from .order import Order
|
||||||
from .user import User
|
from .user import User
|
||||||
|
@ -21,20 +22,20 @@ class OrderItem(db.Model):
|
||||||
comment = db.Column(db.Text(), nullable=True)
|
comment = db.Column(db.Text(), nullable=True)
|
||||||
hlds_data_version = db.Column(db.String(40), nullable=True)
|
hlds_data_version = db.Column(db.String(40), nullable=True)
|
||||||
|
|
||||||
choices = db.relationship("OrderItemChoice", backref="order_item", lazy="dynamic")
|
choices = db.relationship("OrderItemChoice",
|
||||||
|
backref="order_item",
|
||||||
|
lazy="dynamic")
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if name == "dish":
|
if name == "dish":
|
||||||
location_id = (
|
location_id = (Order.query.filter(
|
||||||
Order.query.filter(Order.id == self.order_id).first().location_id
|
Order.id == self.order_id).first().location_id)
|
||||||
)
|
|
||||||
location = first(
|
location = first(
|
||||||
filter(lambda l: l.id == location_id, location_definitions)
|
filter(lambda l: l.id == location_id, location_definitions))
|
||||||
)
|
|
||||||
if location:
|
if location:
|
||||||
return first(filter(lambda d: d.id == self.dish_id, location.dishes))
|
return first(
|
||||||
else:
|
filter(lambda d: d.id == self.dish_id, location.dishes))
|
||||||
raise ValueError("No Location found with id: " + location_id)
|
raise ValueError(f"No Location found with id: {location_id}")
|
||||||
raise AttributeError()
|
raise AttributeError()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -45,11 +46,7 @@ class OrderItem(db.Model):
|
||||||
return self.user_name
|
return self.user_name
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "Order %d: %s wants %s" % (
|
return "Order {self.order_id or 0}: {self.for_name} wants {self.dish_name or 'None'}"
|
||||||
self.order_id or 0,
|
|
||||||
self.for_name,
|
|
||||||
self.dish_name or "None",
|
|
||||||
)
|
|
||||||
|
|
||||||
def update_from_hlds(self) -> None:
|
def update_from_hlds(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
"Script for everything OrderItemChoice related in the database"
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from .database import db
|
from .database import db
|
||||||
|
@ -5,6 +6,7 @@ from .orderitem import OrderItem
|
||||||
|
|
||||||
|
|
||||||
class OrderItemChoice(db.Model):
|
class OrderItemChoice(db.Model):
|
||||||
|
"Class used for configuring the OrderItemChoice model in the database"
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
choice_id = db.Column(db.String(64), nullable=True)
|
choice_id = db.Column(db.String(64), nullable=True)
|
||||||
order_item_id = db.Column(
|
order_item_id = db.Column(
|
||||||
|
@ -16,7 +18,8 @@ class OrderItemChoice(db.Model):
|
||||||
|
|
||||||
# pylint: disable=attribute-defined-outside-init
|
# pylint: disable=attribute-defined-outside-init
|
||||||
def configure(self, order: OrderItem) -> None:
|
def configure(self, order: OrderItem) -> None:
|
||||||
|
"Set the orderitem"
|
||||||
self.order = order
|
self.order = order
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "{}: {}".format(self.name, self.value)
|
return f"{self.name}: {self.value}"
|
||||||
|
|
|
@ -39,4 +39,4 @@ class User(db.Model):
|
||||||
return str(self.id)
|
return str(self.id)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "%s" % self.username
|
return f"{self.username}"
|
||||||
|
|
|
@ -16,7 +16,7 @@ def webhook_text(order: Order) -> typing.Optional[str]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if order.courier is not None:
|
if order.courier is not None:
|
||||||
# pylint: disable=C0301
|
# 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_id", order_id=order.id, _external=True),
|
||||||
order.location_name,
|
order.location_name,
|
||||||
|
@ -24,6 +24,7 @@ def webhook_text(order: Order) -> typing.Optional[str]:
|
||||||
order.courier.username.title(),
|
order.courier.username.title(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# pylint: disable=C0209
|
||||||
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),
|
||||||
|
@ -43,7 +44,7 @@ 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(WebhookSenderThread, self).__init__()
|
super().__init__()
|
||||||
self.message = message
|
self.message = message
|
||||||
self.url = url
|
self.url = url
|
||||||
|
|
||||||
|
@ -64,4 +65,4 @@ def remaining_minutes(value) -> str:
|
||||||
if delta.total_seconds() < 0:
|
if delta.total_seconds() < 0:
|
||||||
return "0"
|
return "0"
|
||||||
minutes = delta.total_seconds() // 60
|
minutes = delta.total_seconds() // 60
|
||||||
return "%02d" % minutes
|
return f"{minutes:02}"
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from tatsu.util import asjson
|
|
||||||
from hlds.parser import parse_files
|
from hlds.parser import parse_files
|
||||||
|
|
||||||
|
|
||||||
USAGE = """{0} [filename]...
|
USAGE = """{0} [filename]...
|
||||||
Parse HLDS files, print as JSON
|
Parse HLDS files, print as JSON
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,9 @@ def euro_string(value: int) -> str:
|
||||||
"""
|
"""
|
||||||
euro, cents = divmod(value, 100)
|
euro, cents = divmod(value, 100)
|
||||||
if cents:
|
if cents:
|
||||||
return "€ {}.{:02}".format(euro, cents)
|
return f"€ {euro}.{cents:02}"
|
||||||
else:
|
else:
|
||||||
return "€ {}".format(euro)
|
return f"€ {euro}"
|
||||||
|
|
||||||
|
|
||||||
def price_range_string(price_range, include_upper=False):
|
def price_range_string(price_range, include_upper=False):
|
||||||
|
|
|
@ -17,12 +17,12 @@ def list_routes() -> str:
|
||||||
for rule in app.url_map.iter_rules():
|
for rule in app.url_map.iter_rules():
|
||||||
options = {}
|
options = {}
|
||||||
for arg in rule.arguments:
|
for arg in rule.arguments:
|
||||||
options[arg] = "[{0}]".format(arg)
|
options[arg] = f"[{arg}]"
|
||||||
print(rule.endpoint)
|
print(rule.endpoint)
|
||||||
methods = ",".join(rule.methods)
|
methods = ",".join(rule.methods)
|
||||||
url = url_for(rule.endpoint, **options)
|
url = url_for(rule.endpoint, **options)
|
||||||
line = urllib.parse.unquote(
|
line = urllib.parse.unquote(
|
||||||
"{:50s} {:20s} {}".format(rule.endpoint, methods, url)
|
f"{rule.endpoint:50s} {methods:20s} {url}"
|
||||||
)
|
)
|
||||||
output.append(line)
|
output.append(line)
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,26 @@
|
||||||
"Script to generate the general views of Haldis"
|
"Script to generate the general views of Haldis"
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import yaml
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from flask import Flask, render_template, make_response
|
import yaml
|
||||||
from flask import request, jsonify
|
from flask import Blueprint, Flask, abort
|
||||||
from flask import Blueprint, abort
|
|
||||||
from flask import current_app as app
|
from flask import current_app as app
|
||||||
from flask import send_from_directory, url_for
|
from flask import (jsonify, make_response, render_template, request,
|
||||||
|
send_from_directory, url_for)
|
||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
|
|
||||||
from utils import first
|
|
||||||
from hlds.definitions import location_definitions
|
from hlds.definitions import location_definitions
|
||||||
from hlds.models import Location
|
from hlds.models import Location
|
||||||
from models import Order
|
from models import Order
|
||||||
|
from utils import first
|
||||||
# import views
|
# import views
|
||||||
from views.order import get_orders
|
from views.order import get_orders
|
||||||
|
|
||||||
import json
|
|
||||||
from flask import jsonify
|
|
||||||
|
|
||||||
general_bp = Blueprint("general_bp", __name__)
|
general_bp = Blueprint("general_bp", __name__)
|
||||||
|
|
||||||
|
|
||||||
with open(os.path.join(os.path.dirname(__file__), "themes.yml"), "r") as _stream:
|
with open(os.path.join(os.path.dirname(__file__), "themes.yml")) as _stream:
|
||||||
_theme_data = yaml.safe_load(_stream)
|
_theme_data = yaml.safe_load(_stream)
|
||||||
THEME_OPTIONS = _theme_data["options"]
|
THEME_OPTIONS = _theme_data["options"]
|
||||||
THEMES = _theme_data["themes"]
|
THEMES = _theme_data["themes"]
|
||||||
|
@ -37,7 +31,7 @@ def home() -> str:
|
||||||
"Generate the home view"
|
"Generate the home view"
|
||||||
prev_day = datetime.now() - timedelta(days=1)
|
prev_day = datetime.now() - timedelta(days=1)
|
||||||
recently_closed = get_orders(
|
recently_closed = get_orders(
|
||||||
((Order.stoptime > prev_day) & (Order.stoptime < datetime.now()))
|
(Order.stoptime > prev_day) & (Order.stoptime < datetime.now())
|
||||||
)
|
)
|
||||||
return render_template(
|
return render_template(
|
||||||
"home.html", orders=get_orders(), recently_closed=recently_closed
|
"home.html", orders=get_orders(), recently_closed=recently_closed
|
||||||
|
@ -60,7 +54,7 @@ def is_theme_active(theme, now):
|
||||||
|
|
||||||
return start_datetime <= now <= end_datetime
|
return start_datetime <= now <= end_datetime
|
||||||
|
|
||||||
raise Exception("Unknown theme type {}".format(theme_type))
|
raise Exception(f"Unknown theme type {theme_type}")
|
||||||
|
|
||||||
|
|
||||||
def get_theme_css(theme, options):
|
def get_theme_css(theme, options):
|
||||||
|
@ -71,13 +65,18 @@ def get_theme_css(theme, options):
|
||||||
|
|
||||||
for option in theme.get("options", []):
|
for option in theme.get("options", []):
|
||||||
theme_name = theme["name"]
|
theme_name = theme["name"]
|
||||||
assert option in THEME_OPTIONS, f"Theme `{theme_name}` uses undefined option `{option}`"
|
assert (
|
||||||
|
option in THEME_OPTIONS
|
||||||
|
), f"Theme `{theme_name}` uses undefined option `{option}`"
|
||||||
|
|
||||||
chosen_value = options[option]
|
chosen_value = options[option]
|
||||||
possible_values = list(THEME_OPTIONS[option].keys())
|
possible_values = list(THEME_OPTIONS[option].keys())
|
||||||
|
|
||||||
value = chosen_value if chosen_value in possible_values \
|
value = (
|
||||||
|
chosen_value
|
||||||
|
if chosen_value in possible_values
|
||||||
else THEME_OPTIONS[option]["_default"]
|
else THEME_OPTIONS[option]["_default"]
|
||||||
|
)
|
||||||
|
|
||||||
filename += "_" + value
|
filename += "_" + value
|
||||||
|
|
||||||
|
@ -119,13 +118,15 @@ def current_theme_js():
|
||||||
themes = get_active_themes()
|
themes = get_active_themes()
|
||||||
|
|
||||||
selected_theme_name = request.cookies.get("theme", None)
|
selected_theme_name = request.cookies.get("theme", None)
|
||||||
matching_theme = first((t for t in themes if t["file"] == selected_theme_name))
|
matching_theme = first(t for t in themes if t["file"] == selected_theme_name)
|
||||||
cur_theme = matching_theme or themes[-1]
|
cur_theme = matching_theme or themes[-1]
|
||||||
|
|
||||||
response = make_response(rf'''
|
response = make_response(
|
||||||
|
rf"""
|
||||||
var currentTheme = {json.dumps(cur_theme['file'])};
|
var currentTheme = {json.dumps(cur_theme['file'])};
|
||||||
var currentThemeOptions = {json.dumps(cur_theme.get('options', []))};
|
var currentThemeOptions = {json.dumps(cur_theme.get('options', []))};
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
response.headers["Content-Type"] = "text/javascript"
|
response.headers["Content-Type"] = "text/javascript"
|
||||||
|
|
||||||
# Theme name that is not valid at this moment: delete cookie
|
# Theme name that is not valid at this moment: delete cookie
|
||||||
|
@ -166,7 +167,8 @@ def location_dish(location_id, dish_id) -> str:
|
||||||
dish = loc.dish_by_id(dish_id)
|
dish = loc.dish_by_id(dish_id)
|
||||||
if dish is None:
|
if dish is None:
|
||||||
abort(404)
|
abort(404)
|
||||||
return jsonify([
|
return jsonify(
|
||||||
|
[
|
||||||
{
|
{
|
||||||
"type": c[0],
|
"type": c[0],
|
||||||
"id": c[1].id,
|
"id": c[1].id,
|
||||||
|
@ -184,7 +186,8 @@ def location_dish(location_id, dish_id) -> str:
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
for c in dish.choices
|
for c in dish.choices
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@general_bp.route("/about/")
|
@general_bp.route("/about/")
|
||||||
|
@ -204,7 +207,7 @@ def profile() -> str:
|
||||||
def favicon() -> str:
|
def favicon() -> str:
|
||||||
"Generate the favicon"
|
"Generate the favicon"
|
||||||
# pylint: disable=R1705
|
# pylint: disable=R1705
|
||||||
if not get_orders((Order.stoptime > datetime.now())):
|
if not get_orders(Order.stoptime > datetime.now()):
|
||||||
return send_from_directory(
|
return send_from_directory(
|
||||||
os.path.join(app.root_path, "static"),
|
os.path.join(app.root_path, "static"),
|
||||||
"favicon.ico",
|
"favicon.ico",
|
||||||
|
|
|
@ -3,27 +3,16 @@ import random
|
||||||
import typing
|
import typing
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from werkzeug.wrappers import Response
|
|
||||||
|
|
||||||
# from flask import current_app as app
|
# from flask import current_app as app
|
||||||
from flask import (
|
from flask import (Blueprint, abort, flash, redirect, render_template, request,
|
||||||
Blueprint,
|
session, url_for, wrappers)
|
||||||
abort,
|
|
||||||
flash,
|
|
||||||
redirect,
|
|
||||||
render_template,
|
|
||||||
request,
|
|
||||||
session,
|
|
||||||
url_for,
|
|
||||||
wrappers,
|
|
||||||
)
|
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
|
|
||||||
from forms import AnonOrderItemForm, OrderForm, OrderItemForm
|
from forms import AnonOrderItemForm, OrderForm, OrderItemForm
|
||||||
|
from hlds.definitions import location_definition_version, location_definitions
|
||||||
from models import Order, OrderItem, User, db
|
from models import Order, OrderItem, User, db
|
||||||
from hlds.definitions import location_definitions, location_definition_version
|
|
||||||
from notification import post_order_to_webhook
|
from notification import post_order_to_webhook
|
||||||
from utils import ignore_none
|
from utils import ignore_none
|
||||||
|
from werkzeug.wrappers import Response
|
||||||
|
|
||||||
order_bp = Blueprint("order_bp", "order")
|
order_bp = Blueprint("order_bp", "order")
|
||||||
|
|
||||||
|
@ -72,8 +61,8 @@ def order_from_id(order_id: int, form: OrderForm = None, dish_id=None) -> str:
|
||||||
form.populate(order.location)
|
form.populate(order.location)
|
||||||
if order.is_closed():
|
if order.is_closed():
|
||||||
form = None
|
form = None
|
||||||
total_price = sum([o.price for o in order.items])
|
total_price = sum(o.price for o in order.items)
|
||||||
debts = sum([o.price for o in order.items if not o.paid])
|
debts = sum(o.price for o in order.items if not o.paid)
|
||||||
|
|
||||||
dish = order.location.dish_by_id(dish_id) if order.location else None
|
dish = order.location.dish_by_id(dish_id) if order.location else None
|
||||||
|
|
||||||
|
@ -96,7 +85,7 @@ def items_shop_view(order_id: int) -> str:
|
||||||
if current_user.is_anonymous() and not order.public:
|
if current_user.is_anonymous() and not order.public:
|
||||||
flash("Please login to see this order.", "info")
|
flash("Please login to see this order.", "info")
|
||||||
abort(401)
|
abort(401)
|
||||||
total_price = sum([o.price for o in order.items])
|
total_price = sum(o.price for o in order.items)
|
||||||
return render_template("order_items.html", order=order, total_price=total_price)
|
return render_template("order_items.html", order=order, total_price=total_price)
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,7 +127,9 @@ def order_item_create(order_id: int) -> typing.Any:
|
||||||
abort(404)
|
abort(404)
|
||||||
form = AnonOrderItemForm() if current_user.is_anonymous() else OrderItemForm()
|
form = AnonOrderItemForm() if current_user.is_anonymous() else OrderItemForm()
|
||||||
|
|
||||||
dish_id = request.form["dish_id"] if form.is_submitted() else request.args.get("dish")
|
dish_id = (
|
||||||
|
request.form["dish_id"] if form.is_submitted() else request.args.get("dish")
|
||||||
|
)
|
||||||
if dish_id and not location.dish_by_id(dish_id):
|
if dish_id and not location.dish_by_id(dish_id):
|
||||||
abort(404)
|
abort(404)
|
||||||
if not form.is_submitted():
|
if not form.is_submitted():
|
||||||
|
@ -347,6 +338,6 @@ def get_orders(expression=None) -> typing.List[Order]:
|
||||||
else:
|
else:
|
||||||
order_list = Order.query.filter(
|
order_list = Order.query.filter(
|
||||||
# pylint: disable=C0121
|
# pylint: disable=C0121
|
||||||
(expression & (Order.public == True))
|
expression & (Order.public == True)
|
||||||
).all()
|
).all()
|
||||||
return order_list
|
return order_list
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
"Script to generate the stats related views of Haldis"
|
"Script to generate the stats related views of Haldis"
|
||||||
|
from fatmodels import FatLocation, FatOrder, FatOrderItem, FatUser
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import current_app as app
|
from flask import current_app as app
|
||||||
from flask import render_template
|
from flask import render_template
|
||||||
|
|
||||||
from fatmodels import FatLocation, FatOrder, FatOrderItem, FatUser
|
|
||||||
|
|
||||||
stats_blueprint = Blueprint("stats_blueprint", __name__)
|
stats_blueprint = Blueprint("stats_blueprint", __name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
15
app/zeus.py
15
app/zeus.py
|
@ -1,12 +1,12 @@
|
||||||
"Script containing everything specific to ZeusWPI"
|
"Script containing everything specific to ZeusWPI"
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from flask import Blueprint, current_app, flash, redirect, request, session, url_for
|
from flask import (Blueprint, current_app, flash, redirect, request, session,
|
||||||
|
url_for)
|
||||||
from flask_login import login_user
|
from flask_login import login_user
|
||||||
from flask_oauthlib.client import OAuth, OAuthException
|
from flask_oauthlib.client import OAuth, OAuthException
|
||||||
from werkzeug.wrappers import Response
|
|
||||||
|
|
||||||
from models import User, db
|
from models import User, db
|
||||||
|
from werkzeug.wrappers import Response
|
||||||
|
|
||||||
oauth_bp = Blueprint("oauth_bp", __name__)
|
oauth_bp = Blueprint("oauth_bp", __name__)
|
||||||
|
|
||||||
|
@ -14,8 +14,7 @@ oauth_bp = Blueprint("oauth_bp", __name__)
|
||||||
def zeus_login():
|
def zeus_login():
|
||||||
"Log in using ZeusWPI"
|
"Log in using ZeusWPI"
|
||||||
return current_app.zeus.authorize(
|
return current_app.zeus.authorize(
|
||||||
callback=url_for("oauth_bp.authorized", _external=True)
|
callback=url_for("oauth_bp.authorized", _external=True))
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@oauth_bp.route("/login/zeus/authorized")
|
@oauth_bp.route("/login/zeus/authorized")
|
||||||
|
@ -25,10 +24,8 @@ def authorized() -> typing.Any:
|
||||||
"Check authorized status"
|
"Check authorized status"
|
||||||
resp = current_app.zeus.authorized_response()
|
resp = current_app.zeus.authorized_response()
|
||||||
if resp is None:
|
if resp is None:
|
||||||
return "Access denied: reason=%s error=%s" % (
|
# pylint: disable=C0301
|
||||||
request.args["error"],
|
return f"Access denied: reason={request.args['error']} error={request.args['error_description']}"
|
||||||
request.args["error_description"],
|
|
||||||
)
|
|
||||||
if isinstance(resp, OAuthException):
|
if isinstance(resp, OAuthException):
|
||||||
return f"Access denied: {resp.message}<br>{resp.data}"
|
return f"Access denied: {resp.message}<br>{resp.data}"
|
||||||
|
|
||||||
|
|
2
pylint-requirement.txt
Normal file
2
pylint-requirement.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pylint-flask
|
||||||
|
pylint-flask-sqlalchemy
|
Loading…
Reference in a new issue