From 9705606c89dfe8b70ea936100f2de72e9dd19e26 Mon Sep 17 00:00:00 2001 From: Yaro Kasear Date: Thu, 14 Aug 2025 12:03:52 -0500 Subject: [PATCH] Lots and lots of unneeded code removed now! --- inventory/models/areas.py | 97 +------------------ inventory/models/brands.py | 91 +----------------- inventory/models/items.py | 87 +---------------- inventory/models/room_functions.py | 64 +------------ inventory/models/rooms.py | 147 +---------------------------- inventory/routes/helpers.py | 1 - inventory/routes/hooks.py | 1 - inventory/routes/inventory.py | 7 +- inventory/routes/settings.py | 102 +------------------- inventory/temp.py | 6 -- inventory/utils/validation.py | 28 ------ 11 files changed, 11 insertions(+), 620 deletions(-) delete mode 100644 inventory/temp.py delete mode 100644 inventory/utils/validation.py diff --git a/inventory/models/areas.py b/inventory/models/areas.py index dbf86b0..45ad4f5 100644 --- a/inventory/models/areas.py +++ b/inventory/models/areas.py @@ -6,12 +6,9 @@ from sqlalchemy import Identity, Integer, Unicode from sqlalchemy.orm import Mapped, mapped_column, relationship from . import db -from ..temp import is_temp_id -from ..utils.validation import ValidatableMixin -class Area(ValidatableMixin, db.Model): +class Area(db.Model): __tablename__ = 'area' - VALIDATION_LABEL = "Area" id: Mapped[int] = mapped_column(Integer, Identity(start=1, increment=1), primary_key=True) name: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True) @@ -23,95 +20,3 @@ class Area(ValidatableMixin, db.Model): def __repr__(self): return f"" - - def serialize(self): - return { - 'id': self.id, - 'name': self.name - } - - @classmethod - def sync_from_state(cls, submitted_items: list[dict]) -> dict[str, int]: - """ - Syncs the Area table (aka 'sections') with the submitted list. - Supports add, update, and delete. - Also returns a mapping of temp IDs to real IDs for resolution. - """ - submitted_clean = [] - seen_ids = set() - temp_id_map = {} - - for item in submitted_items: - if not isinstance(item, dict): - continue - name = str(item.get("name", "")).strip() - raw_id = item.get("id") - if not name: - continue - submitted_clean.append({"id": raw_id, "name": name}) - - # Record real (non-temp) IDs - try: - if raw_id is not None: - parsed_id = int(raw_id) - if parsed_id >= 0: - seen_ids.add(parsed_id) - except (ValueError, TypeError): - pass - - existing_query = db.session.query(cls) - existing_by_id = {area.id: area for area in existing_query.all()} - existing_ids = set(existing_by_id.keys()) - - for entry in submitted_clean: - submitted_id_raw = entry.get("id") - submitted_name = entry["name"] - - if is_temp_id(submitted_id_raw): - new_area = cls(name=submitted_name) - db.session.add(new_area) - db.session.flush() # Get the real ID - temp_id_map[submitted_id_raw] = new_area.id - else: - try: - submitted_id = int(submitted_id_raw) - except (ValueError, TypeError): - continue # Skip malformed ID - - if submitted_id in existing_by_id: - area = existing_by_id[submitted_id] - if area.name != submitted_name: - area.name = submitted_name - - for existing_id in existing_ids - seen_ids: - area = existing_by_id[existing_id] - db.session.delete(area) - - id_map = { - **{str(i): i for i in seen_ids}, # "1" → 1 - **{str(temp): real for temp, real in temp_id_map.items()} # "temp-1" → 5 - } - return id_map - - @classmethod - def validate_state(cls, submitted_items: list[dict]) -> list[str]: - errors = [] - - for index, item in enumerate(submitted_items): - if not isinstance(item, dict): - errors.append(f"Area entry #{index + 1} is not a valid object.") - continue - - name = item.get('name') - if not name or not str(name).strip(): - errors.append(f"Area entry #{index + 1} is missing a name.") - - raw_id = item.get('id') - if raw_id is not None: - try: - _ = int(raw_id) - except (ValueError, TypeError): - if not is_temp_id(raw_id): - errors.append(f"Area entry #{index + 1} has invalid ID: {raw_id}") - - return errors diff --git a/inventory/models/brands.py b/inventory/models/brands.py index 00fd14a..d2adf17 100644 --- a/inventory/models/brands.py +++ b/inventory/models/brands.py @@ -1,4 +1,4 @@ -from typing import List, Optional, TYPE_CHECKING +from typing import List, TYPE_CHECKING if TYPE_CHECKING: from .inventory import Inventory @@ -6,12 +6,9 @@ from sqlalchemy import Identity, Integer, Unicode from sqlalchemy.orm import Mapped, mapped_column, relationship from . import db -from ..temp import is_temp_id -from ..utils.validation import ValidatableMixin -class Brand(ValidatableMixin, db.Model): +class Brand(db.Model): __tablename__ = 'brand' - VALIDATION_LABEL = 'Brand' id: Mapped[int] = mapped_column(Integer, Identity(start=1, increment=1), primary_key=True) name: Mapped[str] = mapped_column(Unicode(255), nullable=False) @@ -24,90 +21,6 @@ class Brand(ValidatableMixin, db.Model): def __repr__(self): return f"" - def serialize(self): - return { - 'id': self.id, - 'name': self.name - } - - @classmethod - def sync_from_state(cls, submitted_items: list[dict]) -> dict[str, int]: - submitted_clean = [] - seen_ids = set() - temp_id_map = {} - - for item in submitted_items: - if not isinstance(item, dict): - continue - name = str(item.get("name", "")).strip() - raw_id = item.get("id") - if not name: - continue - submitted_clean.append({"id": raw_id, "name": name}) - - try: - if raw_id: - parsed_id = int(raw_id) - if parsed_id >= 0: - seen_ids.add(parsed_id) - except (ValueError, TypeError): - pass - - existing_by_id = {b.id: b for b in db.session.query(cls).all()} - existing_ids = set(existing_by_id.keys()) - - for entry in submitted_clean: - submitted_id = entry.get("id") - name = entry["name"] - - if is_temp_id(submitted_id): - obj = cls(name=name) - db.session.add(obj) - db.session.flush() - temp_id_map[submitted_id] = obj.id - else: - try: - parsed_id = int(submitted_id) - except (ValueError, TypeError): - continue - - if parsed_id in existing_by_id: - obj = existing_by_id[parsed_id] - if obj.name != name: - obj.name = name - - for id_to_remove in existing_ids - seen_ids: - db.session.delete(existing_by_id[id_to_remove]) - - id_map = { - **{str(i): i for i in seen_ids}, # "1" → 1 - **{str(temp): real for temp, real in temp_id_map.items()} # "temp-1" → 5 - } - return id_map - - @classmethod - def validate_state(cls, submitted_items: list[dict]) -> list[str]: - errors = [] - - for index, item in enumerate(submitted_items): - if not isinstance(item, dict): - errors.append(f"Area entry #{index + 1} is not a valid object.") - continue - - name = item.get('name') - if not name or not str(name).strip(): - errors.append(f"Area entry #{index + 1} is missing a name.") - - raw_id = item.get('id') - if raw_id is not None: - try: - _ = int(raw_id) - except (ValueError, TypeError): - if not is_temp_id(raw_id): - errors.append(f"Area entry #{index + 1} has invalid ID: {raw_id}") - - return errors - @property def identifier(self) -> str: return self.name if self.name else f"ID: {self.id}" diff --git a/inventory/models/items.py b/inventory/models/items.py index 1641d91..75c06ef 100644 --- a/inventory/models/items.py +++ b/inventory/models/items.py @@ -6,12 +6,9 @@ from sqlalchemy import Identity, Integer, Unicode from sqlalchemy.orm import Mapped, mapped_column, relationship from . import db -from ..temp import is_temp_id -from ..utils.validation import ValidatableMixin -class Item(ValidatableMixin, db.Model): +class Item(db.Model): __tablename__ = 'item' - VALIDATION_LABEL = 'Item' id: Mapped[int] = mapped_column(Integer, Identity(start=1, increment=1), primary_key=True) description: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True) @@ -24,88 +21,6 @@ class Item(ValidatableMixin, db.Model): def __repr__(self): return f"" - def serialize(self): - return { - 'id': self.id, - 'name': self.description - } - @property def identifier(self): return self.description if self.description else f"Item {self.id}" - - @classmethod - def sync_from_state(cls, submitted_items: list[dict]) -> dict[str, int]: - submitted_clean = [] - seen_ids = set() - temp_id_map = {} - - for item in submitted_items: - if not isinstance(item, dict): - continue - name = str(item.get("name", "")).strip() - raw_id = item.get("id") - - if not name: - continue - - try: - if raw_id: - parsed_id = int(raw_id) - if parsed_id >= 0: - seen_ids.add(parsed_id) - except (ValueError, TypeError): - pass - - submitted_clean.append({"id": raw_id, "description": name}) - - existing_by_id = {t.id: t for t in db.session.query(cls).all()} - existing_ids = set(existing_by_id.keys()) - - for entry in submitted_clean: - submitted_id = entry["id"] - description = entry["description"] - - if is_temp_id(submitted_id): - obj = cls(description=description) - db.session.add(obj) - db.session.flush() - temp_id_map[submitted_id] = obj.id - elif isinstance(submitted_id, int) or submitted_id.isdigit(): - submitted_id_int = int(submitted_id) - obj = existing_by_id.get(submitted_id_int) - if obj and obj.description != description: - obj.description = description - - for id_to_remove in existing_ids - seen_ids: - obj = existing_by_id[id_to_remove] - db.session.delete(obj) - - id_map = { - **{str(i): i for i in seen_ids}, - **{str(temp): real for temp, real in temp_id_map.items()} - } - return id_map - - @classmethod - def validate_state(cls, submitted_items: list[dict]) -> list[str]: - errors = [] - - for index, item in enumerate(submitted_items): - if not isinstance(item, dict): - errors.append(f"Area entry #{index + 1} is not a valid object.") - continue - - name = item.get('name') - if not name or not str(name).strip(): - errors.append(f"Area entry #{index + 1} is missing a name.") - - raw_id = item.get('id') - if raw_id is not None: - try: - _ = int(raw_id) - except (ValueError, TypeError): - if not is_temp_id(raw_id): - errors.append(f"Area entry #{index + 1} has invalid ID: {raw_id}") - - return errors \ No newline at end of file diff --git a/inventory/models/room_functions.py b/inventory/models/room_functions.py index 436d3e7..72c1a1f 100644 --- a/inventory/models/room_functions.py +++ b/inventory/models/room_functions.py @@ -6,12 +6,9 @@ from sqlalchemy import Identity, Integer, Unicode from sqlalchemy.orm import Mapped, mapped_column, relationship from . import db -from ..temp import is_temp_id -from ..utils.validation import ValidatableMixin -class RoomFunction(ValidatableMixin, db.Model): +class RoomFunction(db.Model): __tablename__ = 'room_function' - VALIDATION_LABEL = "Function" id: Mapped[int] = mapped_column(Integer, Identity(start=1, increment=1), primary_key=True) description: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True) @@ -23,62 +20,3 @@ class RoomFunction(ValidatableMixin, db.Model): def __repr__(self): return f"" - - def serialize(self): - return { - 'id': self.id, - 'name': self.description - } - - @classmethod - def sync_from_state(cls, submitted_items: list[dict]) -> dict[str, int]: - submitted_clean = [] - seen_ids = set() - temp_id_map = {} - - for item in submitted_items: - if not isinstance(item, dict): - continue - name = str(item.get("name", "")).strip() - raw_id = item.get("id") - - if not name: - continue - - try: - if raw_id: - parsed_id = int(raw_id) - if parsed_id >= 0: - seen_ids.add(parsed_id) - except (ValueError, TypeError): - pass - - submitted_clean.append({"id": raw_id, "description": name}) - - existing_by_id = {f.id: f for f in db.session.query(cls).all()} - existing_ids = set(existing_by_id.keys()) - - for entry in submitted_clean: - submitted_id = entry.get("id") - description = entry["description"] - - if is_temp_id(submitted_id): - obj = cls(description=description) - db.session.add(obj) - db.session.flush() - temp_id_map[submitted_id] = obj.id - elif isinstance(submitted_id, int) or submitted_id.isdigit(): - submitted_id_int = int(submitted_id) - obj = existing_by_id.get(submitted_id_int) - if obj and obj.description != description: - obj.description = description - - for id_to_remove in existing_ids - seen_ids: - obj = existing_by_id[id_to_remove] - db.session.delete(obj) - - id_map = { - **{str(i): i for i in seen_ids}, - **{str(temp): real for temp, real in temp_id_map.items()} - } - return id_map diff --git a/inventory/models/rooms.py b/inventory/models/rooms.py index 4081e87..cd9b7b2 100644 --- a/inventory/models/rooms.py +++ b/inventory/models/rooms.py @@ -10,11 +10,8 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship, joinedload, sele from . import db -from ..utils.validation import ValidatableMixin - -class Room(ValidatableMixin, db.Model): +class Room(db.Model): __tablename__ = 'rooms' - VALIDATION_LABEL = "Room" id: Mapped[int] = mapped_column(Integer, Identity(start=1, increment=1), primary_key=True) name: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True) @@ -56,148 +53,6 @@ class Room(ValidatableMixin, db.Model): func = self.room_function.description if self.room_function else "" return f"{name} - {func}".strip(" -") - def serialize(self): - return { - 'id': self.id, - 'name': self.name, - 'area_id': self.area_id, - 'function_id': self.function_id - } - - @classmethod - def sync_from_state(cls, submitted_rooms: list[dict], section_map: dict[str, int], function_map: dict[str, int]) -> None: - """ - Syncs the Rooms table with the submitted room list. - Resolves foreign keys using section_map and function_map. - Supports add, update, and delete. - """ - def resolve_fk(key, fk_map, label): - if key is None: - return None - key = str(key) - if key.startswith("temp") or not key.isdigit(): - if key in fk_map: - return fk_map[key] - raise ValueError(f"Unable to resolve {label} ID: {key}") - return int(key) # It's already a real ID - - submitted_clean = [] - seen_ids = set() - - for room in submitted_rooms: - if not isinstance(room, dict): - continue - name = str(room.get("name", "")).strip() - if not name: - continue - - rid = room.get("id") - area_id = room.get("area_id") - function_id = room.get("function_id") - - submitted_clean.append({ - "id": rid, - "name": name, - "area_id": area_id, - "function_id": function_id - }) - - if rid and not str(rid).startswith("room-"): - try: - seen_ids.add(int(rid)) - except ValueError: - pass # Not valid? Not seen. - - existing_query = db.session.query(cls) - existing_by_id = {room.id: room for room in existing_query.all()} - existing_ids = set(existing_by_id.keys()) - - for entry in submitted_clean: - rid = entry.get("id") - name = entry["name"] - - resolved_area_id = resolve_fk(entry.get("area_id"), section_map, "section") - resolved_function_id = resolve_fk(entry.get("function_id"), function_map, "function") - - if not rid or str(rid).startswith("room-"): - new_room = cls(name=name, area_id=resolved_area_id, function_id=resolved_function_id) - db.session.add(new_room) - else: - try: - rid_int = int(rid) - except ValueError: - print(f"⚠️ Invalid room ID format: {rid}") - continue - - room = existing_by_id.get(rid_int) - if not room: - print(f"⚠️ No matching room in DB for ID: {rid_int}") - continue - - if room.name != name: - room.name = name - if room.area_id != resolved_area_id: - room.area_id = resolved_area_id - if room.function_id != resolved_function_id: - room.function_id = resolved_function_id - - for existing_id in existing_ids - seen_ids: - room = existing_by_id.get(existing_id) - if not room: - continue - - # Skip if a newly added room matches this one — likely duplicate - if any( - r["name"] == room.name and - resolve_fk(r["area_id"], section_map, "section") == room.area_id and - resolve_fk(r["function_id"], function_map, "function") == room.function_id - for r in submitted_clean - if r.get("id") is None or str(r.get("id")).startswith("room-") - ): - print(f"⚠️ Skipping deletion of likely duplicate: {room}") - continue - - db.session.delete(room) - - @classmethod - def validate_state(cls, submitted_items: list[dict]) -> list[str]: - errors = [] - - for index, item in enumerate(submitted_items): - label = f"Room #{index + 1}" - - if not isinstance(item, dict): - errors.append(f"{label} is not a valid object.") - continue - - name = item.get("name") - if not name or not str(name).strip(): - errors.append(f"{label} is missing a name.") - - raw_id = item.get("id") - if raw_id is not None: - try: - _ = int(raw_id) - except (ValueError, TypeError): - if not str(raw_id).startswith("room-"): - errors.append(f"{label} has an invalid ID: {raw_id}") - - # These fields are FK IDs, so we're just checking for valid formats here. - for fk_field, fk_label in [("area_id", "Section"), ("function_id", "Function")]: - fk_val = item.get(fk_field) - - if fk_val is None: - continue # Let the DB enforce nullability - - try: - _ = int(fk_val) - except (ValueError, TypeError): - fk_val_str = str(fk_val) - if not fk_val_str.startswith("temp-"): - errors.append(f"{label} has invalid {fk_label} ID: {fk_val}") - - return errors - Room.ui_eagerload = ( joinedload(Room.area), joinedload(Room.room_function), diff --git a/inventory/routes/helpers.py b/inventory/routes/helpers.py index d2bdb18..afb4ccb 100644 --- a/inventory/routes/helpers.py +++ b/inventory/routes/helpers.py @@ -112,7 +112,6 @@ def get_image_attachable_class_by_name(name: str): return cls return None - def make_csv(export_func, columns, rows): output = io.StringIO() writer = csv.writer(output) diff --git a/inventory/routes/hooks.py b/inventory/routes/hooks.py index d4e4f5c..41fbf9f 100644 --- a/inventory/routes/hooks.py +++ b/inventory/routes/hooks.py @@ -1,6 +1,5 @@ from bs4 import BeautifulSoup from flask import current_app as app -import re from . import main diff --git a/inventory/routes/inventory.py b/inventory/routes/inventory.py index 56d4351..79b3f79 100644 --- a/inventory/routes/inventory.py +++ b/inventory/routes/inventory.py @@ -1,10 +1,5 @@ -import io -import csv -import base64 - import datetime -from flask import request, render_template, url_for, jsonify -from sqlalchemy.inspection import inspect +from flask import request, render_template, jsonify from . import main from .helpers import FILTER_MAP, inventory_headers, worklog_headers, make_csv diff --git a/inventory/routes/settings.py b/inventory/routes/settings.py index 00cc98c..d257da4 100644 --- a/inventory/routes/settings.py +++ b/inventory/routes/settings.py @@ -1,109 +1,15 @@ -import json -import traceback - -from flask import request, flash, redirect, url_for, render_template, jsonify +from flask import render_template from . import main from .. import db -from ..models import Brand, Item, Area, RoomFunction, Room, Image -from ..utils.load import eager_load_room_relationships, chunk_list +from ..models import Image +from ..utils.load import chunk_list -@main.route('/settings', methods=['GET', 'POST']) +@main.route('/settings') def settings(): - if request.method == 'POST': - form = request.form - - try: - state = json.loads(form['formState']) - except Exception: - flash("Invalid form state submitted. JSON decode failed.", "danger") - traceback.print_exc() - return redirect(url_for('main.settings')) - - try: - with db.session.begin(): - # Sync each table and grab temp ID maps - brand_map = Brand.sync_from_state(state.get("brands", [])) - type_map = Item.sync_from_state(state.get("types", [])) - section_map = Area.sync_from_state(state.get("sections", [])) - function_map = RoomFunction.sync_from_state(state.get("functions", [])) - - # Fix up room foreign keys based on real IDs - submitted_rooms = [] - for room in state.get("rooms", []): - room = dict(room) # shallow copy - sid = room.get("area_id") - fid = room.get("function_id") - - if sid is not None: - sid_key = str(sid) - if sid_key in section_map: - room["area_id"] = section_map[sid_key] - - if fid is not None: - fid_key = str(fid) - if fid_key in function_map: - room["function_id"] = function_map[fid_key] - - submitted_rooms.append(room) - - Room.sync_from_state( - submitted_rooms=submitted_rooms, - section_map=section_map, - function_map=function_map - ) - - flash("Changes saved.", "success") - - except Exception as e: - flash(f"Error saving changes: {e}", "danger") - - return redirect(url_for('main.settings')) - - # === GET === - brands = db.session.query(Brand).order_by(Brand.name).all() - types = db.session.query(Item).order_by(Item.description).all() - sections = db.session.query(Area).order_by(Area.name).all() - functions = db.session.query(RoomFunction).order_by(RoomFunction.description).all() - rooms = eager_load_room_relationships(db.session.query(Room).order_by(Room.name)).all() images = chunk_list(db.session.query(Image).order_by(Image.timestamp).all(), 6) return render_template('settings.html', title="Settings", - brands=[b.serialize() for b in brands], - types=[{"id": t.id, "name": t.description} for t in types], - sections=[s.serialize() for s in sections], - functions=[f.serialize() for f in functions], - rooms=[r.serialize() for r in rooms], image_list=images ) - -@main.route("/api/settings", methods=["POST"]) -def api_settings(): - try: - payload = request.get_json(force=True) - except Exception as e: - return jsonify({"error": "Invalid JSON"}), 400 - - errors = [] - errors += Brand.validate_state(payload.get("brands", [])) - errors += Item.validate_state(payload.get("types", [])) - errors += Area.validate_state(payload.get("sections", [])) - errors += RoomFunction.validate_state(payload.get("functions", [])) - errors += Room.validate_state(payload.get("rooms", [])) - - if errors: - return jsonify({"errors": errors}), 400 - - try: - with db.session.begin(): - section_map = Area.sync_from_state(payload["sections"]) - function_map = RoomFunction.sync_from_state(payload["functions"]) - Brand.sync_from_state(payload["brands"]) - Item.sync_from_state(payload["types"]) - Room.sync_from_state(payload["rooms"], section_map, function_map) - except Exception as e: - db.session.rollback() - return jsonify({"errors": [str(e)]}), 500 - - return jsonify({"message": "Settings updated successfully."}), 200 diff --git a/inventory/temp.py b/inventory/temp.py deleted file mode 100644 index 64394ab..0000000 --- a/inventory/temp.py +++ /dev/null @@ -1,6 +0,0 @@ -def is_temp_id(val): - return ( - val is None or - (isinstance(val, int) and val < 0) or - (isinstance(val, str) and val.startswith("temp-")) - ) diff --git a/inventory/utils/validation.py b/inventory/utils/validation.py deleted file mode 100644 index c68e3f9..0000000 --- a/inventory/utils/validation.py +++ /dev/null @@ -1,28 +0,0 @@ -from ..temp import is_temp_id - -class ValidatableMixin: - VALIDATION_LABEL = "entry" - - @classmethod - def validate_state(cls, submitted_items: list[dict]) -> list[str]: - errors = [] - label = cls.VALIDATION_LABEL or cls.__name__ - - for index, item in enumerate(submitted_items): - if not isinstance(item, dict): - errors.append(f"{label.capitalize()} #{index + 1} is not a valid object.") - continue - - name = item.get("name") - if not name or not str(name).strip(): - errors.append(f"{label.capitalize()} #{index + 1} is missing a name.") - - raw_id = item.get('id') - if raw_id is not None: - try: - _ = int(raw_id) - except (ValueError, TypeError): - if not is_temp_id(raw_id): - errors.append(f"{label.capitalize()} #{index + 1} has invalid ID: {raw_id}") - - return errors \ No newline at end of file