Compare commits
	
		
			4 Commits
		
	
	
		
			5cec28cad1
			...
			1b6d0c40f3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1b6d0c40f3 | |||
| 235ef32efd | |||
| 85598adaab | |||
| 70934a2f85 | 
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							| @ -4,6 +4,11 @@ | ||||
| 
 | ||||
| ## 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 | ||||
| ``` | ||||
| @ -11,6 +16,14 @@ pip install -r requirements.txt | ||||
| You should probably use a virtualenv for that. | ||||
| 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 | ||||
| 
 | ||||
| Most of jon works without JS but there are some features that require it. | ||||
|  | ||||
| @ -22,12 +22,7 @@ def create_app(): | ||||
|     app.config.from_file("config.json", load=json.load, silent=True) | ||||
| 
 | ||||
|     db.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() | ||||
|     auth.init_app(app) | ||||
| 
 | ||||
|     @app.context_processor | ||||
|     def utility_processor(): | ||||
|  | ||||
							
								
								
									
										65
									
								
								jon/auth.py
									
									
									
									
									
								
							
							
						
						
									
										65
									
								
								jon/auth.py
									
									
									
									
									
								
							| @ -1,7 +1,10 @@ | ||||
| import secrets | ||||
| import string | ||||
| 
 | ||||
| from flask import Blueprint, request, redirect, render_template, session | ||||
| from flask import Blueprint, request, redirect, render_template | ||||
| 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") | ||||
| 
 | ||||
| @ -15,27 +18,67 @@ ALLOWED_PATHS = [ | ||||
| ] | ||||
| 
 | ||||
| 
 | ||||
| def before_request(): | ||||
|     """ | ||||
|     If the correct token query parameter is passed along with any request, | ||||
|     we mark this session authenticated by setting `session["authenticated"]`. | ||||
|     Unless the session is authenticated, all requests result in a 403 FORBIDDEN. | ||||
|     """ | ||||
| # A poor man's replacement for memory-backed session solution. | ||||
| # We keep exactly one User (and the corresponding UserData) in | ||||
| # memory and use that to store session data. | ||||
| class UserData: | ||||
|     location: Optional[Dict[str, Any]] | ||||
|     orders: List[Dict[str, Any]] | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.location = None | ||||
|         self.orders = [] | ||||
| 
 | ||||
| 
 | ||||
| class User: | ||||
|     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: | ||||
|             session["authenticated"] = () | ||||
|                 login_user(the_one_and_only_user) | ||||
|             # Reload the page without query parameters | ||||
|             return redirect(request.path) | ||||
| 
 | ||||
|     # Don't deny any paths in `ALLOWED_PATHS` | ||||
|         # Never deny any paths in `ALLOWED_PATHS` | ||||
|         if request.path in ALLOWED_PATHS: | ||||
|             return | ||||
| 
 | ||||
|     if not "authenticated" in session: | ||||
|         if not current_user.is_authenticated: | ||||
|             return render_template("auth/denied.html"), 403 | ||||
| 
 | ||||
| 
 | ||||
| @bp.get("/logout") | ||||
| def logout(): | ||||
|     session.pop("authenticated", None) | ||||
|     logout_user() | ||||
|     return redirect("/") | ||||
|  | ||||
							
								
								
									
										28
									
								
								jon/entry.py
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								jon/entry.py
									
									
									
									
									
								
							| @ -1,5 +1,5 @@ | ||||
| from flask import Blueprint, flash, redirect, render_template, request, session | ||||
| 
 | ||||
| from flask import Blueprint, flash, redirect, render_template, request | ||||
| from flask_login import current_user | ||||
| 
 | ||||
| from . import db | ||||
| 
 | ||||
| @ -9,22 +9,18 @@ bp = Blueprint("entry", __name__, url_prefix="/entry") | ||||
| 
 | ||||
| @bp.route("/", methods=["GET", "POST"]) | ||||
| def index(): | ||||
|     cart = session.get("cart", default=[]) | ||||
|     return render_template( | ||||
|         "entry/index.html", | ||||
|         cart=cart | ||||
|         "entry/index.html" | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| @bp.post("/add-new-items") | ||||
| def add_new_entries(): | ||||
|     print(session) | ||||
| 
 | ||||
|     i_know_what_im_doing = "i-know-what-im-doing" in request.form | ||||
|     if not i_know_what_im_doing: | ||||
|         return "Du weißt nicht was du tust", 400 | ||||
| 
 | ||||
|     orders = session.get("cart", default=[]) | ||||
|     orders = current_user.data.orders | ||||
|     if not orders: | ||||
|         return "Keine Aufträge", 404 | ||||
|      | ||||
| @ -36,7 +32,7 @@ def add_new_entries(): | ||||
|     db.get_db().commit() | ||||
| 
 | ||||
|     # Reset the cart | ||||
|     session["cart"] = [] | ||||
|     current_user.data.orders = [] | ||||
| 
 | ||||
|     return redirect(request.referrer) | ||||
| 
 | ||||
| @ -48,9 +44,7 @@ def delete_order(): | ||||
|     except: | ||||
|         return "Incomplete or mistyped form", 400 | ||||
| 
 | ||||
|     cart = session.get("cart", default=[]) | ||||
|     del cart[order_index] | ||||
|     session["cart"] = cart | ||||
|     del current_user.data.orders[order_index] | ||||
| 
 | ||||
|     return redirect(request.referrer) | ||||
| 
 | ||||
| @ -76,9 +70,7 @@ def new_order(): | ||||
|         except: | ||||
|             return f"Incomplete or mistyped form", 400 | ||||
|          | ||||
|         cart = session.get("cart", default=[]) | ||||
|         print(cart) | ||||
|         cart.append({ | ||||
|         current_user.data.orders.append({ | ||||
|             "barcode": barcode, | ||||
|             "name": name, | ||||
|             "sales_units": sales_units, | ||||
| @ -91,7 +83,6 @@ def new_order(): | ||||
|             "net_unit_price": net_unit_price, | ||||
|             "gross_unit_price": gross_unit_price | ||||
|         }) | ||||
|         session["cart"] = cart | ||||
|         return redirect("/entry") | ||||
| 
 | ||||
|     with db.run_query("entry/get_groups.sql") as cursor: | ||||
| @ -122,10 +113,9 @@ def api_search_items(): | ||||
|     except: | ||||
|         return {"error": "Missing query parameter `search-term`"}, 400 | ||||
| 
 | ||||
|     location = session.get("location", None) | ||||
| 
 | ||||
|     location = current_user.data.location | ||||
|     with db.run_query("search_items.sql", { | ||||
|         "location_id": None if location is None else location["location_id"], | ||||
|         "location_id": location["location_id"] if location else None, | ||||
|         "search_term": search_term | ||||
|     }) as cursor: | ||||
|         items = cursor.fetchall() | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| from flask import Blueprint, redirect, render_template, request, session | ||||
| from flask import Blueprint, redirect, render_template, request | ||||
| from flask_login import current_user | ||||
| 
 | ||||
| from . import db | ||||
| 
 | ||||
| @ -8,7 +9,7 @@ bp = Blueprint("inventory", __name__, url_prefix="/inventory") | ||||
| 
 | ||||
| @bp.get("/") | ||||
| def index(): | ||||
|     location = session.get("location", None) | ||||
|     location = current_user.data.location | ||||
|     items = db.run_query("get_inventory_overview.sql", { | ||||
|         "location_id": None if location is None else location["location_id"] | ||||
|     }).fetchall() | ||||
| @ -20,7 +21,7 @@ def index(): | ||||
| 
 | ||||
| @bp.get("/report") | ||||
| def read_report(): | ||||
|     location = session.get("location", None) | ||||
|     location = current_user.data.location | ||||
|     items = db.run_query("get_inventory_report.sql", { | ||||
|         "location_id": None if location is None else location["location_id"] | ||||
|     }).fetchall() | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| from flask import Blueprint, render_template, request, session | ||||
| from flask import Blueprint, render_template, request | ||||
| from flask_login import current_user | ||||
| 
 | ||||
| from . import db | ||||
| 
 | ||||
| @ -11,13 +12,12 @@ def index(): | ||||
|     if request.method == "POST": | ||||
|         location_id = request.form.get("location_id", "") | ||||
|         if location_id == "": | ||||
|             session.pop("location", None) | ||||
|             current_user.data.location = None | ||||
|         else: | ||||
|             location = db.run_query("get_location_by_id.sql", { | ||||
|                 "location_id": location_id} | ||||
|             ).fetchone() | ||||
|             session["location"] = location | ||||
|          | ||||
|                 "location_id": location_id | ||||
|             }).fetchone() | ||||
|             current_user.data.location = location | ||||
| 
 | ||||
|     locations = db.run_query("get_locations.sql").fetchall() | ||||
| 
 | ||||
|  | ||||
| @ -71,3 +71,6 @@ th { | ||||
|   display: block; | ||||
|   width: 8em; | ||||
| } | ||||
| details { | ||||
|   font-size: 0.8em; | ||||
| } | ||||
|  | ||||
| @ -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("/location") else "" }}> | ||||
|             <a href="/location"> | ||||
|             {% if "location" not in session %} | ||||
|             {% if not current_user.data.location %} | ||||
|               Raum wählen | ||||
|             {% else %} | ||||
|               Raum: {{ session.location.location_name }} | ||||
|               Raum: {{ current_user.data.location.location_name }} | ||||
|             {% endif %} | ||||
|             </a> | ||||
|           </li> | ||||
| @ -30,6 +30,16 @@ | ||||
|       <details> | ||||
|         <summary><code>config</code></summary> | ||||
|         <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> | ||||
|       </details> | ||||
|       {% endif %} | ||||
|  | ||||
| @ -15,7 +15,7 @@ | ||||
|     <th>VK-Preis (Brutto)</th> | ||||
|     <th>Aktionen</th> | ||||
|   </tr> | ||||
|   {% for cart_item in cart %} | ||||
|   {% for cart_item in current_user.data.orders %} | ||||
|   <tr> | ||||
|     <td><code>{{ cart_item.barcode }}</code></td> | ||||
|     <td>{{ cart_item.name }}</td> | ||||
|  | ||||
| @ -3,9 +3,9 @@ | ||||
| {% block content %} | ||||
| <form method="POST"> | ||||
|   <select name="location_id"> | ||||
|     <option value="" {{ "selected" if "location" not in session else ""}}>-</option> | ||||
|     <option value="" {{ "selected" if not current_user.data.location else ""}}>-</option> | ||||
|   {% for location in locations %} | ||||
|     <option value="{{ location.location_id }}" {{ "selected" if "location" in session and session.location.location_id == location.location_id else "" }}>{{ location.location_name }}</option> | ||||
|     <option value="{{ location.location_id }}" {{ "selected" if current_user.data.location.location_id == location.location_id else "" }}>{{ location.location_name }}</option> | ||||
|   {% endfor %} | ||||
|   </select> | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| blinker==1.6.2 | ||||
| click==8.1.3 | ||||
| Flask==2.3.2 | ||||
| Flask-Login==0.6.2 | ||||
| itsdangerous==2.1.2 | ||||
| Jinja2==3.1.2 | ||||
| MarkupSafe==2.1.2 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user