add: car image support
This commit is contained in:
@ -8,11 +8,14 @@ RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
# Создаем директорию для хранения БД
|
||||
RUN mkdir -p /app/data
|
||||
# Создаем директории для хранения файлов
|
||||
RUN mkdir -p /app/static/images
|
||||
|
||||
# Указываем порт, который будет слушать приложение
|
||||
EXPOSE 3000
|
||||
|
||||
# Указываем том для хранения загруженных изображений
|
||||
VOLUME /app/static/images
|
||||
|
||||
# Запускаем приложение
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "3000"]
|
||||
|
90
main.py
90
main.py
@ -1,4 +1,5 @@
|
||||
from fastapi import FastAPI, Depends, HTTPException, status, Query, Path
|
||||
from fastapi import FastAPI, Depends, HTTPException, status, Query, Path, UploadFile, File, Form
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from sqlalchemy.orm import Session
|
||||
import crud
|
||||
import models
|
||||
@ -6,12 +7,17 @@ import schemas
|
||||
from database import engine, get_db
|
||||
from typing import List, Optional
|
||||
import uvicorn
|
||||
from utils import save_image, delete_image
|
||||
import json
|
||||
|
||||
# Создание таблиц в БД
|
||||
models.Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI(title="AutoBro API", description="API для управления базой данных автомобилей")
|
||||
|
||||
# Добавляем обработку статических файлов
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"message": "AutoBro API"}
|
||||
@ -40,38 +46,104 @@ def get_car(
|
||||
return {"car": car}
|
||||
|
||||
@app.post("/cars", response_model=schemas.CarResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_car(
|
||||
car: schemas.CarCreate,
|
||||
async def create_car(
|
||||
car_data: str = Form(..., description="Данные автомобиля в JSON формате"),
|
||||
image: UploadFile = File(None, description="Изображение автомобиля"),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
try:
|
||||
# Преобразуем строку JSON в словарь
|
||||
car_dict = json.loads(car_data)
|
||||
|
||||
# Загружаем изображение, если оно предоставлено
|
||||
if image:
|
||||
image_path = await save_image(image)
|
||||
car_dict["image"] = image_path
|
||||
|
||||
# Создаем объект Pydantic для валидации данных
|
||||
car = schemas.CarCreate(**car_dict)
|
||||
|
||||
# Создаем запись в БД
|
||||
db_car = crud.create_car(db=db, car=car)
|
||||
return {"car": db_car}
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(e)
|
||||
)
|
||||
except json.JSONDecodeError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Неверный формат JSON"
|
||||
)
|
||||
|
||||
@app.put("/cars/{car_id}", response_model=schemas.CarResponse)
|
||||
def update_car(
|
||||
async def update_car(
|
||||
car_id: int = Path(..., description="ID автомобиля", gt=0),
|
||||
car_update: schemas.CarUpdate = ...,
|
||||
car_data: str = Form(None, description="Данные автомобиля в JSON формате"),
|
||||
image: UploadFile = File(None, description="Изображение автомобиля"),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
updated_car = crud.update_car(db=db, car_id=car_id, car_update=car_update)
|
||||
if updated_car is None:
|
||||
# Проверяем существование автомобиля
|
||||
existing_car = crud.get_car(db, car_id=car_id)
|
||||
if existing_car is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Автомобиль не найден"
|
||||
)
|
||||
|
||||
try:
|
||||
# Преобразуем строку JSON в словарь, если она предоставлена
|
||||
car_dict = {}
|
||||
if car_data:
|
||||
car_dict = json.loads(car_data)
|
||||
|
||||
# Загружаем новое изображение, если оно предоставлено
|
||||
if image:
|
||||
# Удаляем старое изображение, если есть
|
||||
if existing_car.image:
|
||||
delete_image(existing_car.image)
|
||||
|
||||
# Сохраняем новое изображение
|
||||
image_path = await save_image(image)
|
||||
car_dict["image"] = image_path
|
||||
|
||||
# Создаем объект Pydantic для валидации данных
|
||||
car_update = schemas.CarUpdate(**car_dict)
|
||||
|
||||
# Обновляем запись в БД
|
||||
updated_car = crud.update_car(db=db, car_id=car_id, car_update=car_update)
|
||||
return {"car": updated_car}
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(e)
|
||||
)
|
||||
except json.JSONDecodeError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Неверный формат JSON"
|
||||
)
|
||||
|
||||
@app.delete("/cars/{car_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_car(
|
||||
car_id: int = Path(..., description="ID автомобиля", gt=0),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
success = crud.delete_car(db=db, car_id=car_id)
|
||||
if not success:
|
||||
# Получаем автомобиль перед удалением
|
||||
car = crud.get_car(db, car_id=car_id)
|
||||
if car is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Автомобиль не найден"
|
||||
)
|
||||
|
||||
# Удаляем изображение, если есть
|
||||
if car.image:
|
||||
delete_image(car.image)
|
||||
|
||||
# Удаляем запись из БД
|
||||
crud.delete_car(db=db, car_id=car_id)
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
BIN
static/images/d5f39167-e347-48b5-8d2b-2b2ce9139e0a.png
Normal file
BIN
static/images/d5f39167-e347-48b5-8d2b-2b2ce9139e0a.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 MiB |
53
utils.py
Normal file
53
utils.py
Normal file
@ -0,0 +1,53 @@
|
||||
import os
|
||||
import uuid
|
||||
from fastapi import UploadFile
|
||||
from pathlib import Path
|
||||
|
||||
# Путь для сохранения изображений
|
||||
IMAGES_DIR = Path("static/images")
|
||||
|
||||
# Разрешенные типы файлов
|
||||
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp"}
|
||||
|
||||
async def save_image(file: UploadFile) -> str:
|
||||
"""
|
||||
Сохраняет изображение в директорию и возвращает относительный путь к нему
|
||||
"""
|
||||
if not file:
|
||||
return None
|
||||
|
||||
# Получаем расширение файла
|
||||
_, ext = os.path.splitext(file.filename)
|
||||
if ext.lower() not in ALLOWED_EXTENSIONS:
|
||||
raise ValueError(f"Неподдерживаемый формат файла. Поддерживаемые форматы: {', '.join(ALLOWED_EXTENSIONS)}")
|
||||
|
||||
# Создаем уникальное имя файла
|
||||
filename = f"{uuid.uuid4()}{ext}"
|
||||
file_path = IMAGES_DIR / filename
|
||||
|
||||
# Создаем директорию, если не существует
|
||||
os.makedirs(IMAGES_DIR, exist_ok=True)
|
||||
|
||||
# Сохраняем файл
|
||||
contents = await file.read()
|
||||
with open(file_path, "wb") as f:
|
||||
f.write(contents)
|
||||
|
||||
# Возвращаем относительный путь для хранения в базе данных
|
||||
return f"/static/images/{filename}"
|
||||
|
||||
def delete_image(image_path: str) -> None:
|
||||
"""
|
||||
Удаляет файл изображения, если оно существует
|
||||
"""
|
||||
if not image_path:
|
||||
return
|
||||
|
||||
# Удаляем первый слэш из пути, если есть
|
||||
if image_path.startswith("/"):
|
||||
image_path = image_path[1:]
|
||||
|
||||
full_path = Path(image_path)
|
||||
|
||||
if full_path.exists():
|
||||
os.unlink(full_path)
|
Reference in New Issue
Block a user