Fix a lot of missing relationship information, plus some upstream CRUDKit changes.
This commit is contained in:
parent
8643d177ca
commit
cf56baabe2
11 changed files with 82 additions and 22 deletions
|
|
@ -10,7 +10,13 @@ class CRUDMixin:
|
|||
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
|
||||
|
||||
def as_dict(self):
|
||||
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
|
||||
# Combine all columns from all inherited tables
|
||||
result = {}
|
||||
for cls in self.__class__.__mro__:
|
||||
if hasattr(cls, "__table__"):
|
||||
for column in cls.__table__.columns:
|
||||
result[column.name] = getattr(self, column.name)
|
||||
return result
|
||||
|
||||
class Version(Base):
|
||||
__tablename__ = "versions"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from typing import Type, TypeVar, Generic
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm import Session, with_polymorphic
|
||||
from crudkit.core.base import Version
|
||||
from crudkit.core.spec import CRUDSpec
|
||||
|
||||
|
|
@ -9,13 +9,22 @@ def _is_truthy(val):
|
|||
return str(val).lower() in ('1', 'true', 'yes', 'on')
|
||||
|
||||
class CRUDService(Generic[T]):
|
||||
def __init__(self, model: Type[T], session: Session):
|
||||
def __init__(self, model: Type[T], session: Session, polymorphic: bool = False):
|
||||
self.model = model
|
||||
self.session = session
|
||||
self.polymorphic = polymorphic
|
||||
self.supports_soft_delete = hasattr(model, 'is_deleted')
|
||||
|
||||
def get_query(self):
|
||||
if self.polymorphic:
|
||||
poly_model = with_polymorphic(self.model, '*')
|
||||
return self.session.query(poly_model)
|
||||
else:
|
||||
base_only = with_polymorphic(self.model, [], flat=True)
|
||||
return self.session.query(base_only)
|
||||
|
||||
def get(self, id: int, include_deleted: bool = False) -> T | None:
|
||||
obj = self.session.get(self.model, id)
|
||||
obj = self.get_query().filter_by(id=id).first()
|
||||
if obj is None:
|
||||
return None
|
||||
if self.supports_soft_delete and not include_deleted and obj.is_deleted:
|
||||
|
|
@ -23,7 +32,7 @@ class CRUDService(Generic[T]):
|
|||
return obj
|
||||
|
||||
def list(self, params=None) -> list[T]:
|
||||
query = self.session.query(self.model)
|
||||
query = self.get_query()
|
||||
|
||||
if params:
|
||||
if self.supports_soft_delete:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ import os
|
|||
|
||||
from flask import Flask
|
||||
|
||||
from .db import init_db, create_all_tables
|
||||
from crudkit.api.flask_api import generate_crud_blueprint
|
||||
from crudkit.core.service import CRUDService
|
||||
|
||||
from .db import init_db, create_all_tables, get_session
|
||||
|
||||
def create_app() -> Flask:
|
||||
app = Flask(__name__)
|
||||
|
|
@ -16,6 +19,21 @@ def create_app() -> Flask:
|
|||
|
||||
create_all_tables()
|
||||
|
||||
session = get_session()
|
||||
|
||||
area_service = CRUDService(_models.Area, session)
|
||||
brand_service = CRUDService(_models.Brand, session)
|
||||
device_type_service = CRUDService(_models.DeviceType, session)
|
||||
image_service = CRUDService(_models.Image, session)
|
||||
inventory_service = CRUDService(_models.Inventory, session)
|
||||
room_function_service = CRUDService(_models.RoomFunction, session)
|
||||
room_service = CRUDService(_models.Room, session)
|
||||
user_service = CRUDService(_models.User, session)
|
||||
work_log_service = CRUDService(_models.WorkLog, session)
|
||||
work_note_service = CRUDService(_models.WorkNote, session)
|
||||
|
||||
app.register_blueprint(generate_crud_blueprint(_models.Area, area_service), url_prefix="/api/area")
|
||||
|
||||
@app.get("/")
|
||||
def index():
|
||||
return {"status": "ok"}
|
||||
|
|
|
|||
|
|
@ -4,3 +4,19 @@ from sqlalchemy import Integer, Unicode
|
|||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from crudkit.core.base import Base, CRUDMixin
|
||||
|
||||
from .area import Area
|
||||
from .brand import Brand
|
||||
from .device_type import DeviceType
|
||||
from .image import Image
|
||||
from .inventory import Inventory
|
||||
from .room_function import RoomFunction
|
||||
from .room import Room
|
||||
from .user import User
|
||||
from .work_log import WorkLog
|
||||
from .work_note import WorkNote
|
||||
|
||||
__all__ = [
|
||||
"Area", "Brand", "DeviceType", "Image", "Inventory",
|
||||
"RoomFunction", "Room", "User", "WorkLog", "WorkNote",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -15,4 +15,4 @@ class Brand(Base, CRUDMixin):
|
|||
is_deleted: Mapped[Boolean] = mapped_column(Boolean, nullable=False, default=False)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Brand(id={self.id}, name={repr(self.name)})>"4
|
||||
return f"<Brand(id={self.id}, name={repr(self.name)})>"
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ class Image(Base, CRUDMixin):
|
|||
caption: Mapped[str] = mapped_column(Unicode(255), default="")
|
||||
timestamp: Mapped[DateTime] = mapped_column(DateTime, default=func.now(), server_default=func.now())
|
||||
|
||||
inventory: Mapped[Optional['Inventory']] = relationship('Inventory', back_populates='images')
|
||||
inventory: Mapped[Optional['Inventory']] = relationship('Inventory', back_populates='image')
|
||||
user: Mapped[Optional['User']] = relationship('User', back_populates='image')
|
||||
worklogs: Mapped[List['WorkLog']] = relationship('WorkLog', secondary=worklog_images, back_populates='images')
|
||||
# worklogs: Mapped[List['WorkLog']] = relationship('WorkLog', back_populates='images')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Image(id={self.id}, filename={self.filename})>"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, Unicode
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
|
@ -15,11 +15,11 @@ class Inventory(Base, CRUDMixin):
|
|||
condition: Mapped[str] = mapped_column(Unicode(255))
|
||||
model: Mapped[Optional[str]] = mapped_column(Unicode(255))
|
||||
notes: Mapped[Optional[str]] = mapped_column(Unicode(255))
|
||||
shared = Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
shared: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
timestamp: Mapped[DateTime] = mapped_column(DateTime)
|
||||
|
||||
brand: Mapped[Optional['Brand']] = relationship('Brand', back_populates='inventory')
|
||||
brand_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey('users.id'), nullable=True, index=True)
|
||||
brand_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey('brand.id'), nullable=True, index=True)
|
||||
|
||||
device_type: Mapped[Optional['DeviceType']] = relationship('DeviceType', back_populates='inventory')
|
||||
device_type_id: Mapped[Optional[int]] = mapped_column('type_id', Integer, ForeignKey("item.id"), nullable=True, index=True)
|
||||
|
|
@ -27,12 +27,14 @@ class Inventory(Base, CRUDMixin):
|
|||
image: Mapped[Optional['Image']] = relationship('Image', back_populates='inventory', passive_deletes=True)
|
||||
image_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey('images.id', ondelete='SET NULL'), nullable=True, index=True)
|
||||
|
||||
location = Mapped[Optional['Room']] = relationship('Room', back_populates='inventory')
|
||||
location_id = Mapped[Optional[int]] = mapped_column(Integer, ForeignKey('rooms.id'), nullable=True, index=True)
|
||||
location: Mapped[Optional['Room']] = relationship('Room', back_populates='inventory')
|
||||
location_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey('rooms.id'), nullable=True, index=True)
|
||||
|
||||
owner: Mapped[Optional['User']] = relationship('User', back_populates='inventory')
|
||||
owner_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id"), nullable=True, index=True)
|
||||
|
||||
work_logs: Mapped[Optional[List['WorkLog']]] = relationship('WorkLog', back_populates='work_item')
|
||||
|
||||
def __repr__(self):
|
||||
parts = [f"id={self.id}"]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
from typing import List, Optional
|
||||
from typing import List, Optional, TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import ForeignKey, Integer, Unicode
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from crudkit.core.base import Base, CRUDMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .user import User
|
||||
|
||||
class Room(Base, CRUDMixin):
|
||||
__tablename__ = 'rooms'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
from typing import List, Optional
|
||||
from typing import List, Optional, TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import Boolean, Integer, ForeignKey, Unicode
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from crudkit.core.base import Base, CRUDMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .room import Room
|
||||
|
||||
class User(Base, CRUDMixin):
|
||||
__tablename__ = 'users'
|
||||
|
||||
|
|
@ -20,12 +23,14 @@ class User(Base, CRUDMixin):
|
|||
|
||||
inventory: Mapped[List['Inventory']] = relationship('Inventory', back_populates='owner')
|
||||
|
||||
location: Mapped[Optional['Room']] = relationship('Room', back_populates='owner')
|
||||
location_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id"), nullable=True, index=True)
|
||||
location: Mapped[Optional['Room']] = relationship('Room', back_populates='users')
|
||||
location_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("rooms.id"), nullable=True, index=True)
|
||||
|
||||
supervisor: Mapped[Optional['User']] = relationship('User', back_populates='subordinates')
|
||||
supervisor_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("user.id"), nullable=True, index=True)
|
||||
supervisor: Mapped[Optional['User']] = relationship('User', back_populates='subordinates', remote_side='User.id')
|
||||
supervisor_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id"), nullable=True, index=True)
|
||||
subordinates: Mapped[List['User']] = relationship('User', back_populates='supervisor')
|
||||
|
||||
work_logs: Mapped[Optional[List['WorkLog']]] = relationship('WorkLog', back_populates='contact')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<User(id={self.id}, first_name={repr(self.first_name)}, last_name={repr(self.last_name)})>"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class WorkLog(Base, CRUDMixin):
|
|||
complete: Mapped[Optional[bool]] = mapped_column(Boolean, nullable=False, default=False)
|
||||
|
||||
contact: Mapped[Optional['User']] = relationship('User', back_populates='work_logs')
|
||||
contact_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("user.id"), nullable=True, index=True)
|
||||
contact_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id"), nullable=True, index=True)
|
||||
|
||||
updates: Mapped[List['WorkNote']] = relationship('WorkNote', back_populates='work_log', cascade='all, delete-orphan')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from sqlalchemy import DateTime, UnicodeText, func
|
||||
from sqlalchemy import DateTime, ForeignKey, Integer, UnicodeText, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from crudkit.core.base import Base, CRUDMixin
|
||||
|
|
@ -10,6 +10,7 @@ class WorkNote(Base, CRUDMixin):
|
|||
timestamp: Mapped[DateTime] = mapped_column(DateTime, default=func.now(), server_default=func.now())
|
||||
|
||||
work_log: Mapped['WorkLog'] = relationship('WorkLog', back_populates='updates')
|
||||
work_log_id: Mapped[int] = mapped_column(Integer, ForeignKey('work_log.id'))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
preview = self.content[:30].replace("\n", " ") + "..." if len(self.content) > 30 else self.content
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue