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:
parent
21dc8c71fc
commit
7e24aa0585
5 changed files with 102 additions and 19 deletions
|
|
@ -9,9 +9,7 @@ function Editor(cfg) {
|
||||||
recordId: cfg.recordId,
|
recordId: cfg.recordId,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// initial render from whatever’s 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++}`;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 }}"
|
||||||
|
|
|
||||||
|
|
@ -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 %}
|
||||||
|
|
|
||||||
|
|
@ -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})
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue