Complete and total rework ahead.

This commit is contained in:
Conrad Nelson 2025-09-03 16:33:52 -05:00
parent 559fd56f33
commit e420110fb3
95 changed files with 394 additions and 6351 deletions

0
crudkit/ui/__init__.py Normal file
View file

81
crudkit/ui/fragments.py Normal file
View file

@ -0,0 +1,81 @@
from jinja2 import Environment, FileSystemLoader, ChoiceLoader
from sqlalchemy.orm import class_mapper, RelationshipProperty
from flask import current_app
import os
def get_env():
app_loader = current_app.jinja_loader
default_path = os.path.join(os.path.dirname(__file__), 'templates')
fallback_loader = FileSystemLoader(default_path)
env = Environment(loader=ChoiceLoader([
app_loader,
fallback_loader
]))
return env
def get_crudkit_template(env, name):
try:
return env.get_template(f'crudkit/{name}')
except Exception:
return env.get_template(name)
def render_field(field, value):
env = get_env()
template = get_crudkit_template(env, 'field.html')
return template.render(
field_name=field['name'],
field_label=field.get('label', field['name']),
value=value,
field_type=field.get('type', 'text'),
options=field.get('options', None)
)
def render_table(objects):
env = get_env()
template = get_crudkit_template(env, 'table.html')
return template.render(objects=objects)
def render_form(model_cls, values, session=None):
env = get_env()
template = get_crudkit_template(env, 'form.html')
fields = []
fk_fields = set()
mapper = class_mapper(model_cls)
for prop in mapper.iterate_properties:
# FK Relationship fields (many-to-one)
if isinstance(prop, RelationshipProperty) and prop.direction.name == 'MANYTOONE':
if session is None:
continue
related_model = prop.mapper.class_
options = session.query(related_model).all()
fields.append({
'name': f"{prop.key}_id",
'label': prop.key,
'type': 'select',
'options': [
{'value': getattr(obj, 'id'), 'label': str(obj)}
for obj in options
]
})
fk_fields.add(f"{prop.key}_id")
# Now add basic columns — excluding FKs already covered
for col in model_cls.__table__.columns:
if col.name in fk_fields:
continue
if col.name in ('id', 'created_at', 'updated_at'):
continue
if col.default or col.server_default or col.onupdate:
continue
fields.append({
'name': col.name,
'label': col.name,
'type': 'text',
})
return template.render(fields=fields, values=values, render_field=render_field)

View file

@ -0,0 +1,16 @@
<label for="{{ field_name }}">{{ field_label }}</label>
{% if field_type == 'select' %}
<select name="{{ field_name }}" {%- if not options %}disabled{% endif %}>
{% if options %}
<option value="">-- Select --</option>
{% for opt in options %}
<option value="{{ opt.value }}" {% if opt.value|string == value|string %}selected{% endif %}>{{ opt.label }}</option>
{% endfor %}
{% else %}
<option value="">-- No selection available --</option>
{% endif %}
</select>
{% else %}
<input type="text" name="{{ field_name }}" value="{{ value }}">
{% endif %}

View file

@ -0,0 +1,6 @@
<form method="POST">
{% for field in fields %}
{{ render_field(field, values.get(field.name, '')) }}
{% endfor %}
<button type="submit">Create</button>
</form>

View file

@ -0,0 +1,12 @@
<table>
{% if objects %}
<tr>
{% for field in objects[0].__table__.columns %}<th>{{ field.name }}</th>{% endfor %}
</tr>
{% for obj in objects %}
<tr>{% for field in obj.__table__.columns %}<td>{{ obj[field.name] }}</td>{% endfor %}</tr>
{% endfor %}
{% else %}
<tr><th>No data.</th></tr>
{% endif %}
</table>