Refactor Editor component to streamline initialization and enhance refresh functionality; add new default_values method for improved data retrieval in defaults module.

This commit is contained in:
Yaro Kasear 2025-08-20 09:20:42 -05:00
parent 21dc8c71fc
commit 7e24aa0585
5 changed files with 102 additions and 19 deletions

View file

@ -9,9 +9,7 @@ function Editor(cfg) {
recordId: cfg.recordId,
init() {
// initial render from whatevers in the textarea
this.renderViewer();
// then pull server value and re-render
if (this.refreshUrl) this.refresh();
},
@ -35,7 +33,6 @@ function Editor(cfg) {
},
triggerRefresh() {
// use this anywhere to force a re-pull
this.$refs.container?.dispatchEvent(new CustomEvent('editor:refresh', { bubbles: true }));
},
@ -58,3 +55,21 @@ function Editor(cfg) {
}
};
}
let tempIdCounter = 1;
function createEditorWidget(template, id, timestamp, content = '') {
let html = template.innerHTML
.replace(/__ID__/g, id)
.replace(/__TIMESTAMP__/g, timestamp)
.replace(/__CONTENT__/g, content);
const wrapper = document.createElement("div");
wrapper.innerHTML = html;
return wrapper.firstElementChild;
}
function createTempId(prefix = "temp") {
return `${prefix}-${tempIdCounter++}`;
}

View file

@ -1,4 +1,4 @@
{# templates/fragments/_list_fragment.html #}
<!-- List Fragment -->
{% for it in options %}
<li>
<a class="dropdown-item" data-inv-value="{{ it.id }}"

View file

@ -51,7 +51,7 @@
{% endif %}
{% endmacro %}
{% macro dynamic_table(headers, rows, id, entry_route=None, title=None, per_page=15) %}
{% macro dynamic_table(id, headers=none, rows=none, entry_route=None, title=None, per_page=15) %}
<!-- Table Fragment -->
{% if rows %}
@ -88,17 +88,6 @@
</tbody>
</table>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
new DataTable('#datatable-{{ id|default('table')|replace(' ', ' - ')|lower }}', {
pageLength: {{ per_page }},
scrollX: true,
scrollY: '60vh',
scrollCollapse: true,
})
})
</script>
{% else %}
<div class="container text-center">No data.</div>
{% endif %}

View file

@ -5,7 +5,7 @@ from sqlalchemy.sql import Select
from typing import Any, List, cast
from .defaults import (
default_query, default_create, default_update, default_delete, default_serialize
default_query, default_create, default_update, default_delete, default_serialize, default_values, default_value
)
from .. import db
@ -178,8 +178,6 @@ def get_value(model_name):
try:
val = call(Model, "ui_value", db.session, id_=id_, field=field)
if val is None:
# None means “no override,” fall back to default
from .defaults import default_value # or hoist to top if you like
val = default_value(db.session, Model, id_=id_, field=field)
except ValueError as e:
return jsonify({"error": str(e)}), 400
@ -190,3 +188,25 @@ def get_value(model_name):
return (str(val) if val is not None else ""), 200, {"Content-Type": "text/plain; charset=utf-8"}
return jsonify({"id": id_, "field": field, "value": val})
@bp.get("<model_name>/values")
def get_values(model_name):
Model = get_model_class(model_name)
raw = request.args.get("fields") or ""
parts = [p for p in raw.split(",") if p.strip()]
parts.extend(request.args.getlist("field"))
id_raw = request.args.get("id")
try:
id_ = int(id_raw)
except (TypeError, ValueError):
return jsonify({"error": "Invalid id"}), 422
try:
data = call(Model, "ui_values", db.session, id_=id_, fields=parts) \
or default_values(db.session, Model, id_=id_, fields=parts)
except ValueError as e:
return jsonify({"error": str(e)}), 400
return jsonify({"id": id_, "fields": parts, "values": data})

View file

@ -1,5 +1,6 @@
from sqlalchemy import select, asc as sa_asc, desc as sa_desc
from sqlalchemy.inspection import inspect
from sqlalchemy.orm import aliased
from sqlalchemy.sql import Select
from typing import Any, Optional, cast, Iterable
@ -85,6 +86,64 @@ def default_query(
return list(session.execute(stmt).scalars().all())
def _resolve_column(Model, path: str):
"""Return (selectable, joins:list[tuple[parent, attr]]) for 'col' or 'rel.col'"""
if '.' not in path:
col = _mapped_column(Model, path)
if col is None:
raise ValueError(f"Column '{path}' is not a mapped column on {Model.__name__}")
return col, []
rel_name, rel_field = path.split('.', 1)
rel_attr = getattr(Model, rel_name, None)
if getattr(rel_attr, 'property', None) is None:
raise ValueError(f"Column '{path}' is not a valid relationship on {Model.__name__}")
Rel = rel_attr.property.mapper.class_
col = _mapped_column(Rel, rel_field)
if col is None:
raise ValueError(f"Column '{path}' is not a mapped column on {Rel.__name__}")
return col, [(Model, rel_name)]
def default_values(session, Model, *, id_: int, fields: Iterable[str]) -> dict[str, Any]:
fields = [f.strip() for f in fields if f.strip()]
if not fields:
raise ValueError("No fields provided for default_values")
mapper = inspect(Model)
pk = mapper.primary_key[0]
selects = []
joins = []
for f in fields:
col, j = _resolve_column(Model, f)
selects.append(col.label(f.replace('.', '_')))
joins.extend(j)
seen = set()
stmt = select(*selects).where(pk == id_)
current = Model
for parent, attr_name in joins:
key = (parent, attr_name)
if key in seen:
continue
seen.add(key)
stmt = stmt.join(getattr(parent, attr_name))
row = session.execute(stmt).one_or_none()
if row is None:
return {}
allow = getattr(Model, "ui_value_allow", None)
if allow:
for f in fields:
if f not in allow:
raise ValueError(f"Field '{f}' not allowed")
data = {}
for f in fields:
key = f.replace('.', '_')
data[f] = getattr(row, key, None)
return data
def default_value(session, Model, *, id_: int, field: str) -> Any:
if '.' not in field:
col = _mapped_column(Model, field)