104 lines
3.5 KiB
Python
104 lines
3.5 KiB
Python
"""Top-level, read-only scope fit guide at ``/scopes``.
|
|
|
|
The site's primary view: a table of scopes with their specs, plus one column per
|
|
non-Scope part type listing the accessories that fit each scope. Supports HTMX
|
|
search/filter over scope attributes and required accessory fits.
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, Request
|
|
from fastapi.responses import HTMLResponse
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.database import get_db
|
|
from app.crud import manufacturer as manufacturer_crud
|
|
from app.crud import part_type as part_type_crud
|
|
from app.crud import scope_model as scope_model_crud
|
|
from app.crud import sub_part_type as sub_part_type_crud
|
|
from app.models.user import User
|
|
from app.services import scope_guide as scope_guide_service
|
|
from app.web.admin import web_require
|
|
from app.web.templates import templates
|
|
|
|
router = APIRouter(include_in_schema=False)
|
|
|
|
|
|
def _to_int(value: str) -> int | None:
|
|
return int(value) if value.strip() else None
|
|
|
|
|
|
async def _build(db: AsyncSession, filters: dict) -> dict:
|
|
"""Run the guide query for the given raw (string) filter values."""
|
|
return await scope_guide_service.build(
|
|
db,
|
|
description=filters["description"],
|
|
manufacturer_id=_to_int(filters["manufacturer_id"]),
|
|
scope_model_id=_to_int(filters["scope_model_id"]),
|
|
sub_part_type_id=_to_int(filters["sub_part_type_id"]),
|
|
fits_part_type_id=_to_int(filters["fits_part_type_id"]),
|
|
)
|
|
|
|
|
|
async def _filter_options(db: AsyncSession) -> dict:
|
|
"""Dropdown option lists for the filter bar."""
|
|
scope_type = await part_type_crud.get_by_name(
|
|
db, scope_guide_service.SCOPE_TYPE_NAME
|
|
)
|
|
sub_part_types = [
|
|
spt
|
|
for spt in await sub_part_type_crud.list_all(db, limit=1000)
|
|
if scope_type is not None and spt.part_type_id == scope_type.id
|
|
]
|
|
return {
|
|
"manufacturers": await manufacturer_crud.list_all(db, limit=1000),
|
|
"scope_models": await scope_model_crud.list_all(db, limit=1000),
|
|
"sub_part_types": sub_part_types,
|
|
}
|
|
|
|
|
|
@router.get("/scopes", response_class=HTMLResponse)
|
|
async def scopes_page(
|
|
request: Request,
|
|
description: str = "",
|
|
manufacturer_id: str = "",
|
|
scope_model_id: str = "",
|
|
sub_part_type_id: str = "",
|
|
fits_part_type_id: str = "",
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(web_require("augmented_parts:read", "fits:read")),
|
|
):
|
|
filters = {
|
|
"description": description,
|
|
"manufacturer_id": manufacturer_id,
|
|
"scope_model_id": scope_model_id,
|
|
"sub_part_type_id": sub_part_type_id,
|
|
"fits_part_type_id": fits_part_type_id,
|
|
}
|
|
guide = await _build(db, filters)
|
|
return templates.TemplateResponse(
|
|
request,
|
|
"scopes.html",
|
|
{"user": user, "filters": filters, **guide, **(await _filter_options(db))},
|
|
)
|
|
|
|
|
|
@router.get("/scopes/filter", response_class=HTMLResponse)
|
|
async def scopes_filter(
|
|
request: Request,
|
|
description: str = "",
|
|
manufacturer_id: str = "",
|
|
scope_model_id: str = "",
|
|
sub_part_type_id: str = "",
|
|
fits_part_type_id: str = "",
|
|
db: AsyncSession = Depends(get_db),
|
|
_: User = Depends(web_require("augmented_parts:read", "fits:read")),
|
|
):
|
|
filters = {
|
|
"description": description,
|
|
"manufacturer_id": manufacturer_id,
|
|
"scope_model_id": scope_model_id,
|
|
"sub_part_type_id": sub_part_type_id,
|
|
"fits_part_type_id": fits_part_type_id,
|
|
}
|
|
guide = await _build(db, filters)
|
|
return templates.TemplateResponse(request, "_scopes_table_card.html", guide)
|