Compare commits

..

No commits in common. "1b6d0c40f371058479ce1177fd034bc6477b2732" and "5cec28cad1e018ab64cf6397a55399b319bc339c" have entirely different histories.

11 changed files with 57 additions and 113 deletions

View File

@ -4,11 +4,6 @@
## Setup ## Setup
`jon` is a Python WSGI application written using Flask.
This means you'll have to install a bunch of Python packages to get up and running.
### Dependencies
``` ```
pip install -r requirements.txt pip install -r requirements.txt
``` ```
@ -16,14 +11,6 @@ pip install -r requirements.txt
You should probably use a virtualenv for that. You should probably use a virtualenv for that.
I develop `jon` using Python 3.10 but it should work with older versions as well. I develop `jon` using Python 3.10 but it should work with older versions as well.
#### Arch Linux
If you're on Arch, these packages are required for running the server:
```
python python-flask python-flask-login python-psycopg2
```
### Building Frontend JS ### Building Frontend JS
Most of jon works without JS but there are some features that require it. Most of jon works without JS but there are some features that require it.

View File

@ -22,7 +22,12 @@ def create_app():
app.config.from_file("config.json", load=json.load, silent=True) app.config.from_file("config.json", load=json.load, silent=True)
db.init_app(app) db.init_app(app)
auth.init_app(app)
# This function denies every request until `auth.ACCESS_TOKEN`
# is passed using `?token=` to authenticate the session.
@app.before_request
def before_req_fun():
return auth.before_request()
@app.context_processor @app.context_processor
def utility_processor(): def utility_processor():

View File

@ -1,10 +1,7 @@
import secrets import secrets
import string import string
from flask import Blueprint, request, redirect, render_template from flask import Blueprint, request, redirect, render_template, session
from flask_login import current_user, login_user, logout_user, LoginManager
from typing import Any, Dict, List, Optional
bp = Blueprint("auth", __name__, url_prefix="/auth") bp = Blueprint("auth", __name__, url_prefix="/auth")
@ -18,67 +15,27 @@ ALLOWED_PATHS = [
] ]
# A poor man's replacement for memory-backed session solution. def before_request():
# We keep exactly one User (and the corresponding UserData) in """
# memory and use that to store session data. If the correct token query parameter is passed along with any request,
class UserData: we mark this session authenticated by setting `session["authenticated"]`.
location: Optional[Dict[str, Any]] Unless the session is authenticated, all requests result in a 403 FORBIDDEN.
orders: List[Dict[str, Any]] """
if "token" in request.args:
if request.args["token"] == ACCESS_TOKEN:
session["authenticated"] = ()
# Reload the page without query parameters
return redirect(request.path)
def __init__(self): # Don't deny any paths in `ALLOWED_PATHS`
self.location = None if request.path in ALLOWED_PATHS:
self.orders = [] return
if not "authenticated" in session:
class User: return render_template("auth/denied.html"), 403
is_authenticated: bool
is_active: bool
is_anonymous: bool
data: UserData
def __init__(self):
self.is_authenticated = True
self.is_active = True
self.is_anonymous = False
self.data = UserData()
def get_id(self) -> str:
return ""
def init_app(app):
login_manager = LoginManager(app)
the_one_and_only_user = User()
@login_manager.user_loader
def load_user(user_id: str) -> User:
assert user_id == ""
return the_one_and_only_user
# This function denies every request until `auth.ACCESS_TOKEN`
# is passed using `?token=` to authenticate the user.
# We use this instead of @login_required because otherwise we'd have
# to add that annotation to all routes.
# See also: https://flask-login.readthedocs.io/en/latest/#flask_login.login_required
@app.before_request
def before_request():
if "token" in request.args:
if request.args["token"] == ACCESS_TOKEN:
login_user(the_one_and_only_user)
# Reload the page without query parameters
return redirect(request.path)
# Never deny any paths in `ALLOWED_PATHS`
if request.path in ALLOWED_PATHS:
return
if not current_user.is_authenticated:
return render_template("auth/denied.html"), 403
@bp.get("/logout") @bp.get("/logout")
def logout(): def logout():
logout_user() session.pop("authenticated", None)
return redirect("/") return redirect("/")

View File

@ -1,5 +1,5 @@
from flask import Blueprint, flash, redirect, render_template, request from flask import Blueprint, flash, redirect, render_template, request, session
from flask_login import current_user
from . import db from . import db
@ -9,18 +9,22 @@ bp = Blueprint("entry", __name__, url_prefix="/entry")
@bp.route("/", methods=["GET", "POST"]) @bp.route("/", methods=["GET", "POST"])
def index(): def index():
cart = session.get("cart", default=[])
return render_template( return render_template(
"entry/index.html" "entry/index.html",
cart=cart
) )
@bp.post("/add-new-items") @bp.post("/add-new-items")
def add_new_entries(): def add_new_entries():
print(session)
i_know_what_im_doing = "i-know-what-im-doing" in request.form i_know_what_im_doing = "i-know-what-im-doing" in request.form
if not i_know_what_im_doing: if not i_know_what_im_doing:
return "Du weißt nicht was du tust", 400 return "Du weißt nicht was du tust", 400
orders = current_user.data.orders orders = session.get("cart", default=[])
if not orders: if not orders:
return "Keine Aufträge", 404 return "Keine Aufträge", 404
@ -32,7 +36,7 @@ def add_new_entries():
db.get_db().commit() db.get_db().commit()
# Reset the cart # Reset the cart
current_user.data.orders = [] session["cart"] = []
return redirect(request.referrer) return redirect(request.referrer)
@ -44,7 +48,9 @@ def delete_order():
except: except:
return "Incomplete or mistyped form", 400 return "Incomplete or mistyped form", 400
del current_user.data.orders[order_index] cart = session.get("cart", default=[])
del cart[order_index]
session["cart"] = cart
return redirect(request.referrer) return redirect(request.referrer)
@ -70,7 +76,9 @@ def new_order():
except: except:
return f"Incomplete or mistyped form", 400 return f"Incomplete or mistyped form", 400
current_user.data.orders.append({ cart = session.get("cart", default=[])
print(cart)
cart.append({
"barcode": barcode, "barcode": barcode,
"name": name, "name": name,
"sales_units": sales_units, "sales_units": sales_units,
@ -83,6 +91,7 @@ def new_order():
"net_unit_price": net_unit_price, "net_unit_price": net_unit_price,
"gross_unit_price": gross_unit_price "gross_unit_price": gross_unit_price
}) })
session["cart"] = cart
return redirect("/entry") return redirect("/entry")
with db.run_query("entry/get_groups.sql") as cursor: with db.run_query("entry/get_groups.sql") as cursor:
@ -113,9 +122,10 @@ def api_search_items():
except: except:
return {"error": "Missing query parameter `search-term`"}, 400 return {"error": "Missing query parameter `search-term`"}, 400
location = current_user.data.location location = session.get("location", None)
with db.run_query("search_items.sql", { with db.run_query("search_items.sql", {
"location_id": location["location_id"] if location else None, "location_id": None if location is None else location["location_id"],
"search_term": search_term "search_term": search_term
}) as cursor: }) as cursor:
items = cursor.fetchall() items = cursor.fetchall()

View File

@ -1,5 +1,4 @@
from flask import Blueprint, redirect, render_template, request from flask import Blueprint, redirect, render_template, request, session
from flask_login import current_user
from . import db from . import db
@ -9,7 +8,7 @@ bp = Blueprint("inventory", __name__, url_prefix="/inventory")
@bp.get("/") @bp.get("/")
def index(): def index():
location = current_user.data.location location = session.get("location", None)
items = db.run_query("get_inventory_overview.sql", { items = db.run_query("get_inventory_overview.sql", {
"location_id": None if location is None else location["location_id"] "location_id": None if location is None else location["location_id"]
}).fetchall() }).fetchall()
@ -21,7 +20,7 @@ def index():
@bp.get("/report") @bp.get("/report")
def read_report(): def read_report():
location = current_user.data.location location = session.get("location", None)
items = db.run_query("get_inventory_report.sql", { items = db.run_query("get_inventory_report.sql", {
"location_id": None if location is None else location["location_id"] "location_id": None if location is None else location["location_id"]
}).fetchall() }).fetchall()

View File

@ -1,5 +1,4 @@
from flask import Blueprint, render_template, request from flask import Blueprint, render_template, request, session
from flask_login import current_user
from . import db from . import db
@ -12,12 +11,13 @@ def index():
if request.method == "POST": if request.method == "POST":
location_id = request.form.get("location_id", "") location_id = request.form.get("location_id", "")
if location_id == "": if location_id == "":
current_user.data.location = None session.pop("location", None)
else: else:
location = db.run_query("get_location_by_id.sql", { location = db.run_query("get_location_by_id.sql", {
"location_id": location_id "location_id": location_id}
}).fetchone() ).fetchone()
current_user.data.location = location session["location"] = location
locations = db.run_query("get_locations.sql").fetchall() locations = db.run_query("get_locations.sql").fetchall()

View File

@ -71,6 +71,3 @@ th {
display: block; display: block;
width: 8em; width: 8em;
} }
details {
font-size: 0.8em;
}

View File

@ -15,10 +15,10 @@
<li{{ " class=current-page" if request.path.startswith("/entry") else "" }}><a href="/entry">Eintragen</a></li> <li{{ " class=current-page" if request.path.startswith("/entry") else "" }}><a href="/entry">Eintragen</a></li>
<li{{ " class=current-page" if request.path.startswith("/location") else "" }}> <li{{ " class=current-page" if request.path.startswith("/location") else "" }}>
<a href="/location"> <a href="/location">
{% if not current_user.data.location %} {% if "location" not in session %}
Raum wählen Raum wählen
{% else %} {% else %}
Raum: {{ current_user.data.location.location_name }} Raum: {{ session.location.location_name }}
{% endif %} {% endif %}
</a> </a>
</li> </li>
@ -30,16 +30,6 @@
<details> <details>
<summary><code>config</code></summary> <summary><code>config</code></summary>
<pre>{% for key, value in config.items() %}{{ key }} = {{ value }} <pre>{% for key, value in config.items() %}{{ key }} = {{ value }}
{% endfor %}</pre>
</details>
<details>
<summary><code>session</code></summary>
<pre>{% for key, value in session.items() %}{{ key }} = {{ value }}
{% endfor %}</pre>
</details>
<details>
<summary><code>current_user.data</code></summary>
<pre>{% for key, value in current_user.data.__dict__.items() %}{{ key }} = {{ value }}
{% endfor %}</pre> {% endfor %}</pre>
</details> </details>
{% endif %} {% endif %}

View File

@ -15,7 +15,7 @@
<th>VK-Preis (Brutto)</th> <th>VK-Preis (Brutto)</th>
<th>Aktionen</th> <th>Aktionen</th>
</tr> </tr>
{% for cart_item in current_user.data.orders %} {% for cart_item in cart %}
<tr> <tr>
<td><code>{{ cart_item.barcode }}</code></td> <td><code>{{ cart_item.barcode }}</code></td>
<td>{{ cart_item.name }}</td> <td>{{ cart_item.name }}</td>

View File

@ -3,9 +3,9 @@
{% block content %} {% block content %}
<form method="POST"> <form method="POST">
<select name="location_id"> <select name="location_id">
<option value="" {{ "selected" if not current_user.data.location else ""}}>-</option> <option value="" {{ "selected" if "location" not in session else ""}}>-</option>
{% for location in locations %} {% for location in locations %}
<option value="{{ location.location_id }}" {{ "selected" if current_user.data.location.location_id == location.location_id else "" }}>{{ location.location_name }}</option> <option value="{{ location.location_id }}" {{ "selected" if "location" in session and session.location.location_id == location.location_id else "" }}>{{ location.location_name }}</option>
{% endfor %} {% endfor %}
</select> </select>

View File

@ -1,7 +1,6 @@
blinker==1.6.2 blinker==1.6.2
click==8.1.3 click==8.1.3
Flask==2.3.2 Flask==2.3.2
Flask-Login==0.6.2
itsdangerous==2.1.2 itsdangerous==2.1.2
Jinja2==3.1.2 Jinja2==3.1.2
MarkupSafe==2.1.2 MarkupSafe==2.1.2