upload from file and url
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,13 @@
|
||||
from sqlalchemy.orm import Session
|
||||
import os
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
from . import models, schemas
|
||||
|
||||
def get_celebrity(db: Session, celebrity_id: int):
|
||||
return db.query(models.Celebrity).filter(models.Celebrity.id == celebrity_id).first()
|
||||
# Usiamo joinedload per caricare in anticipo le relazioni e evitare query N+1
|
||||
return db.query(models.Celebrity).options(
|
||||
joinedload(models.Celebrity.profile_image),
|
||||
joinedload(models.Celebrity.images)
|
||||
).filter(models.Celebrity.id == celebrity_id).first()
|
||||
|
||||
def get_celebrities(db: Session, skip: int = 0, limit: int = 100):
|
||||
return db.query(models.Celebrity).offset(skip).limit(limit).all()
|
||||
@@ -34,4 +39,49 @@ def delete_celebrity(db: Session, celebrity_id: int):
|
||||
|
||||
db.delete(db_celebrity)
|
||||
db.commit()
|
||||
return db_celebrity
|
||||
return db_celebrity
|
||||
|
||||
# --- Funzioni CRUD per le Immagini ---
|
||||
|
||||
def get_image(db: Session, image_id: int):
|
||||
return db.query(models.Image).filter(models.Image.id == image_id).first()
|
||||
|
||||
def create_celebrity_image(db: Session, celebrity_id: int, file_path: str):
|
||||
db_image = models.Image(celebrity_id=celebrity_id, file_path=file_path)
|
||||
db.add(db_image)
|
||||
db.commit()
|
||||
db.refresh(db_image)
|
||||
return db_image
|
||||
|
||||
def set_profile_image(db: Session, celebrity_id: int, image_id: int):
|
||||
db_celebrity = get_celebrity(db, celebrity_id)
|
||||
db_image = get_image(db, image_id)
|
||||
|
||||
if not db_celebrity or not db_image or db_image.celebrity_id != celebrity_id:
|
||||
return None
|
||||
|
||||
db_celebrity.profile_image_id = image_id
|
||||
db.commit()
|
||||
db.refresh(db_celebrity)
|
||||
return get_celebrity(db, celebrity_id) # Ritorna con le relazioni aggiornate
|
||||
|
||||
def delete_image(db: Session, image_id: int):
|
||||
db_image = get_image(db, image_id)
|
||||
if not db_image:
|
||||
return None
|
||||
|
||||
db_celebrity = get_celebrity(db, db_image.celebrity_id)
|
||||
if db_celebrity and db_celebrity.profile_image_id == image_id:
|
||||
db_celebrity.profile_image_id = None
|
||||
db.add(db_celebrity)
|
||||
|
||||
try:
|
||||
file_on_disk = os.path.join("uploads", db_image.file_path)
|
||||
if os.path.exists(file_on_disk):
|
||||
os.remove(file_on_disk)
|
||||
except OSError as e:
|
||||
print(f"Error deleting file {file_on_disk}: {e}")
|
||||
|
||||
db.delete(db_image)
|
||||
db.commit()
|
||||
return db_image
|
||||
@@ -1,8 +1,13 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from .routers import celebrities # Importa il nuovo router
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# Monta la directory 'uploads' per servire i file statici (immagini caricate)
|
||||
# Ora saranno accessibili tramite l'URL /api/uploads/<nome_file>
|
||||
app.mount("/api/uploads", StaticFiles(directory="uploads"), name="uploads")
|
||||
|
||||
# Includi il router delle celebrities nell'app principale
|
||||
app.include_router(celebrities.router)
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,6 +1,8 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Depends, HTTPException, File, UploadFile
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
import uuid
|
||||
import shutil
|
||||
|
||||
from .. import crud, models, schemas
|
||||
from ..database import get_db
|
||||
@@ -13,7 +15,7 @@ router = APIRouter(
|
||||
|
||||
@router.post("/", response_model=schemas.Celebrity, status_code=201)
|
||||
def create_celebrity(celebrity: schemas.CelebrityCreate, db: Session = Depends(get_db)):
|
||||
# Qui potresti aggiungere un check per vedere se una celebrità con lo stesso nome esiste già
|
||||
# Qui potresti aggiungere un check per vedere se una celebrità con lo stesso nome esiste già
|
||||
return crud.create_celebrity(db=db, celebrity=celebrity)
|
||||
|
||||
@router.get("/", response_model=List[schemas.Celebrity])
|
||||
@@ -40,4 +42,80 @@ def delete_celebrity(celebrity_id: int, db: Session = Depends(get_db)):
|
||||
db_celebrity = crud.delete_celebrity(db, celebrity_id=celebrity_id)
|
||||
if db_celebrity is None:
|
||||
raise HTTPException(status_code=404, detail="Celebrity not found")
|
||||
return db_celebrity
|
||||
return db_celebrity
|
||||
|
||||
# --- NUOVI ENDPOINT PER LE IMMAGINI ---
|
||||
|
||||
@router.post("/{celebrity_id}/images", response_model=schemas.Image, status_code=201)
|
||||
def upload_celebrity_image(celebrity_id: int, file: UploadFile = File(...), db: Session = Depends(get_db)):
|
||||
db_celebrity = crud.get_celebrity(db, celebrity_id=celebrity_id)
|
||||
if db_celebrity is None:
|
||||
raise HTTPException(status_code=404, detail="Celebrity not found")
|
||||
|
||||
file_extension = file.filename.split('.')[-1]
|
||||
unique_filename = f"{uuid.uuid4()}.{file_extension}"
|
||||
file_location = f"uploads/{unique_filename}"
|
||||
|
||||
with open(file_location, "wb+") as file_object:
|
||||
shutil.copyfileobj(file.file, file_object)
|
||||
|
||||
return crud.create_celebrity_image(db=db, celebrity_id=celebrity_id, file_path=unique_filename)
|
||||
|
||||
@router.post("/{celebrity_id}/images/from-url", response_model=schemas.Image, status_code=201)
|
||||
async def add_image_from_url(celebrity_id: int, request: schemas.ImageUrlRequest, db: Session = Depends(get_db)):
|
||||
db_celebrity = crud.get_celebrity(db, celebrity_id=celebrity_id)
|
||||
if not db_celebrity:
|
||||
raise HTTPException(status_code=404, detail="Celebrity not found")
|
||||
|
||||
url = str(request.url)
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
# Eseguiamo prima una richiesta HEAD per controllare il tipo e la dimensione
|
||||
head_response = await client.head(url, follow_redirects=True, timeout=10)
|
||||
head_response.raise_for_status()
|
||||
|
||||
content_type = head_response.headers.get('content-type')
|
||||
if not content_type or not content_type.startswith('image/'):
|
||||
raise HTTPException(status_code=400, detail="URL does not point to a valid image.")
|
||||
|
||||
# Limite di dimensione (es. 10MB)
|
||||
content_length = int(head_response.headers.get('content-length', 0))
|
||||
if content_length > 10 * 1024 * 1024:
|
||||
raise HTTPException(status_code=400, detail="Image size exceeds 10MB limit.")
|
||||
|
||||
# Scarica l'immagine
|
||||
get_response = await client.get(url, follow_redirects=True, timeout=30)
|
||||
get_response.raise_for_status()
|
||||
|
||||
# Determina l'estensione del file
|
||||
file_extension = content_type.split('/')[-1]
|
||||
if file_extension == 'jpeg': file_extension = 'jpg'
|
||||
|
||||
unique_filename = f"{uuid.uuid4()}.{file_extension}"
|
||||
file_location = f"uploads/{unique_filename}"
|
||||
|
||||
with open(file_location, "wb") as file_object:
|
||||
file_object.write(get_response.content)
|
||||
|
||||
return crud.create_celebrity_image(db=db, celebrity_id=celebrity_id, file_path=unique_filename)
|
||||
|
||||
except httpx.RequestError as exc:
|
||||
raise HTTPException(status_code=400, detail=f"Failed to fetch image from URL: {exc}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"An internal error occurred: {e}")
|
||||
|
||||
@router.put("/{celebrity_id}/profile-image", response_model=schemas.Celebrity)
|
||||
def set_celebrity_profile_image(celebrity_id: int, request: schemas.SetProfileImageRequest, db: Session = Depends(get_db)):
|
||||
updated_celebrity = crud.set_profile_image(db, celebrity_id=celebrity_id, image_id=request.image_id)
|
||||
if updated_celebrity is None:
|
||||
raise HTTPException(status_code=404, detail="Celebrity or Image not found, or image does not belong to celebrity.")
|
||||
return updated_celebrity
|
||||
|
||||
# NOTA: Un approccio pi├╣ RESTful sarebbe /api/images/{image_id} in un router dedicato.
|
||||
# Per semplicità, lo inseriamo qui.
|
||||
@router.delete("/images/{image_id}", response_model=schemas.Image)
|
||||
def delete_image(image_id: int, db: Session = Depends(get_db)):
|
||||
db_image = crud.delete_image(db, image_id=image_id)
|
||||
if db_image is None:
|
||||
raise HTTPException(status_code=404, detail="Image not found")
|
||||
return db_image
|
||||
@@ -1,4 +1,4 @@
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, HttpUrl
|
||||
from typing import Optional, List
|
||||
from datetime import date, datetime
|
||||
from .models import GenderType, ShoeSystemType, BraSystemType, SurgeryType
|
||||
@@ -74,6 +74,12 @@ class Image(ImageBase):
|
||||
uploaded_at: datetime
|
||||
class Config: from_attributes = True
|
||||
|
||||
# --- Richiesta per impostare l'immagine del profilo ---
|
||||
class SetProfileImageRequest(BaseModel):
|
||||
image_id: int
|
||||
|
||||
class ImageUrlRequest(BaseModel):
|
||||
url: HttpUrl # Pydantic valida automaticamente che sia un URL valido
|
||||
|
||||
# =============================================================================
|
||||
# SCHEMI PER CELEBRITY
|
||||
@@ -107,7 +113,7 @@ class CelebrityBase(BaseModel):
|
||||
official_website: Optional[str] = None
|
||||
profile_image_id: Optional[int] = None
|
||||
|
||||
# Schema per la creazione di una nuova celebrità (eredita da Base)
|
||||
# Schema per la creazione di una nuova celebrità (eredita da Base)
|
||||
class CelebrityCreate(CelebrityBase):
|
||||
pass
|
||||
|
||||
@@ -146,11 +152,12 @@ class Celebrity(CelebrityBase):
|
||||
updated_at: datetime
|
||||
|
||||
# Campi relazionali che verranno popolati automaticamente da SQLAlchemy
|
||||
profile_image: Optional[Image] = None
|
||||
images: List[Image] = []
|
||||
tattoos: List[Tattoo] = []
|
||||
aliases: List[CelebrityAlias] = []
|
||||
|
||||
# Per semplicità, possiamo usare qui gli schemi di base,
|
||||
# Per semplicità, possiamo usare qui gli schemi di base,
|
||||
# ma in un'app reale potresti volere schemi specifici per la "lettura".
|
||||
professions: List[Profession] = []
|
||||
studios: List[Studio] = []
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# FastAPI e server
|
||||
fastapi
|
||||
uvicorn[standard]
|
||||
python-multipart
|
||||
|
||||
# Pydantic per la validazione e le impostazioni
|
||||
pydantic
|
||||
@@ -9,4 +10,7 @@ pydantic-settings
|
||||
# Database
|
||||
sqlalchemy
|
||||
psycopg2-binary
|
||||
alembic
|
||||
alembic
|
||||
|
||||
# Per richieste HTTP (scaricare immagini da URL)
|
||||
httpx
|
||||
Reference in New Issue
Block a user