Compare commits

...

2 Commits

Author SHA1 Message Date
dc96d7a15a Stop tracking static/ folder 2025-07-07 20:06:35 +05:00
5c4d5bb5a2 add: car image support 2025-07-07 19:50:54 +05:00
5 changed files with 143 additions and 14 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
__pycache__
venv
static

BIN
cars.db

Binary file not shown.

View File

@ -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"]

96
main.py
View File

@ -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)
):
db_car = crud.create_car(db=db, car=car)
return {"car": db_car}
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="Автомобиль не найден"
)
return {"car": updated_car}
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__":

53
utils.py Normal file
View 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)