From cf795086f1c2885451bd4795b8a2c35c53cd61f6 Mon Sep 17 00:00:00 2001 From: Yaro Kasear Date: Mon, 15 Sep 2025 11:51:56 -0500 Subject: [PATCH] Soem crudkit fixes aimed at eliminating session sharing. --- crudkit/config.py | 2 ++ crudkit/core/service.py | 11 +++--- crudkit/registry.py | 4 +-- inventory/models/room.py | 16 +++++++-- inventory/routes/index.py | 14 +++----- inventory/routes/listing.py | 48 +++++++++++++++++++++++--- inventory/templates/base.html | 2 +- inventory/templates/crudkit/table.html | 46 ++++++++++++------------ inventory/templates/index.html | 6 ++-- inventory/templates/listing.html | 5 +++ 10 files changed, 107 insertions(+), 47 deletions(-) diff --git a/crudkit/config.py b/crudkit/config.py index a19d4d3..0439a3e 100644 --- a/crudkit/config.py +++ b/crudkit/config.py @@ -123,6 +123,7 @@ def build_database_url( "Trusted_Connection": "yes", "Encrypt": "yes", "TrustServerCertificate": "yes", + "MARS_Connection": "yes", } base_opts.update(options) qs = "?" + "&".join(f"{k}={quote_plus(v)}" for k, v in base_opts.items()) @@ -135,6 +136,7 @@ def build_database_url( "driver": driver, "Encrypt": "yes", "TrustServerCertificate": "yes", + "MARS_Connection": "yes", } base_opts.update(options) qs = "?" + "&".join(f"{k}={quote_plus(v)}" for k, v in base_opts.items()) diff --git a/crudkit/core/service.py b/crudkit/core/service.py index ed0063a..6c0afd6 100644 --- a/crudkit/core/service.py +++ b/crudkit/core/service.py @@ -1,4 +1,4 @@ -from typing import Any, Type, TypeVar, Generic, Optional, Protocol, runtime_checkable, cast +from typing import Any, Callable, Type, TypeVar, Generic, Optional, Protocol, runtime_checkable, cast from sqlalchemy.orm import Load, Session, raiseload, with_polymorphic, Mapper from sqlalchemy.orm.attributes import InstrumentedAttribute from sqlalchemy.orm.util import AliasedClass @@ -37,13 +37,13 @@ class CRUDService(Generic[T]): def __init__( self, model: Type[T], - session: Session, + session_factory: Callable[[], Session], polymorphic: bool = False, *, backend: Optional[BackendInfo] = None ): self.model = model - self.session = session + self._session_factory = session_factory self.polymorphic = polymorphic self.supports_soft_delete = hasattr(model, 'is_deleted') # Cache backend info once. If not provided, derive from session bind. @@ -51,6 +51,10 @@ class CRUDService(Generic[T]): eng: Engine = bind.engine if isinstance(bind, Connection) else cast(Engine, bind) self.backend = backend or make_backend_info(eng) + @property + def session(self) -> Session: + return self._session_factory() + def get_query(self): if self.polymorphic: poly = with_polymorphic(self.model, "*") @@ -69,7 +73,6 @@ class CRUDService(Generic[T]): return cols or [text("1")] def get(self, id: int, params=None) -> T | None: - print(f"I AM GETTING A THING! A THINGS! {params}") query, root_alias = self.get_query() include_deleted = False diff --git a/crudkit/registry.py b/crudkit/registry.py index 9c66153..fe3bfb7 100644 --- a/crudkit/registry.py +++ b/crudkit/registry.py @@ -71,10 +71,10 @@ class CRUDRegistry: SessionMaker = self._rt.session_factory if SessionMaker is None: raise RuntimeError("CRUDKitRuntime.session_factory is not initialized.") - session: Session = SessionMaker() + svc = CRUDService( model, - session=session, + session_factory=SessionMaker, polymorphic=polymorphic, backend=self._rt.backend, **(service_kwargs or {}), diff --git a/inventory/models/room.py b/inventory/models/room.py index e8cec2f..5ed3d14 100644 --- a/inventory/models/room.py +++ b/inventory/models/room.py @@ -1,12 +1,14 @@ from typing import List, Optional, TYPE_CHECKING -from sqlalchemy import Boolean, ForeignKey, Integer, Unicode +from sqlalchemy import Boolean, ForeignKey, Integer, Unicode, func, case, literal from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.sql import expression as sql from crudkit.core.base import Base, CRUDMixin +from . import RoomFunction + if TYPE_CHECKING: from .user import User @@ -32,4 +34,14 @@ class Room(Base, CRUDMixin): @hybrid_property def label(self): - return f"{self.name} - {self.room_function.description}" + name = self.name or "Unassigned" + desc = self.room_function.description if (self.room_function and self.room_function.description) else None + return f"{name} - {desc}" + + @label.expression + def label(cls): + return func.concat( + func.coalesce(cls.name, literal("Unassigned")), + case((cls.function_id.isnot(None), literal(" - ")), else_=literal("")), + func.coalesce(RoomFunction.description, literal("")) + ) diff --git a/inventory/routes/index.py b/inventory/routes/index.py index 1d7c83f..87f4b91 100644 --- a/inventory/routes/index.py +++ b/inventory/routes/index.py @@ -1,15 +1,12 @@ -from flask import Blueprint, current_app, jsonify, render_template, request, send_file +from flask import Blueprint, current_app, render_template, send_file from pathlib import Path -from sqlalchemy import func, select import pandas as pd -from crudkit.core.service import CRUDService -from crudkit.core.spec import CRUDSpec +import crudkit + from crudkit.ui.fragments import render_table -from ..db import get_session -from ..models.device_type import DeviceType from ..models.inventory import Inventory from ..models.work_log import WorkLog @@ -18,9 +15,8 @@ bp_index = Blueprint("index", __name__) def init_index_routes(app): @bp_index.get("/") def index(): - session = get_session() - inventory_service = CRUDService(Inventory, session) - work_log_service = CRUDService(WorkLog, session) + inventory_service = crudkit.crud.get_service(Inventory) + work_log_service = crudkit.crud.get_service(WorkLog) work_logs = work_log_service.list({ "complete__ne": 1, "fields": [ diff --git a/inventory/routes/listing.py b/inventory/routes/listing.py index 19daec2..eed1ad2 100644 --- a/inventory/routes/listing.py +++ b/inventory/routes/listing.py @@ -1,4 +1,4 @@ -from flask import Blueprint, render_template, abort +from flask import Blueprint, render_template, abort, request import crudkit @@ -9,25 +9,65 @@ bp_listing = Blueprint("listing", __name__) def init_listing_routes(app): @bp_listing.get("/listing/") def show_list(model): + page_num = int(request.args.get("page", 1)) + if model.lower() not in {"inventory", "user", "worklog"}: + abort(404) + cls = crudkit.crud.get_model(model) if cls is None: abort(404) spec = {} + columns = [] if model.lower() == 'inventory': spec = {"fields": [ "label", "name", "barcode", "serial", - "device_type.description" + "brand.name", + "model", + "device_type.description", + "condition", + "owner.label", + "location.label", ]} - spec["limit"] = 0 + columns = [ + {"field": "label"}, + {"field": "name"}, + {"field": "barcode", "label": "Barcode #"}, + {"field": "serial", "label": "Serial #"}, + {"field": "brand.name", "label": "Brand"}, + {"field": "model"}, + {"field": "device_type.description", "label": "Device Type"}, + {"field": "condition"}, + {"field": "owner.label", "label": "Contact", "link": {"endpoint": "user.get_item", "params": {"id": "{owner.id}"}}}, + {"field": "location.label", "label": "Room"}, + ] + if model.lower() == 'user': + spec = {"fields": [ + "label", + "last_name", + "first_name", + "supervisor.label", + "staff", + "active", + ]} + columns = [ + {"field": "label", "label": "Full Name"}, + {"field": "last_name"}, + {"field": "first_name"}, + {"field": "supervisor.label", "label": "Supervisor", "link": {"endpoint": "user.get_item", "params": {"id": "{supervisor.id}"}}}, + {"field": "staff"}, + {"field": "active"}, + ] + spec["limit"] = 15 + spec["offset"] = (page_num - 1) * 15 service = crudkit.crud.get_service(cls) rows = service.list(spec) - table = render_table(rows, opts={"object_class": model}) + table = render_table(rows, columns=columns, opts={"object_class": model}) return render_template("listing.html", model=model, table=table) diff --git a/inventory/templates/base.html b/inventory/templates/base.html index 0018ae1..4eaa349 100644 --- a/inventory/templates/base.html +++ b/inventory/templates/base.html @@ -29,7 +29,7 @@ {% block premain %} {% endblock %} -
+
{% block main %} {% endblock %}
diff --git a/inventory/templates/crudkit/table.html b/inventory/templates/crudkit/table.html index 771c661..f546163 100644 --- a/inventory/templates/crudkit/table.html +++ b/inventory/templates/crudkit/table.html @@ -1,26 +1,28 @@ - - - - {% for col in columns %} +
+
+ + + {% for col in columns %} - {% endfor %} - - - - {% if rows %} + {% endfor %} + + + + {% if rows %} {% for row in rows %} - - {% for cell in row.cells %} - {% if cell.href %} - - {% else %} - - {% endif %} - {% endfor %} - + + {% for cell in row.cells %} + {% if cell.href %} + + {% else %} + + {% endif %} + {% endfor %} + {% endfor %} - {% else %} + {% else %} - {% endif %} - -
{{ col.label }}
{{ cell.text if cell.text is not none else '-' }}{{ cell.text if cell.text is not none else '-' }}
{{ cell.text if cell.text is not none else '-' }}{{ cell.text if cell.text is not none else '-' }}
No data.
+ {% endif %} + + + diff --git a/inventory/templates/index.html b/inventory/templates/index.html index c3bc9fc..b6c429c 100644 --- a/inventory/templates/index.html +++ b/inventory/templates/index.html @@ -14,13 +14,13 @@

{{ title or "Inventory Manager" }}

Find out about all of your assets.

-
-
+
+

Active Worklogs

{{ logs | safe }}
-
+

Inventory Report

diff --git a/inventory/templates/listing.html b/inventory/templates/listing.html index 2d23558..2ed75cf 100644 --- a/inventory/templates/listing.html +++ b/inventory/templates/listing.html @@ -1,5 +1,10 @@ {% extends 'base.html' %} +{% block title %} +Inventory Manager - {{ model|title }} Listing +{% endblock %} + {% block main %} +

{{ model|title }} Listing

{{ table | safe }} {% endblock %} \ No newline at end of file