WIP on getting work_log support on inventory page.

This commit is contained in:
Yaro Kasear 2025-10-03 16:26:04 -05:00
parent 7935b5b57d
commit 57104824a1
5 changed files with 117 additions and 68 deletions

View file

@ -247,32 +247,30 @@ class CRUDService(Generic[T]):
# Detect first hops that have deeper, nested tails requested (e.g. "contact.supervisor")
nested_first_hops = { path[0] for path in (rel_field_names or {}).keys() if len(path) > 1 }
used_contains_eager = False
# IMPORTANT:
# - Only attach loader options for first-hop relations from the root.
# - Always use selectinload here (avoid contains_eager joins).
# - Let compile_projections() supply deep chained options.
for base_alias, rel_attr, target_alias in join_paths:
is_collection = bool(getattr(getattr(rel_attr, "property", None), "uselist", False))
is_firsthop_from_root = (base_alias is root_alias)
if not is_firsthop_from_root:
# Deeper hops are handled by proj_opts below
continue
prop = getattr(rel_attr, "property", None)
is_collection = bool(getattr(prop, "uselist", False))
is_nested_firsthop = rel_attr.key in nested_first_hops
if is_collection or is_nested_firsthop:
# Use selectinload so deeper hops can chain cleanly (and to avoid
# contains_eager/loader conflicts on nested paths).
opt = selectinload(rel_attr)
# Narrow columns for collections if we know child scalar names
if is_collection:
child_names = (collection_field_names or {}).get(rel_attr.key, [])
if child_names:
target_cls = rel_attr.property.mapper.class_
cols = [getattr(target_cls, n, None) for n in child_names]
cols = [c for c in cols if isinstance(c, InstrumentedAttribute)]
if cols:
opt = opt.load_only(*cols)
query = query.options(opt)
else:
# Simple first-hop scalar rel with no deeper tails: safe to join + contains_eager
query = query.join(target_alias, rel_attr.of_type(target_alias), isouter=True)
query = query.options(contains_eager(rel_attr, alias=target_alias))
used_contains_eager = True
opt = selectinload(rel_attr)
# Optional narrowng for collections
if is_collection:
child_names = (collection_field_names or {}).get(rel_attr.key, [])
if child_names:
target_cls = prop.mapper.class_
cols = [getattr(target_cls, n, None) for n in child_names]
cols = [c for c in cols if isinstance(c, InstrumentedAttribute)]
if cols:
opt = opt.load_only(*cols)
query = query.options(opt)
# Filters AFTER joins → no cartesian products
if filters:
@ -365,6 +363,10 @@ class CRUDService(Generic[T]):
last_key = None
# Count DISTINCT ids with mirrored joins
# Apply deep projection loader options (safe: we avoided contains_eager)
if proj_opts:
query = query.options(*proj_opts)
total = None
if include_total:
base = session.query(getattr(root_alias, "id"))
@ -466,26 +468,25 @@ class CRUDService(Generic[T]):
nested_first_hops = { path[0] for path in (rel_field_names or {}).keys() if len(path) > 1 }
used_contains_eager = False
# First-hop only; use selectinload (no contains_eager)
for base_alias, rel_attr, target_alias in join_paths:
is_collection = bool(getattr(getattr(rel_attr, "property", None), "uselist", False))
is_nested_firsthop = rel_attr.key in nested_first_hops
is_firsthop_from_root = (base_alias is root_alias)
if not is_firsthop_from_root:
continue
prop = getattr(rel_attr, "property", None)
is_collection = bool(getattr(prop, "uselist", False))
_is_nested_firsthop = rel_attr.key in nested_first_hops
if is_collection or is_nested_firsthop:
opt = selectinload(rel_attr)
if is_collection:
child_names = (collection_field_names or {}).get(rel_attr.key, [])
if child_names:
target_cls = rel_attr.property.mapper.class_
cols = [getattr(target_cls, n, None) for n in child_names]
cols = [c for c in cols if isinstance(c, InstrumentedAttribute)]
if cols:
opt = opt.load_only(*cols)
query = query.options(opt)
else:
query = query.join(target_alias, rel_attr.of_type(target_alias), isouter=True)
query = query.options(contains_eager(rel_attr, alias=target_alias))
used_contains_eager = True
opt = selectinload(rel_attr)
if is_collection:
child_names = (collection_field_names or {}).get(rel_attr.key, [])
if child_names:
target_cls = prop.mapper.class_
cols = [getattr(target_cls, n, None) for n in child_names]
cols = [c for c in cols if isinstance(c, InstrumentedAttribute)]
if cols:
opt = opt.load_only(*cols)
query = query.options(opt)
# Apply filters (joins are in place → no cartesian products)
if filters:
@ -497,7 +498,7 @@ class CRUDService(Generic[T]):
# Projection loader options compiled from requested fields.
# Skip if we used contains_eager to avoid loader-strategy conflicts.
expanded_fields, proj_opts = compile_projection(self.model, req_fields) if req_fields else ([], [])
if proj_opts and not used_contains_eager:
if proj_opts:
query = query.options(*proj_opts)
obj = query.first()
@ -565,26 +566,25 @@ class CRUDService(Generic[T]):
nested_first_hops = { path[0] for path in (rel_field_names or {}).keys() if len(path) > 1 }
used_contains_eager = False
for _base_alias, rel_attr, target_alias in join_paths:
is_collection = bool(getattr(getattr(rel_attr, "property", None), "uselist", False))
is_nested_firsthop = rel_attr.key in nested_first_hops
# First-hop only; use selectinload
for base_alias, rel_attr, target_alias in join_paths:
is_firsthop_from_root = (base_alias is root_alias)
if not is_firsthop_from_root:
continue
prop = getattr(rel_attr, "property", None)
is_collection = bool(getattr(prop, "uselist", False))
_is_nested_firsthop = rel_attr.key in nested_first_hops
if is_collection or is_nested_firsthop:
opt = selectinload(rel_attr)
if is_collection:
child_names = (collection_field_names or {}).get(rel_attr.key, [])
if child_names:
target_cls = rel_attr.property.mapper.class_
cols = [getattr(target_cls, n, None) for n in child_names]
cols = [c for c in cols if isinstance(c, InstrumentedAttribute)]
if cols:
opt = opt.load_only(*cols)
query = query.options(opt)
else:
query = query.join(target_alias, rel_attr.of_type(target_alias), isouter=True)
query = query.options(contains_eager(rel_attr, alias=target_alias))
used_contains_eager = True
opt = selectinload(rel_attr)
if is_collection:
child_names = (collection_field_names or {}).get(rel_attr.key, [])
if child_names:
target_cls = prop.mapper.class_
cols = [getattr(target_cls, n, None) for n in child_names]
cols = [c for c in cols if isinstance(c, InstrumentedAttribute)]
if cols:
opt = opt.load_only(*cols)
query = query.options(opt)
# Filters AFTER joins → no cartesian products
if filters:
@ -608,7 +608,7 @@ class CRUDService(Generic[T]):
# Projection loaders only if we didnt use contains_eager
expanded_fields, proj_opts = compile_projection(self.model, req_fields) if req_fields else ([], [])
if proj_opts and not used_contains_eager:
if proj_opts:
query = query.options(*proj_opts)
else:

View file

@ -66,13 +66,46 @@ _ENUMS = {
}
def get_env():
"""
Return an overlay Jinja Environment that knows how to load crudkit templates
and has our helper functions available as globals.
"""
app = current_app
default_path = os.path.join(os.path.dirname(__file__), 'templates')
fallback_loader = FileSystemLoader(default_path)
return app.jinja_env.overlay(
loader=ChoiceLoader([app.jinja_loader, fallback_loader])
)
env = app.jinja_env.overlay(loader=ChoiceLoader([app.jinja_loader, fallback_loader]))
# Ensure helpers are available even when we render via this overlay env.
# These names are resolved at *call time* (not at def time), so it's safe.
try:
env.globals.setdefault("render_table", render_table)
env.globals.setdefault("render_form", render_form)
env.globals.setdefault("render_field", render_field)
except NameError:
# Functions may not be defined yet at import time; later calls will set them.
pass
return env
def register_template_globals(app=None):
"""
Register crudkit helpers as app-wide Jinja globals so they can be used
directly in any template via {{ render_table(...) }}, {{ render_form(...) }},
and {{ render_field(...) }}.
"""
if app is None:
app = current_app
# Idempotent install using an extension flag
installed = app.extensions.setdefault("crudkit_ui_helpers", set())
to_register = {
"render_table": render_table,
"render_form": render_form,
"render_field": render_field,
}
for name, fn in to_register.items():
if name not in installed:
app.add_template_global(fn, name)
installed.add(name)
def expand_projection(model_cls, fields):
req = getattr(model_cls, "__crudkit_field_requires__", {}) or {}