More changes brought through testing.
This commit is contained in:
parent
40e727f5bf
commit
c22ecf44ec
9 changed files with 149 additions and 11 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -10,4 +10,3 @@ inventory/static/uploads/*
|
|||
alembic.ini
|
||||
alembic/
|
||||
*.egg-info/
|
||||
test_app/
|
||||
|
|
@ -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>')
|
||||
|
|
|
|||
|
|
@ -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
53
crudkit/core/spec.py
Normal 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
|
||||
|
|
@ -1,8 +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>
|
||||
31
test_app/app.py
Normal file
31
test_app/app.py
Normal 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
6
test_app/db.py
Normal 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
18
test_app/models.py
Normal 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')
|
||||
14
test_app/templates/index.html
Normal file
14
test_app/templates/index.html
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue