Serverside spool sorting

This commit is contained in:
Donkie
2023-08-25 07:21:57 +02:00
parent 38d42746d1
commit 4fdea75e3f
3 changed files with 62 additions and 2 deletions

View File

@@ -14,6 +14,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from spoolman.api.v1.models import Message, Spool from spoolman.api.v1.models import Message, Spool
from spoolman.database import spool from spoolman.database import spool
from spoolman.database.database import get_db_session from spoolman.database.database import get_db_session
from spoolman.database.utils import SortOrder
from spoolman.exceptions import ItemCreateError from spoolman.exceptions import ItemCreateError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -110,7 +111,21 @@ async def find(
title="Allow Archived", title="Allow Archived",
description="Whether to include archived spools in the search results.", description="Whether to include archived spools in the search results.",
), ),
sort: Optional[str] = Query(
default=None,
title="Sort",
description=(
'Sort the results by the given field. Should be a comma-separate string with "field:direction" items.'
),
example="filament.name:asc,location:desc",
),
) -> list[Spool]: ) -> list[Spool]:
sort_by: dict[str, SortOrder] = {}
if sort is not None:
for sort_item in sort.split(","):
field, direction = sort_item.split(":")
sort_by[field] = SortOrder[direction.upper()]
db_items = await spool.find( db_items = await spool.find(
db=db, db=db,
filament_name=filament_name, filament_name=filament_name,
@@ -121,6 +136,7 @@ async def find(
location=location, location=location,
lot_nr=lot_nr, lot_nr=lot_nr,
allow_archived=allow_archived, allow_archived=allow_archived,
sort_by=sort_by,
) )
return [Spool.from_db(db_item) for db_item in db_items] return [Spool.from_db(db_item) for db_item in db_items]

View File

@@ -10,6 +10,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import contains_eager, joinedload from sqlalchemy.orm import contains_eager, joinedload
from spoolman.database import filament, models from spoolman.database import filament, models
from spoolman.database.utils import SortOrder
from spoolman.exceptions import ItemCreateError, ItemNotFoundError from spoolman.exceptions import ItemCreateError, ItemNotFoundError
from spoolman.math import weight_from_length from spoolman.math import weight_from_length
@@ -75,7 +76,29 @@ async def get_by_id(db: AsyncSession, spool_id: int) -> models.Spool:
return spool return spool
async def find( def parse_nested_field(base_obj: type[models.Base], field: str) -> sqlalchemy.Column:
"""Parse a nested field string into a sqlalchemy field object."""
fields = field.split(".")
if not hasattr(base_obj, fields[0]):
raise ValueError(f"Invalid field name '{field}'")
if fields[0] == "filament" and len(fields) == 1:
raise ValueError("No field specified for filament")
if fields[0] == "filament":
return parse_nested_field(models.Filament, ".".join(fields[1:]))
if fields[0] == "vendor" and len(fields) == 1:
raise ValueError("No field specified for vendor")
if fields[0] == "vendor":
return parse_nested_field(models.Vendor, ".".join(fields[1:]))
if len(fields) > 1:
raise ValueError(f"Field '{fields[0]}' does not have any nested fields")
return getattr(base_obj, fields[0])
async def find( # noqa: C901
*, *,
db: AsyncSession, db: AsyncSession,
filament_name: Optional[str] = None, filament_name: Optional[str] = None,
@@ -86,8 +109,13 @@ async def find(
location: Optional[str] = None, location: Optional[str] = None,
lot_nr: Optional[str] = None, lot_nr: Optional[str] = None,
allow_archived: bool = False, allow_archived: bool = False,
sort_by: Optional[dict[str, SortOrder]] = None,
) -> list[models.Spool]: ) -> list[models.Spool]:
"""Find a list of spool objects by search criteria.""" """Find a list of spool objects by search criteria.
Sort by a field by passing a dict with the field name as key and the sort order as value.
The field name can contain nested fields, e.g. filament.name.
"""
stmt = ( stmt = (
sqlalchemy.select(models.Spool) sqlalchemy.select(models.Spool)
.join(models.Spool.filament, isouter=True) .join(models.Spool.filament, isouter=True)
@@ -117,6 +145,14 @@ async def find(
), ),
) )
if sort_by is not None:
for fieldstr, order in sort_by.items():
field = parse_nested_field(models.Spool, fieldstr)
if order == SortOrder.ASC:
stmt = stmt.order_by(field.asc())
elif order == SortOrder.DESC:
stmt = stmt.order_by(field.desc())
rows = await db.execute(stmt) rows = await db.execute(stmt)
return list(rows.scalars().all()) return list(rows.scalars().all())

View File

@@ -0,0 +1,8 @@
"""Utility functions for the database module."""
from enum import Enum
class SortOrder(Enum):
ASC = 1
DESC = 2