Added database support for finding empty fields
This commit is contained in:
@@ -61,6 +61,7 @@ ignore = [
|
|||||||
"EM102",
|
"EM102",
|
||||||
"DTZ003",
|
"DTZ003",
|
||||||
"PLR0913",
|
"PLR0913",
|
||||||
|
"SIM108",
|
||||||
]
|
]
|
||||||
line-length = 120
|
line-length = 120
|
||||||
target-version = "py39"
|
target-version = "py39"
|
||||||
|
|||||||
@@ -9,7 +9,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
from sqlalchemy.orm import contains_eager, joinedload
|
from sqlalchemy.orm import contains_eager, joinedload
|
||||||
|
|
||||||
from spoolman.database import models, vendor
|
from spoolman.database import models, vendor
|
||||||
from spoolman.database.utils import SortOrder, parse_nested_field
|
from spoolman.database.utils import (
|
||||||
|
SortOrder,
|
||||||
|
add_where_clause,
|
||||||
|
add_where_clause_str,
|
||||||
|
add_where_clause_str_opt,
|
||||||
|
parse_nested_field,
|
||||||
|
)
|
||||||
from spoolman.exceptions import ItemDeleteError, ItemNotFoundError
|
from spoolman.exceptions import ItemDeleteError, ItemNotFoundError
|
||||||
|
|
||||||
|
|
||||||
@@ -67,7 +73,7 @@ async def get_by_id(db: AsyncSession, filament_id: int) -> models.Filament:
|
|||||||
return filament
|
return filament
|
||||||
|
|
||||||
|
|
||||||
async def find( # noqa: C901
|
async def find(
|
||||||
*,
|
*,
|
||||||
db: AsyncSession,
|
db: AsyncSession,
|
||||||
vendor_name: Optional[str] = None,
|
vendor_name: Optional[str] = None,
|
||||||
@@ -91,16 +97,12 @@ async def find( # noqa: C901
|
|||||||
.options(contains_eager(models.Filament.vendor))
|
.options(contains_eager(models.Filament.vendor))
|
||||||
.join(models.Filament.vendor, isouter=True)
|
.join(models.Filament.vendor, isouter=True)
|
||||||
)
|
)
|
||||||
if vendor_name is not None:
|
|
||||||
stmt = stmt.where(models.Vendor.name.ilike(f"%{vendor_name}%"))
|
stmt = add_where_clause(stmt, models.Filament.vendor_id, vendor_id)
|
||||||
if vendor_id is not None:
|
stmt = add_where_clause_str(stmt, models.Vendor.name, vendor_name)
|
||||||
stmt = stmt.where(models.Filament.vendor_id == vendor_id)
|
stmt = add_where_clause_str_opt(stmt, models.Filament.name, name)
|
||||||
if name is not None:
|
stmt = add_where_clause_str_opt(stmt, models.Filament.material, material)
|
||||||
stmt = stmt.where(models.Filament.name.ilike(f"%{name}%"))
|
stmt = add_where_clause_str_opt(stmt, models.Filament.article_number, article_number)
|
||||||
if material is not None:
|
|
||||||
stmt = stmt.where(models.Filament.material.ilike(f"%{material}%"))
|
|
||||||
if article_number is not None:
|
|
||||||
stmt = stmt.where(models.Filament.article_number.ilike(f"%{article_number}%"))
|
|
||||||
|
|
||||||
total_count = None
|
total_count = None
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,13 @@ 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, parse_nested_field
|
from spoolman.database.utils import (
|
||||||
|
SortOrder,
|
||||||
|
add_where_clause,
|
||||||
|
add_where_clause_str,
|
||||||
|
add_where_clause_str_opt,
|
||||||
|
parse_nested_field,
|
||||||
|
)
|
||||||
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
|
||||||
|
|
||||||
@@ -76,7 +82,7 @@ async def get_by_id(db: AsyncSession, spool_id: int) -> models.Spool:
|
|||||||
return spool
|
return spool
|
||||||
|
|
||||||
|
|
||||||
async def find( # noqa: C901, PLR0912
|
async def find(
|
||||||
*,
|
*,
|
||||||
db: AsyncSession,
|
db: AsyncSession,
|
||||||
filament_name: Optional[str] = None,
|
filament_name: Optional[str] = None,
|
||||||
@@ -104,20 +110,15 @@ async def find( # noqa: C901, PLR0912
|
|||||||
.join(models.Filament.vendor, isouter=True)
|
.join(models.Filament.vendor, isouter=True)
|
||||||
.options(contains_eager(models.Spool.filament).contains_eager(models.Filament.vendor))
|
.options(contains_eager(models.Spool.filament).contains_eager(models.Filament.vendor))
|
||||||
)
|
)
|
||||||
if filament_name is not None:
|
|
||||||
stmt = stmt.where(models.Filament.name.ilike(f"%{filament_name}%"))
|
stmt = add_where_clause(stmt, models.Spool.filament_id, filament_id)
|
||||||
if filament_id is not None:
|
stmt = add_where_clause(stmt, models.Filament.vendor_id, vendor_id)
|
||||||
stmt = stmt.where(models.Spool.filament_id == filament_id)
|
stmt = add_where_clause_str(stmt, models.Vendor.name, vendor_name)
|
||||||
if filament_material is not None:
|
stmt = add_where_clause_str_opt(stmt, models.Filament.name, filament_name)
|
||||||
stmt = stmt.where(models.Filament.material.ilike(f"%{filament_material}%"))
|
stmt = add_where_clause_str_opt(stmt, models.Filament.material, filament_material)
|
||||||
if vendor_name is not None:
|
stmt = add_where_clause_str_opt(stmt, models.Spool.location, location)
|
||||||
stmt = stmt.where(models.Vendor.name.ilike(f"%{vendor_name}%"))
|
stmt = add_where_clause_str_opt(stmt, models.Spool.lot_nr, lot_nr)
|
||||||
if vendor_id is not None:
|
|
||||||
stmt = stmt.where(models.Filament.vendor_id == vendor_id)
|
|
||||||
if location is not None:
|
|
||||||
stmt = stmt.where(models.Spool.location.ilike(f"%{location}%"))
|
|
||||||
if lot_nr is not None:
|
|
||||||
stmt = stmt.where(models.Spool.lot_nr.ilike(f"%{lot_nr}%"))
|
|
||||||
if not allow_archived:
|
if not allow_archived:
|
||||||
# Since the archived field is nullable, and default is false, we need to check for both false or null
|
# Since the archived field is nullable, and default is false, we need to check for both false or null
|
||||||
stmt = stmt.where(
|
stmt = stmt.where(
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
"""Utility functions for the database module."""
|
"""Utility functions for the database module."""
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any
|
from typing import Any, Optional, TypeVar
|
||||||
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
from sqlalchemy import Select
|
||||||
|
from sqlalchemy.orm import attributes
|
||||||
|
|
||||||
from spoolman.database import models
|
from spoolman.database import models
|
||||||
|
|
||||||
@@ -13,7 +15,7 @@ class SortOrder(Enum):
|
|||||||
DESC = 2
|
DESC = 2
|
||||||
|
|
||||||
|
|
||||||
def parse_nested_field(base_obj: type[models.Base], field: str) -> sqlalchemy.Column[Any]:
|
def parse_nested_field(base_obj: type[models.Base], field: str) -> attributes.InstrumentedAttribute[Any]:
|
||||||
"""Parse a nested field string into a sqlalchemy field object."""
|
"""Parse a nested field string into a sqlalchemy field object."""
|
||||||
fields = field.split(".")
|
fields = field.split(".")
|
||||||
if not hasattr(base_obj, fields[0]):
|
if not hasattr(base_obj, fields[0]):
|
||||||
@@ -33,3 +35,45 @@ def parse_nested_field(base_obj: type[models.Base], field: str) -> sqlalchemy.Co
|
|||||||
raise ValueError(f"Field '{fields[0]}' does not have any nested fields")
|
raise ValueError(f"Field '{fields[0]}' does not have any nested fields")
|
||||||
|
|
||||||
return getattr(base_obj, fields[0])
|
return getattr(base_obj, fields[0])
|
||||||
|
|
||||||
|
|
||||||
|
def add_where_clause_str_opt(
|
||||||
|
stmt: Select,
|
||||||
|
field: attributes.InstrumentedAttribute[Optional[str]],
|
||||||
|
value: Optional[str],
|
||||||
|
) -> Select:
|
||||||
|
"""Add a where clause to a select statement for an optional string field."""
|
||||||
|
if value is not None:
|
||||||
|
if len(value) == 0:
|
||||||
|
stmt = stmt.where(sqlalchemy.or_(field.is_(None), field == ""))
|
||||||
|
else:
|
||||||
|
stmt = stmt.where(field.ilike(f"%{value}%"))
|
||||||
|
return stmt
|
||||||
|
|
||||||
|
|
||||||
|
def add_where_clause_str(
|
||||||
|
stmt: Select,
|
||||||
|
field: attributes.InstrumentedAttribute[str],
|
||||||
|
value: Optional[str],
|
||||||
|
) -> Select:
|
||||||
|
"""Add a where clause to a select statement for a string field."""
|
||||||
|
if value is not None:
|
||||||
|
if len(value) == 0:
|
||||||
|
stmt = stmt.where(field == "")
|
||||||
|
else:
|
||||||
|
stmt = stmt.where(field.ilike(f"%{value}%"))
|
||||||
|
return stmt
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
def add_where_clause(
|
||||||
|
stmt: Select,
|
||||||
|
field: attributes.InstrumentedAttribute[T],
|
||||||
|
value: Optional[T],
|
||||||
|
) -> Select:
|
||||||
|
"""Add a where clause to a select statement for a field."""
|
||||||
|
if value is not None:
|
||||||
|
stmt = stmt.where(field == value)
|
||||||
|
return stmt
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from sqlalchemy import func, select
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from spoolman.database import models
|
from spoolman.database import models
|
||||||
from spoolman.database.utils import SortOrder
|
from spoolman.database.utils import SortOrder, add_where_clause_str
|
||||||
from spoolman.exceptions import ItemNotFoundError
|
from spoolman.exceptions import ItemNotFoundError
|
||||||
|
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ async def find(
|
|||||||
Returns a tuple containing the list of items and the total count of matching items.
|
Returns a tuple containing the list of items and the total count of matching items.
|
||||||
"""
|
"""
|
||||||
stmt = select(models.Vendor)
|
stmt = select(models.Vendor)
|
||||||
if name is not None:
|
|
||||||
stmt = stmt.where(models.Vendor.name.ilike(f"%{name}%"))
|
stmt = add_where_clause_str(stmt, models.Vendor.name, name)
|
||||||
|
|
||||||
total_count = None
|
total_count = None
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user