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')
|
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
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='Состав')
|
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):
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ pydantic
|
|||||||
uvicorn
|
uvicorn
|
||||||
uvicorn[standard]
|
uvicorn[standard]
|
||||||
gunicorn
|
gunicorn
|
||||||
|
python-multipart
|
||||||
|
|
||||||
# Security
|
# Security
|
||||||
python-jose[cryptography]
|
python-jose[cryptography]
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
33
test/test.py
33
test/test.py
@@ -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.
Reference in New Issue
Block a user