From 1cdd22c1c0bfe893bbc1cf2b532a9f762eb8d431 Mon Sep 17 00:00:00 2001 From: Jan-Pieter Baert Date: Tue, 19 Apr 2022 21:08:08 +0200 Subject: [PATCH 1/3] Add fail-under to pylint --- .pylintrc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.pylintrc b/.pylintrc index 1f6b81c..ca0b539 100644 --- a/.pylintrc +++ b/.pylintrc @@ -5,6 +5,8 @@ # run arbitrary code. extension-pkg-whitelist= +fail-under=9 + # Add files or directories to the blacklist. They should be base names, not # paths. ignore=CVS @@ -28,7 +30,7 @@ limit-inference-results=100 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. -load-plugins= +load-plugins=pylint_flask_sqlalchemy,pylint_flask # Pickle collected data for later comparisons. persistent=yes @@ -60,7 +62,7 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use "--disable=all --enable=classes # --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 # either give multiple identifier separated by comma (,) or put this option From 5e29f2a5f7d86b14f34cd5b30e74570cc1698038 Mon Sep 17 00:00:00 2001 From: Jan-Pieter Baert Date: Tue, 19 Apr 2022 22:03:00 +0200 Subject: [PATCH 2/3] Fix formatting --- app/__init__.py | 0 app/add_admins.py | 2 +- app/admin.py | 29 ++++-- app/app.py | 18 ++-- app/create_database.py | 10 +- app/fatmodels.py | 5 +- app/forms.py | 19 +--- app/hlds/__init__.py | 2 +- app/hlds/definitions.py | 6 +- app/hlds/models.py | 73 ++++++++------- app/hlds/parser.py | 29 +++--- app/login.py | 3 +- app/migrations/env.py | 2 - .../9159a6fed021_initial_haldis_support.py | 91 +++++++++++++------ app/models/order.py | 31 ++++--- app/models/orderitem.py | 27 +++--- app/models/orderitemchoice.py | 5 +- app/models/user.py | 2 +- app/notification.py | 7 +- app/parse_hlds.py | 2 - app/utils.py | 4 +- app/views/debug.py | 4 +- app/views/general.py | 83 +++++++++-------- app/views/order.py | 31 +++---- app/views/stats.py | 3 +- app/zeus.py | 15 ++- 26 files changed, 272 insertions(+), 231 deletions(-) create mode 100644 app/__init__.py diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/add_admins.py b/app/add_admins.py index 661c880..3e71c60 100644 --- a/app/add_admins.py +++ b/app/add_admins.py @@ -1,7 +1,7 @@ """Script for adding users as admin to Haldis.""" +from models import User from app import db -from models import User def add() -> None: diff --git a/app/admin.py b/app/admin.py index 553ec7d..8c7acac 100644 --- a/app/admin.py +++ b/app/admin.py @@ -3,7 +3,6 @@ from flask import Flask from flask_admin import Admin from flask_admin.contrib.sqla import ModelView from flask_sqlalchemy import SQLAlchemy - from models import Order, OrderItem, OrderItemChoice, User @@ -26,8 +25,10 @@ class OrderAdminModel(ModelBaseView): column_default_sort = ("starttime", True) column_list = ["starttime", "stoptime", "location_name", "location_id", "courier"] column_labels = { - "starttime": "Start Time", "stoptime": "Closing Time", - "location_id": "HLDS Location ID"} + "starttime": "Start Time", + "stoptime": "Closing Time", + "location_id": "HLDS Location ID", + } form_excluded_columns = ["items", "courier_id"] can_delete = False @@ -36,13 +37,25 @@ class OrderItemAdminModel(ModelBaseView): # pylint: disable=too-few-public-methods column_default_sort = ("order_id", True) column_list = [ - "order_id", "order.location_name", "user_name", "user", "dish_name", "dish_id", "comment", "price", "paid", - "hlds_data_version" + "order_id", + "order.location_name", + "user_name", + "user", + "dish_name", + "dish_id", + "comment", + "price", + "paid", + "hlds_data_version", ] column_labels = { - "order_id": "Order", "order.location_name": "Order's Location", - "user_name": "Anon. User", "user_id": "Registered User", - "hlds_data_version": "HLDS Data Version", "dish_id": "HLDS Dish ID"} + "order_id": "Order", + "order.location_name": "Order's Location", + "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: diff --git a/app/app.py b/app/app.py index 0273c47..0df5cf6 100755 --- a/app/app.py +++ b/app/app.py @@ -3,10 +3,11 @@ """Main Haldis script""" import logging -from logging.handlers import TimedRotatingFileHandler import typing from datetime import datetime +from logging.handlers import TimedRotatingFileHandler +from admin import init_admin from flask import Flask, render_template from flask_bootstrap import Bootstrap, StaticCDN from flask_debugtoolbar import DebugToolbarExtension @@ -14,10 +15,8 @@ from flask_login import LoginManager from flask_migrate import Migrate, MigrateCommand from flask_oauthlib.client import OAuth, OAuthException from flask_script import Manager, Server -from markupsafe import Markup - -from admin import init_admin from login import init_login +from markupsafe import Markup from models import db from models.anonymous_user import AnonymouseUser from utils import euro_string, price_range_string @@ -69,7 +68,8 @@ def register_plugins(app: Flask) -> Manager: # Make cookies more secure app.config.update( - SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE="Lax", + SESSION_COOKIE_HTTPONLY=True, + SESSION_COOKIE_SAMESITE="Lax", ) if not app.debug: @@ -95,11 +95,11 @@ def add_routes(application: Flask) -> None: # import views # 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 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 application.register_blueprint(general_bp, url_prefix="/") diff --git a/app/create_database.py b/app/create_database.py index e11305b..a583eb6 100644 --- a/app/create_database.py +++ b/app/create_database.py @@ -2,7 +2,7 @@ import add_admins -from app import db, app_manager +from app import app_manager, db entry_sets = { "admins": add_admins.add, @@ -27,7 +27,7 @@ def check_if_overwrite() -> bool: def add_all() -> None: "Add all possible entries in the entry_sets to the database" for entry_set, function in entry_sets.items(): - print("Adding {}.".format(entry_set)) + print(f"Adding {entry_set}.") function() @@ -45,14 +45,14 @@ def add_to_current() -> None: def add_numbers() -> str: return " ".join( - ["{}({}), ".format(loc, i) for i, loc in enumerate(available)] + [f"{loc}({i}), " for i, loc in enumerate(available)] ).rstrip(", ") while input("Do you still want to add something? (Y/n) ").lower() not in no: print( "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": add_all() available = [] @@ -60,7 +60,7 @@ def add_to_current() -> None: pass elif answer.isnumeric() and answer in [str(x) for x in range(len(available))]: answer_index = int(answer) - print("Adding {}.".format(available[answer_index])) + print(f"Adding {available[answer_index]}.") entry_sets[str(available[answer_index])]() del available[answer_index] else: diff --git a/app/fatmodels.py b/app/fatmodels.py index d8c614f..a2d349e 100644 --- a/app/fatmodels.py +++ b/app/fatmodels.py @@ -1,10 +1,9 @@ import typing -from sqlalchemy.sql import desc, func - 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 sqlalchemy.sql import desc, func class FatModel: diff --git a/app/forms.py b/app/forms.py index 1317c21..d50c9f1 100644 --- a/app/forms.py +++ b/app/forms.py @@ -1,25 +1,16 @@ "Script for everything form related in Haldis" from datetime import datetime, timedelta - from typing import Optional -from flask import session, request +from flask import request, session from flask_login import current_user 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.models import Location, Dish, Choice +from hlds.models import Choice, Dish, Location 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): diff --git a/app/hlds/__init__.py b/app/hlds/__init__.py index bef79a6..cc0a8a3 100644 --- a/app/hlds/__init__.py +++ b/app/hlds/__init__.py @@ -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. """ -from .models import Location, Choice, Option +from .models import Choice, Location, Option diff --git a/app/hlds/definitions.py b/app/hlds/definitions.py index 0da89aa..961d14b 100644 --- a/app/hlds/definitions.py +++ b/app/hlds/definitions.py @@ -1,11 +1,11 @@ # Import this class to load the standard HLDS definitions +import subprocess from os import path 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"] diff --git a/app/hlds/models.py b/app/hlds/models.py index 93d8fde..b85559c 100644 --- a/app/hlds/models.py +++ b/app/hlds/models.py @@ -1,26 +1,28 @@ #!/usr/bin/env python3 # 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 def _format_tags(tags: Iterable[str]) -> str: - return " :: {}".format(" ".join(["{" + tag + "}" for tag in tags])) \ - if tags \ - else "" + # pylint: disable=consider-using-f-string + return " :: {}".format(" ".join(["{" + tag + "}" + for tag in tags])) if tags else "" 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): type_, choice = type_and_choice - return "{} {}".format(type_, choice) + return f"{type_} {choice}" class Option: + def __init__(self, id_, *, name, description, price, tags): self.id: str = id_ self.name: str = name @@ -29,15 +31,17 @@ class Option: self.tags: List[str] = tags def __str__(self): + # pylint: disable=consider-using-f-string return "{0.id}: {0.name}{1}{2}{3}".format( self, - " -- {}".format(self.description) if self.description else "", + f" -- {self.description}" if self.description else "", _format_tags(self.tags), _format_price(self.price), ) class Choice: + def __init__(self, id_, *, name, description, options): self.id: str = id_ self.name: str = name @@ -48,7 +52,7 @@ class Choice: def __str__(self): return "{0.id}: {0.name}{1}\n\t\t{2}".format( self, - " -- {}".format(self.description) if self.description else "", + f" -- {self.description}" if self.description else "", "\n\t\t".join(map(str, self.options)), ) @@ -57,6 +61,7 @@ class Choice: class Dish: + def __init__(self, id_, *, name, description, price, tags, choices): self.id: str = id_ self.name: str = name @@ -70,7 +75,7 @@ class Dish: def __str__(self): return "dish {0.id}: {0.name}{1}{2}{3}\n\t{4}".format( self, - " -- {}".format(self.description) if self.description else "", + f" -- {self.description}" if self.description else "", _format_tags(self.tags), _format_price(self.price), "\n\t".join(map(_format_type_and_choice, self.choices)), @@ -86,14 +91,20 @@ class Dish: return sum( f(option.price for option in choice.options) for (choice_type, choice) in self.choices - if choice_type == "single_choice" - ) + if choice_type == "single_choice") 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.name: str = name self.osm: Optional[str] = osm @@ -107,24 +118,18 @@ class Location: return first(filter(lambda d: d.id == dish_id, self.dishes)) def __str__(self): - return ( - "============================\n" - "{0.id}: {0.name}" - "{1}\n" - "============================\n" - "\n" - "{2}" - ).format( - self, - "".join( - "\n\t{} {}".format(k, v) - for k, v in ( - ("osm", self.osm), - ("address", self.address), - ("telephone", self.telephone), - ("website", self.website), + return ("============================\n" + "{0.id}: {0.name}" + "{1}\n" + "============================\n" + "\n" + "{2}").format( + self, + "".join(f"\n\t{k} {v}" for k, v in ( + ("osm", self.osm), + ("address", self.address), + ("telephone", self.telephone), + ("website", self.website), + ) if v is not None), + "\n".join(map(str, self.dishes)), ) - if v is not None - ), - "\n".join(map(str, self.dishes)), - ) diff --git a/app/hlds/parser.py b/app/hlds/parser.py index 3f46b3e..21956e2 100644 --- a/app/hlds/parser.py +++ b/app/hlds/parser.py @@ -1,16 +1,17 @@ #!/usr/bin/env python3 -from glob import glob -from os import path import itertools 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.ast import AST from tatsu.exceptions import SemanticError -from .models import Location, Choice, Option, Dish from utils import first +from .models import Choice, Dish, Location, Option # TODO Use proper way to get resources, see https://stackoverflow.com/a/10935674 with open(path.join(path.dirname(__file__), "hlds.tatsu")) as fh: @@ -58,14 +59,16 @@ class HldsSemanticActions: option.price += dish.price dish.price = 0 dishes = list(dishes) - dishes.append(Dish( - "custom", - name="Vrije keuze", - description="Zet wat je wil in comment", - price=0, - tags=[], - choices=[], - )) + dishes.append( + Dish( + "custom", + name="Vrije keuze", + description="Zet wat je wil in comment", + price=0, + tags=[], + choices=[], + ) + ) 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]: - with open(filename, "r") as file_handle: + with open(filename) as file_handle: return parse(file_handle.read()) diff --git a/app/login.py b/app/login.py index 984ee4f..a7382fa 100644 --- a/app/login.py +++ b/app/login.py @@ -1,9 +1,8 @@ "Script for everything related to logging in and out" from flask import Blueprint, abort, redirect, session, url_for from flask_login import current_user, logout_user -from werkzeug.wrappers import Response - from models import User +from werkzeug.wrappers import Response from zeus import zeus_login auth_bp = Blueprint("auth_bp", __name__) diff --git a/app/migrations/env.py b/app/migrations/env.py index af87ade..819a4f3 100644 --- a/app/migrations/env.py +++ b/app/migrations/env.py @@ -1,10 +1,8 @@ "Script that runs migrations online or offline" -from __future__ import with_statement from logging.config import fileConfig from alembic import context - # add your model's MetaData object here # for 'autogenerate' support # from myapp import mymodel diff --git a/app/migrations/versions/9159a6fed021_initial_haldis_support.py b/app/migrations/versions/9159a6fed021_initial_haldis_support.py index 5e4406f..710feb2 100644 --- a/app/migrations/versions/9159a6fed021_initial_haldis_support.py +++ b/app/migrations/versions/9159a6fed021_initial_haldis_support.py @@ -12,11 +12,11 @@ revision = "9159a6fed021" down_revision = "150252c1cdb1" 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 sqlalchemy.sql import column, table, text LOCATION_LEGACY_TO_HLDS = { 2: "blauw_kotje", @@ -50,71 +50,106 @@ LOCATION_LEGACY_TO_HLDS = { def upgrade(): # 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("choice_id", sa.String(length=64), nullable=True), sa.Column("order_item_id", sa.Integer, nullable=False), sa.Column("kind", sa.String(length=1), nullable=False), sa.Column("name", sa.String(length=120), nullable=True), sa.Column("value", sa.String(length=120), nullable=True), - sa.ForeignKeyConstraint(["order_item_id"], ["order_item.id"], ), - sa.PrimaryKeyConstraint("id") + sa.ForeignKeyConstraint( + ["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 # 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("order_item", sa.Column("dish_name", sa.String(length=120), nullable=True)) + op.add_column( + "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)) # Brief, ad-hoc table constructs just for our UPDATE statement, see # 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("dish_id", sa.String), column("dish_name", sa.String), - column("price", sa.Integer) + column("price", sa.Integer), ) # Construct and execute queries - op.execute(text(""" + op.execute( + text( + """ UPDATE order_item 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)""" - )) + ) + ) # Historical product data migrated, drop obsolete column and table op.execute(text("ALTER TABLE order_item DROP FOREIGN KEY order_item_ibfk_3")) op.drop_column("order_item", "product_id") op.drop_table("product") - #---------------------------------------------------------------------------------------------- + # ---------------------------------------------------------------------------------------------- # Migrate historical location data to orders op.execute(text("ALTER TABLE `order` DROP FOREIGN KEY order_ibfk_2")) - op.alter_column("order", "location_id", 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)) + op.alter_column( + "order", + "location_id", + 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 # 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("location_id", sa.String), - column("location_name", sa.String) + column("location_name", sa.String), ) # Construct and execute queries new_location_id = [ order.update() - .where(order.c.legacy_location_id == old_id) - .values(location_id=new_id) + .where(order.c.legacy_location_id == old_id) + .values(location_id=new_id) for old_id, new_id in LOCATION_LEGACY_TO_HLDS.items() ] - location_name_from_location = text(""" + location_name_from_location = text( + """ UPDATE `order` SET location_name = (SELECT location.name FROM location WHERE location.id = `order`.legacy_location_id)""" diff --git a/app/models/order.py b/app/models/order.py index b990f04..b738c45 100644 --- a/app/models/order.py +++ b/app/models/order.py @@ -1,10 +1,11 @@ "Script for everything Order related in the database" import typing -from datetime import datetime from collections import defaultdict +from datetime import datetime -from utils import first from hlds.definitions import location_definitions +from utils import first + from .database import db from .user import User @@ -31,9 +32,9 @@ class Order(db.Model): def __repr__(self) -> str: # pylint: disable=R1705 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: - return "Order %d" % (self.id) + return f"Order {self.id}" def update_from_hlds(self) -> None: """ @@ -46,19 +47,21 @@ class Order(db.Model): self.location_name = self.location.name def for_user(self, anon=None, user=None) -> typing.List: + "Get the items for a certain user" return list( filter( (lambda i: i.user == user) if user is not None else (lambda i: i.user_name == anon), - self.items + self.items, ) ) def group_by_user(self) -> typing.List[typing.Tuple[str, typing.List]]: "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: if item.for_name not in group: 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 ""))) - def group_by_dish(self) \ - -> typing.List[typing.Tuple[str, int, typing.List[typing.Tuple[str, typing.List]]]]: + def group_by_dish( + self, + ) -> typing.List[ + typing.Tuple[str, int, typing.List[typing.Tuple[str, typing.List]]] + ]: "Group items of an Order by dish" - group: typing.Dict[str, typing.Dict[str, typing.List]] = \ - defaultdict(lambda: defaultdict(list)) + group: typing.Dict[str, typing.Dict[str, typing.List]] = defaultdict( + lambda: defaultdict(list) + ) + # pylint: disable=E1133 for item in self.items: group[item.dish_name][item.comment].append(item) @@ -87,12 +95,13 @@ class Order(db.Model): sorted( (comment, sorted(items, key=lambda x: (x.for_name or ""))) for comment, items in comment_group.items() - ) + ), ) for dish_name, comment_group in group.items() ) def is_closed(self) -> bool: + "Return whether or not the order is closed" return self.stoptime and datetime.now() > self.stoptime def can_close(self, user_id: int) -> bool: diff --git a/app/models/orderitem.py b/app/models/orderitem.py index 253cd85..b865b51 100644 --- a/app/models/orderitem.py +++ b/app/models/orderitem.py @@ -1,8 +1,9 @@ "Script for everything OrderItem related in the database" from datetime import datetime -from utils import first from hlds.definitions import location_definitions +from utils import first + from .database import db from .order import Order from .user import User @@ -21,20 +22,20 @@ class OrderItem(db.Model): comment = db.Column(db.Text(), 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): if name == "dish": - location_id = ( - Order.query.filter(Order.id == self.order_id).first().location_id - ) + location_id = (Order.query.filter( + Order.id == self.order_id).first().location_id) location = first( - filter(lambda l: l.id == location_id, location_definitions) - ) + filter(lambda l: l.id == location_id, location_definitions)) if location: - return first(filter(lambda d: d.id == self.dish_id, location.dishes)) - else: - raise ValueError("No Location found with id: " + location_id) + return first( + filter(lambda d: d.id == self.dish_id, location.dishes)) + raise ValueError(f"No Location found with id: {location_id}") raise AttributeError() @property @@ -45,11 +46,7 @@ class OrderItem(db.Model): return self.user_name def __repr__(self) -> str: - return "Order %d: %s wants %s" % ( - self.order_id or 0, - self.for_name, - self.dish_name or "None", - ) + return "Order {self.order_id or 0}: {self.for_name} wants {self.dish_name or 'None'}" def update_from_hlds(self) -> None: """ diff --git a/app/models/orderitemchoice.py b/app/models/orderitemchoice.py index d87c802..3bcd609 100644 --- a/app/models/orderitemchoice.py +++ b/app/models/orderitemchoice.py @@ -1,3 +1,4 @@ +"Script for everything OrderItemChoice related in the database" from datetime import datetime from .database import db @@ -5,6 +6,7 @@ from .orderitem import OrderItem class OrderItemChoice(db.Model): + "Class used for configuring the OrderItemChoice model in the database" id = db.Column(db.Integer, primary_key=True) choice_id = db.Column(db.String(64), nullable=True) order_item_id = db.Column( @@ -16,7 +18,8 @@ class OrderItemChoice(db.Model): # pylint: disable=attribute-defined-outside-init def configure(self, order: OrderItem) -> None: + "Set the orderitem" self.order = order def __repr__(self) -> str: - return "{}: {}".format(self.name, self.value) + return f"{self.name}: {self.value}" diff --git a/app/models/user.py b/app/models/user.py index 35596d1..8e78b2f 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -39,4 +39,4 @@ class User(db.Model): return str(self.id) def __repr__(self) -> str: - return "%s" % self.username + return f"{self.username}" diff --git a/app/notification.py b/app/notification.py index 59d6c3d..fa97045 100644 --- a/app/notification.py +++ b/app/notification.py @@ -16,7 +16,7 @@ def webhook_text(order: Order) -> typing.Optional[str]: return None if order.courier is not None: - # pylint: disable=C0301 + # pylint: disable=C0301, C0209 return " {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), order.location_name, @@ -24,6 +24,7 @@ def webhook_text(order: Order) -> typing.Optional[str]: order.courier.username.title(), ) + # pylint: disable=C0209 return " New order for {}. Deadline in {} minutes. <{}|Open here.>".format( order.location_name, remaining_minutes(order.stoptime), @@ -43,7 +44,7 @@ class WebhookSenderThread(Thread): "Extension of the Thread class, which sends a webhook for the notification" def __init__(self, message: str, url: str) -> None: - super(WebhookSenderThread, self).__init__() + super().__init__() self.message = message self.url = url @@ -64,4 +65,4 @@ def remaining_minutes(value) -> str: if delta.total_seconds() < 0: return "0" minutes = delta.total_seconds() // 60 - return "%02d" % minutes + return f"{minutes:02}" diff --git a/app/parse_hlds.py b/app/parse_hlds.py index ac58c44..d55a271 100755 --- a/app/parse_hlds.py +++ b/app/parse_hlds.py @@ -1,9 +1,7 @@ #!/usr/bin/env python3 -from tatsu.util import asjson from hlds.parser import parse_files - USAGE = """{0} [filename]... Parse HLDS files, print as JSON diff --git a/app/utils.py b/app/utils.py index 60a382f..5ec5133 100644 --- a/app/utils.py +++ b/app/utils.py @@ -9,9 +9,9 @@ def euro_string(value: int) -> str: """ euro, cents = divmod(value, 100) if cents: - return "€ {}.{:02}".format(euro, cents) + return f"€ {euro}.{cents:02}" else: - return "€ {}".format(euro) + return f"€ {euro}" def price_range_string(price_range, include_upper=False): diff --git a/app/views/debug.py b/app/views/debug.py index aed320d..ea0930b 100644 --- a/app/views/debug.py +++ b/app/views/debug.py @@ -17,12 +17,12 @@ def list_routes() -> str: for rule in app.url_map.iter_rules(): options = {} for arg in rule.arguments: - options[arg] = "[{0}]".format(arg) + options[arg] = f"[{arg}]" print(rule.endpoint) methods = ",".join(rule.methods) url = url_for(rule.endpoint, **options) line = urllib.parse.unquote( - "{:50s} {:20s} {}".format(rule.endpoint, methods, url) + f"{rule.endpoint:50s} {methods:20s} {url}" ) output.append(line) diff --git a/app/views/general.py b/app/views/general.py index 5511856..42753b6 100644 --- a/app/views/general.py +++ b/app/views/general.py @@ -1,32 +1,26 @@ "Script to generate the general views of Haldis" +import json import os from datetime import datetime, timedelta - -import yaml from typing import Optional -from flask import Flask, render_template, make_response -from flask import request, jsonify -from flask import Blueprint, abort +import yaml +from flask import Blueprint, Flask, abort 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 utils import first from hlds.definitions import location_definitions from hlds.models import Location from models import Order - +from utils import first # import views from views.order import get_orders -import json -from flask import jsonify - 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_OPTIONS = _theme_data["options"] THEMES = _theme_data["themes"] @@ -37,7 +31,7 @@ def home() -> str: "Generate the home view" prev_day = datetime.now() - timedelta(days=1) recently_closed = get_orders( - ((Order.stoptime > prev_day) & (Order.stoptime < datetime.now())) + (Order.stoptime > prev_day) & (Order.stoptime < datetime.now()) ) return render_template( "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 - raise Exception("Unknown theme type {}".format(theme_type)) + raise Exception(f"Unknown theme type {theme_type}") def get_theme_css(theme, options): @@ -71,13 +65,18 @@ def get_theme_css(theme, options): for option in theme.get("options", []): 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] 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"] + ) filename += "_" + value @@ -119,13 +118,15 @@ def current_theme_js(): themes = get_active_themes() 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] - response = make_response(rf''' + response = make_response( + rf""" var currentTheme = {json.dumps(cur_theme['file'])}; var currentThemeOptions = {json.dumps(cur_theme.get('options', []))}; -''') +""" + ) response.headers["Content-Type"] = "text/javascript" # 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) if dish is None: abort(404) - return jsonify([ - { - "type": c[0], - "id": c[1].id, - "name": c[1].name, - "description": c[1].description, - "options": [ - { - "id": o.id, - "name": o.name, - "description": o.description, - "price": o.price, - "tags": o.tags, - } - for o in c[1].options - ], - } - for c in dish.choices - ]) + return jsonify( + [ + { + "type": c[0], + "id": c[1].id, + "name": c[1].name, + "description": c[1].description, + "options": [ + { + "id": o.id, + "name": o.name, + "description": o.description, + "price": o.price, + "tags": o.tags, + } + for o in c[1].options + ], + } + for c in dish.choices + ] + ) @general_bp.route("/about/") @@ -204,7 +207,7 @@ def profile() -> str: def favicon() -> str: "Generate the favicon" # pylint: disable=R1705 - if not get_orders((Order.stoptime > datetime.now())): + if not get_orders(Order.stoptime > datetime.now()): return send_from_directory( os.path.join(app.root_path, "static"), "favicon.ico", diff --git a/app/views/order.py b/app/views/order.py index 177c195..49ca679 100644 --- a/app/views/order.py +++ b/app/views/order.py @@ -3,27 +3,16 @@ import random import typing from datetime import datetime -from werkzeug.wrappers import Response - # from flask import current_app as app -from flask import ( - Blueprint, - abort, - flash, - redirect, - render_template, - request, - session, - url_for, - wrappers, -) +from flask import (Blueprint, abort, flash, redirect, render_template, request, + session, url_for, wrappers) from flask_login import current_user, login_required - from forms import AnonOrderItemForm, OrderForm, OrderItemForm +from hlds.definitions import location_definition_version, location_definitions from models import Order, OrderItem, User, db -from hlds.definitions import location_definitions, location_definition_version from notification import post_order_to_webhook from utils import ignore_none +from werkzeug.wrappers import Response 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) if order.is_closed(): form = None - total_price = sum([o.price for o in order.items]) - debts = sum([o.price for o in order.items if not o.paid]) + total_price = sum(o.price for o in order.items) + debts = sum(o.price for o in order.items if not o.paid) 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: flash("Please login to see this order.", "info") 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) @@ -138,7 +127,9 @@ def order_item_create(order_id: int) -> typing.Any: abort(404) 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): abort(404) if not form.is_submitted(): @@ -347,6 +338,6 @@ def get_orders(expression=None) -> typing.List[Order]: else: order_list = Order.query.filter( # pylint: disable=C0121 - (expression & (Order.public == True)) + expression & (Order.public == True) ).all() return order_list diff --git a/app/views/stats.py b/app/views/stats.py index 19ef0a2..863c4ae 100644 --- a/app/views/stats.py +++ b/app/views/stats.py @@ -1,10 +1,9 @@ "Script to generate the stats related views of Haldis" +from fatmodels import FatLocation, FatOrder, FatOrderItem, FatUser from flask import Blueprint from flask import current_app as app from flask import render_template -from fatmodels import FatLocation, FatOrder, FatOrderItem, FatUser - stats_blueprint = Blueprint("stats_blueprint", __name__) diff --git a/app/zeus.py b/app/zeus.py index bbb58d2..ebc16ab 100644 --- a/app/zeus.py +++ b/app/zeus.py @@ -1,12 +1,12 @@ "Script containing everything specific to ZeusWPI" 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_oauthlib.client import OAuth, OAuthException -from werkzeug.wrappers import Response - from models import User, db +from werkzeug.wrappers import Response oauth_bp = Blueprint("oauth_bp", __name__) @@ -14,8 +14,7 @@ oauth_bp = Blueprint("oauth_bp", __name__) def zeus_login(): "Log in using ZeusWPI" 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") @@ -25,10 +24,8 @@ def authorized() -> typing.Any: "Check authorized status" resp = current_app.zeus.authorized_response() if resp is None: - return "Access denied: reason=%s error=%s" % ( - request.args["error"], - request.args["error_description"], - ) + # pylint: disable=C0301 + return f"Access denied: reason={request.args['error']} error={request.args['error_description']}" if isinstance(resp, OAuthException): return f"Access denied: {resp.message}
{resp.data}" From 781e4cd45b87fcf6db76d90963401519aa9e9760 Mon Sep 17 00:00:00 2001 From: Jan-Pieter Baert Date: Tue, 19 Apr 2022 22:05:38 +0200 Subject: [PATCH 3/3] Add requirements for running pylint --- pylint-requirement.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 pylint-requirement.txt diff --git a/pylint-requirement.txt b/pylint-requirement.txt new file mode 100644 index 0000000..3f622c3 --- /dev/null +++ b/pylint-requirement.txt @@ -0,0 +1,2 @@ +pylint-flask +pylint-flask-sqlalchemy