Implementieren der PWA, Multi-Instanzen-Passwort-Schutz und Kassen-Löschfunktion
This commit is contained in:
288
app.py
288
app.py
@@ -1,7 +1,10 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
from flask import Flask, render_template, request, session, send_from_directory, g
|
||||
import sqlite3
|
||||
import uuid
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from flask import Flask, render_template, request, session, send_from_directory, g, redirect, url_for
|
||||
from flask_bootstrap import Bootstrap
|
||||
|
||||
app = Flask(__name__)
|
||||
@@ -18,133 +21,210 @@ app.logger.addHandler(handler)
|
||||
|
||||
Bootstrap(app)
|
||||
|
||||
version = "1.0.9/2024-05-28"
|
||||
postcounter = 0
|
||||
gesamtwert = 0
|
||||
change = 0
|
||||
givenfloat = 0
|
||||
sum = ""
|
||||
item1 = 0
|
||||
item2 = 0
|
||||
item3 = 0
|
||||
item4 = 0
|
||||
item5 = 0
|
||||
item6 = 0
|
||||
version = "1.1.0/2026-02-24"
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect('kasse.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def init_db():
|
||||
conn = get_db_connection()
|
||||
conn.execute('''
|
||||
CREATE TABLE IF NOT EXISTS instances (
|
||||
id TEXT PRIMARY KEY,
|
||||
password TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
try:
|
||||
conn.execute('ALTER TABLE instances ADD COLUMN password TEXT')
|
||||
except sqlite3.OperationalError:
|
||||
pass # Column already exists
|
||||
|
||||
conn.execute('''
|
||||
CREATE TABLE IF NOT EXISTS products (
|
||||
instance_id TEXT,
|
||||
position INTEGER,
|
||||
name TEXT,
|
||||
price REAL,
|
||||
icon TEXT,
|
||||
color_class TEXT,
|
||||
FOREIGN KEY (instance_id) REFERENCES instances (id),
|
||||
PRIMARY KEY (instance_id, position)
|
||||
)
|
||||
''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
init_db()
|
||||
|
||||
app.logger.info('Starting erdbeerhannah ' + version)
|
||||
|
||||
# https://code-maven.com/flask-display-elapsed-time
|
||||
@app.before_request
|
||||
def before_request():
|
||||
g.request_start_time = time.time()
|
||||
g.request_time = lambda: "%.4fs" % (time.time() - g.request_start_time)
|
||||
|
||||
# prevent cached responses
|
||||
# https://stackoverflow.com/questions/47376744/how-to-prevent-cached-response-flask-server-using-chrome
|
||||
@app.after_request
|
||||
def add_header(r):
|
||||
"""
|
||||
Add headers to both force latest IE rendering engine or Chrome Frame,
|
||||
and also to cache the rendered page for 10 minutes.
|
||||
"""
|
||||
r.headers["Cache-Control"] = "no-store, max-age=0"
|
||||
return r
|
||||
|
||||
@app.route("/", methods=["GET", "POST"])
|
||||
def index():
|
||||
global gesamtwert, item1, item2, item3, item4, item5, item6, sum, givenfloat, change, given, background, postcounter, version
|
||||
background = "bg-white"
|
||||
|
||||
def landing():
|
||||
if request.method == "POST":
|
||||
postcounter += 1
|
||||
# wert = float(request.form["wert"])
|
||||
wert = request.form.get('wert', "0", type=float)
|
||||
given = request.form.get('given', "0", type=float)
|
||||
app.logger.debug('wert: %s, given: %s', wert, given)
|
||||
wertfloat = float(wert)
|
||||
givenfloat = float(given)
|
||||
|
||||
# reset button
|
||||
if wertfloat == 0:
|
||||
global gesamtwert
|
||||
gesamtwert = 0
|
||||
change = 0
|
||||
sum = "0"
|
||||
item1 = 0
|
||||
item2 = 0
|
||||
item3 = 0
|
||||
item4 = 0
|
||||
item5 = 0
|
||||
item6 = 0
|
||||
password = request.form.get('password', '')
|
||||
instance_id = str(uuid.uuid4())
|
||||
conn = get_db_connection()
|
||||
conn.execute('INSERT INTO instances (id, password) VALUES (?, ?)', (instance_id, generate_password_hash(password)))
|
||||
default_products = [
|
||||
(instance_id, 1, "500g Erdbeeren", 4.9, "🍓", "btn-primary"),
|
||||
(instance_id, 2, "Marmelade groß", 4.8, "🫙🫙", "btn-danger"),
|
||||
(instance_id, 3, "Marmelade klein", 3.3, "🫙", "btn-danger"),
|
||||
(instance_id, 4, "500g Kirschen", 5.0, "🍒", "btn-warning"),
|
||||
(instance_id, 5, "500g Himbeeren", 4.5, "🫐", "btn-warning"),
|
||||
(instance_id, 6, "Tragetasche", 0.2, "🛍️", "btn-success")
|
||||
]
|
||||
conn.executemany('INSERT INTO products (instance_id, position, name, price, icon, color_class) VALUES (?, ?, ?, ?, ?, ?)', default_products)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return redirect(url_for('admin', instance_id=instance_id))
|
||||
return render_template("landing.html", version=version)
|
||||
|
||||
@app.route("/<instance_id>", methods=["GET", "POST"])
|
||||
def index(instance_id):
|
||||
conn = get_db_connection()
|
||||
instance = conn.execute('SELECT * FROM instances WHERE id = ?', (instance_id,)).fetchone()
|
||||
if not instance:
|
||||
conn.close()
|
||||
return "Instance not found", 404
|
||||
|
||||
products_rows = conn.execute('SELECT * FROM products WHERE instance_id = ? ORDER BY position', (instance_id,)).fetchall()
|
||||
conn.close()
|
||||
|
||||
products = {row['position']: dict(row) for row in products_rows}
|
||||
|
||||
session_prefix = f"kasse_{instance_id}_"
|
||||
|
||||
gesamtwert = session.get(f'{session_prefix}gesamtwert', 0.0)
|
||||
change = session.get(f'{session_prefix}change', "0.00")
|
||||
givenfloat = session.get(f'{session_prefix}given', 0.0)
|
||||
items = {i: session.get(f'{session_prefix}item{i}', 0) for i in range(1, 7)}
|
||||
|
||||
background = "bg-white"
|
||||
|
||||
if request.method == "POST":
|
||||
action = request.form.get('action')
|
||||
position = request.form.get('position', type=int)
|
||||
|
||||
if action == "reset":
|
||||
gesamtwert = 0.0
|
||||
change = "0.00"
|
||||
givenfloat = 0.0
|
||||
items = {i: 0 for i in range(1, 7)}
|
||||
background = "bg-white"
|
||||
# summarize value
|
||||
elif wertfloat != -2:
|
||||
gesamtwert += wertfloat
|
||||
gesamtwert = round(gesamtwert, 2)
|
||||
if gesamtwert > 0:
|
||||
sum = str(gesamtwert) + "0"
|
||||
elif action == "calculate_change":
|
||||
given = request.form.get('given', "0", type=float)
|
||||
givenfloat = given
|
||||
change_val = givenfloat - gesamtwert
|
||||
change = f"{change_val:.2f}"
|
||||
background = "bg-danger" if change_val < 0 else "bg-white"
|
||||
elif position and position in products:
|
||||
price = products[position]['price']
|
||||
gesamtwert += price
|
||||
items[position] += 1
|
||||
|
||||
# summarize items
|
||||
if wertfloat == 4.9:
|
||||
item1 += 1
|
||||
if wertfloat == 4.8:
|
||||
item2 += 1
|
||||
if wertfloat == 3.3:
|
||||
item3 += 1
|
||||
if wertfloat == 4.5:
|
||||
item4 += 1
|
||||
if wertfloat == 5:
|
||||
item5 += 1
|
||||
if wertfloat == .2:
|
||||
item6 += 1
|
||||
if givenfloat > 0:
|
||||
try:
|
||||
gesamtwert = session['summefloat'] or 0
|
||||
sum = str(gesamtwert) + "0"
|
||||
change = str(round((givenfloat - gesamtwert) * -1, 2)) + "0"
|
||||
except:
|
||||
app.logger.warning("Failed to read sum")
|
||||
if givenfloat - gesamtwert < 0:
|
||||
background = "bg-danger"
|
||||
else:
|
||||
background = "bg-white"
|
||||
|
||||
session['item1'] = item1
|
||||
session['item2'] = item2
|
||||
session['item3'] = item3
|
||||
session['item4'] = item4
|
||||
session['item5'] = item5
|
||||
session['item6'] = item6
|
||||
session['summefloat'] = gesamtwert
|
||||
session['summestring'] = sum
|
||||
session['change'] = change
|
||||
session['given'] = givenfloat
|
||||
|
||||
app.logger.info('*** sum %s, given %s, change %s', sum, givenfloat, change)
|
||||
app.logger.info('*** postcounter %s', postcounter)
|
||||
|
||||
return render_template("index.html", gesamtwert=session.get('summestring', 0),
|
||||
change=session.get('change', 0),
|
||||
given=session.get('given', 0),
|
||||
item1=session.get('item1', 0),
|
||||
item2=session.get('item2', 0),
|
||||
item3=session.get('item3', 0),
|
||||
item4=session.get('item4', 0),
|
||||
item5=session.get('item5', 0),
|
||||
item6=session.get('item6', 0),
|
||||
session[f'{session_prefix}gesamtwert'] = round(gesamtwert, 2)
|
||||
session[f'{session_prefix}change'] = change
|
||||
session[f'{session_prefix}given'] = round(givenfloat, 2)
|
||||
for i in range(1, 7):
|
||||
session[f'{session_prefix}item{i}'] = items[i]
|
||||
|
||||
# Update our local variables after processing so they reflect what is rendered
|
||||
gesamtwert = round(gesamtwert, 2)
|
||||
|
||||
return render_template("index.html",
|
||||
instance_id=instance_id,
|
||||
products=products,
|
||||
gesamtwert=f"{gesamtwert:.2f}",
|
||||
change=change,
|
||||
given=f"{givenfloat:.2f}" if givenfloat > 0 else "0",
|
||||
items=items,
|
||||
background=background,
|
||||
version=version,
|
||||
)
|
||||
version=version
|
||||
)
|
||||
|
||||
@app.route("/<instance_id>/admin", methods=["GET", "POST"])
|
||||
def admin(instance_id):
|
||||
conn = get_db_connection()
|
||||
instance = conn.execute('SELECT * FROM instances WHERE id = ?', (instance_id,)).fetchone()
|
||||
if not instance:
|
||||
conn.close()
|
||||
return "Instance not found", 404
|
||||
|
||||
auth_key = f'admin_auth_{instance_id}'
|
||||
|
||||
# Handle Login Submission
|
||||
if request.method == "POST" and 'admin_password' in request.form:
|
||||
if check_password_hash(instance['password'], request.form['admin_password']):
|
||||
session[auth_key] = True
|
||||
conn.close()
|
||||
return redirect(url_for('admin', instance_id=instance_id))
|
||||
else:
|
||||
conn.close()
|
||||
return render_template("login.html", instance_id=instance_id, error="Falsches Passwort", version=version)
|
||||
|
||||
# Require Authentication
|
||||
if not session.get(auth_key):
|
||||
conn.close()
|
||||
return render_template("login.html", instance_id=instance_id, error=None, version=version)
|
||||
|
||||
if request.method == "POST":
|
||||
action = request.form.get('action')
|
||||
|
||||
if action == "delete":
|
||||
conn.execute('DELETE FROM products WHERE instance_id = ?', (instance_id,))
|
||||
conn.execute('DELETE FROM instances WHERE id = ?', (instance_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
session.pop(auth_key, None)
|
||||
return redirect(url_for('landing'))
|
||||
|
||||
# Additional safety: Ensure that the post request isn't processed if not authenticated
|
||||
# (Though we checked above, this is for the product update form submission)
|
||||
for i in range(1, 7):
|
||||
name = request.form.get(f'name_{i}')
|
||||
price = request.form.get(f'price_{i}', type=float)
|
||||
icon = request.form.get(f'icon_{i}')
|
||||
color = request.form.get(f'color_{i}')
|
||||
if name is not None and price is not None:
|
||||
conn.execute('''
|
||||
UPDATE products SET name = ?, price = ?, icon = ?, color_class = ?
|
||||
WHERE instance_id = ? AND position = ?
|
||||
''', (name, price, icon, color, instance_id, i))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return redirect(url_for('index', instance_id=instance_id))
|
||||
|
||||
products_rows = conn.execute('SELECT * FROM products WHERE instance_id = ? ORDER BY position', (instance_id,)).fetchall()
|
||||
conn.close()
|
||||
|
||||
products = {row['position']: dict(row) for row in products_rows}
|
||||
return render_template("admin.html", instance_id=instance_id, products=products, version=version)
|
||||
|
||||
@app.route('/favicon.ico')
|
||||
def favicon():
|
||||
return send_from_directory(os.path.join(app.root_path, 'static'),
|
||||
'favicon.ico', mimetype='image/vnd.microsoft.icon')
|
||||
|
||||
# @app.route('/about')
|
||||
# def about():
|
||||
# return send_from_directory(os.path.join(app.root_path, 'static'),
|
||||
# 'about.html')
|
||||
@app.route('/manifest.json')
|
||||
def manifest():
|
||||
return send_from_directory('static', 'manifest.json')
|
||||
|
||||
@app.route('/sw.js')
|
||||
def service_worker():
|
||||
return send_from_directory('static', 'sw.js', mimetype='application/javascript')
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True, host='127.0.0.1')
|
||||
Reference in New Issue
Block a user