CRUDkit fixes and changes.
This commit is contained in:
parent
9d36d600bb
commit
7ddfe084ba
5 changed files with 98 additions and 43 deletions
|
|
@ -11,26 +11,37 @@ class CRUDMixin:
|
|||
|
||||
def as_dict(self, fields: list[str] | None = None):
|
||||
"""
|
||||
Serialize mapped columns. Honors projection if either:
|
||||
- 'fields' is passed explicitly, or
|
||||
-
|
||||
Serialize the instance.
|
||||
- If 'fields' (possibly dotted) is provided, emit exactly those keys.
|
||||
- Else, if '__crudkit_projection__' is set on the instance, emit those keys.
|
||||
- Else, fall back to all mapped columns on this class hierarchy.
|
||||
Always includes 'id' when present unless explicitly excluded.
|
||||
"""
|
||||
allowed = None
|
||||
if fields is None:
|
||||
fields = getattr(self, "__crudkit_projection__", None)
|
||||
|
||||
if fields:
|
||||
allowed = set(fields)
|
||||
else:
|
||||
allowed = getattr(self, "__crudkit_root_fields__", None)
|
||||
out = {}
|
||||
if "id" not in fields and hasattr(self, "id"):
|
||||
out["id"] = getattr(self, "id")
|
||||
for f in fields:
|
||||
cur = self
|
||||
for part in f.split("."):
|
||||
if cur is None:
|
||||
break
|
||||
cur = getattr(cur, part, None)
|
||||
out[f] = cur
|
||||
return out
|
||||
|
||||
result = {}
|
||||
for cls in self.__class__.__mro__:
|
||||
if not hasattr(cls, "__table__"):
|
||||
continue
|
||||
for column in cls.__table__.columns:
|
||||
name = column.name
|
||||
if allowed is not None and name not in allowed and name != "id":
|
||||
continue
|
||||
result[name] = getattr(self, name)
|
||||
for cls in self.__clas__.__mro__:
|
||||
if hasattr(cls, "__table__"):
|
||||
for column in cls.__table__.columns:
|
||||
name = column.name
|
||||
result[name] = getattr(self, name)
|
||||
return result
|
||||
|
||||
|
||||
class Version(Base):
|
||||
__tablename__ = "versions"
|
||||
|
||||
|
|
|
|||
|
|
@ -81,8 +81,8 @@ class CRUDService(Generic[T]):
|
|||
for eager in spec.get_eager_loads(root_alias, fields_map=rel_field_names):
|
||||
query = query.options(eager)
|
||||
|
||||
if root_fields or rel_field_names:
|
||||
query = query.options(Load(root_alias).raiseload("*"))
|
||||
# if root_fields or rel_field_names:
|
||||
# query = query.options(Load(root_alias).raiseload("*"))
|
||||
|
||||
if filters:
|
||||
query = query.filter(*filters)
|
||||
|
|
@ -98,26 +98,43 @@ class CRUDService(Generic[T]):
|
|||
# Only apply offset/limit when not None.
|
||||
if offset is not None and offset != 0:
|
||||
query = query.offset(offset)
|
||||
if limit is not None:
|
||||
if limit is not None and limit > 0:
|
||||
query = query.limit(limit)
|
||||
|
||||
# return query.all()
|
||||
rows = query.all()
|
||||
|
||||
try:
|
||||
rf_names = [c.key for c in (root_fields or [])]
|
||||
except NameError:
|
||||
rf_names = []
|
||||
if rf_names:
|
||||
allow = set(rf_names)
|
||||
if "id" not in allow and hasattr(self.model, "id"):
|
||||
allow.add("id")
|
||||
proj = []
|
||||
if root_fields:
|
||||
proj.extend(c.key for c in root_fields)
|
||||
for path, names in (rel_field_names or {}).items():
|
||||
prefix = ".".join(path)
|
||||
for n in names:
|
||||
proj.append(f"{prefix}.{n}")
|
||||
|
||||
if proj and "id" not in proj and hasattr(self.model, "id"):
|
||||
proj.insert(0, "id")
|
||||
|
||||
if proj:
|
||||
for obj in rows:
|
||||
try:
|
||||
setattr(obj, "__crudkit_root_fields__", allow)
|
||||
setattr(obj, "__crudkit_projection__", tuple(proj))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# try:
|
||||
# rf_names = [c.key for c in (root_fields or [])]
|
||||
# except NameError:
|
||||
# rf_names = []
|
||||
# if rf_names:
|
||||
# allow = set(rf_names)
|
||||
# if "id" not in allow and hasattr(self.model, "id"):
|
||||
# allow.add("id")
|
||||
# for obj in rows:
|
||||
# try:
|
||||
# setattr(obj, "__crudkit_root_fields__", allow)
|
||||
# except Exception:
|
||||
# pass
|
||||
|
||||
return rows
|
||||
|
||||
def create(self, data: dict, actor=None) -> T:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import os
|
||||
|
||||
from flask import current_app
|
||||
from jinja2 import Environment, FileSystemLoader, ChoiceLoader
|
||||
from sqlalchemy.orm import class_mapper, RelationshipProperty
|
||||
from flask import current_app
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
def get_env():
|
||||
app_loader = current_app.jinja_loader
|
||||
|
|
@ -32,10 +34,15 @@ def render_field(field, value):
|
|||
options=field.get('options', None)
|
||||
)
|
||||
|
||||
def render_table(objects):
|
||||
def render_table(objects, headers: List[str] | None = None):
|
||||
env = get_env()
|
||||
template = get_crudkit_template(env, 'table.html')
|
||||
return template.render(objects=objects)
|
||||
if not objects:
|
||||
return template.render(fields=[], rows=[])
|
||||
proj = getattr(objects[0], "__crudkit_projection__", None)
|
||||
rows = [obj.as_dict(proj) for obj in objects]
|
||||
fields = list(rows[0].keys())
|
||||
return template.render(fields=fields, rows=rows, headers=headers)
|
||||
|
||||
def render_form(model_cls, values, session=None):
|
||||
env = get_env()
|
||||
|
|
|
|||
|
|
@ -1,12 +1,20 @@
|
|||
<table>
|
||||
{% if objects %}
|
||||
{% if rows %}
|
||||
<thead>
|
||||
<tr>
|
||||
{% for field in objects[0].__table__.columns %}<th>{{ field.name }}</th>{% endfor %}
|
||||
{% if headers %}
|
||||
{% for header in headers %}<th>{{ header }}</th>{% endfor %}
|
||||
{% else %}
|
||||
{% for field in fields if field != "id" %}<th>{{ field }}</th>{% endfor %}
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% for obj in objects %}
|
||||
<tr>{% for field in obj.__table__.columns %}<td>{{ obj[field.name] }}</td>{% endfor %}</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in rows %}
|
||||
<tr>{% for _, cell in row.items() if _ != "id" %}<td>{{ cell if cell else "-" }}</td>{% endfor %}</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr><th>No data.</th></tr>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<tr><th>No data.</th></tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -16,11 +16,23 @@ def init_index_routes(app):
|
|||
def index():
|
||||
session = get_session()
|
||||
work_log_service = CRUDService(WorkLog, session)
|
||||
work_logs = work_log_service.list({"complete__ne": 1, "fields": ["start_time"]})
|
||||
print(work_logs)
|
||||
logs = render_table(work_logs)
|
||||
work_logs = work_log_service.list({
|
||||
"complete__ne": 1,
|
||||
"fields": [
|
||||
"start_time",
|
||||
"contact.last_name",
|
||||
"work_item.name"
|
||||
],
|
||||
"limit": 10
|
||||
})
|
||||
headers = [
|
||||
"Start Time",
|
||||
"Contact Last Name",
|
||||
"Work Item"
|
||||
]
|
||||
logs = render_table(work_logs, headers)
|
||||
|
||||
return render_template("index.html", logs=logs)
|
||||
return render_template("index.html", logs=logs, headers=headers)
|
||||
|
||||
@bp_index.get("/LICENSE")
|
||||
def license():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue