temp images on products

This commit is contained in:
2024-05-24 09:45:54 +03:00
parent 3a4f91d751
commit 88679089b6
11 changed files with 115 additions and 31 deletions

View File

@@ -15,3 +15,4 @@ PG_HOST = os.environ.get('PG_HOST')
TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN') TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN')
SECRET_KEY = os.environ.get('SECRET_KEY') SECRET_KEY = os.environ.get('SECRET_KEY')
S3_API_KEY = '1cc46590-4532-4046-97aa-baf3e49f20ad-AUF'

0
external/__init__.py vendored Normal file
View File

0
external/s3_uploader/__init__.py vendored Normal file
View File

49
external/s3_uploader/uploader.py vendored Normal file
View 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)

View File

@@ -24,6 +24,19 @@ class Product(BaseModel):
composition = Column(String, nullable=True, comment='Состав') composition = Column(String, nullable=True, comment='Состав')
size = Column(String, nullable=True, comment='Размер') size = Column(String, nullable=True, comment='Размер')
additional_info = 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): class ProductBarcode(BaseModel):

View File

@@ -4,6 +4,7 @@ pydantic
uvicorn uvicorn
uvicorn[standard] uvicorn[standard]
gunicorn gunicorn
python-multipart
# Security # Security
python-jose[cryptography] python-jose[cryptography]

View File

@@ -1,6 +1,6 @@
from typing import Annotated, Union from typing import Annotated, Union
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends, UploadFile
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
import utils.dependecies import utils.dependecies
@@ -15,7 +15,7 @@ from services.product import ProductService
product_router = APIRouter( product_router = APIRouter(
prefix="/product", prefix="/product",
tags=["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)] session: Annotated[AsyncSession, Depends(get_session)]
): ):
return await BarcodeService(session).get_barcode(request) 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)

View File

@@ -94,4 +94,8 @@ class ProductGenerateBarcodeResponse(OkMessageSchema):
class ProductExistsBarcodeResponse(CustomModelCamel): class ProductExistsBarcodeResponse(CustomModelCamel):
exists: bool exists: bool
class ProductUploadImageResponse(OkMessageSchema):
image_url: str | None = None
# endregion # endregion

View File

@@ -3,7 +3,9 @@ from sqlalchemy import select, func, Integer, update
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
import utils.barcodes 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 schemas.base import PaginationSchema
from services.base import BaseService from services.base import BaseService
from schemas.product import * from schemas.product import *
@@ -146,6 +148,28 @@ class ProductService(BaseService):
raise HTTPException(status_code=404, detail='Товар не найден') raise HTTPException(status_code=404, detail='Товар не найден')
return ProductSchema.model_validate(product) 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 # region Barcodes
async def add_barcode(self, request: ProductAddBarcodeRequest): async def add_barcode(self, request: ProductAddBarcodeRequest):
try: try:

View File

@@ -5,6 +5,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from backend.session import session_maker from backend.session import session_maker
from external.s3_uploader.uploader import S3Uploader
from models import Deal, DealProduct, Service from models import Deal, DealProduct, Service
import models import models
@@ -12,34 +13,10 @@ import models.secondary
async def main(session: AsyncSession): async def main(session: AsyncSession):
deal_services_subquery = ( file_bytes = open('photo_2024-04-01 10.26.39.jpeg', 'rb').read()
select( uploader = S3Uploader('1cc46590-4532-4046-97aa-baf3e49f20ad-AUF')
models.secondary.DealService.deal_id, response = await uploader.upload(file_bytes)
func.sum(models.secondary.DealService.quantity * Service.price).label('total_price') print(response)
)
.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)
async def preload(): async def preload():

Binary file not shown.