Add job creation with UUID, STL upload/library, search, job log, TZ, and CLAUDE.md
- PrintJob: job_uuid (UUID4), customer, logs relationship - JobLog model + migration 0004 (also includes job_logs table) - POST /jobs: upload STL or select from library, auto-logs 'Job created' - GET /jobs?q=: search by customer or filename across all statuses - app/stl.py: STL_UPLOAD_DIR helper (from env, default /data/stl) - docker-compose: named volume stl_files mounted at /data/stl - .env.install: added STL_UPLOAD_DIR and TZ=Europe/Berlin - CLAUDE.md: full project context for future sessions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,22 +1,30 @@
|
||||
from fastapi import APIRouter, Depends, Form, Request
|
||||
import uuid as _uuid
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, Depends, File, Form, Request, UploadFile
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy.orm import Session
|
||||
from starlette.status import HTTP_303_SEE_OTHER
|
||||
|
||||
from app.db import get_db
|
||||
from app.models.filament import Filament
|
||||
from app.models.job_log import JobLog
|
||||
from app.models.print_job import JobStatus, PrintJob
|
||||
from app.models.printer import Printer, PrinterStatus
|
||||
from app.models.printer_log import PrinterLog
|
||||
from app.models.printer_type import PrinterType
|
||||
from app.models.pricing import PricingConfig
|
||||
from app.models.todo import Todo
|
||||
from app.stl import get_stl_dir, list_stl_files
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory="app/templates")
|
||||
|
||||
|
||||
# ── Dashboard ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
def dashboard(request: Request, db: Session = Depends(get_db)):
|
||||
printers = db.query(Printer).order_by(Printer.name).all()
|
||||
@@ -34,6 +42,8 @@ def dashboard(request: Request, db: Session = Depends(get_db)):
|
||||
})
|
||||
|
||||
|
||||
# ── Printers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/printers", response_class=HTMLResponse)
|
||||
def printers_page(request: Request, db: Session = Depends(get_db)):
|
||||
return templates.TemplateResponse("printers.html", {
|
||||
@@ -66,9 +76,9 @@ def create_printer(
|
||||
|
||||
@router.get("/printers/{printer_id}", response_class=HTMLResponse)
|
||||
def printer_detail(printer_id: int, request: Request, db: Session = Depends(get_db)):
|
||||
from fastapi import HTTPException
|
||||
printer = db.query(Printer).filter(Printer.id == printer_id).first()
|
||||
if not printer:
|
||||
from fastapi import HTTPException
|
||||
raise HTTPException(status_code=404, detail="Printer not found")
|
||||
return templates.TemplateResponse("printer_detail.html", {
|
||||
"request": request,
|
||||
@@ -79,12 +89,13 @@ def printer_detail(printer_id: int, request: Request, db: Session = Depends(get_
|
||||
|
||||
@router.post("/printers/{printer_id}/log")
|
||||
def add_printer_log(printer_id: int, message: str = Form(...), db: Session = Depends(get_db)):
|
||||
entry = PrinterLog(printer_id=printer_id, message=message.strip())
|
||||
db.add(entry)
|
||||
db.add(PrinterLog(printer_id=printer_id, message=message.strip()))
|
||||
db.commit()
|
||||
return RedirectResponse(url=f"/printers/{printer_id}", status_code=HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
# ── Printer types ──────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/printer-types", response_class=HTMLResponse)
|
||||
def printer_types_page(request: Request, db: Session = Depends(get_db)):
|
||||
return templates.TemplateResponse("printer_types.html", {
|
||||
@@ -102,21 +113,67 @@ def create_printer_type(
|
||||
bed_z_mm: int = Form(...),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
pt = PrinterType(name=name.strip(), bed_x_mm=bed_x_mm, bed_y_mm=bed_y_mm, bed_z_mm=bed_z_mm)
|
||||
db.add(pt)
|
||||
db.add(PrinterType(name=name.strip(), bed_x_mm=bed_x_mm, bed_y_mm=bed_y_mm, bed_z_mm=bed_z_mm))
|
||||
db.commit()
|
||||
return RedirectResponse(url="/printer-types", status_code=HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
# ── Jobs ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/jobs", response_class=HTMLResponse)
|
||||
def jobs_page(request: Request, db: Session = Depends(get_db)):
|
||||
def jobs_page(request: Request, q: str = "", db: Session = Depends(get_db)):
|
||||
query = db.query(PrintJob)
|
||||
if q:
|
||||
like = f"%{q}%"
|
||||
query = query.filter(
|
||||
or_(PrintJob.customer.ilike(like), PrintJob.file_name.ilike(like))
|
||||
)
|
||||
return templates.TemplateResponse("jobs.html", {
|
||||
"request": request,
|
||||
"active": "jobs",
|
||||
"jobs": db.query(PrintJob).order_by(PrintJob.created_at.desc()).all(),
|
||||
"jobs": query.order_by(PrintJob.created_at.desc()).all(),
|
||||
"q": q,
|
||||
"printers": db.query(Printer).order_by(Printer.name).all(),
|
||||
"filaments": db.query(Filament).order_by(Filament.material, Filament.color).all(),
|
||||
"stl_files": list_stl_files(),
|
||||
})
|
||||
|
||||
|
||||
@router.post("/jobs")
|
||||
async def create_job(
|
||||
printer_id: int = Form(...),
|
||||
customer: str = Form(""),
|
||||
filament_id: str = Form(""),
|
||||
notes: str = Form(""),
|
||||
stl_file: UploadFile = File(None),
|
||||
stl_select: str = Form(""),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
filename = None
|
||||
if stl_file and stl_file.filename:
|
||||
filename = stl_file.filename
|
||||
(get_stl_dir() / filename).write_bytes(await stl_file.read())
|
||||
elif stl_select:
|
||||
filename = stl_select
|
||||
|
||||
job = PrintJob(
|
||||
job_uuid=str(_uuid.uuid4()),
|
||||
name=Path(filename).stem if filename else (customer.strip() or "Unnamed"),
|
||||
file_name=filename,
|
||||
printer_id=printer_id,
|
||||
filament_id=int(filament_id) if filament_id else None,
|
||||
customer=customer.strip() or None,
|
||||
notes=notes.strip() or None,
|
||||
)
|
||||
db.add(job)
|
||||
db.flush()
|
||||
db.add(JobLog(job_id=job.id, message="Job created"))
|
||||
db.commit()
|
||||
return RedirectResponse(url="/jobs", status_code=HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
# ── Filaments ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/filaments", response_class=HTMLResponse)
|
||||
def filaments_page(request: Request, db: Session = Depends(get_db)):
|
||||
return templates.TemplateResponse("filaments.html", {
|
||||
@@ -126,6 +183,8 @@ def filaments_page(request: Request, db: Session = Depends(get_db)):
|
||||
})
|
||||
|
||||
|
||||
# ── To-Do ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/todos", response_class=HTMLResponse)
|
||||
def todos_page(request: Request, db: Session = Depends(get_db)):
|
||||
return templates.TemplateResponse("todos.html", {
|
||||
@@ -135,6 +194,8 @@ def todos_page(request: Request, db: Session = Depends(get_db)):
|
||||
})
|
||||
|
||||
|
||||
# ── Pricing ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/pricing", response_class=HTMLResponse)
|
||||
def pricing_page(request: Request, db: Session = Depends(get_db)):
|
||||
return templates.TemplateResponse("pricing.html", {
|
||||
|
||||
Reference in New Issue
Block a user