temp images on products
This commit is contained in:
@@ -15,3 +15,4 @@ PG_HOST = os.environ.get('PG_HOST')
|
||||
TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN')
|
||||
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY')
|
||||
S3_API_KEY = '1cc46590-4532-4046-97aa-baf3e49f20ad-AUF'
|
||||
|
||||
0
external/__init__.py
vendored
Normal file
0
external/__init__.py
vendored
Normal file
0
external/s3_uploader/__init__.py
vendored
Normal file
0
external/s3_uploader/__init__.py
vendored
Normal file
49
external/s3_uploader/uploader.py
vendored
Normal file
49
external/s3_uploader/uploader.py
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
import uuid
|
||||
from io import BytesIO
|
||||
from typing import Union
|
||||
|
||||
import aiohttp
|
||||
|
||||
|
||||
class S3Uploader:
|
||||
def __init__(self, api_key: str):
|
||||
# constants
|
||||
self.base_url = 'https://s3.denco.store'
|
||||
self.prefix = 'crm'
|
||||
self.api_key = api_key
|
||||
|
||||
self.headers = {
|
||||
'X-API-Key': self.api_key,
|
||||
}
|
||||
|
||||
async def _method(self, http_method, method, **kwargs):
|
||||
async with aiohttp.ClientSession(headers=self.headers) as session:
|
||||
async with session.request(http_method, self.base_url + method, **kwargs) as response:
|
||||
return await response.json()
|
||||
|
||||
async def _create(self):
|
||||
method = '/create'
|
||||
json_data = {
|
||||
'prefix': self.prefix,
|
||||
'immediate': True
|
||||
}
|
||||
return await self._method('POST', method, json=json_data)
|
||||
|
||||
async def upload(self, file: bytes, file_id: Union[int, None] = None) -> dict:
|
||||
"""
|
||||
|
||||
Args:
|
||||
file_id: database id created by _create
|
||||
file: bytes of image to upload
|
||||
|
||||
Returns:
|
||||
url to file
|
||||
"""
|
||||
if not file_id:
|
||||
create_response = await self._create()
|
||||
file_id = create_response['databaseId']
|
||||
data = {
|
||||
'file': BytesIO(file),
|
||||
}
|
||||
method = f'/upload/{file_id}'
|
||||
return await self._method('POST', method, data=data)
|
||||
@@ -24,6 +24,19 @@ class Product(BaseModel):
|
||||
composition = Column(String, nullable=True, comment='Состав')
|
||||
size = Column(String, nullable=True, comment='Размер')
|
||||
additional_info = Column(String, nullable=True, comment='Дополнительное поле')
|
||||
images = relationship('ProductImage',
|
||||
back_populates='product',
|
||||
|
||||
cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class ProductImage(BaseModel):
|
||||
__tablename__ = 'product_images'
|
||||
id = Column(Integer, autoincrement=True, primary_key=True, index=True)
|
||||
product_id = Column(Integer, ForeignKey('products.id'), nullable=False)
|
||||
product = relationship('Product', back_populates='images')
|
||||
|
||||
image_url = Column(String, nullable=False)
|
||||
|
||||
|
||||
class ProductBarcode(BaseModel):
|
||||
|
||||
@@ -4,6 +4,7 @@ pydantic
|
||||
uvicorn
|
||||
uvicorn[standard]
|
||||
gunicorn
|
||||
python-multipart
|
||||
|
||||
# Security
|
||||
python-jose[cryptography]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Annotated, Union
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter, Depends, UploadFile
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
import utils.dependecies
|
||||
@@ -15,7 +15,7 @@ from services.product import ProductService
|
||||
product_router = APIRouter(
|
||||
prefix="/product",
|
||||
tags=["product"],
|
||||
dependencies=[Depends(get_current_user)]
|
||||
# dependencies=[Depends(get_current_user)]
|
||||
)
|
||||
|
||||
|
||||
@@ -125,3 +125,18 @@ async def get_product_barcode(
|
||||
session: Annotated[AsyncSession, Depends(get_session)]
|
||||
):
|
||||
return await BarcodeService(session).get_barcode(request)
|
||||
|
||||
|
||||
@product_router.post(
|
||||
'/images/upload/{product_id}',
|
||||
response_model=ProductUploadImageResponse,
|
||||
operation_id='upload_product_image'
|
||||
)
|
||||
async def upload_product_image(
|
||||
product_id: int,
|
||||
upload_file: UploadFile,
|
||||
session: Annotated[AsyncSession, Depends(get_session)]
|
||||
|
||||
):
|
||||
file_bytes = upload_file.file.read()
|
||||
return await ProductService(session).upload_image(product_id, file_bytes)
|
||||
|
||||
@@ -94,4 +94,8 @@ class ProductGenerateBarcodeResponse(OkMessageSchema):
|
||||
|
||||
class ProductExistsBarcodeResponse(CustomModelCamel):
|
||||
exists: bool
|
||||
|
||||
|
||||
class ProductUploadImageResponse(OkMessageSchema):
|
||||
image_url: str | None = None
|
||||
# endregion
|
||||
|
||||
@@ -3,7 +3,9 @@ from sqlalchemy import select, func, Integer, update
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
import utils.barcodes
|
||||
from models.product import Product, ProductBarcode
|
||||
from backend import config
|
||||
from external.s3_uploader.uploader import S3Uploader
|
||||
from models.product import Product, ProductBarcode, ProductImage
|
||||
from schemas.base import PaginationSchema
|
||||
from services.base import BaseService
|
||||
from schemas.product import *
|
||||
@@ -146,6 +148,28 @@ class ProductService(BaseService):
|
||||
raise HTTPException(status_code=404, detail='Товар не найден')
|
||||
return ProductSchema.model_validate(product)
|
||||
|
||||
async def upload_image(self, product_id: int, file_bytes: bytes) -> ProductUploadImageResponse:
|
||||
try:
|
||||
product = await self.get_by_id(product_id)
|
||||
if not product:
|
||||
raise Exception("Неудалось найти товар с указанным ID")
|
||||
s3_uploader = S3Uploader(config.S3_API_KEY)
|
||||
response = await s3_uploader.upload(file_bytes)
|
||||
response_url = response.get('link')
|
||||
if not response_url:
|
||||
raise Exception("Неудалось загрузить изображение")
|
||||
product_image = ProductImage(
|
||||
product_id=product_id,
|
||||
image_url=response_url,
|
||||
)
|
||||
self.session.add(product_image)
|
||||
await self.session.commit()
|
||||
return ProductUploadImageResponse(ok=True,
|
||||
message='Изображение успешно загружено',
|
||||
image_url=response_url)
|
||||
except Exception as e:
|
||||
return ProductUploadImageResponse(ok=False, message=str(e))
|
||||
|
||||
# region Barcodes
|
||||
async def add_barcode(self, request: ProductAddBarcodeRequest):
|
||||
try:
|
||||
|
||||
33
test/test.py
33
test/test.py
@@ -5,6 +5,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from backend.session import session_maker
|
||||
from external.s3_uploader.uploader import S3Uploader
|
||||
from models import Deal, DealProduct, Service
|
||||
|
||||
import models
|
||||
@@ -12,34 +13,10 @@ import models.secondary
|
||||
|
||||
|
||||
async def main(session: AsyncSession):
|
||||
deal_services_subquery = (
|
||||
select(
|
||||
models.secondary.DealService.deal_id,
|
||||
func.sum(models.secondary.DealService.quantity * Service.price).label('total_price')
|
||||
)
|
||||
.join(Service)
|
||||
.group_by(models.secondary.DealService.deal_id)
|
||||
)
|
||||
product_services_subquery = select(
|
||||
select(
|
||||
models.secondary.DealProductService.deal_id,
|
||||
func.sum(models.DealProduct.quantity * models.secondary.DealProductService.price).label('total_price')
|
||||
)
|
||||
.join(models.secondary.DealProduct)
|
||||
.group_by(models.secondary.DealProductService.deal_id)
|
||||
.subquery()
|
||||
)
|
||||
union_subqueries = deal_services_subquery.union(product_services_subquery).subquery()
|
||||
final_subquery = (
|
||||
select(
|
||||
union_subqueries.c.deal_id,
|
||||
func.sum(union_subqueries.c.total_price).label('total_sum')
|
||||
)
|
||||
.group_by(union_subqueries.c.deal_id)
|
||||
.subquery()
|
||||
)
|
||||
|
||||
print(final_subquery)
|
||||
file_bytes = open('photo_2024-04-01 10.26.39.jpeg', 'rb').read()
|
||||
uploader = S3Uploader('1cc46590-4532-4046-97aa-baf3e49f20ad-AUF')
|
||||
response = await uploader.upload(file_bytes)
|
||||
print(response)
|
||||
|
||||
|
||||
async def preload():
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user