add image flow

This commit is contained in:
2026-04-19 12:54:25 +02:00
parent 57800f2123
commit 5fdf3f4ab2
14 changed files with 606 additions and 21 deletions
+72 -2
View File
@@ -1,12 +1,13 @@
from contextlib import asynccontextmanager
from fastapi import Depends, FastAPI, Form, HTTPException, Request, status
from fastapi.responses import RedirectResponse
from fastapi import Depends, FastAPI, File, Form, HTTPException, Request, UploadFile, status
from fastapi.responses import RedirectResponse, Response
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
from app.db import get_db, init_db
from app.images import process_upload
from app.models import Box, Item, SubItem
templates = Jinja2Templates(directory="app/templates")
@@ -56,6 +57,30 @@ def _require_container_item(item: Item) -> None:
raise HTTPException(status_code=400, detail="Only container items can have sub-items")
def _set_image_fields(target, processed_image) -> None:
if processed_image is None:
return
target.image_blob = processed_image.blob
target.image_mime_type = processed_image.mime_type
target.image_width = processed_image.width
target.image_height = processed_image.height
def _clear_image_fields(target) -> None:
target.image_blob = None
target.image_mime_type = None
target.image_width = None
target.image_height = None
def _image_response_or_404(target) -> Response:
if target.image_blob is None or target.image_mime_type is None:
raise HTTPException(status_code=404, detail="Image not found")
return Response(content=target.image_blob, media_type=target.image_mime_type)
def create_app() -> FastAPI:
@asynccontextmanager
async def lifespan(app: FastAPI):
@@ -97,6 +122,7 @@ def create_app() -> FastAPI:
note: str | None = Form(default=None),
room: str | None = Form(default=None),
status_text: str | None = Form(default=None, alias="status"),
image_file: UploadFile | None = File(default=None),
db: Session = Depends(get_db),
) -> RedirectResponse:
box = Box(
@@ -105,6 +131,7 @@ def create_app() -> FastAPI:
room=_clean_text(room),
status=_clean_text(status_text),
)
_set_image_fields(box, process_upload(image_file))
db.add(box)
db.commit()
db.refresh(box)
@@ -119,6 +146,10 @@ def create_app() -> FastAPI:
context={"page_title": box.name, "box": box},
)
@app.get("/boxes/{box_id}/image")
def get_box_image(box_id: int, db: Session = Depends(get_db)) -> Response:
return _image_response_or_404(_get_box_or_404(db, box_id))
@app.get("/boxes/{box_id}/edit")
def edit_box_page(box_id: int, request: Request, db: Session = Depends(get_db)):
box = _get_box_or_404(db, box_id)
@@ -140,6 +171,7 @@ def create_app() -> FastAPI:
note: str | None = Form(default=None),
room: str | None = Form(default=None),
status_text: str | None = Form(default=None, alias="status"),
image_file: UploadFile | None = File(default=None),
db: Session = Depends(get_db),
) -> RedirectResponse:
box = _get_box_or_404(db, box_id)
@@ -147,9 +179,17 @@ def create_app() -> FastAPI:
box.note = _clean_text(note)
box.room = _clean_text(room)
box.status = _clean_text(status_text)
_set_image_fields(box, process_upload(image_file))
db.commit()
return RedirectResponse(url=f"/boxes/{box.id}", status_code=status.HTTP_303_SEE_OTHER)
@app.post("/boxes/{box_id}/image/delete")
def delete_box_image(box_id: int, db: Session = Depends(get_db)) -> RedirectResponse:
box = _get_box_or_404(db, box_id)
_clear_image_fields(box)
db.commit()
return RedirectResponse(url=f"/boxes/{box.id}/edit", status_code=status.HTTP_303_SEE_OTHER)
@app.post("/boxes/{box_id}/delete")
def delete_box(box_id: int, db: Session = Depends(get_db)) -> RedirectResponse:
box = _get_box_or_404(db, box_id)
@@ -179,6 +219,7 @@ def create_app() -> FastAPI:
note: str | None = Form(default=None),
quantity: str | None = Form(default=None),
is_container: str | None = Form(default=None),
image_file: UploadFile | None = File(default=None),
db: Session = Depends(get_db),
) -> RedirectResponse:
box = _get_box_or_404(db, box_id)
@@ -189,6 +230,7 @@ def create_app() -> FastAPI:
quantity=_parse_quantity(quantity),
is_container=_is_checked(is_container),
)
_set_image_fields(item, process_upload(image_file))
db.add(item)
db.commit()
db.refresh(item)
@@ -203,6 +245,10 @@ def create_app() -> FastAPI:
context={"page_title": item.name, "item": item},
)
@app.get("/items/{item_id}/image")
def get_item_image(item_id: int, db: Session = Depends(get_db)) -> Response:
return _image_response_or_404(_get_item_or_404(db, item_id))
@app.get("/items/{item_id}/edit")
def edit_item_page(item_id: int, request: Request, db: Session = Depends(get_db)):
item = _get_item_or_404(db, item_id)
@@ -225,6 +271,7 @@ def create_app() -> FastAPI:
note: str | None = Form(default=None),
quantity: str | None = Form(default=None),
is_container: str | None = Form(default=None),
image_file: UploadFile | None = File(default=None),
db: Session = Depends(get_db),
) -> RedirectResponse:
item = _get_item_or_404(db, item_id)
@@ -234,9 +281,17 @@ def create_app() -> FastAPI:
item.is_container = _is_checked(is_container)
if not item.is_container:
item.subitems.clear()
_set_image_fields(item, process_upload(image_file))
db.commit()
return RedirectResponse(url=f"/items/{item.id}", status_code=status.HTTP_303_SEE_OTHER)
@app.post("/items/{item_id}/image/delete")
def delete_item_image(item_id: int, db: Session = Depends(get_db)) -> RedirectResponse:
item = _get_item_or_404(db, item_id)
_clear_image_fields(item)
db.commit()
return RedirectResponse(url=f"/items/{item.id}/edit", status_code=status.HTTP_303_SEE_OTHER)
@app.post("/items/{item_id}/delete")
def delete_item(item_id: int, db: Session = Depends(get_db)) -> RedirectResponse:
item = _get_item_or_404(db, item_id)
@@ -267,6 +322,7 @@ def create_app() -> FastAPI:
name: str = Form(...),
note: str | None = Form(default=None),
quantity: str | None = Form(default=None),
image_file: UploadFile | None = File(default=None),
db: Session = Depends(get_db),
) -> RedirectResponse:
item = _get_item_or_404(db, item_id)
@@ -277,11 +333,16 @@ def create_app() -> FastAPI:
note=_clean_text(note),
quantity=_parse_quantity(quantity),
)
_set_image_fields(subitem, process_upload(image_file))
db.add(subitem)
db.commit()
db.refresh(subitem)
return RedirectResponse(url=f"/items/{item.id}", status_code=status.HTTP_303_SEE_OTHER)
@app.get("/subitems/{subitem_id}/image")
def get_subitem_image(subitem_id: int, db: Session = Depends(get_db)) -> Response:
return _image_response_or_404(_get_subitem_or_404(db, subitem_id))
@app.get("/subitems/{subitem_id}/edit")
def edit_subitem_page(subitem_id: int, request: Request, db: Session = Depends(get_db)):
subitem = _get_subitem_or_404(db, subitem_id)
@@ -303,18 +364,27 @@ def create_app() -> FastAPI:
name: str = Form(...),
note: str | None = Form(default=None),
quantity: str | None = Form(default=None),
image_file: UploadFile | None = File(default=None),
db: Session = Depends(get_db),
) -> RedirectResponse:
subitem = _get_subitem_or_404(db, subitem_id)
subitem.name = name.strip()
subitem.note = _clean_text(note)
subitem.quantity = _parse_quantity(quantity)
_set_image_fields(subitem, process_upload(image_file))
db.commit()
return RedirectResponse(
url=f"/items/{subitem.parent_item_id}",
status_code=status.HTTP_303_SEE_OTHER,
)
@app.post("/subitems/{subitem_id}/image/delete")
def delete_subitem_image(subitem_id: int, db: Session = Depends(get_db)) -> RedirectResponse:
subitem = _get_subitem_or_404(db, subitem_id)
_clear_image_fields(subitem)
db.commit()
return RedirectResponse(url=f"/subitems/{subitem.id}/edit", status_code=status.HTTP_303_SEE_OTHER)
@app.post("/subitems/{subitem_id}/delete")
def delete_subitem(subitem_id: int, db: Session = Depends(get_db)) -> RedirectResponse:
subitem = _get_subitem_or_404(db, subitem_id)