feat: product barcode images
This commit is contained in:
@ -33,4 +33,4 @@ class ProductBarcodeImage(BaseModel):
|
|||||||
)
|
)
|
||||||
product: Mapped["Product"] = relationship(back_populates="barcode_image")
|
product: Mapped["Product"] = relationship(back_populates="barcode_image")
|
||||||
|
|
||||||
filename: Mapped[str] = mapped_column()
|
image_url: Mapped[str] = mapped_column()
|
||||||
|
|||||||
@ -52,7 +52,6 @@ class Product(BaseModel, IdMixin, SoftDeleteMixin):
|
|||||||
barcode_image: Mapped["ProductBarcodeImage"] = relationship(
|
barcode_image: Mapped["ProductBarcodeImage"] = relationship(
|
||||||
back_populates="product",
|
back_populates="product",
|
||||||
lazy="joined",
|
lazy="joined",
|
||||||
uselist=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from sqlalchemy import or_, delete
|
from sqlalchemy import or_, delete
|
||||||
from sqlalchemy.orm import selectinload, joinedload
|
from sqlalchemy.orm import selectinload, joinedload
|
||||||
|
|
||||||
from modules.fulfillment_base.models import Product, ProductBarcode, BarcodeTemplate
|
from modules.fulfillment_base.models import Product, ProductBarcode, BarcodeTemplate, ProductBarcodeImage
|
||||||
from modules.fulfillment_base.models.product import ProductImage
|
from modules.fulfillment_base.models.product import ProductImage
|
||||||
from modules.fulfillment_base.schemas.product import (
|
from modules.fulfillment_base.schemas.product import (
|
||||||
CreateProductSchema,
|
CreateProductSchema,
|
||||||
@ -106,6 +106,13 @@ class ProductRepository(
|
|||||||
else:
|
else:
|
||||||
await self.session.flush()
|
await self.session.flush()
|
||||||
|
|
||||||
|
async def delete_barcode_image(self, barcode_image: ProductBarcodeImage, with_commit: bool = False):
|
||||||
|
await self.session.delete(barcode_image)
|
||||||
|
if with_commit:
|
||||||
|
await self.session.commit()
|
||||||
|
else:
|
||||||
|
await self.session.flush()
|
||||||
|
|
||||||
async def create_image(self, product_id: int, image_url: str) -> ProductImage:
|
async def create_image(self, product_id: int, image_url: str) -> ProductImage:
|
||||||
product_image = ProductImage(
|
product_image = ProductImage(
|
||||||
product_id=product_id,
|
product_id=product_id,
|
||||||
@ -114,3 +121,12 @@ class ProductRepository(
|
|||||||
self.session.add(product_image)
|
self.session.add(product_image)
|
||||||
await self.session.commit()
|
await self.session.commit()
|
||||||
return product_image
|
return product_image
|
||||||
|
|
||||||
|
async def create_barcode_image(self, product_id: int, image_url: str) -> ProductBarcodeImage:
|
||||||
|
product_barcode_image = ProductBarcodeImage(
|
||||||
|
product_id=product_id,
|
||||||
|
image_url=image_url,
|
||||||
|
)
|
||||||
|
self.session.add(product_barcode_image)
|
||||||
|
await self.session.commit()
|
||||||
|
return product_barcode_image
|
||||||
|
|||||||
@ -16,6 +16,11 @@ class ProductImageSchema(BaseSchema):
|
|||||||
image_url: str
|
image_url: str
|
||||||
|
|
||||||
|
|
||||||
|
class ProductBarcodeImageSchema(BaseSchema):
|
||||||
|
product_id: int
|
||||||
|
image_url: str
|
||||||
|
|
||||||
|
|
||||||
class CreateProductSchema(BaseSchema):
|
class CreateProductSchema(BaseSchema):
|
||||||
name: str
|
name: str
|
||||||
article: str
|
article: str
|
||||||
@ -27,21 +32,27 @@ class CreateProductSchema(BaseSchema):
|
|||||||
composition: Optional[str]
|
composition: Optional[str]
|
||||||
size: Optional[str]
|
size: Optional[str]
|
||||||
additional_info: Optional[str]
|
additional_info: Optional[str]
|
||||||
barcodes: list[str]
|
barcodes: list[str] = []
|
||||||
image_url: str | None = None
|
|
||||||
images: list[ProductImageSchema] | None = []
|
|
||||||
|
class BaseProductSchema(CreateProductSchema):
|
||||||
|
image_url: Optional[str] = None
|
||||||
|
images: Optional[list[ProductImageSchema]] = []
|
||||||
|
barcode_image_url: Optional[str] = None
|
||||||
|
barcode_image: Optional[ProductBarcodeImageSchema] = None
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def images_list_to_image_url(cls, values):
|
def images_list_to_image_url(cls, values):
|
||||||
images = values.images
|
if values.images:
|
||||||
if not images:
|
latest_image = values.images[-1]
|
||||||
return values
|
values.image_url = latest_image.image_url
|
||||||
latest_image = images[-1]
|
|
||||||
values.image_url = latest_image.image_url
|
if values.barcode_image:
|
||||||
|
values.barcode_image_url = values.barcode_image.image_url
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
class ProductSchema(CreateProductSchema):
|
class ProductSchema(BaseProductSchema):
|
||||||
id: int
|
id: int
|
||||||
barcode_template: BarcodeTemplateSchema
|
barcode_template: BarcodeTemplateSchema
|
||||||
|
|
||||||
@ -116,4 +127,12 @@ class GetProductBarcodePdfResponse(BasePdfResponse):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BarcodeUploadImageResponse(BaseResponse):
|
||||||
|
image_url: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteBarcodeImageResponse(BaseResponse):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|||||||
@ -48,19 +48,51 @@ class ProductService(
|
|||||||
async def upload_image(
|
async def upload_image(
|
||||||
self, product_id: int, upload_file: UploadFile
|
self, product_id: int, upload_file: UploadFile
|
||||||
) -> ProductUploadImageResponse:
|
) -> ProductUploadImageResponse:
|
||||||
try:
|
product: Product = await self.repository.get_by_id(product_id)
|
||||||
product: Product = await self.repository.get_by_id(product_id)
|
s3_uploader = S3Uploader()
|
||||||
s3_uploader = S3Uploader()
|
|
||||||
|
|
||||||
|
if len(product.images) > 0:
|
||||||
for image in product.images:
|
for image in product.images:
|
||||||
s3_key = image.image_url.split("/")[-1]
|
s3_key = image.image_url.split("/")[-1]
|
||||||
await s3_uploader.delete_image(s3_key)
|
await s3_uploader.delete_image(s3_key)
|
||||||
await self.repository.delete_images(product.images)
|
await self.repository.delete_images(product.images)
|
||||||
|
|
||||||
image_url = await s3_uploader.upload_from_upload_file_obj(upload_file)
|
image_url = await s3_uploader.upload_from_upload_file_obj(upload_file)
|
||||||
await self.repository.create_image(product_id, image_url)
|
await self.repository.create_image(product_id, image_url)
|
||||||
return ProductUploadImageResponse(
|
return ProductUploadImageResponse(
|
||||||
ok=True, message="Изображение успешно загружено", image_url=image_url
|
message="Изображение успешно загружено", image_url=image_url
|
||||||
|
)
|
||||||
|
|
||||||
|
async def upload_barcode_image(
|
||||||
|
self, product_id: int, upload_file: UploadFile
|
||||||
|
) -> BarcodeUploadImageResponse:
|
||||||
|
product: Product = await self.repository.get_by_id(product_id)
|
||||||
|
s3_uploader = S3Uploader()
|
||||||
|
|
||||||
|
if product.barcode_image:
|
||||||
|
s3_key = product.barcode_image.image_url.split("/")[-1]
|
||||||
|
await s3_uploader.delete_image(s3_key)
|
||||||
|
await self.repository.delete_barcode_image(product.barcode_image)
|
||||||
|
|
||||||
|
image_url = await s3_uploader.upload_from_upload_file_obj(upload_file)
|
||||||
|
await self.repository.create_barcode_image(product_id, image_url)
|
||||||
|
return BarcodeUploadImageResponse(
|
||||||
|
message="Изображение штрихкода успешно загружено", image_url=image_url
|
||||||
|
)
|
||||||
|
|
||||||
|
async def delete_barcode_image(self, product_id: int) -> DeleteBarcodeImageResponse:
|
||||||
|
product: Product = await self.repository.get_by_id(product_id)
|
||||||
|
|
||||||
|
if not product.barcode_image:
|
||||||
|
return DeleteBarcodeImageResponse(
|
||||||
|
message="У товара нет изображения штрихкода"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
|
||||||
return ProductUploadImageResponse(ok=False, message=str(e))
|
s3_uploader = S3Uploader()
|
||||||
|
s3_key = product.barcode_image.image_url.split("/")[-1]
|
||||||
|
await s3_uploader.delete_image(s3_key)
|
||||||
|
await self.repository.delete_barcode_image(product.barcode_image, True)
|
||||||
|
|
||||||
|
return DeleteBarcodeImageResponse(
|
||||||
|
message="Изображение штрихкода успешно удалено"
|
||||||
|
)
|
||||||
|
|||||||
@ -49,16 +49,16 @@ async def update_product(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/images/upload/{productId}",
|
"{pk}/images/upload",
|
||||||
response_model=ProductUploadImageResponse,
|
response_model=ProductUploadImageResponse,
|
||||||
operation_id="upload_product_image",
|
operation_id="upload_product_image",
|
||||||
)
|
)
|
||||||
async def upload_product_image(
|
async def upload_product_image(
|
||||||
session: SessionDependency,
|
session: SessionDependency,
|
||||||
upload_file: Annotated[UploadFile, File()],
|
upload_file: Annotated[UploadFile, File()],
|
||||||
product_id: int = Path(alias="productId"),
|
pk: int = Path(),
|
||||||
):
|
):
|
||||||
return await ProductService(session).upload_image(product_id, upload_file)
|
return await ProductService(session).upload_image(pk, upload_file)
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
@ -86,3 +86,28 @@ async def get_product_barcode_pdf(
|
|||||||
return GetProductBarcodePdfResponse(
|
return GetProductBarcodePdfResponse(
|
||||||
base64_string=base64_string, filename=filename, mime_type="application/pdf"
|
base64_string=base64_string, filename=filename, mime_type="application/pdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"{pk}/barcode/image/upload",
|
||||||
|
response_model=BarcodeUploadImageResponse,
|
||||||
|
operation_id="upload_product_barcode_image",
|
||||||
|
)
|
||||||
|
async def upload_product_barcode_image(
|
||||||
|
upload_file: UploadFile,
|
||||||
|
session: SessionDependency,
|
||||||
|
pk: int,
|
||||||
|
):
|
||||||
|
return await ProductService(session).upload_barcode_image(pk, upload_file)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"{pk}/barcode/image",
|
||||||
|
response_model=DeleteBarcodeImageResponse,
|
||||||
|
operation_id="delete_product_barcode_image",
|
||||||
|
)
|
||||||
|
async def delete_product_barcode_image(
|
||||||
|
session: SessionDependency,
|
||||||
|
pk: int,
|
||||||
|
):
|
||||||
|
return await ProductService(session).delete_barcode_image(pk)
|
||||||
|
|||||||
Reference in New Issue
Block a user