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,
|
||||
|
||||
init() {
|
||||
// initial render from whatever’s 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++}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }}"
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue