Archive improvements: list view actions, object count, cross-view highlighting

Added:
  - Archive list view: edit/delete buttons and context menu with full feature parity
  - Archive object count display on cards (extracted from 3MF metadata)
  - Cross-view archive highlighting: click in calendar/project to highlight in card view
  - Context menu button (⋮) on cards and list rows for easy access
  - Spoolman: clear location when spools are removed from AMS

  Fixed:
  - QR code endpoint 500 error (added qrcode[pil] dependency)
This commit is contained in:
maziggy
2026-01-09 08:24:14 +01:00
parent b4bc046684
commit e2b2877965
12 changed files with 1203 additions and 541 deletions
+12 -5
View File
@@ -1464,16 +1464,20 @@ async def get_qrcode(
db: AsyncSession = Depends(get_db),
):
"""Generate a QR code that links to this archive."""
import qrcode
try:
import qrcode
from PIL import Image as PILImage
except ImportError:
raise HTTPException(500, "QR code generation not available - qrcode package not installed")
result = await db.execute(select(PrintArchive).where(PrintArchive.id == archive_id))
archive = result.scalar_one_or_none()
if not archive:
raise HTTPException(404, "Archive not found")
# Build URL to archive detail page
# Build URL to archive download
base_url = str(request.base_url).rstrip("/")
archive_url = f"{base_url}/archives?id={archive_id}"
archive_url = f"{base_url}/api/v1/archives/{archive_id}/download"
# Generate QR code
qr = qrcode.QRCode(
@@ -1487,13 +1491,16 @@ async def get_qrcode(
img = qr.make_image(fill_color="black", back_color="white")
# Convert to PIL Image for resizing
pil_img = img.get_image()
# Resize if needed
if size != 200:
img = img.resize((size, size))
pil_img = pil_img.resize((size, size), PILImage.Resampling.LANCZOS)
# Convert to bytes
buffer = io.BytesIO()
img.save(buffer, format="PNG")
pil_img.save(buffer, format="PNG")
buffer.seek(0)
return Response(
+13 -1
View File
@@ -1,6 +1,6 @@
from datetime import datetime
from pydantic import BaseModel
from pydantic import BaseModel, model_validator
class ArchiveBase(BaseModel):
@@ -45,6 +45,9 @@ class ArchiveResponse(BaseModel):
duplicates: list[ArchiveDuplicate] | None = None
duplicate_count: int = 0 # Quick count for list views
# Object count (computed from extra_data.printable_objects)
object_count: int | None = None
print_name: str | None
print_time_seconds: int | None # Estimated time from slicer
actual_time_seconds: int | None = None # Computed from started_at/completed_at
@@ -81,6 +84,15 @@ class ArchiveResponse(BaseModel):
created_at: datetime
@model_validator(mode="after")
def compute_object_count(self) -> "ArchiveResponse":
"""Compute object_count from extra_data.printable_objects if not set."""
if self.object_count is None and self.extra_data:
printable_objects = self.extra_data.get("printable_objects")
if printable_objects and isinstance(printable_objects, dict):
self.object_count = len(printable_objects)
return self
class Config:
from_attributes = True