Getting hybrid property support.
This commit is contained in:
parent
b68dbfc7ae
commit
506713c748
3 changed files with 39 additions and 15 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
from typing import List, Tuple, Set, Dict, Optional
|
from typing import List, Tuple, Set, Dict, Optional
|
||||||
from sqlalchemy import asc, desc
|
from sqlalchemy import asc, desc
|
||||||
|
from sqlalchemy.ext.hybrid import hybrid_property
|
||||||
from sqlalchemy.orm import aliased, selectinload
|
from sqlalchemy.orm import aliased, selectinload
|
||||||
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
||||||
|
|
||||||
|
|
@ -48,7 +49,7 @@ class CRUDSpec:
|
||||||
current_alias = alias
|
current_alias = alias
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(attr_obj, InstrumentedAttribute) or hasattr(attr_obj, "clauses"):
|
if isinstance(attr_obj, InstrumentedAttribute) or getattr(attr_obj, "comparator", None) is not None or hasattr(attr_obj, "clauses"):
|
||||||
return attr_obj, tuple(join_path) if join_path else None
|
return attr_obj, tuple(join_path) if join_path else None
|
||||||
|
|
||||||
return None, None
|
return None, None
|
||||||
|
|
@ -152,31 +153,34 @@ class CRUDSpec:
|
||||||
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
|
||||||
|
|
||||||
def get_eager_loads(self, root_alias, *, fields_map: Optional[Dict[Tuple[str, ...], List[str]]] = None):
|
def get_eager_loads(self, root_alias, *, fields_map=None):
|
||||||
loads = []
|
loads = []
|
||||||
for path in self.eager_paths:
|
for path in self.eager_paths:
|
||||||
current = root_alias
|
current = root_alias
|
||||||
loader = None
|
loader = None
|
||||||
|
|
||||||
for idx, name in enumerate(path):
|
for idx, name in enumerate(path):
|
||||||
rel_attr = getattr(current, name)
|
rel_attr = getattr(current, name)
|
||||||
|
loader = selectinload(rel_attr) if loader is None else loader.selectinload(rel_attr)
|
||||||
|
|
||||||
if loader is None:
|
# step to target class for the next hop
|
||||||
loader = selectinload(rel_attr)
|
target_cls = rel_attr.property.mapper.class_
|
||||||
else:
|
current = target_cls
|
||||||
loader = loader.selectinload(rel_attr)
|
|
||||||
|
|
||||||
current = rel_attr.property.mapper.class_
|
|
||||||
|
|
||||||
|
# if final hop and we have a fields map, narrow columns
|
||||||
if fields_map and idx == len(path) - 1 and path in fields_map:
|
if fields_map and idx == len(path) - 1 and path in fields_map:
|
||||||
target_cls = current
|
cols = []
|
||||||
cols = [getattr(target_cls, n) for n in fields_map[path] if hasattr(target_cls, n)]
|
for n in fields_map[path]:
|
||||||
|
attr = getattr(target_cls, n, None)
|
||||||
|
# Only include real column attributes; skip hybrids/expressions
|
||||||
|
if isinstance(attr, InstrumentedAttribute):
|
||||||
|
cols.append(attr)
|
||||||
|
|
||||||
|
# Only apply load_only if we have at least one real column
|
||||||
if cols:
|
if cols:
|
||||||
loader = loader.load_only(*cols)
|
loader = loader.load_only(*cols)
|
||||||
|
|
||||||
if loader is not None:
|
if loader is not None:
|
||||||
loads.append(loader)
|
loads.append(loader)
|
||||||
|
|
||||||
return loads
|
return loads
|
||||||
|
|
||||||
def get_join_paths(self):
|
def get_join_paths(self):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, Unicode
|
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Unicode, case, cast, 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
|
||||||
|
|
||||||
|
|
@ -57,3 +58,22 @@ class Inventory(Base, CRUDMixin):
|
||||||
parts.append(f"location={repr(self.location.identifier)}")
|
parts.append(f"location={repr(self.location.identifier)}")
|
||||||
|
|
||||||
return f"<Inventory({', '.join(parts)})>"
|
return f"<Inventory({', '.join(parts)})>"
|
||||||
|
|
||||||
|
@hybrid_property
|
||||||
|
def label(self):
|
||||||
|
if self.name:
|
||||||
|
return f"Name: {self.name}"
|
||||||
|
if self.barcode:
|
||||||
|
return f"Barcode: {self.barcode}"
|
||||||
|
if self.serial:
|
||||||
|
return f"Serial: {self.serial}"
|
||||||
|
return f"ID: {self.id}"
|
||||||
|
|
||||||
|
@label.expression
|
||||||
|
def label(cls):
|
||||||
|
return case(
|
||||||
|
(cls.name.isnot(None) & (cls.name != ''), func.concat("Name: ", cls.name)),
|
||||||
|
(cls.barcode.isnot(None) & (cls.barcode != ''), func.concat("Barcode: ", cls.barcode)),
|
||||||
|
(cls.serial.isnot(None) & (cls.serial != ''), func.concat("Serial: ", cls.serial)),
|
||||||
|
else_=func.concat("ID: ", cast(cls.id, String)),
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ def init_index_routes(app):
|
||||||
"start_time",
|
"start_time",
|
||||||
"contact.last_name",
|
"contact.last_name",
|
||||||
"contact.first_name",
|
"contact.first_name",
|
||||||
"work_item.name",
|
"work_item.label",
|
||||||
"work_item.device_type.description"
|
"work_item.device_type.description"
|
||||||
],
|
],
|
||||||
"sort": "start_time"
|
"sort": "start_time"
|
||||||
|
|
@ -32,7 +32,7 @@ def init_index_routes(app):
|
||||||
{"field": "start_time", "label": "Start", "format": "date"},
|
{"field": "start_time", "label": "Start", "format": "date"},
|
||||||
{"field": "contact.last_name", "label": "Contact",
|
{"field": "contact.last_name", "label": "Contact",
|
||||||
"link": {"endpoint": "user.get_item", "params": {"id": "{contact.id}"}}},
|
"link": {"endpoint": "user.get_item", "params": {"id": "{contact.id}"}}},
|
||||||
{"field": "work_item.name", "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}"}}},
|
||||||
{"field": "work_item.device_type.description", "label": "Device Type"}
|
{"field": "work_item.device_type.description", "label": "Device Type"}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue