144 lines
4.7 KiB
Python
144 lines
4.7 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, HTTPException, Request, Response, status
|
|
from fastapi.responses import HTMLResponse
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.database import get_db
|
|
from app.crud import audit as audit_crud
|
|
from app.crud import augmented_part as augmented_part_crud
|
|
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
|
|
|
|
|
|
def _can_write_scopes(user: User) -> bool:
|
|
return user.is_superuser or "augmented_parts:write" in user.permissions
|
|
|
|
|
|
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,
|
|
"can_write_scopes": _can_write_scopes(user),
|
|
**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: 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, "can_write_scopes": _can_write_scopes(user)},
|
|
)
|
|
|
|
|
|
@router.delete("/scopes/{augmented_part_id}")
|
|
async def delete_scope(
|
|
augmented_part_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
actor: User = Depends(web_require("augmented_parts:write")),
|
|
):
|
|
ap = await augmented_part_crud.get(db, augmented_part_id)
|
|
if ap is None:
|
|
raise HTTPException(status_code=404, detail="Scope not found")
|
|
label = ap.sku or ap.description
|
|
await augmented_part_crud.delete(db, ap)
|
|
await audit_crud.log(
|
|
db,
|
|
actor=actor,
|
|
action="delete",
|
|
resource_type="AugmentedPart",
|
|
resource_id=augmented_part_id,
|
|
description=audit_crud.describe_delete(
|
|
"AugmentedPart", augmented_part_id, label
|
|
),
|
|
)
|
|
return Response(status_code=status.HTTP_200_OK)
|