Redesign1 #1

Merged
yaro merged 36 commits from Redesign1 into main 2025-09-22 14:12:39 -05:00
9 changed files with 149 additions and 11 deletions
Showing only changes of commit c22ecf44ec - Show all commits

1
.gitignore vendored
View file

@ -10,4 +10,3 @@ inventory/static/uploads/*
alembic.ini
alembic/
*.egg-info/
test_app/

View file

@ -5,7 +5,7 @@ def generate_crud_blueprint(model, service):
@bp.get('/')
def list_items():
items = service.list()
items = service.list(request.args)
return jsonify([item.as_dict() for item in items])
@bp.get('/<int:id>')

View file

@ -1,5 +1,6 @@
from typing import Type, TypeVar, Generic
from sqlalchemy.orm import Session
from crudkit.core.spec import CRUDSpec
T = TypeVar("T")
@ -11,8 +12,20 @@ class CRUDService(Generic[T]):
def get(self, id: int) -> T:
return self.session.get(self.model, id)
def list(self, limit=100, offset=0) -> list[T]:
return self.session.query(self.model).offset(offset).limit(limit).all()
def list(self, params=None) -> list[T]:
query = self.session.query(self.model)
if params:
spec = CRUDSpec(self.model, params)
filters = spec.parse_filters()
order_by = spec.parse_sort()
limit, offset = spec.parse_pagination()
if filters:
query = query.filter(*filters)
if order_by:
query = query.order_by(*order_by)
query = query.offset(offset).limit(limit)
return query.all()
def create(self, data: dict) -> T:
obj = self.model(**data)

53
crudkit/core/spec.py Normal file
View file

@ -0,0 +1,53 @@
from typing import List, Tuple
from sqlalchemy import asc, desc, or_, and_
OPERATORS = {
'eq': lambda col, val: col == val,
'lt': lambda col, val: col < val,
'lte': lambda col, val: col <= val,
'gt': lambda col, val: col > val,
'gte': lambda col, val: col >= val,
'ne': lambda col, val: col != val,
'icontains': lambda col, val: col.ilike(f"%{val}%"),
}
class CRUDSpec:
def __init__(self, model, params):
self.model = model
self.params = params
def parse_filters(self):
filters = []
for key, value in self.params.items():
if key in ('sort', 'limit', 'offset'):
continue
if '__' in key:
field, op = key.split('__', 1)
else:
field, op = key, 'eq'
if hasattr(self.model, field):
col = getattr(self.model, field)
filters.append(OPERATORS[op](col, value))
return filters
def parse_sort(self):
sort_args = self.params.get('sort', '')
result = []
for part in sort_args.split(','):
part = part.strip()
if not part:
continue
if part.startswith('-'):
field = part[1:]
order = desc
else:
field = part
order = asc
if hasattr(self.model, field):
result.append(order(getattr(self.model, field)))
return result
def parse_pagination(self):
limit = int(self.params.get('limit', 100))
offset = int(self.params.get('offset', 0))
return limit, offset

View file

@ -1,8 +1,12 @@
<table>
<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 %}
{% 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>

31
test_app/app.py Normal file
View file

@ -0,0 +1,31 @@
from flask import Flask, render_template, request, redirect, url_for
from test_app.models import Device, User
from test_app.db import Base, engine, SessionLocal
from crudkit.core.service import CRUDService
from crudkit.api.flask_api import generate_crud_blueprint
from crudkit.ui.fragments import render_table, render_form
app = Flask(__name__)
Base.metadata.create_all(engine)
session = SessionLocal()
device_service = CRUDService(Device, session)
user_service = CRUDService(User, session)
app.register_blueprint(generate_crud_blueprint(Device, device_service), url_prefix='/api/devices')
app.register_blueprint(generate_crud_blueprint(User, user_service), url_prefix='/api/users')
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
device_service.create(request.form.to_dict())
return redirect(url_for('index'))
devices = device_service.list()
table = render_table(devices)
form = render_form(Device, {})
return render_template('index.html', table=table, form=form)
if __name__ == '__main__':
app.run(debug=True, host='127.0.0.1', port=5050)

6
test_app/db.py Normal file
View file

@ -0,0 +1,6 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
engine = create_engine('sqlite:///test.db', echo=True)
SessionLocal = sessionmaker(bind=engine)
Base = declarative_base()

18
test_app/models.py Normal file
View file

@ -0,0 +1,18 @@
from sqlalchemy import Column, String, Integer, ForeignKey
from sqlalchemy.orm import relationship
from crudkit.core.base import CRUDMixin
from test_app.db import Base
class User(CRUDMixin, Base):
__tablename__ = 'users'
name = Column(String)
email = Column(String)
supervisor_id = Column(Integer, ForeignKey('users.id'))
supervisor = relationship('User', remote_side='User.id')
class Device(CRUDMixin, Base):
__tablename__ = 'devices'
name = Column(String)
serial = Column(String)
assigned_to_id = Column(Integer, ForeignKey('users.id'))
assigned_to = relationship('User')

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>Device List</title>
</head>
<body>
<h1>Devices</h1>
{{ table|safe }}
<!-- RENDERING FORM START -->
<h2>Add Device</h2>
{{ form|safe }}
</body>
</html>