Adding pervasive column sorting
This commit is contained in:
1 parent
34679d53e8
commit
2fb662080b
27 files changed
+614
-111
No files matched your search
@@ -4,9 +4,47 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.models.associations import augmented_part_imported_parts
|
||||
from app.models.augmented_part import AugmentedPart
|
||||
from app.models.imported_part import ImportedPart
|
||||
from app.models.manufacturer import Manufacturer
|
||||
from app.models.part_colour import PartColour
|
||||
from app.models.part_type import PartType
|
||||
from app.models.scope_model import ScopeModel
|
||||
from app.models.sub_part_type import SubPartType
|
||||
|
||||
_RELATIONSHIPS = ["colour", "manufacturer", "scope_model", "part_type", "sub_part_type"]
|
||||
|
||||
# Sortable columns for the admin list, keyed by the query-string "sort" value.
|
||||
# Columns backed by a related table's name require an outer join (the FK may be
|
||||
# nullable, e.g. scope_model_id) so unrelated rows still appear, ordered last.
|
||||
SORT_COLUMNS = {
|
||||
"id": (AugmentedPart.id, None),
|
||||
"sku": (AugmentedPart.sku, None),
|
||||
"description": (AugmentedPart.description, None),
|
||||
"colour": (PartColour.name, (PartColour, AugmentedPart.colour_id == PartColour.id)),
|
||||
"manufacturer": (
|
||||
Manufacturer.name,
|
||||
(Manufacturer, AugmentedPart.manufacturer_id == Manufacturer.id),
|
||||
),
|
||||
"scope_model": (
|
||||
ScopeModel.name,
|
||||
(ScopeModel, AugmentedPart.scope_model_id == ScopeModel.id),
|
||||
),
|
||||
"part_type": (PartType.name, (PartType, AugmentedPart.part_type_id == PartType.id)),
|
||||
"sub_part_type": (
|
||||
SubPartType.name,
|
||||
(SubPartType, AugmentedPart.sub_part_type_id == SubPartType.id),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _apply_sort(q, sort: str, direction: str):
|
||||
column, join = SORT_COLUMNS.get(sort, SORT_COLUMNS["description"])
|
||||
if join is not None:
|
||||
table, cond = join
|
||||
q = q.outerjoin(table, cond)
|
||||
order = column.desc() if direction == "desc" else column.asc()
|
||||
# AugmentedPart.id as a tiebreaker keeps paginated ordering stable.
|
||||
return q.order_by(order, AugmentedPart.id)
|
||||
|
||||
|
||||
async def get(db: AsyncSession, augmented_part_id: int) -> AugmentedPart | None:
|
||||
return await db.get(AugmentedPart, augmented_part_id)
|
||||
@@ -35,9 +73,7 @@ async def get_many(db: AsyncSession, ids: list[int]) -> list[AugmentedPart]:
|
||||
"""Fetch augmented parts by id in one query (selectin relationships loaded)."""
|
||||
if not ids:
|
||||
return []
|
||||
result = await db.execute(
|
||||
select(AugmentedPart).where(AugmentedPart.id.in_(ids))
|
||||
)
|
||||
result = await db.execute(select(AugmentedPart).where(AugmentedPart.id.in_(ids)))
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
@@ -49,6 +85,8 @@ async def list_filtered(
|
||||
scope_model_id: int | None = None,
|
||||
part_type_id: int | None = None,
|
||||
sub_part_type_id: int | None = None,
|
||||
sort: str = "description",
|
||||
direction: str = "asc",
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
) -> list[AugmentedPart]:
|
||||
@@ -65,9 +103,8 @@ async def list_filtered(
|
||||
q = q.where(AugmentedPart.part_type_id == part_type_id)
|
||||
if sub_part_type_id is not None:
|
||||
q = q.where(AugmentedPart.sub_part_type_id == sub_part_type_id)
|
||||
result = await db.execute(
|
||||
q.order_by(AugmentedPart.description).offset(skip).limit(limit)
|
||||
)
|
||||
q = _apply_sort(q, sort, direction)
|
||||
result = await db.execute(q.offset(skip).limit(limit))
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
|
||||
@@ -16,14 +16,37 @@ _FIELDS = frozenset(
|
||||
}
|
||||
)
|
||||
|
||||
# Sortable columns for the admin list, keyed by the query-string "sort" value.
|
||||
SORT_COLUMNS = {
|
||||
"InventoryID": ImportedPart.InventoryID,
|
||||
"Description": ImportedPart.Description,
|
||||
"TBXColour": ImportedPart.TBXColour,
|
||||
"ProductMaxThreadDepthmm": ImportedPart.ProductMaxThreadDepthmm,
|
||||
"ProductMaxThreadDepthin": ImportedPart.ProductMaxThreadDepthin,
|
||||
"ProductODmm": ImportedPart.ProductODmm,
|
||||
"ProductODin": ImportedPart.ProductODin,
|
||||
"TBXThreadAttribute": ImportedPart.TBXThreadAttribute,
|
||||
}
|
||||
|
||||
|
||||
def _apply_sort(q, sort: str, direction: str):
|
||||
column = SORT_COLUMNS.get(sort, ImportedPart.InventoryID)
|
||||
order = column.desc() if direction == "desc" else column.asc()
|
||||
return q.order_by(order, ImportedPart.InventoryID)
|
||||
|
||||
|
||||
async def get(db: AsyncSession, inventory_id: str) -> ImportedPart | None:
|
||||
return await db.get(ImportedPart, inventory_id)
|
||||
|
||||
|
||||
async def list_all(db: AsyncSession, skip: int = 0, limit: int = 500) -> list[ImportedPart]:
|
||||
async def list_all(
|
||||
db: AsyncSession, skip: int = 0, limit: int = 500
|
||||
) -> list[ImportedPart]:
|
||||
result = await db.execute(
|
||||
select(ImportedPart).order_by(ImportedPart.InventoryID).offset(skip).limit(limit)
|
||||
select(ImportedPart)
|
||||
.order_by(ImportedPart.InventoryID)
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
@@ -31,13 +54,16 @@ async def list_all(db: AsyncSession, skip: int = 0, limit: int = 500) -> list[Im
|
||||
async def list_filtered(
|
||||
db: AsyncSession,
|
||||
description: str = "",
|
||||
sort: str = "InventoryID",
|
||||
direction: str = "asc",
|
||||
skip: int = 0,
|
||||
limit: int = 500,
|
||||
) -> list[ImportedPart]:
|
||||
q = select(ImportedPart)
|
||||
if description.strip():
|
||||
q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%"))
|
||||
result = await db.execute(q.order_by(ImportedPart.InventoryID).offset(skip).limit(limit))
|
||||
q = _apply_sort(q, sort, direction)
|
||||
result = await db.execute(q.offset(skip).limit(limit))
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
@@ -78,7 +104,11 @@ async def upsert_bulk(db: AsyncSession, records: list[dict]) -> tuple[int, int]:
|
||||
return 0, 0
|
||||
|
||||
clean = [
|
||||
{k: str(v).strip() if v is not None else None for k, v in r.items() if k in _FIELDS}
|
||||
{
|
||||
k: str(v).strip() if v is not None else None
|
||||
for k, v in r.items()
|
||||
if k in _FIELDS
|
||||
}
|
||||
for r in records
|
||||
if r.get("InventoryID")
|
||||
]
|
||||
@@ -88,7 +118,9 @@ async def upsert_bulk(db: AsyncSession, records: list[dict]) -> tuple[int, int]:
|
||||
incoming_ids = {r["InventoryID"] for r in clean}
|
||||
|
||||
result = await db.execute(
|
||||
select(ImportedPart.InventoryID).where(ImportedPart.InventoryID.in_(incoming_ids))
|
||||
select(ImportedPart.InventoryID).where(
|
||||
ImportedPart.InventoryID.in_(incoming_ids)
|
||||
)
|
||||
)
|
||||
existing_ids = {row[0] for row in result.all()}
|
||||
|
||||
@@ -103,7 +135,9 @@ async def upsert_bulk(db: AsyncSession, records: list[dict]) -> tuple[int, int]:
|
||||
vals = {k: v for k, v in record.items() if k != "InventoryID"}
|
||||
if vals:
|
||||
await db.execute(
|
||||
update(ImportedPart).where(ImportedPart.InventoryID == inv_id).values(vals)
|
||||
update(ImportedPart)
|
||||
.where(ImportedPart.InventoryID == inv_id)
|
||||
.values(vals)
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
|
||||
Reference in new issue
Block a user