Fix formatting

This commit is contained in:
Jan-Pieter Baert 2022-04-19 22:03:00 +02:00
parent 1cdd22c1c0
commit 5e29f2a5f7
No known key found for this signature in database
GPG key ID: C61C42632C5952F3
26 changed files with 272 additions and 231 deletions

0
app/__init__.py Normal file
View file

View 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:

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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}").format(
"{2}" self,
).format( "".join(f"\n\t{k} {v}" for k, v in (
self, ("osm", self.osm),
"".join( ("address", self.address),
"\n\t{} {}".format(k, v) ("telephone", self.telephone),
for k, v in ( ("website", self.website),
("osm", self.osm), ) if v is not None),
("address", self.address), "\n".join(map(str, self.dishes)),
("telephone", self.telephone),
("website", self.website),
) )
if v is not None
),
"\n".join(map(str, self.dishes)),
)

View file

@ -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(
"custom", Dish(
name="Vrije keuze", "custom",
description="Zet wat je wil in comment", name="Vrije keuze",
price=0, description="Zet wat je wil in comment",
tags=[], price=0,
choices=[], tags=[],
)) 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())

View file

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

View file

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

View file

@ -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,71 +50,106 @@ 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")
op.drop_table("product") op.drop_table("product")
#---------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------
# 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 = [
order.update() order.update()
.where(order.c.legacy_location_id == old_id) .where(order.c.legacy_location_id == old_id)
.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)"""

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,25 +167,27 @@ 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], {
"id": c[1].id, "type": c[0],
"name": c[1].name, "id": c[1].id,
"description": c[1].description, "name": c[1].name,
"options": [ "description": c[1].description,
{ "options": [
"id": o.id, {
"name": o.name, "id": o.id,
"description": o.description, "name": o.name,
"price": o.price, "description": o.description,
"tags": o.tags, "price": o.price,
} "tags": o.tags,
for o in c[1].options }
], for o in c[1].options
} ],
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",

View file

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

View file

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

View file

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