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, recordId: cfg.recordId,
init() { init() {
// initial render from whatevers in the textarea
this.renderViewer(); this.renderViewer();
// then pull server value and re-render
if (this.refreshUrl) this.refresh(); if (this.refreshUrl) this.refresh();
}, },
@ -35,7 +33,6 @@ function Editor(cfg) {
}, },
triggerRefresh() { triggerRefresh() {
// use this anywhere to force a re-pull
this.$refs.container?.dispatchEvent(new CustomEvent('editor:refresh', { bubbles: true })); 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 %} {% for it in options %}
<li> <li>
<a class="dropdown-item" data-inv-value="{{ it.id }}" <a class="dropdown-item" data-inv-value="{{ it.id }}"

View file

@ -51,7 +51,7 @@
{% endif %} {% endif %}
{% endmacro %} {% 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 --> <!-- Table Fragment -->
{% if rows %} {% if rows %}
@ -88,17 +88,6 @@
</tbody> </tbody>
</table> </table>
</div> </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 %} {% else %}
<div class="container text-center">No data.</div> <div class="container text-center">No data.</div>
{% endif %} {% endif %}

View file

@ -5,7 +5,7 @@ from sqlalchemy.sql import Select
from typing import Any, List, cast from typing import Any, List, cast
from .defaults import ( 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 from .. import db
@ -178,8 +178,6 @@ def get_value(model_name):
try: try:
val = call(Model, "ui_value", db.session, id_=id_, field=field) val = call(Model, "ui_value", db.session, id_=id_, field=field)
if val is None: 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) val = default_value(db.session, Model, id_=id_, field=field)
except ValueError as e: except ValueError as e:
return jsonify({"error": str(e)}), 400 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 (str(val) if val is not None else ""), 200, {"Content-Type": "text/plain; charset=utf-8"}
return jsonify({"id": id_, "field": field, "value": val}) 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 import select, asc as sa_asc, desc as sa_desc
from sqlalchemy.inspection import inspect from sqlalchemy.inspection import inspect
from sqlalchemy.orm import aliased
from sqlalchemy.sql import Select from sqlalchemy.sql import Select
from typing import Any, Optional, cast, Iterable from typing import Any, Optional, cast, Iterable
@ -85,6 +86,64 @@ def default_query(
return list(session.execute(stmt).scalars().all()) 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: def default_value(session, Model, *, id_: int, field: str) -> Any:
if '.' not in field: if '.' not in field:
col = _mapped_column(Model, field) col = _mapped_column(Model, field)