Compare commits
1 commit
master
...
hlds-verif
Author | SHA1 | Date | |
---|---|---|---|
|
02d52f08dd |
46 changed files with 253 additions and 673 deletions
|
@ -1,10 +0,0 @@
|
|||
# Ignore everything
|
||||
*
|
||||
|
||||
# Include source, config and scripts
|
||||
!app
|
||||
!etc
|
||||
!*.md
|
||||
!*.sh
|
||||
!*.txt
|
||||
!LICENSE
|
|
@ -1 +0,0 @@
|
|||
python 3.9.2
|
26
Dockerfile
26
Dockerfile
|
@ -1,26 +0,0 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
FROM python:3.9.2-slim AS development
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
RUN pip install pymysql
|
||||
|
||||
ADD https://git.zeus.gent/haldis/menus/-/archive/master/menus-master.tar /tmp
|
||||
RUN mkdir menus && \
|
||||
tar --directory=menus --extract --strip-components=1 --file=/tmp/menus-master.tar
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
WORKDIR /src/app
|
||||
CMD python app.py db upgrade && \
|
||||
python app.py runserver -h 0.0.0.0 -p 8000
|
||||
|
||||
FROM development AS production
|
||||
|
||||
RUN pip install waitress
|
||||
|
||||
CMD python app.py db upgrade && \
|
||||
python waitress_wsgi.py
|
|
@ -26,7 +26,7 @@ Afterwards upgrade the database to the latest version using
|
|||
cd app
|
||||
python3 app.py db upgrade
|
||||
|
||||
You can now still seed the database by running, note that you might want to put your name in the `HALDIS_ADMINS` in `app/config.py`
|
||||
You can now still seed the database by running, note that you might want to put your name in the `HALDIS_ADMIN_USERS` in `app/config.py`
|
||||
|
||||
./populate-db.sh
|
||||
|
||||
|
|
|
@ -10,5 +10,5 @@ def add() -> None:
|
|||
"""Add users as admin."""
|
||||
for username in Configuration.HALDIS_ADMINS:
|
||||
user = User()
|
||||
user.configure(username, True, 0, associations=["zeus"])
|
||||
user.configure(username, True, 0)
|
||||
db.session.add(user)
|
||||
|
|
|
@ -28,12 +28,11 @@ class OrderAdminModel(ModelBaseView):
|
|||
"Class for the model of a OrderAdmin"
|
||||
# pylint: disable=too-few-public-methods
|
||||
column_default_sort = ("starttime", True)
|
||||
column_list = ["starttime", "stoptime", "location_name", "location_id", "courier", "association"]
|
||||
column_list = ["starttime", "stoptime", "location_name", "location_id", "courier"]
|
||||
column_labels = {
|
||||
"starttime": "Start Time",
|
||||
"stoptime": "Closing Time",
|
||||
"location_id": "HLDS Location ID",
|
||||
"association": "Association",
|
||||
}
|
||||
form_excluded_columns = ["items", "courier_id"]
|
||||
can_delete = False
|
||||
|
@ -45,7 +44,6 @@ class OrderItemAdminModel(ModelBaseView):
|
|||
column_default_sort = ("order_id", True)
|
||||
column_list = [
|
||||
"order_id",
|
||||
"slug",
|
||||
"order.location_name",
|
||||
"user_name",
|
||||
"user",
|
||||
|
|
39
app/app.py
39
app/app.py
|
@ -3,29 +3,24 @@
|
|||
"""Main Haldis script"""
|
||||
|
||||
import logging
|
||||
import sentry_sdk
|
||||
import typing
|
||||
from datetime import datetime
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
|
||||
from admin import init_admin
|
||||
from config import Configuration
|
||||
from flask import Flask, render_template, Response
|
||||
from flask import Flask, render_template
|
||||
from flask_bootstrap import Bootstrap, StaticCDN
|
||||
from flask_debugtoolbar import DebugToolbarExtension
|
||||
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 login import init_login
|
||||
from markupsafe import Markup
|
||||
|
||||
from admin import init_admin
|
||||
from auth.login import init_login
|
||||
from auth.zeus import init_oauth
|
||||
from config import Configuration
|
||||
from models import db
|
||||
from models.anonymous_user import AnonymouseUser
|
||||
from sentry_sdk.integrations.flask import FlaskIntegration
|
||||
from utils import euro_string, price_range_string, ignore_none
|
||||
from zeus import init_oauth
|
||||
|
||||
|
||||
def register_plugins(app: Flask) -> Manager:
|
||||
|
@ -102,22 +97,18 @@ def add_routes(application: Flask) -> None:
|
|||
# import views # TODO convert to blueprint
|
||||
# import views.stats # TODO convert to blueprint
|
||||
|
||||
from auth.login import auth_bp
|
||||
from auth.microsoft import auth_microsoft_bp
|
||||
from auth.zeus import auth_zeus_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="/")
|
||||
application.register_blueprint(order_bp, url_prefix="/order")
|
||||
application.register_blueprint(stats_blueprint, url_prefix="/stats")
|
||||
application.register_blueprint(auth_bp, url_prefix="/")
|
||||
if Configuration.ENABLE_MICROSOFT_AUTH:
|
||||
application.register_blueprint(auth_microsoft_bp,
|
||||
url_prefix="/users/auth/microsoft_graph_auth") # "/auth/microsoft")
|
||||
application.register_blueprint(auth_zeus_bp, url_prefix="/auth/zeus")
|
||||
application.register_blueprint(oauth_bp, url_prefix="/")
|
||||
|
||||
if application.debug:
|
||||
application.register_blueprint(debug_bp, url_prefix="/debug")
|
||||
|
@ -167,12 +158,6 @@ def create_app():
|
|||
"""Initializer for the Flask app object"""
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/robots.txt')
|
||||
def noindex():
|
||||
r = Response(response="User-Agent: *\nDisallow: /\n", status=200, mimetype="text/plain")
|
||||
r.headers["Content-Type"] = "text/plain; charset=utf-8"
|
||||
return r
|
||||
|
||||
# Load the config file
|
||||
app.config.from_object("config.Configuration")
|
||||
|
||||
|
@ -181,20 +166,10 @@ def create_app():
|
|||
add_routes(app)
|
||||
add_template_filters(app)
|
||||
|
||||
@app.context_processor
|
||||
def inject_config():
|
||||
return dict(configuration=Configuration)
|
||||
|
||||
return app, app_manager
|
||||
|
||||
|
||||
# For usage when you directly call the script with python
|
||||
if __name__ == "__main__":
|
||||
if Configuration.SENTRY_DSN:
|
||||
sentry_sdk.init(
|
||||
dsn=Configuration.SENTRY_DSN,
|
||||
integrations=[FlaskIntegration()]
|
||||
)
|
||||
|
||||
app, app_mgr = create_app()
|
||||
app_mgr.run()
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
import typing
|
||||
|
||||
from flask import Blueprint, url_for, request, redirect, flash, Response
|
||||
from flask_login import login_user
|
||||
from microsoftgraph.client import Client
|
||||
|
||||
from config import Configuration
|
||||
from models import User, db
|
||||
|
||||
auth_microsoft_bp = Blueprint("auth_microsoft_bp", __name__)
|
||||
|
||||
client = Client(Configuration.MICROSOFT_AUTH_ID,
|
||||
Configuration.MICROSOFT_AUTH_SECRET,
|
||||
account_type="ugentbe.onmicrosoft.com")
|
||||
|
||||
|
||||
def microsoft_login():
|
||||
"""Log in using Microsoft"""
|
||||
scope = ["openid", "profile", "User.Read", "User.Read.All"]
|
||||
url = client.authorization_url(url_for("auth_microsoft_bp.authorized", _external=True), scope, state=None)
|
||||
return redirect(url)
|
||||
|
||||
|
||||
@auth_microsoft_bp.route("/login")
|
||||
def login():
|
||||
"""Function to handle a user trying to log in"""
|
||||
return microsoft_login()
|
||||
|
||||
|
||||
@auth_microsoft_bp.route("callback") # "/authorized")
|
||||
def authorized() -> typing.Any:
|
||||
# type is 'typing.Union[str, Response]', but this errors due to
|
||||
# https://github.com/python/mypy/issues/7187
|
||||
"""Check authorized status"""
|
||||
|
||||
oauth_code = request.args['code']
|
||||
|
||||
resp = client.exchange_code(url_for("auth_microsoft_bp.authorized", _external=True), oauth_code)
|
||||
client.set_token(resp.data)
|
||||
|
||||
resp = client.users.get_me()
|
||||
microsoft_uuid = resp.data['id']
|
||||
username = resp.data['userPrincipalName']
|
||||
|
||||
# Fail if fields are not populated
|
||||
if not microsoft_uuid or not username:
|
||||
flash("You're not allowed to enter, please contact a system administrator")
|
||||
return redirect(url_for("general_bp.home"))
|
||||
|
||||
# Find existing user by Microsoft UUID (userPrincipalName can change)
|
||||
user = User.query.filter_by(microsoft_uuid=microsoft_uuid).first()
|
||||
if user:
|
||||
return login_and_redirect_user(user)
|
||||
|
||||
# Find existing user by username (pre-existing account)
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if user:
|
||||
return login_and_redirect_user(user)
|
||||
|
||||
# No user found, create a new one
|
||||
user = create_user(username, microsoft_uuid=microsoft_uuid)
|
||||
return login_and_redirect_user(user)
|
||||
|
||||
|
||||
def login_and_redirect_user(user) -> Response:
|
||||
"""Log in the user and then redirect them"""
|
||||
login_user(user)
|
||||
return redirect(url_for("general_bp.home"))
|
||||
|
||||
|
||||
def create_user(username, *, microsoft_uuid) -> User:
|
||||
"""Create a temporary user if it is needed"""
|
||||
user = User()
|
||||
user.configure(username, False, 1, microsoft_uuid=microsoft_uuid)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
|
@ -1,26 +1,16 @@
|
|||
"""An example for a Haldis config"""
|
||||
# import os
|
||||
"An example for a Haldis config"
|
||||
# config
|
||||
|
||||
|
||||
class Configuration:
|
||||
"Haldis configuration object"
|
||||
# pylint: disable=too-few-public-methods
|
||||
SQLALCHEMY_DATABASE_URI = "sqlite:///haldis.db"
|
||||
# MARIADB_HOST = os.environ.get("MARIADB_HOST")
|
||||
# MARIADB_DB = os.environ.get("MARIADB_DATABASE")
|
||||
# MARIADB_USER = os.environ.get("MARIADB_USER")
|
||||
# MARIADB_PASS = os.environ.get("MARIADB_PASSWORD")
|
||||
# SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{MARIADB_USER}:{MARIADB_PASS}@{MARIADB_HOST}/{MARIADB_DB}"
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
DEBUG = True
|
||||
HALDIS_ADMINS = []
|
||||
HALDIS_ADMIN_USERS = []
|
||||
SECRET_KEY = "<change>"
|
||||
SLACK_WEBHOOK = None
|
||||
LOGFILE = "haldis.log"
|
||||
SENTRY_DSN = None
|
||||
ZEUS_KEY = "tomtest"
|
||||
ZEUS_SECRET = "blargh"
|
||||
|
||||
ENABLE_MICROSOFT_AUTH = False
|
||||
MICROSOFT_AUTH_ID = ""
|
||||
MICROSOFT_AUTH_SECRET = ""
|
||||
|
|
|
@ -9,7 +9,6 @@ user
|
|||
|
||||
order
|
||||
id
|
||||
slug secret used in URL
|
||||
courier_id
|
||||
location_id HLDS identifier
|
||||
location_name this allows historical orders to keep the same location name
|
||||
|
|
|
@ -24,17 +24,13 @@ class OrderForm(Form):
|
|||
"Starttime", default=datetime.now, format="%d-%m-%Y %H:%M"
|
||||
)
|
||||
stoptime = DateTimeField("Stoptime", format="%d-%m-%Y %H:%M")
|
||||
association = SelectField("Association", coerce=str, validators=[validators.required()])
|
||||
submit_button = SubmitField("Submit")
|
||||
|
||||
def populate(self) -> None:
|
||||
"Fill in the options for courier for an Order"
|
||||
if current_user.is_admin():
|
||||
self.courier_id.choices = [
|
||||
(0, None),
|
||||
(current_user.id, current_user.username),
|
||||
] + [
|
||||
(u.id, u.username) for u in User.query.order_by("username") if u.id != current_user.id
|
||||
self.courier_id.choices = [(0, None)] + [
|
||||
(u.id, u.username) for u in User.query.order_by("username")
|
||||
]
|
||||
else:
|
||||
self.courier_id.choices = [
|
||||
|
@ -42,7 +38,6 @@ class OrderForm(Form):
|
|||
(current_user.id, current_user.username),
|
||||
]
|
||||
self.location_id.choices = [(l.id, l.name) for l in location_definitions]
|
||||
self.association.choices = current_user.association_list()
|
||||
if self.stoptime.data is None:
|
||||
self.stoptime.data = datetime.now() + timedelta(hours=1)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Import this class to load the standard HLDS definitions
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from os import path
|
||||
from typing import List
|
||||
|
||||
from .models import Location
|
||||
|
@ -11,14 +12,10 @@ __all__ = ["location_definitions", "location_definition_version"]
|
|||
# pylint: disable=invalid-name
|
||||
|
||||
# TODO Use proper way to get resources, see https://stackoverflow.com/a/10935674
|
||||
ROOT_DIR = Path(__file__).parent.parent.parent
|
||||
DATA_DIR = ROOT_DIR / "menus"
|
||||
DATA_DIR = path.join(path.dirname(__file__), "..", "..", "menus")
|
||||
|
||||
location_definitions: List[Location] = parse_all_directory(str(DATA_DIR))
|
||||
location_definitions: List[Location] = parse_all_directory(DATA_DIR)
|
||||
location_definitions.sort(key=lambda l: l.name)
|
||||
|
||||
try:
|
||||
proc = subprocess.run(["git", "rev-parse", "HEAD"], stdout=subprocess.PIPE, cwd=str(ROOT_DIR), check=True)
|
||||
location_definition_version = proc.stdout.decode().strip()
|
||||
except FileNotFoundError:
|
||||
location_definition_version = ""
|
||||
proc = subprocess.run(["git", "rev-parse", "HEAD"], stdout=subprocess.PIPE, check=True)
|
||||
location_definition_version = proc.stdout.decode().strip()
|
||||
|
|
|
@ -29,9 +29,9 @@ location = >location_header items:{ block } ;
|
|||
|
||||
|
||||
attributes =
|
||||
name:/[^\n#]*?(?= +-- | | €| *\n| *#)/
|
||||
name:/[^\n#]*?(?= +-- | | *\n| *#)/
|
||||
[ s '--' ~ s description:/[^\n#]*?(?= | *\n| *#)/ ]
|
||||
[ / +/ ~
|
||||
[ / {2,}/ ~
|
||||
[ {[ s ] ('{' tags+:identifier '}')} / +|$/ ]
|
||||
[ price:price ]
|
||||
]
|
||||
|
|
|
@ -88,6 +88,12 @@ class Dish:
|
|||
)
|
||||
|
||||
def _sum_f_option_prices(self, f):
|
||||
for (_, choice) in self.choices:
|
||||
if len(choice.options) == 0:
|
||||
print((f"[PARSE ERROR] At least 1 option expected in dish choice.\n"
|
||||
f"\tDish:\t'{self.name}'\n"
|
||||
f"\tChoice:\t'{choice.name}'\n"))
|
||||
exit(1)
|
||||
return sum(
|
||||
f(option.price for option in choice.options)
|
||||
for (choice_type, choice) in self.choices
|
||||
|
|
|
@ -1,25 +1,31 @@
|
|||
"""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_login import current_user, logout_user
|
||||
from models import User
|
||||
from werkzeug.wrappers import Response
|
||||
from zeus import zeus_login
|
||||
|
||||
auth_bp = Blueprint("auth_bp", __name__)
|
||||
|
||||
|
||||
def init_login(app) -> None:
|
||||
"""Initialize the login"""
|
||||
|
||||
"Initialize the login"
|
||||
# pylint: disable=W0612
|
||||
@app.login_manager.user_loader
|
||||
def load_user(userid) -> User:
|
||||
"""Load the user"""
|
||||
"Load the user"
|
||||
return User.query.filter_by(id=userid).first()
|
||||
|
||||
|
||||
@auth_bp.route("/login")
|
||||
def login():
|
||||
"Function to handle a user trying to log in"
|
||||
return zeus_login()
|
||||
|
||||
|
||||
@auth_bp.route("/logout")
|
||||
def logout() -> Response:
|
||||
"""Function to handle a user trying to log out"""
|
||||
"Function to handle a user trying to log out"
|
||||
if "zeus_token" in session:
|
||||
session.pop("zeus_token", None)
|
||||
logout_user()
|
||||
|
@ -27,6 +33,6 @@ def logout() -> Response:
|
|||
|
||||
|
||||
def before_request() -> None:
|
||||
"""Function for what has to be done before a request"""
|
||||
"Function for what has to be done before a request"
|
||||
if current_user.is_anonymous() or not current_user.is_allowed():
|
||||
abort(401)
|
|
@ -43,7 +43,7 @@ def upgrade():
|
|||
sa.Column("starttime", sa.DateTime(), nullable=True),
|
||||
sa.Column("stoptime", sa.DateTime(), nullable=True),
|
||||
sa.Column("public", sa.Boolean(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["location_id"], ["location.id"], name="order_ibfk_1"),
|
||||
sa.ForeignKeyConstraint(["location_id"], ["location.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
|
@ -65,7 +65,7 @@ def upgrade():
|
|||
sa.Column("extra", sa.String(length=254), nullable=True),
|
||||
sa.Column("name", sa.String(length=120), nullable=True),
|
||||
sa.ForeignKeyConstraint(["order_id"], ["order.id"]),
|
||||
sa.ForeignKeyConstraint(["product_id"], ["product.id"], name="order_item_ibfk_3"),
|
||||
sa.ForeignKeyConstraint(["product_id"], ["product.id"]),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["user.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
"""add slug
|
||||
|
||||
Revision ID: 29ccbe077c57
|
||||
Revises: 55013fe95bea
|
||||
Create Date: 2022-05-20 19:46:11.924218
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '29ccbe077c57'
|
||||
down_revision = '55013fe95bea'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.sql import text
|
||||
|
||||
def upgrade():
|
||||
op.add_column('order', sa.Column(
|
||||
'slug',
|
||||
sa.String(length=8),
|
||||
nullable=False,
|
||||
# Default: random alphanumerical string
|
||||
server_default=text('SUBSTRING(MD5(RAND()) FROM 1 FOR 7)')
|
||||
))
|
||||
op.create_unique_constraint('order_slug_unique', 'order', ['slug'])
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_constraint('order_slug_unique', 'order', type_='unique')
|
||||
op.drop_column('order', 'slug')
|
|
@ -1,26 +0,0 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 89b2c980b663
|
||||
Revises: 9eac0f3d7b1e
|
||||
Create Date: 2023-04-20 02:01:54.558602
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '89b2c980b663'
|
||||
down_revision = '9eac0f3d7b1e'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('user', sa.Column('microsoft_uuid', sa.VARCHAR(length=120), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('user', 'microsoft_uuid')
|
||||
# ### end Alembic commands ###
|
|
@ -112,12 +112,14 @@ def upgrade():
|
|||
)
|
||||
)
|
||||
# Historical product data migrated, drop obsolete column and table
|
||||
op.drop_constraint("order_item_ibfk_3", "order_item", type_="foreignkey")
|
||||
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",
|
||||
|
@ -155,7 +157,6 @@ def upgrade():
|
|||
for query in chain(new_location_id, [location_name_from_location]):
|
||||
op.execute(query)
|
||||
# Historical location data migrated, drop obsolete column and table
|
||||
op.drop_constraint("order_ibfk_1", "order", type_="foreignkey")
|
||||
op.drop_column("order", "legacy_location_id")
|
||||
op.drop_table("location")
|
||||
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 9eac0f3d7b1e
|
||||
Revises: ('f6a6004bf4b9', '29ccbe077c57')
|
||||
Create Date: 2022-05-30 18:35:43.918797
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '9eac0f3d7b1e'
|
||||
down_revision = ('f6a6004bf4b9', '29ccbe077c57')
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
pass
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|
|
@ -1,28 +0,0 @@
|
|||
"""Add user associations
|
||||
|
||||
Revision ID: f6a6004bf4b9
|
||||
Revises: 55013fe95bea
|
||||
Create Date: 2022-05-24 21:23:27.770365
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f6a6004bf4b9'
|
||||
down_revision = '55013fe95bea'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('order', sa.Column('association', sa.String(length=120), server_default='', nullable=False))
|
||||
op.add_column('user', sa.Column('associations', sa.String(length=255), server_default='', nullable=False))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('user', 'associations')
|
||||
op.drop_column('order', 'association')
|
||||
# ### end Alembic commands ###
|
|
@ -1,14 +1,10 @@
|
|||
"AnonymouseUser for people who are not logged in the normal way"
|
||||
from typing import List
|
||||
# pylint: disable=R0201,C0111
|
||||
|
||||
|
||||
class AnonymouseUser:
|
||||
id = None
|
||||
|
||||
def association_list(self) -> List[str]:
|
||||
return []
|
||||
|
||||
def is_active(self) -> bool:
|
||||
return False
|
||||
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
"""Script for everything Order related in the database"""
|
||||
"Script for everything Order related in the database"
|
||||
import typing
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
import secrets
|
||||
import string
|
||||
|
||||
from hlds.definitions import location_definitions
|
||||
from utils import first
|
||||
|
@ -11,16 +9,9 @@ from utils import first
|
|||
from .database import db
|
||||
from .user import User
|
||||
|
||||
BASE31_ALPHABET = '23456789abcdefghjkmnpqrstuvwxyz'
|
||||
|
||||
def generate_slug():
|
||||
secret = ''.join(secrets.choice(BASE31_ALPHABET) for i in range(8))
|
||||
while Order.query.filter(Order.slug == secret).first() is not None:
|
||||
secret = ''.join(secrets.choice(BASE31_ALPHABET) for i in range(8))
|
||||
return secret
|
||||
|
||||
class Order(db.Model):
|
||||
"""Class used for configuring the Order model in the database"""
|
||||
"Class used for configuring the Order model in the database"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
courier_id = db.Column(db.Integer, nullable=True)
|
||||
location_id = db.Column(db.String(64))
|
||||
|
@ -28,8 +19,6 @@ class Order(db.Model):
|
|||
starttime = db.Column(db.DateTime)
|
||||
stoptime = db.Column(db.DateTime)
|
||||
public = db.Column(db.Boolean, default=True)
|
||||
slug = db.Column(db.String(8), default=generate_slug, unique=True)
|
||||
association = db.Column(db.String(120), nullable=False, server_default="")
|
||||
|
||||
items = db.relationship("OrderItem", backref="order", lazy="dynamic")
|
||||
|
||||
|
@ -58,7 +47,7 @@ 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"""
|
||||
"Get the items for a certain user"
|
||||
return list(
|
||||
filter(
|
||||
(lambda i: i.user == user)
|
||||
|
@ -69,7 +58,7 @@ class Order(db.Model):
|
|||
)
|
||||
|
||||
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] = {}
|
||||
|
||||
# pylint: disable=E1133
|
||||
|
@ -89,7 +78,7 @@ class Order(db.Model):
|
|||
) -> typing.List[
|
||||
typing.Tuple[str, int, typing.List[typing.Tuple[str, typing.List]]]
|
||||
]:
|
||||
"""Group items of an Order by dish"""
|
||||
"Group items of an Order by dish"
|
||||
group: typing.Dict[str, typing.Dict[str, typing.List]] = defaultdict(
|
||||
lambda: defaultdict(list)
|
||||
)
|
||||
|
@ -112,11 +101,11 @@ class Order(db.Model):
|
|||
)
|
||||
|
||||
def is_closed(self) -> bool:
|
||||
"""Return whether the order is closed"""
|
||||
"Return whether or not the order is closed"
|
||||
return self.stoptime and datetime.now() > self.stoptime
|
||||
|
||||
def can_close(self, user_id: int) -> bool:
|
||||
"""Check if a user can close the Order"""
|
||||
"Check if a user can close the Order"
|
||||
if self.stoptime and self.stoptime < datetime.now():
|
||||
return False
|
||||
user = None
|
||||
|
|
|
@ -10,7 +10,7 @@ from .user import User
|
|||
|
||||
|
||||
class OrderItem(db.Model):
|
||||
"""Class used for configuring the OrderItem model in the database"""
|
||||
"Class used for configuring the OrderItem model in the database"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
order_id = db.Column(db.Integer, db.ForeignKey("order.id"), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
|
||||
|
@ -61,7 +61,7 @@ class OrderItem(db.Model):
|
|||
|
||||
# pylint: disable=W0613
|
||||
def can_delete(self, order_id: int, user_id: int, name: str) -> bool:
|
||||
"""Check if a user can delete an item"""
|
||||
"Check if a user can delete an item"
|
||||
if int(self.order_id) != int(order_id):
|
||||
return False
|
||||
if self.order.is_closed():
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
"Script for everything User related in the database"
|
||||
from typing import List, Optional
|
||||
|
||||
from models import db
|
||||
|
||||
|
||||
class User(db.Model):
|
||||
"""Class used for configuring the User model in the database"""
|
||||
"Class used for configuring the User model in the database"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||
admin = db.Column(db.Boolean)
|
||||
bias = db.Column(db.Integer)
|
||||
# Microsoft OAUTH info
|
||||
microsoft_uuid = db.Column(db.String(120), unique=True)
|
||||
# Association logic
|
||||
associations = db.Column(db.String(255), nullable=False, server_default="")
|
||||
|
||||
# Relations
|
||||
runs = db.relation(
|
||||
"Order",
|
||||
backref="courier",
|
||||
|
@ -24,18 +16,11 @@ class User(db.Model):
|
|||
)
|
||||
orderItems = db.relationship("OrderItem", backref="user", lazy="dynamic")
|
||||
|
||||
def association_list(self) -> List[str]:
|
||||
return self.associations.split(",")
|
||||
|
||||
def configure(self, username: str, admin: bool, bias: int, *, microsoft_uuid: str = None, associations: Optional[List[str]] = None) -> None:
|
||||
"""Configure the User"""
|
||||
if associations is None:
|
||||
associations = []
|
||||
def configure(self, username: str, admin: bool, bias: int) -> None:
|
||||
"Configure the User"
|
||||
self.username = username
|
||||
self.admin = admin
|
||||
self.bias = bias
|
||||
self.microsoft_uuid = microsoft_uuid
|
||||
self.associations = ",".join(associations)
|
||||
|
||||
# pylint: disable=C0111, R0201
|
||||
def is_authenticated(self) -> bool:
|
||||
|
|
|
@ -11,29 +11,29 @@ from models.order import Order
|
|||
|
||||
|
||||
def webhook_text(order: Order) -> typing.Optional[str]:
|
||||
"""Function that makes the text for the notification"""
|
||||
"Function that makes the text for the notification"
|
||||
if order.location_id == "test":
|
||||
return None
|
||||
|
||||
if order.courier is not None:
|
||||
# pylint: disable=C0301, C0209
|
||||
return "<!channel|@channel> {3} is going to {1}, order <{0}|here>! Deadline in {2} minutes!".format(
|
||||
url_for("order_bp.order_from_slug", order_slug=order.slug, _external=True),
|
||||
url_for("order_bp.order_from_id", order_id=order.id, _external=True),
|
||||
order.location_name,
|
||||
remaining_minutes(order.stoptime),
|
||||
order.courier.username,
|
||||
order.courier.username.title(),
|
||||
)
|
||||
|
||||
# pylint: disable=C0209
|
||||
return "<!channel|@channel> New order for {}. Deadline in {} minutes. <{}|Open here.>".format(
|
||||
order.location_name,
|
||||
remaining_minutes(order.stoptime),
|
||||
url_for("order_bp.order_from_slug", order_slug=order.slug, _external=True),
|
||||
url_for("order_bp.order_from_id", order_id=order.id, _external=True),
|
||||
)
|
||||
|
||||
|
||||
def post_order_to_webhook(order: Order) -> None:
|
||||
"""Function that sends the notification for the order"""
|
||||
"Function that sends the notification for the order"
|
||||
message = webhook_text(order)
|
||||
if message:
|
||||
webhookthread = WebhookSenderThread(message, app.config["SLACK_WEBHOOK"])
|
||||
|
@ -41,7 +41,7 @@ def post_order_to_webhook(order: Order) -> None:
|
|||
|
||||
|
||||
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:
|
||||
super().__init__()
|
||||
|
@ -52,7 +52,7 @@ class WebhookSenderThread(Thread):
|
|||
self.slack_webhook()
|
||||
|
||||
def slack_webhook(self) -> None:
|
||||
"""The webhook for the specified chat platform"""
|
||||
"The webhook for the specified chat platform"
|
||||
if self.url:
|
||||
requests.post(self.url, json={"text": self.message})
|
||||
else:
|
||||
|
@ -60,9 +60,9 @@ class WebhookSenderThread(Thread):
|
|||
|
||||
|
||||
def remaining_minutes(value) -> str:
|
||||
"""Return the remaining minutes until the deadline of and order"""
|
||||
"Return the remaining minutes until the deadline of and order"
|
||||
delta = value - datetime.now()
|
||||
if delta.total_seconds() < 0:
|
||||
return "0"
|
||||
minutes = int(delta.total_seconds() // 60)
|
||||
minutes = delta.total_seconds() // 60
|
||||
return f"{minutes:02}"
|
||||
|
|
|
@ -243,9 +243,9 @@ details summary {
|
|||
}
|
||||
details summary:before {
|
||||
font-style: normal;
|
||||
content: "▸";
|
||||
content: "⯈";
|
||||
padding-right: 0.4em;
|
||||
}
|
||||
details[open] summary:before {
|
||||
content: "▾";
|
||||
content: "⯆";
|
||||
}
|
||||
|
|
2
app/static/js/jquery.min.js
vendored
2
app/static/js/jquery.min.js
vendored
File diff suppressed because one or more lines are too long
1
app/static/js/qrcode.min.js
vendored
1
app/static/js/qrcode.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -63,8 +63,7 @@
|
|||
<nav class="navbar navbar-default navbar-fixed-top">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
|
||||
aria-expanded="false" aria-controls="navbar">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
|
@ -82,10 +81,7 @@
|
|||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{% if current_user.is_anonymous() %}
|
||||
{% if configuration.ENABLE_MICROSOFT_AUTH %}
|
||||
<li><a href="{{ url_for('auth_microsoft_bp.login') }}">Login with Microsoft</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{{ url_for('auth_zeus_bp.login') }}">Login with Zeus</a></li>
|
||||
<li><a href="{{ url_for('auth_bp.login') }}">Login</a></li>
|
||||
{% else %}
|
||||
<li><a href="{{ url_for('general_bp.profile') }}">{{ current_user.username }}</a></li>
|
||||
<li><a href="{{ url_for('auth_bp.logout') }}">Logout</a></li>
|
||||
|
@ -100,8 +96,8 @@
|
|||
{{ utils.flashed_messages(container=True) }}
|
||||
|
||||
<div class="container main">
|
||||
{% block container -%}
|
||||
{%- endblock %}
|
||||
{% block container -%}
|
||||
{%- endblock %}
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
|
|
|
@ -12,39 +12,18 @@
|
|||
{% block metas %}
|
||||
{{ super() }}
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/qrcode.min.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block container %}
|
||||
<header class="row">
|
||||
<div class="col-md-2" style="padding-top: 2em">
|
||||
<div id="qrcode"></div>
|
||||
<script type="text/javascript">
|
||||
var qrcode = new QRCode(document.getElementById("qrcode"), {
|
||||
text: "{{ url_for("order_bp.order_from_slug", order_slug=order.slug, _external=True) }}",
|
||||
width: 128,
|
||||
height: 128,
|
||||
colorDark : "#000000",
|
||||
colorLight : "#ffffff",
|
||||
correctLevel : QRCode.CorrectLevel.H
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<h2 id="order-title">Order {{ order.id }}</h2>
|
||||
<header>
|
||||
<h2 id="order-title">Order {{ order.id }}</h2>
|
||||
|
||||
<div class="location">
|
||||
{% if order.location %}
|
||||
<a href="{{ url_for('general_bp.location', location_id=order.location_id) }}">{{ order.location_name }}</a>
|
||||
{% else %}
|
||||
{{ order.location_name }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
Unique order link: <code>{{ url_for("order_bp.order_from_slug", order_slug=order.slug, _external=True) }}</code>
|
||||
</div>
|
||||
<div class="location">
|
||||
{% if order.location %}
|
||||
<a href="{{ url_for('general_bp.location', location_id=order.location_id) }}">{{ order.location_name }}</a>
|
||||
{% else %}
|
||||
{{ order.location_name }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
@ -57,7 +36,7 @@
|
|||
{% for item in my_items %}
|
||||
<li class="spacecake">
|
||||
{% if item.can_delete(order.id, current_user.id, session.get('anon_name', '')) -%}
|
||||
<form action="{{ url_for('order_bp.delete_item', order_slug=order.slug, item_id=item.id) }}" method="post" style="display:inline">
|
||||
<form action="{{ url_for('order_bp.delete_item', order_id=order.id, item_id=item.id) }}" method="post" style="display:inline">
|
||||
<button class="btn btn-link btn-sm" type="submit" style="padding: 0 0.5em;"><span class="glyphicon glyphicon-remove"></span></button>
|
||||
</form>
|
||||
{%- endif %}
|
||||
|
@ -86,7 +65,7 @@
|
|||
<h3>Add item to order</h3>
|
||||
|
||||
{% for dish in order.location.dishes %}
|
||||
<form method="post" action="{{ url_for('order_bp.order_item_create', order_slug=order.slug) }}" id="dish_{{ dish.id }}">
|
||||
<form method="post" action="{{ url_for('order_bp.order_item_create', order_id=order.id) }}" id="dish_{{ dish.id }}">
|
||||
{{ form.csrf_token }}
|
||||
<input type="hidden" name="dish_id" value="{{ dish.id }}" />
|
||||
|
||||
|
@ -155,66 +134,60 @@
|
|||
|
||||
<div class="box" id="order_info">
|
||||
<h3>Order information</h3>
|
||||
<div class="row">
|
||||
<dl class="col-md-10 col-lg-8">
|
||||
<div>
|
||||
<dt>Order opens</dt>
|
||||
<dd>{{ order.starttime.strftime("%Y-%m-%d, %H:%M") }}</dd>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>Order opens</dt>
|
||||
<dd>{{ order.starttime.strftime("%Y-%m-%d, %H:%M") }}</dd>
|
||||
|
||||
<dt>Order closes</dt>
|
||||
<dd>
|
||||
{% if order.stoptime %}
|
||||
{% set stoptimefmt = (
|
||||
"%H:%M" if order.stoptime.date() == order.starttime.date()
|
||||
else "%Y-%m-%d, %H:%M"
|
||||
) %}
|
||||
{{ order.stoptime.strftime(stoptimefmt) }} ({{ order.stoptime|countdown }})
|
||||
{% else %}
|
||||
Never
|
||||
{% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt>Location</dt>
|
||||
<dd>
|
||||
{% if order.location %}
|
||||
<a href="{{ url_for('general_bp.location', location_id=order.location_id) }}">{{ order.location_name }}</a>
|
||||
{% else %}
|
||||
{{ order.location_name }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt>Courier</dt>
|
||||
<dd>
|
||||
{% if order.courier == None %}
|
||||
{% if not current_user.is_anonymous() %}
|
||||
<form action="{{ url_for('order_bp.volunteer', order_slug=order.slug) }}" method="post" style="display:inline">
|
||||
<input type="submit" class="btn btn-primary btn-sm" value="Volunteer"></input>
|
||||
</form>
|
||||
{% else %}No-one yet{% endif %}
|
||||
{% else %}
|
||||
{{ order.courier.username }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
</dl>
|
||||
|
||||
<div class="col-md-2 col-lg-4">
|
||||
<img src="https://dsa.ugent.be/api/verenigingen/{{ order.association }}/logo" class="img-responsive align-top" style="max-width:200px;width:100%">
|
||||
<dt>Order closes</dt>
|
||||
<dd>
|
||||
{% if order.stoptime %}
|
||||
{% set stoptimefmt = (
|
||||
"%H:%M" if order.stoptime.date() == order.starttime.date()
|
||||
else "%Y-%m-%d, %H:%M"
|
||||
) %}
|
||||
{{ order.stoptime.strftime(stoptimefmt) }} ({{ order.stoptime|countdown }})
|
||||
{% else %}
|
||||
Never
|
||||
{% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt>Location</dt>
|
||||
<dd>
|
||||
{% if order.location %}
|
||||
<a href="{{ url_for('general_bp.location', location_id=order.location_id) }}">{{ order.location_name }}</a>
|
||||
{% else %}
|
||||
{{ order.location_name }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt>Courier</dt>
|
||||
<dd>
|
||||
{% if order.courier == None %}
|
||||
{% if not current_user.is_anonymous() %}
|
||||
<form action="{{ url_for('order_bp.volunteer', order_id=order.id) }}" method="post" style="display:inline">
|
||||
<input type="submit" class="btn btn-primary btn-sm" value="Volunteer"></input>
|
||||
</form>
|
||||
{% else %}No-one yet{% endif %}
|
||||
{% else %}
|
||||
{{ order.courier.username }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<div>
|
||||
{% if order.can_close(current_user.id) -%}
|
||||
<form action="{{ url_for('order_bp.close_order', order_id=order.id) }}" method="post" style="display:inline">
|
||||
<input type="submit" class="btn btn-danger" value="Close"></input>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if courier_or_admin %}
|
||||
<a class="btn" href="{{ url_for('order_bp.order_edit', order_id=order.id) }}">Edit</a>
|
||||
{%- endif %}
|
||||
</div>
|
||||
|
||||
{% if order.can_close(current_user.id) -%}
|
||||
<form action="{{ url_for('order_bp.close_order', order_slug=order.slug) }}" method="post" style="display:inline">
|
||||
<input type="submit" class="btn btn-danger" value="Close"></input>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if courier_or_admin %}
|
||||
<a class="btn" href="{{ url_for('order_bp.order_edit', order_slug=order.slug) }}">Edit</a>
|
||||
{%- endif %}
|
||||
|
||||
</div>
|
||||
|
||||
<div class="box" id="how_to_order">
|
||||
|
@ -285,7 +258,7 @@
|
|||
<div class="footer">
|
||||
Total {{ order.items.count() }} items — {{ total_price|euro }}
|
||||
|
||||
<a class="btn btn-sm" href="{{ url_for('order_bp.items_shop_view', order_slug=order.slug) }}">Shop view</a>
|
||||
<a class="btn btn-sm" href="{{ url_for('order_bp.items_shop_view', order_id=order.id) }}">Shop view</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -294,7 +267,7 @@
|
|||
<section class="single_column">
|
||||
<div class="box" id="per_person">
|
||||
<h3>Items per person</h3>
|
||||
<form action="{{ url_for('order_bp.modify_items', order_slug=order.slug) }}" method="post">
|
||||
<form action="{{ url_for('order_bp.modify_items', order_id=order.id) }}" method="post">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr><th>Total</th><th>Name</th><th>Items</th></tr>
|
||||
|
@ -352,7 +325,7 @@
|
|||
|
||||
{% if order.can_modify_prices(current_user.id) %}
|
||||
<span style="border-left: 1px solid var(--gray0); display: inline-block;"> </span>
|
||||
<a href="{{ url_for('order_bp.prices', order_slug=order.slug) }}" class="btn btn-sm">
|
||||
<a href="{{ url_for('order_bp.prices', order_id=order.id) }}" class="btn btn-sm">
|
||||
<span class="glyphicon glyphicon-pencil"></span> Edit prices
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<h3>Edit order</h3>
|
||||
<div class="row darker">
|
||||
<div class="col-sm-12">
|
||||
<form method="post" action="{{ url_for('.order_edit', order_slug=order_slug) }}">
|
||||
<form method="post" action="{{ url_for('.order_edit', order_id=order_id) }}">
|
||||
{{ form.csrf_token }}
|
||||
<div class="form-group select2 {{ 'has-errors' if form.courier_id.errors else ''}}">
|
||||
{{ form.courier_id.label(class='control-label') }}<br>
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
{% block container %}
|
||||
<header>
|
||||
<h2 id="order-title">Edit prices</h2>
|
||||
<div>Only applied to <a href="{{ url_for('order_bp.order_from_slug', order_slug=order.slug) }}">order {{ order.id }}</a>. To permanently change prices for {{ order.location_name }}, edit the <a href="https://git.zeus.gent/haldis/menus/-/blob/master/{{order.location_id}}.hlds">HLDS location definition</a>.</div>
|
||||
<div>Only applied to <a href="{{ url_for('order_bp.order_from_id', order_id=order.id) }}">order {{ order.id }}</a>. To permanently change prices for {{ order.location_name }}, edit the <a href="https://git.zeus.gent/haldis/menus/-/blob/master/{{order.location_id}}.hlds">HLDS location definition</a>.</div>
|
||||
</header>
|
||||
|
||||
<form action="{{ url_for('order_bp.prices', order_slug=order.slug) }}" method="post">
|
||||
<form action="{{ url_for('order_bp.prices', order_id=order.id) }}" method="post">
|
||||
<div class="col-md-6" id="per_dish">
|
||||
<h3>Per dish</h3>
|
||||
<div class="noscript">This functionality requires JavaScript.</div>
|
||||
|
@ -86,7 +86,7 @@
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<a href="{{ url_for('order_bp.order_from_slug', order_slug=order.slug) }}" class="btn btn-sm">Cancel</a>
|
||||
<a href="{{ url_for('order_bp.order_from_id', order_id=order.id) }}" class="btn btn-sm">Cancel</a>
|
||||
<button class="btn btn-sm btn-primary">Apply</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -38,11 +38,6 @@
|
|||
{{ form.location_id(class='form-control select') }}
|
||||
{{ util.render_form_field_errors(form.location_id) }}
|
||||
</div>
|
||||
<div class="form-group select2 {{ 'has-errors' if form.association.errors else ''}}{{ ' required' if form.association.flags.required }}">
|
||||
{{ form.association.label(class='control-label') }}
|
||||
{{ form.association(class='form-control select') }}
|
||||
{{ util.render_form_field_errors(form.association) }}
|
||||
</div>
|
||||
{% if current_user.is_admin() %}
|
||||
<div class="form-group{{ ' has-error' if form.starttime.errors }}{{ ' required' if form.starttime.flags.required }}{{ ' hidden' if not current_user.is_admin() }}">
|
||||
{{ form.starttime.label(class='control-label') }}
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
{% macro render_order(order) -%}
|
||||
<div class="row order_row">
|
||||
<div class="col-md-6 order_data">
|
||||
<div class="col-md-8 col-lg-9 order_data">
|
||||
<h5>{{ order.location_name }}</h5>
|
||||
<b class="amount_of_orders">{{ order.items.count() }} items ordered for {{ order.association }}</b></p>
|
||||
<b class="amount_of_orders">{{ order.items.count() }} orders</b></p>
|
||||
<p class="time_data">
|
||||
{% if order.stoptime %}
|
||||
<span><b>Closes </b>{{ order.stoptime.strftime("%H:%M") }}</span>{{ order.stoptime|countdown }}
|
||||
{% else %}open{% endif %}<br/>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<img src="https://dsa.ugent.be/api/verenigingen/{{ order.association }}/logo" class="img-responsive align-bottom" style="max-width:200px;width:100%">
|
||||
</div>
|
||||
<div class="col-md-3 expand_button_wrapper">
|
||||
<a class="btn btn-primary btn-block align-bottom expand_button" href="{{ url_for('order_bp.order_from_slug', order_slug=order.slug) }}">Expand</a>
|
||||
<div class="col-md-4 col-lg-3 expand_button_wrapper">
|
||||
<a class="btn btn-primary btn-block align-bottom expand_button" href="{{ url_for('order_bp.order_from_id', order_id=order.id) }}">Expand</a>
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
|
|
|
@ -9,7 +9,7 @@ from flask import Blueprint, Flask, abort
|
|||
from flask import current_app as app
|
||||
from flask import (jsonify, make_response, render_template, request,
|
||||
send_from_directory, url_for)
|
||||
from flask_login import current_user, login_required
|
||||
from flask_login import login_required
|
||||
from hlds.definitions import location_definitions
|
||||
from hlds.models import Location
|
||||
from models import Order
|
||||
|
@ -34,9 +34,7 @@ def home() -> str:
|
|||
(Order.stoptime > prev_day) & (Order.stoptime < datetime.now())
|
||||
)
|
||||
return render_template(
|
||||
"home.html", orders=get_orders(
|
||||
((datetime.now() > Order.starttime) & (Order.stoptime > datetime.now()) | (Order.stoptime == None))
|
||||
), recently_closed=recently_closed
|
||||
"home.html", orders=get_orders(), recently_closed=recently_closed
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Script to generate the order related views of Haldis"""
|
||||
"Script to generate the order related views of Haldis"
|
||||
import random
|
||||
import re
|
||||
import typing
|
||||
|
@ -20,8 +20,8 @@ order_bp = Blueprint("order_bp", "order")
|
|||
|
||||
@order_bp.route("/")
|
||||
def orders(form: OrderForm = None) -> str:
|
||||
"""Generate general order view"""
|
||||
if form is None and current_user.association_list():
|
||||
"Generate general order view"
|
||||
if form is None and not current_user.is_anonymous():
|
||||
form = OrderForm()
|
||||
location_id = request.args.get("location_id")
|
||||
form.location_id.default = location_id
|
||||
|
@ -33,10 +33,7 @@ def orders(form: OrderForm = None) -> str:
|
|||
@order_bp.route("/create", methods=["POST"])
|
||||
@login_required
|
||||
def order_create() -> typing.Union[str, Response]:
|
||||
"""Generate order create view"""
|
||||
if not current_user.association_list():
|
||||
flash("Not allowed to create an order.", "info")
|
||||
abort(401)
|
||||
"Generate order create view"
|
||||
orderForm = OrderForm()
|
||||
orderForm.populate()
|
||||
if orderForm.validate_on_submit():
|
||||
|
@ -46,14 +43,14 @@ def order_create() -> typing.Union[str, Response]:
|
|||
db.session.add(order)
|
||||
db.session.commit()
|
||||
post_order_to_webhook(order)
|
||||
return redirect(url_for("order_bp.order_from_slug", order_slug=order.slug))
|
||||
return redirect(url_for("order_bp.order_from_id", order_id=order.id))
|
||||
return orders(form=orderForm)
|
||||
|
||||
|
||||
@order_bp.route("/<order_slug>")
|
||||
def order_from_slug(order_slug: str, form: OrderForm = None, dish_id=None) -> str:
|
||||
"""Generate order view from id"""
|
||||
order = Order.query.filter(Order.slug == order_slug).first()
|
||||
@order_bp.route("/<order_id>")
|
||||
def order_from_id(order_id: int, form: OrderForm = None, dish_id=None) -> str:
|
||||
"Generate order view from id"
|
||||
order = Order.query.filter(Order.id == order_id).first()
|
||||
if order is None:
|
||||
abort(404)
|
||||
if current_user.is_anonymous() and not order.public:
|
||||
|
@ -80,10 +77,10 @@ def order_from_slug(order_slug: str, form: OrderForm = None, dish_id=None) -> st
|
|||
)
|
||||
|
||||
|
||||
@order_bp.route("/<order_slug>/items")
|
||||
def items_shop_view(order_slug: int) -> str:
|
||||
"""Generate order items view from id"""
|
||||
order = Order.query.filter(Order.slug == order_slug).first()
|
||||
@order_bp.route("/<order_id>/items")
|
||||
def items_shop_view(order_id: int) -> str:
|
||||
"Generate order items view from id"
|
||||
order = Order.query.filter(Order.id == order_id).first()
|
||||
if order is None:
|
||||
abort(404)
|
||||
if current_user.is_anonymous() and not order.public:
|
||||
|
@ -93,31 +90,31 @@ def items_shop_view(order_slug: int) -> str:
|
|||
return render_template("order_items.html", order=order, total_price=total_price)
|
||||
|
||||
|
||||
@order_bp.route("/<order_slug>/edit", methods=["GET", "POST"])
|
||||
@order_bp.route("/<order_id>/edit", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def order_edit(order_slug: str) -> typing.Union[str, Response]:
|
||||
"""Generate order edit view from id"""
|
||||
order = Order.query.filter(Order.slug == order_slug).first()
|
||||
def order_edit(order_id: int) -> typing.Union[str, Response]:
|
||||
"Generate order edit view from id"
|
||||
order = Order.query.filter(Order.id == order_id).first()
|
||||
if current_user.id is not order.courier_id and not current_user.is_admin():
|
||||
abort(401)
|
||||
if order is None:
|
||||
abort(404)
|
||||
order_form = OrderForm(obj=order)
|
||||
order_form.populate()
|
||||
if order_form.validate_on_submit():
|
||||
order_form.populate_obj(order)
|
||||
orderForm = OrderForm(obj=order)
|
||||
orderForm.populate()
|
||||
if orderForm.validate_on_submit():
|
||||
orderForm.populate_obj(order)
|
||||
order.update_from_hlds()
|
||||
db.session.commit()
|
||||
return redirect(url_for("order_bp.order_from_slug", order_slug=order.slug))
|
||||
return render_template("order_edit.html", form=order_form, order_slug=order.slug)
|
||||
return redirect(url_for("order_bp.order_from_id", order_id=order.id))
|
||||
return render_template("order_edit.html", form=orderForm, order_id=order_id)
|
||||
|
||||
|
||||
@order_bp.route("/<order_slug>/create", methods=["GET", "POST"])
|
||||
def order_item_create(order_slug: str) -> typing.Any:
|
||||
@order_bp.route("/<order_id>/create", methods=["GET", "POST"])
|
||||
def order_item_create(order_id: int) -> typing.Any:
|
||||
# type is 'typing.Union[str, Response]', but this errors due to
|
||||
# https://github.com/python/mypy/issues/7187
|
||||
"""Add item to order from slug"""
|
||||
current_order = Order.query.filter(Order.slug == order_slug).first()
|
||||
"Add item to order from id"
|
||||
current_order = Order.query.filter(Order.id == order_id).first()
|
||||
if current_order is None:
|
||||
abort(404)
|
||||
if current_order.is_closed():
|
||||
|
@ -126,7 +123,7 @@ def order_item_create(order_slug: str) -> typing.Any:
|
|||
flash("Please login to see this order.", "info")
|
||||
abort(401)
|
||||
location = current_order.location
|
||||
# If location doesn't exist anymore, adding items is nonsensical
|
||||
# If location doesn't exist any more, adding items is nonsensical
|
||||
if not location:
|
||||
abort(404)
|
||||
form = AnonOrderItemForm() if current_user.is_anonymous() else OrderItemForm()
|
||||
|
@ -174,7 +171,7 @@ def order_item_create(order_slug: str) -> typing.Any:
|
|||
return redirect(
|
||||
url_for(
|
||||
"order_bp.order_item_create",
|
||||
order_slug=current_order.slug,
|
||||
order_id=order_id,
|
||||
dish=form.dish_id.data,
|
||||
user_name=user_name,
|
||||
comment=comment,
|
||||
|
@ -183,13 +180,14 @@ def order_item_create(order_slug: str) -> typing.Any:
|
|||
|
||||
# If the form was not submitted (GET request) or the form had errors: show form again
|
||||
if not form.validate_on_submit():
|
||||
return order_from_slug(current_order.slug, form=form, dish_id=dish_id)
|
||||
return order_from_id(order_id, form=form, dish_id=dish_id)
|
||||
|
||||
# Form was submitted and is valid
|
||||
|
||||
item = OrderItem()
|
||||
form.populate_obj(item)
|
||||
item.hlds_data_version = location_definition_version
|
||||
item.order_id = current_order.id
|
||||
item.order_id = order_id
|
||||
if not current_user.is_anonymous():
|
||||
item.user_id = current_user.id
|
||||
else:
|
||||
|
@ -224,27 +222,26 @@ def order_item_create(order_slug: str) -> typing.Any:
|
|||
|
||||
db.session.add(item)
|
||||
db.session.commit()
|
||||
flash("Ordered %s" % item.dish_name, "success")
|
||||
return redirect(url_for("order_bp.order_from_slug", order_slug=order_slug))
|
||||
flash("Ordered %s" % (item.dish_name), "success")
|
||||
return redirect(url_for("order_bp.order_from_id", order_id=order_id))
|
||||
|
||||
|
||||
@order_bp.route("/<order_slug>/modify_items", methods=["POST"])
|
||||
@order_bp.route("/<order_id>/modify_items", methods=["POST"])
|
||||
@login_required
|
||||
# pylint: disable=R1710
|
||||
def modify_items(order_slug: str) -> typing.Optional[Response]:
|
||||
def modify_items(order_id: int) -> typing.Optional[Response]:
|
||||
if "delete_item" in request.form:
|
||||
return delete_item(order_slug, int(request.form["delete_item"]))
|
||||
return delete_item(order_id, int(request.form["delete_item"]))
|
||||
user_names = request.form.getlist("user_names")
|
||||
if request.form.get("action") == "mark_paid":
|
||||
return set_items_paid(order_slug, user_names, True)
|
||||
return set_items_paid(order_id, user_names, True)
|
||||
elif request.form.get("action") == "mark_unpaid":
|
||||
return set_items_paid(order_slug, user_names, False)
|
||||
return set_items_paid(order_id, user_names, False)
|
||||
else:
|
||||
abort(404)
|
||||
return None
|
||||
|
||||
def set_items_paid(order_slug: str, user_names: typing.Iterable[str], paid: bool):
|
||||
order = Order.query.filter(Order.slug == order_slug).first()
|
||||
def set_items_paid(order_id: int, user_names: typing.Iterable[str], paid: bool):
|
||||
total_paid_items = 0
|
||||
total_failed_items = 0
|
||||
for user_name in user_names:
|
||||
|
@ -252,15 +249,15 @@ def set_items_paid(order_slug: str, user_names: typing.Iterable[str], paid: bool
|
|||
items: typing.List[OrderItem] = []
|
||||
if user:
|
||||
items = OrderItem.query.filter(
|
||||
(OrderItem.user_id == user.id) & (OrderItem.order_id == order.id)
|
||||
(OrderItem.user_id == user.id) & (OrderItem.order_id == order_id)
|
||||
).all()
|
||||
else:
|
||||
items = OrderItem.query.filter(
|
||||
(OrderItem.user_name == user_name) & (OrderItem.order_id == order.id)
|
||||
(OrderItem.user_name == user_name) & (OrderItem.order_id == order_id)
|
||||
).all()
|
||||
|
||||
for item in items:
|
||||
if item.can_modify_payment(order.id, current_user.id):
|
||||
if item.can_modify_payment(order_id, current_user.id):
|
||||
if item.paid != paid:
|
||||
item.paid = paid
|
||||
total_paid_items += 1
|
||||
|
@ -272,34 +269,33 @@ def set_items_paid(order_slug: str, user_names: typing.Iterable[str], paid: bool
|
|||
flash("Marked %d items as paid" % (total_paid_items,), "success")
|
||||
else:
|
||||
flash("Failed to mark %d items as paid (succeeded in marking %d items as paid)" % (total_failed_items, total_paid_items), "error")
|
||||
return redirect(url_for("order_bp.order_from_slug", order_slug=order_slug))
|
||||
return redirect(url_for("order_bp.order_from_id", order_id=order_id))
|
||||
|
||||
|
||||
@order_bp.route("/<order_slug>/<item_id>/delete", methods=["POST"])
|
||||
@order_bp.route("/<order_id>/<item_id>/delete", methods=["POST"])
|
||||
# pylint: disable=R1710
|
||||
def delete_item(order_slug: str, item_id: int) -> typing.Any:
|
||||
def delete_item(order_id: int, item_id: int) -> typing.Any:
|
||||
# type is 'typing.Optional[Response]', but this errors due to
|
||||
# https://github.com/python/mypy/issues/7187
|
||||
"""Delete an item from an order"""
|
||||
item: OrderItem = OrderItem.query.filter(OrderItem.id == item_id).first()
|
||||
order: Order = Order.query.filter(Order.slug == order_slug).first()
|
||||
"Delete an item from an order"
|
||||
item = OrderItem.query.filter(OrderItem.id == item_id).first()
|
||||
user_id = None
|
||||
if not current_user.is_anonymous():
|
||||
user_id = current_user.id
|
||||
if item.can_delete(order.id, user_id, session.get("anon_name", "")):
|
||||
if item.can_delete(order_id, user_id, session.get("anon_name", "")):
|
||||
dish_name = item.dish_name
|
||||
db.session.delete(item)
|
||||
db.session.commit()
|
||||
flash("Deleted %s" % dish_name, "success")
|
||||
return redirect(url_for("order_bp.order_from_slug", order_slug=order_slug))
|
||||
flash("Deleted %s" % (dish_name), "success")
|
||||
return redirect(url_for("order_bp.order_from_id", order_id=order_id))
|
||||
abort(404)
|
||||
|
||||
|
||||
@order_bp.route("/<order_slug>/volunteer", methods=["POST"])
|
||||
@order_bp.route("/<order_id>/volunteer", methods=["POST"])
|
||||
@login_required
|
||||
def volunteer(order_slug: str) -> Response:
|
||||
"""Add a volunteer to an order"""
|
||||
order = Order.query.filter(Order.slug == order_slug).first()
|
||||
def volunteer(order_id: int) -> Response:
|
||||
"Add a volunteer to an order"
|
||||
order = Order.query.filter(Order.id == order_id).first()
|
||||
if order is None:
|
||||
abort(404)
|
||||
if order.courier_id is None or order.courier_id == 0:
|
||||
|
@ -308,14 +304,14 @@ def volunteer(order_slug: str) -> Response:
|
|||
flash("Thank you for volunteering!")
|
||||
else:
|
||||
flash("Volunteering not possible!")
|
||||
return redirect(url_for("order_bp.order_from_slug", order_slug=order.slug))
|
||||
return redirect(url_for("order_bp.order_from_id", order_id=order_id))
|
||||
|
||||
|
||||
@order_bp.route("/<order_slug>/close", methods=["POST"])
|
||||
@order_bp.route("/<order_id>/close", methods=["POST"])
|
||||
@login_required
|
||||
def close_order(order_slug: str) -> typing.Optional[Response]:
|
||||
"""Close an order"""
|
||||
order = Order.query.filter(Order.slug == order_slug).first()
|
||||
def close_order(order_id: int) -> typing.Optional[Response]:
|
||||
"Close an order"
|
||||
order = Order.query.filter(Order.id == order_id).first()
|
||||
if order is None:
|
||||
abort(404)
|
||||
if (
|
||||
|
@ -327,19 +323,19 @@ def close_order(order_slug: str) -> typing.Optional[Response]:
|
|||
if courier is not None:
|
||||
order.courier_id = courier.id
|
||||
db.session.commit()
|
||||
return redirect(url_for("order_bp.order_from_slug", order_slug=order_slug))
|
||||
return redirect(url_for("order_bp.order_from_id", order_id=order_id))
|
||||
return None
|
||||
|
||||
|
||||
@order_bp.route("/<order_slug>/prices", methods=["GET", "POST"])
|
||||
@order_bp.route("/<order_id>/prices", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def prices(order_slug: str) -> typing.Optional[Response]:
|
||||
order = Order.query.filter(Order.slug == order_slug).first()
|
||||
def prices(order_id: int) -> typing.Optional[Response]:
|
||||
order = Order.query.filter(Order.id == order_id).first()
|
||||
if order is None:
|
||||
abort(404)
|
||||
if not order.can_modify_prices(current_user.id):
|
||||
flash("You cannot modify the prices at this time.", "error")
|
||||
return redirect(url_for("order_bp.order_from_slug", order_slug=order.slug))
|
||||
return redirect(url_for("order_bp.order_from_id", order_id=order_id))
|
||||
|
||||
if request.method == "GET":
|
||||
return render_template(
|
||||
|
@ -369,12 +365,12 @@ def prices(order_slug: str) -> typing.Optional[Response]:
|
|||
item.price_modified = datetime.now()
|
||||
db.session.commit()
|
||||
|
||||
return redirect(url_for("order_bp.order_from_slug", order_slug=order.slug))
|
||||
return redirect(url_for("order_bp.order_from_id", order_id=order_id))
|
||||
|
||||
|
||||
|
||||
def select_user(items) -> typing.Optional[User]:
|
||||
"""Select a random user from those who are signed up for the order"""
|
||||
"Select a random user from those who are signed up for the order"
|
||||
user = None
|
||||
# remove non users
|
||||
items = [i for i in items if i.user_id]
|
||||
|
@ -393,20 +389,19 @@ def select_user(items) -> typing.Optional[User]:
|
|||
|
||||
|
||||
def get_orders(expression=None) -> typing.List[Order]:
|
||||
"""Give the list of all currently open and public Orders"""
|
||||
"Give the list of all currently open and public Orders"
|
||||
order_list: typing.List[OrderForm] = []
|
||||
if expression is None:
|
||||
expression = ((datetime.now() > Order.starttime) & (
|
||||
Order.stoptime
|
||||
> datetime.now()
|
||||
# pylint: disable=C0121
|
||||
) | (Order.stoptime == None)
|
||||
) & (Order.association.in_(current_user.association_list()))
|
||||
expression = (datetime.now() > Order.starttime) & (
|
||||
Order.stoptime
|
||||
> datetime.now()
|
||||
# pylint: disable=C0121
|
||||
) | (Order.stoptime == None)
|
||||
if not current_user.is_anonymous():
|
||||
order_list = Order.query.filter(expression).all()
|
||||
else:
|
||||
order_list = Order.query.filter(
|
||||
# pylint: disable=C0121
|
||||
expression & (Order.public == True) & (Order.association.in_(current_user.association_list()))
|
||||
expression & (Order.public == True)
|
||||
).all()
|
||||
return order_list
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import sentry_sdk
|
||||
from sentry_sdk.integrations.flask import FlaskIntegration
|
||||
from waitress import serve
|
||||
|
||||
from app import create_app
|
||||
from config import Configuration
|
||||
|
||||
if __name__ == "__main__":
|
||||
if Configuration.SENTRY_DSN:
|
||||
sentry_sdk.init(
|
||||
dsn=Configuration.SENTRY_DSN,
|
||||
integrations=[FlaskIntegration()]
|
||||
)
|
||||
|
||||
app, app_mgr = create_app()
|
||||
serve(app, host="0.0.0.0", port=8000)
|
|
@ -4,30 +4,24 @@ import typing
|
|||
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, OAuthRemoteApp
|
||||
from flask_oauthlib.client import OAuth, OAuthException
|
||||
from models import User, db
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
auth_zeus_bp = Blueprint("auth_zeus_bp", __name__)
|
||||
oauth_bp = Blueprint("oauth_bp", __name__)
|
||||
|
||||
|
||||
def zeus_login():
|
||||
"""Log in using ZeusWPI"""
|
||||
"Log in using ZeusWPI"
|
||||
return current_app.zeus.authorize(
|
||||
callback=url_for("auth_zeus_bp.authorized", _external=True))
|
||||
callback=url_for("oauth_bp.authorized", _external=True))
|
||||
|
||||
|
||||
@auth_zeus_bp.route("/login")
|
||||
def login():
|
||||
"""Function to handle a user trying to log in"""
|
||||
return zeus_login()
|
||||
|
||||
|
||||
@auth_zeus_bp.route("/authorized")
|
||||
@oauth_bp.route("/login/zeus/authorized")
|
||||
def authorized() -> typing.Any:
|
||||
# type is 'typing.Union[str, Response]', but this errors due to
|
||||
# https://github.com/python/mypy/issues/7187
|
||||
"""Check authorized status"""
|
||||
"Check authorized status"
|
||||
resp = current_app.zeus.authorized_response()
|
||||
if resp is None:
|
||||
# pylint: disable=C0301
|
||||
|
@ -51,8 +45,8 @@ def authorized() -> typing.Any:
|
|||
return redirect(url_for("general_bp.home"))
|
||||
|
||||
|
||||
def init_oauth(app) -> OAuthRemoteApp:
|
||||
"""Initialize the OAuth for ZeusWPI"""
|
||||
def init_oauth(app):
|
||||
"Initialize the OAuth for ZeusWPI"
|
||||
oauth = OAuth(app)
|
||||
|
||||
zeus = oauth.remote_app(
|
||||
|
@ -75,15 +69,15 @@ def init_oauth(app) -> OAuthRemoteApp:
|
|||
|
||||
|
||||
def login_and_redirect_user(user) -> Response:
|
||||
"""Log in the user and then redirect them"""
|
||||
"Log in the user and then redirect them"
|
||||
login_user(user)
|
||||
return redirect(url_for("general_bp.home"))
|
||||
|
||||
|
||||
def create_user(username) -> User:
|
||||
"""Create a temporary user if it is needed"""
|
||||
"Create a temporary user if it is needed"
|
||||
user = User()
|
||||
user.configure(username, False, 1, associations=["zeus"])
|
||||
user.configure(username, False, 1)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
|
@ -1,17 +0,0 @@
|
|||
version: "3.4"
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
target: "development"
|
||||
environment:
|
||||
- MARIADB_DATABASE=haldis
|
||||
- MARIADB_USER=haldis
|
||||
- MARIADB_PASSWORD=haldis
|
||||
volumes: ["$PWD:/src"]
|
||||
database:
|
||||
environment:
|
||||
- MARIADB_DATABASE=haldis
|
||||
- MARIADB_ROOT_PASSWORD=mariadb
|
||||
- MARIADB_USER=haldis
|
||||
- MARIADB_PASSWORD=haldis
|
|
@ -1,31 +0,0 @@
|
|||
version: "3.4"
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
target: production
|
||||
restart: on-failure
|
||||
depends_on: [database]
|
||||
ports: ["8000:8000"]
|
||||
environment:
|
||||
- MARIADB_HOST=database
|
||||
- MARIADB_DATABASE
|
||||
- MARIADB_USER
|
||||
- MARIADB_PASSWORD
|
||||
networks: [haldis]
|
||||
database:
|
||||
image: mariadb:10.8
|
||||
hostname: database
|
||||
restart: on-failure
|
||||
environment:
|
||||
- MARIADB_DATABASE
|
||||
- MARIADB_ROOT_PASSWORD
|
||||
- MARIADB_USER
|
||||
- MARIADB_PASSWORD
|
||||
networks: [haldis]
|
||||
volumes: [haldis_data:/var/lib/mysql]
|
||||
networks:
|
||||
haldis:
|
||||
volumes:
|
||||
haldis_data:
|
|
@ -25,7 +25,7 @@ syn keyword hldsChoiceType single_choice multi_choice nextgroup=hldsBlockIdAf
|
|||
syn match hldsBlockId "^[a-z0-9_-]\+: "
|
||||
syn match hldsBlockIdAftrKywrd "[a-z0-9_-]\+: " contained
|
||||
|
||||
syn match _space " \+" nextgroup=hldsTag,hldsPrice
|
||||
syn match _doubleSpace " \+" nextgroup=hldsTag,hldsPrice
|
||||
syn match hldsTag "{[a-z0-9_-]\+}\( \|$\)" contained nextgroup=hldsTag,hldsPrice
|
||||
syn match hldsPrice "€ *[0-9]\+\(\.[0-9]\+\|\)\( \|$\)" contained
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ E="${normal}"
|
|||
if [ ! -d "venv" ]; then
|
||||
PYTHON_VERSION=$(cat .python-version)
|
||||
echo -e "${B} No venv found, creating a new one with version ${PYTHON_VERSION} ${E}"
|
||||
python3 -m virtualenv -p "$PYTHON_VERSION" venv
|
||||
python3 -m virtualenv -p $PYTHON_VERSION venv
|
||||
fi
|
||||
source venv/bin/activate
|
||||
|
||||
|
|
|
@ -4,6 +4,3 @@ set -euo pipefail
|
|||
cd "$(dirname "$0")/app"
|
||||
|
||||
env python create_database.py setup_database
|
||||
latest_revision=$(env python app.py db heads | sed "s/ (head)$//")
|
||||
echo Stamping db at $latest_revision
|
||||
env python app.py db stamp $latest_revision
|
||||
|
|
|
@ -12,5 +12,3 @@ black
|
|||
pymysql
|
||||
pyyaml
|
||||
tatsu<5.6 # >=5.6 needs Python >=3.8
|
||||
microsoftgraph-python
|
||||
sentry-sdk[flask]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# This file is autogenerated by pip-compile with python 3.9
|
||||
# This file is autogenerated by pip-compile
|
||||
# To update, run:
|
||||
#
|
||||
# pip-compile
|
||||
|
@ -11,15 +11,11 @@ appdirs==1.4.4
|
|||
black==21.6b0
|
||||
# via -r requirements.in
|
||||
blinker==1.4
|
||||
# via
|
||||
# flask-debugtoolbar
|
||||
# sentry-sdk
|
||||
# via flask-debugtoolbar
|
||||
cachelib==0.1.1
|
||||
# via flask-oauthlib
|
||||
certifi==2021.5.30
|
||||
# via
|
||||
# requests
|
||||
# sentry-sdk
|
||||
# via requests
|
||||
chardet==4.0.0
|
||||
# via requests
|
||||
click==7.1.2
|
||||
|
@ -28,19 +24,6 @@ click==7.1.2
|
|||
# flask
|
||||
dominate==2.6.0
|
||||
# via flask-bootstrap
|
||||
flask==1.1.4
|
||||
# via
|
||||
# -r requirements.in
|
||||
# flask-admin
|
||||
# flask-bootstrap
|
||||
# flask-debugtoolbar
|
||||
# flask-login
|
||||
# flask-migrate
|
||||
# flask-oauthlib
|
||||
# flask-script
|
||||
# flask-sqlalchemy
|
||||
# flask-wtf
|
||||
# sentry-sdk
|
||||
flask-admin==1.5.8
|
||||
# via -r requirements.in
|
||||
flask-bootstrap==3.3.7.1
|
||||
|
@ -61,6 +44,18 @@ flask-sqlalchemy==2.5.1
|
|||
# flask-migrate
|
||||
flask-wtf==0.15.1
|
||||
# via -r requirements.in
|
||||
flask==1.1.4
|
||||
# via
|
||||
# -r requirements.in
|
||||
# flask-admin
|
||||
# flask-bootstrap
|
||||
# flask-debugtoolbar
|
||||
# flask-login
|
||||
# flask-migrate
|
||||
# flask-oauthlib
|
||||
# flask-script
|
||||
# flask-sqlalchemy
|
||||
# flask-wtf
|
||||
greenlet==1.1.0
|
||||
# via sqlalchemy
|
||||
idna==2.10
|
||||
|
@ -79,8 +74,6 @@ markupsafe==2.0.1
|
|||
# jinja2
|
||||
# mako
|
||||
# wtforms
|
||||
microsoftgraph-python==1.1.3
|
||||
# via -r requirements.in
|
||||
mypy-extensions==0.4.3
|
||||
# via black
|
||||
oauthlib==2.1.0
|
||||
|
@ -99,14 +92,10 @@ pyyaml==5.4.1
|
|||
# via -r requirements.in
|
||||
regex==2021.4.4
|
||||
# via black
|
||||
requests==2.25.1
|
||||
# via
|
||||
# microsoftgraph-python
|
||||
# requests-oauthlib
|
||||
requests-oauthlib==1.1.0
|
||||
# via flask-oauthlib
|
||||
sentry-sdk[flask]==1.10.1
|
||||
# via -r requirements.in
|
||||
requests==2.25.1
|
||||
# via requests-oauthlib
|
||||
six==1.16.0
|
||||
# via python-dateutil
|
||||
sqlalchemy==1.4.18
|
||||
|
@ -117,10 +106,8 @@ tatsu==4.4.0
|
|||
# via -r requirements.in
|
||||
toml==0.10.2
|
||||
# via black
|
||||
urllib3==1.26.12
|
||||
# via
|
||||
# requests
|
||||
# sentry-sdk
|
||||
urllib3==1.26.5
|
||||
# via requests
|
||||
visitor==0.1.3
|
||||
# via flask-bootstrap
|
||||
werkzeug==1.0.1
|
||||
|
|
Loading…
Reference in a new issue