Some additional work done with CRUDKit. May start this over with a better design for CRUDKit.
This commit is contained in:
parent
f47fb6b505
commit
7738f1c9c2
5 changed files with 67 additions and 53 deletions
|
|
@ -56,7 +56,7 @@
|
|||
</li>
|
||||
{% else %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" hx-get="{{ _rows_url(model, page, per_page, sort, filters, fields_csv) }}" hx-target="#rows"
|
||||
<a class="page-link state-modifier" hx-get="{{ _rows_url(model, page, per_page, sort, filters, fields_csv) }}" hx-target="#rows"
|
||||
hx-swap="innerHTML" data-page="{{ page }}" {{ attrs|safe }}>
|
||||
{{ label }}
|
||||
</a>
|
||||
|
|
@ -103,18 +103,6 @@
|
|||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{# keep hidden pager-state in sync with one handler instead of inline click spam #}
|
||||
<script>
|
||||
document.currentScript?.previousElementSibling?.addEventListener?.('click', function (e) {
|
||||
const a = e.target.closest('a.page-link');
|
||||
if (!a) return;
|
||||
const targetPage = a.getAttribute('data-page');
|
||||
if (!targetPage) return;
|
||||
const inp = document.querySelector('#pager-state input[name=page]');
|
||||
if (inp) inp.value = targetPage;
|
||||
}, { capture: true });
|
||||
</script>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro form(schema, action, method="POST", obj_id=None, hx=False, csrf_token=None) -%}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ def _apply_dotted_ordering(stmt, Model, sort_tokens):
|
|||
rel = current_mapper.relationships.get(rel_name)
|
||||
if rel is None:
|
||||
# invalid sort key; skip quietly or raise
|
||||
# raise ValueError(f"Unknown relationship {current_mapper.class_.__name__}.{rel_name}")
|
||||
print(f"Unknown relationship {current_mapper.class_.__name__}.{rel_name}")
|
||||
entity = None
|
||||
break
|
||||
|
||||
|
|
@ -78,13 +78,20 @@ def _apply_dotted_ordering(stmt, Model, sort_tokens):
|
|||
continue
|
||||
|
||||
col_name = parts[-1]
|
||||
# Validate final column
|
||||
if col_name not in current_mapper.columns:
|
||||
# raise ValueError(f"Unknown column {current_mapper.class_.__name__}.{col_name}")
|
||||
continue
|
||||
# # Validate final column
|
||||
# if col_name not in current_mapper.columns:
|
||||
# print(f"Unknown column {current_mapper.class_.__name__}.{col_name}")
|
||||
# continue
|
||||
|
||||
col = getattr(entity, col_name) if entity is not Model else getattr(Model, col_name)
|
||||
stmt = stmt.order_by(col.desc() if direction == "desc" else col.asc())
|
||||
# col = getattr(entity, col_name) if entity is not Model else getattr(Model, col_name)
|
||||
|
||||
attr = getattr(entity, col_name, None)
|
||||
if attr is None:
|
||||
attr = getattr(current_mapper.class_, col_name, None)
|
||||
if attr is None:
|
||||
print(f"Unknown column {current_mapper.class_.__name__}.{col_name}")
|
||||
continue
|
||||
stmt = stmt.order_by(attr.desc() if direction == "desc" else attr.asc())
|
||||
|
||||
return stmt
|
||||
|
||||
|
|
|
|||
|
|
@ -29,22 +29,9 @@ worklog_images = image_links.worklog_images
|
|||
User = users.User
|
||||
|
||||
# Now it’s safe to configure mappers and set global eagerloads
|
||||
from sqlalchemy.orm import configure_mappers, joinedload, selectinload
|
||||
from sqlalchemy.orm import configure_mappers
|
||||
configure_mappers()
|
||||
|
||||
User.ui_eagerload = (
|
||||
joinedload(User.supervisor),
|
||||
joinedload(User.location).joinedload(Room.room_function),
|
||||
)
|
||||
|
||||
Room.ui_eagerload = (
|
||||
joinedload(Room.area),
|
||||
joinedload(Room.room_function),
|
||||
selectinload(Room.inventory),
|
||||
selectinload(Room.users)
|
||||
)
|
||||
|
||||
|
||||
registry = {
|
||||
"area": Area,
|
||||
"brand": Brand,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
from typing import Optional, TYPE_CHECKING, List
|
||||
if TYPE_CHECKING:
|
||||
from .areas import Area
|
||||
from .room_functions import RoomFunction
|
||||
from .inventory import Inventory
|
||||
from .users import User
|
||||
|
||||
from .room_functions import RoomFunction
|
||||
from crudkit import CrudMixin
|
||||
from sqlalchemy import ForeignKey, Identity, Integer, Unicode
|
||||
from sqlalchemy import ForeignKey, Identity, Integer, Unicode, func, select, literal
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from . import db
|
||||
|
|
@ -27,18 +28,6 @@ class Room(db.Model, CrudMixin):
|
|||
ui_eagerload = tuple()
|
||||
ui_extra_attrs = ('area_id', 'function_id')
|
||||
|
||||
@classmethod
|
||||
def ui_update(cls, session, id_, payload):
|
||||
print(payload)
|
||||
obj = session.get(cls, id_)
|
||||
if not obj:
|
||||
return None
|
||||
obj.name = payload.get("name", obj.name)
|
||||
obj.area_id = payload.get("area_id", obj.area_id)
|
||||
obj.function_id = payload.get("function_id", obj.function_id)
|
||||
session.commit()
|
||||
return obj
|
||||
|
||||
def __init__(self, name: Optional[str] = None, area_id: Optional[int] = None, function_id: Optional[int] = None):
|
||||
self.name = name
|
||||
self.area_id = area_id
|
||||
|
|
@ -47,8 +36,22 @@ class Room(db.Model, CrudMixin):
|
|||
def __repr__(self):
|
||||
return f"<Room(id={self.id}, room={repr(self.name)}, area_id={self.area_id}, function_id={self.function_id})>"
|
||||
|
||||
@property
|
||||
@hybrid_property
|
||||
def identifier(self):
|
||||
name = self.name or ""
|
||||
func = self.room_function.description if self.room_function else ""
|
||||
return f"{name} - {func}".strip(" -")
|
||||
function = self.room_function.description if self.room_function else ""
|
||||
return f"{name} - {function}".strip(" -")
|
||||
|
||||
@identifier.expression
|
||||
def identifier(cls):
|
||||
rf_desc = (
|
||||
select(RoomFunction.description)
|
||||
.where(RoomFunction.id == cls.function_id)
|
||||
.correlate(cls)
|
||||
.scalar_subquery()
|
||||
)
|
||||
return func.concat(
|
||||
func.coalesce(cls.name, ''), # left part
|
||||
literal(' - '), # separator
|
||||
func.coalesce(rf_desc, '') # right part via correlated subquery
|
||||
)
|
||||
|
|
|
|||
|
|
@ -70,7 +70,9 @@ refresh_url=none, model=none, sort=none) %}
|
|||
<thead class="sticky-top">
|
||||
<tr>
|
||||
{% for h in headers %}
|
||||
<th class="text-nowrap">{{ h }}</th>
|
||||
<th class="text-nowrap state-modifier" {% if fields %}
|
||||
hx-get="/ui/{{ model }}/frag/rows?page={{ page }}&per_page={{ per_page }}&fields_csv={{ fields|join(',') }}&sort={{ fields[loop.index0] }}"
|
||||
hx-target="#rows" hx-swap="innerHTML" hx-trigger="click" data-sort={{ fields[loop.index0] }}{% endif %}>{{ h }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -82,7 +84,7 @@ refresh_url=none, model=none, sort=none) %}
|
|||
<input type="hidden" name="page" value="{{ page }}">
|
||||
<input type="hidden" name="per_page" value="{{ per_page }}">
|
||||
<input type="hidden" name="fields_csv" value="{{ fields|join(',') }}">
|
||||
{% if sort %}<input type="hidden" name="sort" value="{{ sort }}">{% endif %}
|
||||
<input type="hidden" name="sort" value="{{ sort }}">
|
||||
{% for k,v in (filters or {}).items() %}
|
||||
<input type="hidden" name="{{ k }}" value="{{ v }}">
|
||||
{% endfor %}
|
||||
|
|
@ -90,4 +92,31 @@ refresh_url=none, model=none, sort=none) %}
|
|||
</div>
|
||||
<div id="pager" hx-get="/ui/{{ model }}/frag/pager" hx-include="#pager-state"
|
||||
hx-trigger="load, htmx:afterSwap from:#rows" hx-target="#pager" hx-swap="innerHTML"></div>
|
||||
|
||||
{# keep hidden pager-state in sync with one handler instead of inline click spam #}
|
||||
<script>
|
||||
document.addEventListener('click', function (e) {
|
||||
const a = e.target.closest('a.page-link.state-modifier');
|
||||
const th = e.target.closest('th.state-modifier');
|
||||
if (!a && !th) return;
|
||||
|
||||
// run BEFORE htmx handles the click
|
||||
if (a) {
|
||||
const targetPage = a.dataset.page;
|
||||
if (targetPage) {
|
||||
const inp = document.querySelector('#pager-state input[name=page]');
|
||||
if (inp) inp.value = targetPage;
|
||||
}
|
||||
}
|
||||
|
||||
if (th) {
|
||||
const targetSort = th.dataset.sort;
|
||||
if (targetSort) {
|
||||
const inp = document.querySelector('#pager-state input[name=sort]');
|
||||
if (inp) inp.value = targetSort;
|
||||
}
|
||||
}
|
||||
}, { capture: true });
|
||||
</script>
|
||||
|
||||
{% endmacro %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue