From 2ae96e5c807a1325b0e6a9e2337ae0af5f830455 Mon Sep 17 00:00:00 2001 From: Conrad Nelson Date: Thu, 18 Sep 2025 16:03:12 -0500 Subject: [PATCH] Lots of form code done! --- crudkit/core/service.py | 46 ++++++- crudkit/ui/fragments.py | 223 +++++++++++++++++++++++++++++--- crudkit/ui/templates/field.html | 72 +++++++---- crudkit/ui/templates/form.html | 40 +++++- inventory/routes/entry.py | 75 +++++++++-- inventory/templates/entry.html | 4 +- 6 files changed, 400 insertions(+), 60 deletions(-) diff --git a/crudkit/core/service.py b/crudkit/core/service.py index e7acf1d..1220e04 100644 --- a/crudkit/core/service.py +++ b/crudkit/core/service.py @@ -1,7 +1,7 @@ from typing import Any, Callable, Dict, Iterable, List, Tuple, Type, TypeVar, Generic, Optional, Protocol, runtime_checkable, cast from sqlalchemy import and_, func, inspect, or_, text from sqlalchemy.engine import Engine, Connection -from sqlalchemy.orm import Load, Session, raiseload, selectinload, with_polymorphic, Mapper, RelationshipProperty +from sqlalchemy.orm import Load, Session, raiseload, selectinload, with_polymorphic, Mapper, RelationshipProperty, class_mapper from sqlalchemy.orm.attributes import InstrumentedAttribute from sqlalchemy.orm.util import AliasedClass from sqlalchemy.sql import operators @@ -12,6 +12,35 @@ from crudkit.core.spec import CRUDSpec from crudkit.core.types import OrderSpec, SeekWindow from crudkit.backend import BackendInfo, make_backend_info +def _loader_options_for_fields(root_alias, model_cls, fields: list[str]) -> list[Load]: + """ + For bare MANYTOONE names in fields (e.g. "location"), selectinload the relationship + and only fetch the related PK. This is enough for preselecting - {% if options %} - - {% for opt in options %} - - {% endfor %} - {% else %} - - {% endif %} - + {% elif field_type == 'textarea' %} - + {% elif field_type == 'checkbox' %} - + + +{% elif field_type == 'hidden' %} + + +{% elif field_type == 'display' %} +
{{ value }}
{% else %} - + {% endif %} {% if help %} -
{{ help }}
+
{{ help }}
{% endif %} diff --git a/crudkit/ui/templates/form.html b/crudkit/ui/templates/form.html index 7ab1d50..b073fc3 100644 --- a/crudkit/ui/templates/form.html +++ b/crudkit/ui/templates/form.html @@ -1,6 +1,40 @@
- {% for field in fields %} - {{ render_field(field, values.get(field.name, '')) | safe }} + {% macro render_row(row) %} + + {% if row.fields or row.children or row.legend %} + {% if row.legend %}{{ row.legend }}{% endif %} +
+ {% for field in row.fields %} +
+ {{ render_field(field, values.get(field.name, '')) | safe }} +
+ {% endfor %} + {% for child in row.children %} + {{ render_row(child) }} + {% endfor %} +
+ {% endif %} + {% endmacro %} + + {% if rows %} + {% for row in rows %} + {{ render_row(row) }} {% endfor %} - + {% else %} + {% for field in fields %} + {{ render_field(field, values.get(field.name, '')) | safe }} + {% endfor %} + {% endif %} + +
diff --git a/inventory/routes/entry.py b/inventory/routes/entry.py index 8ef2f88..000b0a4 100644 --- a/inventory/routes/entry.py +++ b/inventory/routes/entry.py @@ -14,26 +14,79 @@ def init_entry_routes(app): cls = crudkit.crud.get_model(model) if cls is None: abort(404) - obj = crudkit.crud.get_service(cls).get(id, {"fields": ["first_name", "last_name", "title", "active", "staff", "location", "location_id"]}) + if model not in ["inventory", "worklog", "user"]: + abort(404) + + fields = {} + fields_spec = [] + layout = [] + if model == "inventory": + fields["fields"] = ["label", "name", "barcode", "serial"] + fields_spec = [ + {"name": "label", "label": "", "row": "label", "wrap": {"class": "col"}}, + {"name": "name", "label": "Name", "row": "identification", "wrap": {"class": "col"}}, + {"name": "barcode", "label": "Bar Code #", "row": "identification", "wrap": {"class": "col"}}, + {"name": "serial", "label": "Serial #", "row": "identification", "wrap": {"class": "col"}}, + ] + layout = [ + {"name": "label", "order": 10, "attrs": {"class": "row"}}, + {"name": "identification", "order": 20, "attrs": {"class": "row"}}, + ] + elif model.lower() == 'user': + fields["fields"] = ["label", "first_name", "last_name", "title", "active", "staff", "location", "supervisor"] + fields_spec = [ + {"name": "label", "row": "label", "label": "User Record", + "label_attrs": {"class": "display-6"}, "type": "display", + "attrs": {"class": "display-4 mb-3"}, "wrap": {"class": "text-center"}}, + + {"name": "last_name", "label": "Last Name", "label_attrs": {"class": "form-label"}, + "attrs": {"placeholder": "Doe", "class": "form-control"}, + "row": "name", "wrap": {"class": "col-2"}}, + + {"name": "first_name", "label": "First Name", "label_attrs": {"class": "form-label"}, + "attrs": {"placeholder": "John", "class": "form-control"}, + "row": "name", "wrap": {"class": "col-2"}}, + + {"name": "title", "label": "Title", "label_attrs": {"class": "form-label"}, + "attrs": {"placeholder": "President of the Universe", "class": "form-control"}, + "row": "name", "wrap": {"class": "col-2"}}, + + {"name": "supervisor", "label": "Supervisor", "label_attrs": {"class": "form-label"}, + "label_spec": "{first_name} {last_name}", "row": "details", "wrap": {"class": "col-3"}, + "attrs": {"class": "form-control"}}, + + {"name": "location", "label": "Room", "label_attrs": {"class": "form-label"}, + "label_spec": "{name} - {room_function.description}", + "row": "details", "wrap": {"class": "col-3"}, "attrs": {"class": "form-control"}}, + + {"name": "active", "label": "Active", "label_attrs": {"class": "form-check-label"}, + "row": "checkboxes", "attrs": {"class": "form-check-input"}, "wrap": {"class": "form-check"}}, + + {"name": "staff", "label": "Staff Member", "label_attrs": {"class": "form-check-label"}, + "row": "checkboxes", "attrs": {"class": "form-check-input"}, "wrap": {"class": "form-check"}}, + ] + layout = [ + {"name": "label", "order": 0}, + {"name": "name", "order": 10, "attrs": {"class": "row mb-3"}}, + {"name": "details", "order": 20, "attrs": {"class": "row"}}, + {"name": "checkboxes", "order": 30, "parent": "name", "attrs": {"class": "col d-flex flex-column justify-content-end"}} + ] + elif model == "worklog": + pass + + obj = crudkit.crud.get_service(cls).get(id, fields) if obj is None: abort(404) - fields_spec = [ - {"name": "last_name", "label": "Last Name", "attrs": {"placeholder": "Doe"}}, - {"name": "first_name", "label": "First Name", "attrs": {"placeholder": "John"}}, - {"name": "title", "label": "Title", "attrs": {"placeholder": "President of the Universe"}}, - {"name": "active", "label": "Active"}, - {"name": "staff", "label": "Staff Member"}, - {"name": "location", "label": "Room", "label_spec": "{name} - {room_function.description}"} - ] form = render_form( cls, obj.as_dict(), crudkit.crud.get_service(cls).session, instance=obj, - fields_spec=fields_spec + fields_spec=fields_spec, + layout=layout, + submit_attrs={"class": "btn btn-primary"} ) - return render_template("entry.html", form=form) app.register_blueprint(bp_entry) diff --git a/inventory/templates/entry.html b/inventory/templates/entry.html index 7db3db6..d107960 100644 --- a/inventory/templates/entry.html +++ b/inventory/templates/entry.html @@ -1,5 +1,7 @@ {% extends 'base.html' %} {% block main %} -{{ form | safe }} +
+ {{ form | safe }} +
{% endblock %} \ No newline at end of file