Soem crudkit fixes aimed at eliminating session sharing.
This commit is contained in:
parent
d045a1a05f
commit
cf795086f1
10 changed files with 107 additions and 47 deletions
|
|
@ -123,6 +123,7 @@ def build_database_url(
|
||||||
"Trusted_Connection": "yes",
|
"Trusted_Connection": "yes",
|
||||||
"Encrypt": "yes",
|
"Encrypt": "yes",
|
||||||
"TrustServerCertificate": "yes",
|
"TrustServerCertificate": "yes",
|
||||||
|
"MARS_Connection": "yes",
|
||||||
}
|
}
|
||||||
base_opts.update(options)
|
base_opts.update(options)
|
||||||
qs = "?" + "&".join(f"{k}={quote_plus(v)}" for k, v in base_opts.items())
|
qs = "?" + "&".join(f"{k}={quote_plus(v)}" for k, v in base_opts.items())
|
||||||
|
|
@ -135,6 +136,7 @@ def build_database_url(
|
||||||
"driver": driver,
|
"driver": driver,
|
||||||
"Encrypt": "yes",
|
"Encrypt": "yes",
|
||||||
"TrustServerCertificate": "yes",
|
"TrustServerCertificate": "yes",
|
||||||
|
"MARS_Connection": "yes",
|
||||||
}
|
}
|
||||||
base_opts.update(options)
|
base_opts.update(options)
|
||||||
qs = "?" + "&".join(f"{k}={quote_plus(v)}" for k, v in base_opts.items())
|
qs = "?" + "&".join(f"{k}={quote_plus(v)}" for k, v in base_opts.items())
|
||||||
|
|
|
||||||
|
|
@ -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 import Load, Session, raiseload, with_polymorphic, Mapper
|
||||||
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
||||||
from sqlalchemy.orm.util import AliasedClass
|
from sqlalchemy.orm.util import AliasedClass
|
||||||
|
|
@ -37,13 +37,13 @@ class CRUDService(Generic[T]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
model: Type[T],
|
model: Type[T],
|
||||||
session: Session,
|
session_factory: Callable[[], Session],
|
||||||
polymorphic: bool = False,
|
polymorphic: bool = False,
|
||||||
*,
|
*,
|
||||||
backend: Optional[BackendInfo] = None
|
backend: Optional[BackendInfo] = None
|
||||||
):
|
):
|
||||||
self.model = model
|
self.model = model
|
||||||
self.session = session
|
self._session_factory = session_factory
|
||||||
self.polymorphic = polymorphic
|
self.polymorphic = polymorphic
|
||||||
self.supports_soft_delete = hasattr(model, 'is_deleted')
|
self.supports_soft_delete = hasattr(model, 'is_deleted')
|
||||||
# Cache backend info once. If not provided, derive from session bind.
|
# 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)
|
eng: Engine = bind.engine if isinstance(bind, Connection) else cast(Engine, bind)
|
||||||
self.backend = backend or make_backend_info(eng)
|
self.backend = backend or make_backend_info(eng)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def session(self) -> Session:
|
||||||
|
return self._session_factory()
|
||||||
|
|
||||||
def get_query(self):
|
def get_query(self):
|
||||||
if self.polymorphic:
|
if self.polymorphic:
|
||||||
poly = with_polymorphic(self.model, "*")
|
poly = with_polymorphic(self.model, "*")
|
||||||
|
|
@ -69,7 +73,6 @@ class CRUDService(Generic[T]):
|
||||||
return cols or [text("1")]
|
return cols or [text("1")]
|
||||||
|
|
||||||
def get(self, id: int, params=None) -> T | None:
|
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()
|
query, root_alias = self.get_query()
|
||||||
|
|
||||||
include_deleted = False
|
include_deleted = False
|
||||||
|
|
|
||||||
|
|
@ -71,10 +71,10 @@ class CRUDRegistry:
|
||||||
SessionMaker = self._rt.session_factory
|
SessionMaker = self._rt.session_factory
|
||||||
if SessionMaker is None:
|
if SessionMaker is None:
|
||||||
raise RuntimeError("CRUDKitRuntime.session_factory is not initialized.")
|
raise RuntimeError("CRUDKitRuntime.session_factory is not initialized.")
|
||||||
session: Session = SessionMaker()
|
|
||||||
svc = CRUDService(
|
svc = CRUDService(
|
||||||
model,
|
model,
|
||||||
session=session,
|
session_factory=SessionMaker,
|
||||||
polymorphic=polymorphic,
|
polymorphic=polymorphic,
|
||||||
backend=self._rt.backend,
|
backend=self._rt.backend,
|
||||||
**(service_kwargs or {}),
|
**(service_kwargs or {}),
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
from typing import List, Optional, TYPE_CHECKING
|
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.ext.hybrid import hybrid_property
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
from sqlalchemy.sql import expression as sql
|
from sqlalchemy.sql import expression as sql
|
||||||
|
|
||||||
from crudkit.core.base import Base, CRUDMixin
|
from crudkit.core.base import Base, CRUDMixin
|
||||||
|
|
||||||
|
from . import RoomFunction
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .user import User
|
from .user import User
|
||||||
|
|
||||||
|
|
@ -32,4 +34,14 @@ class Room(Base, CRUDMixin):
|
||||||
|
|
||||||
@hybrid_property
|
@hybrid_property
|
||||||
def label(self):
|
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(""))
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -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 pathlib import Path
|
||||||
from sqlalchemy import func, select
|
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from crudkit.core.service import CRUDService
|
import crudkit
|
||||||
from crudkit.core.spec import CRUDSpec
|
|
||||||
from crudkit.ui.fragments import render_table
|
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.inventory import Inventory
|
||||||
from ..models.work_log import WorkLog
|
from ..models.work_log import WorkLog
|
||||||
|
|
||||||
|
|
@ -18,9 +15,8 @@ bp_index = Blueprint("index", __name__)
|
||||||
def init_index_routes(app):
|
def init_index_routes(app):
|
||||||
@bp_index.get("/")
|
@bp_index.get("/")
|
||||||
def index():
|
def index():
|
||||||
session = get_session()
|
inventory_service = crudkit.crud.get_service(Inventory)
|
||||||
inventory_service = CRUDService(Inventory, session)
|
work_log_service = crudkit.crud.get_service(WorkLog)
|
||||||
work_log_service = CRUDService(WorkLog, session)
|
|
||||||
work_logs = work_log_service.list({
|
work_logs = work_log_service.list({
|
||||||
"complete__ne": 1,
|
"complete__ne": 1,
|
||||||
"fields": [
|
"fields": [
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import Blueprint, render_template, abort
|
from flask import Blueprint, render_template, abort, request
|
||||||
|
|
||||||
import crudkit
|
import crudkit
|
||||||
|
|
||||||
|
|
@ -9,25 +9,65 @@ bp_listing = Blueprint("listing", __name__)
|
||||||
def init_listing_routes(app):
|
def init_listing_routes(app):
|
||||||
@bp_listing.get("/listing/<model>")
|
@bp_listing.get("/listing/<model>")
|
||||||
def show_list(model):
|
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)
|
cls = crudkit.crud.get_model(model)
|
||||||
if cls is None:
|
if cls is None:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
spec = {}
|
spec = {}
|
||||||
|
columns = []
|
||||||
if model.lower() == 'inventory':
|
if model.lower() == 'inventory':
|
||||||
spec = {"fields": [
|
spec = {"fields": [
|
||||||
"label",
|
"label",
|
||||||
"name",
|
"name",
|
||||||
"barcode",
|
"barcode",
|
||||||
"serial",
|
"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)
|
service = crudkit.crud.get_service(cls)
|
||||||
rows = service.list(spec)
|
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)
|
return render_template("listing.html", model=model, table=table)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
{% block premain %}
|
{% block premain %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<main class="container mt-3">
|
<main class="container-fluid mt-3">
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,28 @@
|
||||||
<table class="table table-light table-striped table-hover table-bordered border-tertiary text-nowrap overflow-x-auto">
|
<div class="table-responsive mx-5" style="max-height: 80vh;">
|
||||||
<thead>
|
<table class="table table-light table-striped table-hover table-bordered border-tertiary text-nowrap overflow-x-auto mx-auto">
|
||||||
<tr>
|
<thead>
|
||||||
{% for col in columns %}
|
<tr>
|
||||||
|
{% for col in columns %}
|
||||||
<th>{{ col.label }}</th>
|
<th>{{ col.label }}</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% if rows %}
|
{% if rows %}
|
||||||
{% for row in rows %}
|
{% for row in rows %}
|
||||||
<tr onclick="location.href='{{ url_for( 'crudkit.' + kwargs['opts']['object_class'] + '.get_item', id=row.id) }}'" style="cursor: pointer;">
|
<tr onclick="location.href='{{ url_for( 'crudkit.' + kwargs['opts']['object_class'] + '.get_item', id=row.id) }}'" style="cursor: pointer;">
|
||||||
{% for cell in row.cells %}
|
{% for cell in row.cells %}
|
||||||
{% if cell.href %}
|
{% if cell.href %}
|
||||||
<td class="{{ cell.class or '' }}"><a href="{{ cell.href }}" class="link-success link-underline link-underline-opacity-0 fw-semibold">{{ cell.text if cell.text is not none else '-' }}</a></td>
|
<td class="{{ cell.class or '' }}"><a href="{{ cell.href }}" class="link-success link-underline link-underline-opacity-0 fw-semibold">{{ cell.text if cell.text is not none else '-' }}</a></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td class="{{ cell.class or '' }}">{{ cell.text if cell.text is not none else '-' }}</td>
|
<td class="{{ cell.class or '' }}">{{ cell.text if cell.text is not none else '-' }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr><td colspan="{{ columns|length }}">No data.</td></tr>
|
<tr><td colspan="{{ columns|length }}">No data.</td></tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@
|
||||||
<h1 class="display-2 text-center">{{ title or "Inventory Manager" }}</h1>
|
<h1 class="display-2 text-center">{{ title or "Inventory Manager" }}</h1>
|
||||||
<p class="lead text-center">Find out about all of your assets.</p>
|
<p class="lead text-center">Find out about all of your assets.</p>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row mx-5">
|
||||||
<div class="col pivot-cell">
|
<div class="col pivot-cell ms-5">
|
||||||
<p class="display-6 text-center">Active Worklogs</p>
|
<p class="display-6 text-center">Active Worklogs</p>
|
||||||
{{ logs | safe }}
|
{{ logs | safe }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col pivot-cell">
|
<div class="col pivot-cell me-5">
|
||||||
<p class="display-6 text-center">Inventory Report</p>
|
<p class="display-6 text-center">Inventory Report</p>
|
||||||
|
|
||||||
<div class="d-flex flex-wrap gap-2 align-items-end mb-2">
|
<div class="d-flex flex-wrap gap-2 align-items-end mb-2">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Inventory Manager - {{ model|title }} Listing
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
<h1 class="display-4 text-center mt-5">{{ model|title }} Listing</h1>
|
||||||
{{ table | safe }}
|
{{ table | safe }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue