Adding some enhancements.

This commit is contained in:
Yaro Kasear 2025-09-11 10:47:44 -05:00
parent 013d1c0bd5
commit a8b07eb115
4 changed files with 24 additions and 9 deletions

View file

@ -1,5 +1,6 @@
from typing import Type, TypeVar, Generic, Optional from typing import Type, TypeVar, Generic, Optional
from sqlalchemy.orm import Load, Session, raiseload, with_polymorphic from sqlalchemy.orm import Load, Session, raiseload, with_polymorphic
from sqlalchemy.orm.attributes import InstrumentedAttribute
from sqlalchemy import inspect, text from sqlalchemy import inspect, text
from crudkit.core.base import Version from crudkit.core.base import Version
from crudkit.core.spec import CRUDSpec from crudkit.core.spec import CRUDSpec
@ -55,6 +56,7 @@ class CRUDService(Generic[T]):
query, root_alias = self.get_query() query, root_alias = self.get_query()
root_fields = [] root_fields = []
root_field_names = {}
rel_field_names = {} rel_field_names = {}
if params: if params:
@ -77,10 +79,11 @@ class CRUDService(Generic[T]):
) )
if params: if params:
root_fields, rel_field_names = spec.parse_fields() root_fields, rel_field_names, root_field_names = spec.parse_fields()
if root_fields: only_cols = [c for c in root_fields if isinstance(c, InstrumentedAttribute)]
query = query.options(Load(root_alias).load_only(*root_fields)) if only_cols:
query = query.options(Load(root_alias).load_only(*only_cols))
for eager in spec.get_eager_loads(root_alias, fields_map=rel_field_names): for eager in spec.get_eager_loads(root_alias, fields_map=rel_field_names):
query = query.options(eager) query = query.options(eager)
@ -105,6 +108,8 @@ class CRUDService(Generic[T]):
rows = query.all() rows = query.all()
proj = [] proj = []
if root_field_names:
proj.extend(root_field_names)
if root_fields: if root_fields:
proj.extend(c.key for c in root_fields) proj.extend(c.key for c in root_fields)
for path, names in (rel_field_names or {}).items(): for path, names in (rel_field_names or {}).items():

View file

@ -121,7 +121,7 @@ class CRUDSpec:
""" """
raw = self.params.get('fields') raw = self.params.get('fields')
if not raw: if not raw:
return [], {} return [], {}, {}
if isinstance(raw, list): if isinstance(raw, list):
tokens = [] tokens = []
@ -131,6 +131,7 @@ class CRUDSpec:
tokens = [p.strip() for p in str(raw).split(',') if p.strip()] tokens = [p.strip() for p in str(raw).split(',') if p.strip()]
root_fields: List[InstrumentedAttribute] = [] root_fields: List[InstrumentedAttribute] = []
root_field_names: list[str] = []
rel_field_names: Dict[Tuple[str, ...], List[str]] = {} rel_field_names: Dict[Tuple[str, ...], List[str]] = {}
for token in tokens: for token in tokens:
@ -142,6 +143,7 @@ class CRUDSpec:
self.eager_paths.add(join_path) self.eager_paths.add(join_path)
else: else:
root_fields.append(col) root_fields.append(col)
root_field_names.append(getattr(col, "key", token))
seen = set() seen = set()
root_fields = [c for c in root_fields if not (c.key in seen or seen.add(c.key))] root_fields = [c for c in root_fields if not (c.key in seen or seen.add(c.key))]
@ -151,7 +153,7 @@ class CRUDSpec:
self._root_fields = root_fields self._root_fields = root_fields
self._rel_field_names = rel_field_names self._rel_field_names = rel_field_names
return root_fields, rel_field_names return root_fields, rel_field_names, root_field_names
def get_eager_loads(self, root_alias, *, fields_map=None): def get_eager_loads(self, root_alias, *, fields_map=None):
loads = [] loads = []

View file

@ -1,6 +1,7 @@
from typing import List, Optional, TYPE_CHECKING from typing import List, Optional, TYPE_CHECKING
from sqlalchemy import Boolean, Integer, ForeignKey, Unicode from sqlalchemy import Boolean, Integer, ForeignKey, Unicode, func
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
@ -37,3 +38,11 @@ class User(Base, CRUDMixin):
def __repr__(self): def __repr__(self):
return f"<User(id={self.id}, first_name={repr(self.first_name)}, last_name={repr(self.last_name)})>" return f"<User(id={self.id}, first_name={repr(self.first_name)}, last_name={repr(self.last_name)})>"
@hybrid_property
def label(self):
return f"{self.first_name} {self.last_name}"
@label.expression
def label(cls):
return func.concat(cls.first_name, " ", cls.last_name)

View file

@ -20,8 +20,7 @@ def init_index_routes(app):
"complete__ne": 1, "complete__ne": 1,
"fields": [ "fields": [
"start_time", "start_time",
"contact.last_name", "contact.label",
"contact.first_name",
"work_item.label", "work_item.label",
"work_item.device_type.description" "work_item.device_type.description"
], ],
@ -30,7 +29,7 @@ def init_index_routes(app):
columns = [ columns = [
{"field": "start_time", "label": "Start", "format": "date"}, {"field": "start_time", "label": "Start", "format": "date"},
{"field": "contact.last_name", "label": "Contact", {"field": "contact.label", "label": "Contact",
"link": {"endpoint": "user.get_item", "params": {"id": "{contact.id}"}}}, "link": {"endpoint": "user.get_item", "params": {"id": "{contact.id}"}}},
{"field": "work_item.label", "label": "Work Item", {"field": "work_item.label", "label": "Work Item",
"link": {"endpoint": "inventory.get_item", "params": {"id": "{work_item.id}"}}}, "link": {"endpoint": "inventory.get_item", "params": {"id": "{work_item.id}"}}},