diff --git a/barcodes/generator/default_generator.py b/barcodes/generator/default_generator.py
index 70b9288..ad94194 100644
--- a/barcodes/generator/default_generator.py
+++ b/barcodes/generator/default_generator.py
@@ -1,43 +1,44 @@
from io import BytesIO
-from typing import List, Dict
+from typing import List
from barcodes.attributes import AttributeWriterFactory
from barcodes.generator.base import BaseBarcodeGenerator
from barcodes.pdf import PDFGenerator
-from models import Product, BarcodeTemplate
-from schemas.barcode import PdfBarcodeGenData
+from barcodes.types import BarcodeData, PdfBarcodeGenData, PdfBarcodeImageGenData
class DefaultBarcodeGenerator(BaseBarcodeGenerator):
- def generate(self, barcodes_data: List[Dict[str, str | Product | BarcodeTemplate | int]]) -> BytesIO:
+ def generate(self, barcodes_data: List[BarcodeData | PdfBarcodeImageGenData]) -> BytesIO:
pdf_generator = PDFGenerator()
- pdf_barcodes_gen_data: List[PdfBarcodeGenData] = []
+ pdf_barcodes_gen_data: List[PdfBarcodeGenData | PdfBarcodeImageGenData] = []
for barcode_data in barcodes_data:
- attributes = {}
- for attribute in barcode_data["template"].attributes:
- attribute_getter = AttributeWriterFactory.get_writer(attribute.key)
- if not attribute_getter:
- continue
- value = attribute_getter.get_value(barcode_data["product"])
+ if "barcode" in barcode_data:
+ attributes = {}
+ for attribute in barcode_data["template"].attributes:
+ attribute_getter = AttributeWriterFactory.get_writer(attribute.key)
+ if not attribute_getter:
+ continue
+ value = attribute_getter.get_value(barcode_data["product"])
- if not value or not value.strip():
- continue
- attributes[attribute.name] = value
- for additional_attribute in barcode_data["template"].additional_attributes:
- value = additional_attribute.value
- if not value:
- continue
- attributes[additional_attribute.name] = value
- barcode_text = '
'.join([f'{key}: {value}' for key, value in attributes.items()])
+ if not value or not value.strip():
+ continue
+ attributes[attribute.name] = value
+ for additional_attribute in barcode_data["template"].additional_attributes:
+ value = additional_attribute.value
+ if not value:
+ continue
+ attributes[additional_attribute.name] = value
+ barcode_text = '
'.join([f'{key}: {value}' for key, value in attributes.items()])
- pdf_barcodes_gen_data.append(
- PdfBarcodeGenData(
- barcode_value=barcode_data["barcode"],
- text=barcode_text,
- num_duplicates=barcode_data["quantity"]
- )
- )
+ pdf_barcodes_gen_data.append({
+ "barcode_value": barcode_data["barcode"],
+ "text": barcode_text,
+ "num_duplicates": barcode_data["num_duplicates"]
+ })
+ else:
+ pdf_barcodes_gen_data.append(barcode_data)
return pdf_generator.generate(pdf_barcodes_gen_data)
+
diff --git a/barcodes/images_uploader/base.py b/barcodes/images_uploader/base.py
index 03b39f2..4f727bc 100644
--- a/barcodes/images_uploader/base.py
+++ b/barcodes/images_uploader/base.py
@@ -9,6 +9,10 @@ class BaseImagesUploader:
def get_url(self, filename: str) -> bytes:
pass
+ @abstractmethod
+ def get_abs_path(self, filename: str) -> bytes:
+ pass
+
@abstractmethod
def delete(self, filename: str):
pass
diff --git a/barcodes/images_uploader/images_uploader.py b/barcodes/images_uploader/images_uploader.py
index 3a97452..4b3c9ee 100644
--- a/barcodes/images_uploader/images_uploader.py
+++ b/barcodes/images_uploader/images_uploader.py
@@ -20,6 +20,10 @@ class BarcodeImagesUploader(BaseImagesUploader):
file_location = self.relative_path / filename
return f"{API_ROOT}/{file_location}"
+ def get_abs_path(self, filename: str) -> str:
+ file_location = self.storage_path / filename
+ return file_location
+
def delete(self, filename: str):
file_location = self.storage_path / filename
if file_location.exists():
diff --git a/barcodes/pdf/generator.py b/barcodes/pdf/generator.py
index 7c55371..b25e27b 100644
--- a/barcodes/pdf/generator.py
+++ b/barcodes/pdf/generator.py
@@ -10,8 +10,9 @@ from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
+from barcodes.pdf.pdf_maker import PdfMaker
+from barcodes.types import PdfBarcodeImageGenData, PdfBarcodeGenData
from constants import APP_PATH
-from schemas.barcode import PdfBarcodeGenData
class PDFGenerator:
@@ -102,90 +103,94 @@ class PDFGenerator:
bottomMargin=1 * mm
)
- def generate(self, barcodes_data: List[PdfBarcodeGenData]) -> BytesIO:
+ def _generate_for_one_product(self, barcode_data: PdfBarcodeGenData) -> BytesIO:
buffer = BytesIO()
-
- # Создаем документ с указанным размером страницы
doc = self._create_doc(buffer)
- # Список элементов для добавления в документ
+ # Создаем абзац с новым стилем
+ paragraph = Paragraph(barcode_data['text'], self.small_style)
+
+ # Получаем ширину и высоту абзаца
+ paragraph_width, paragraph_height = paragraph.wrap(self.page_width - 2 * mm, self.page_height)
+
+ # Рассчитываем доступное пространство для штрихкода
+ human_readable_height = 6 * mm # Высота human-readable текста
+ space_between_text_and_barcode = 4 * mm # Отступ между текстом и штрихкодом
+ barcode_height = self.page_height - paragraph_height - human_readable_height - space_between_text_and_barcode - 4 * mm # Учитываем поля и отступы
+
+ # Создаем штрихкод
+ available_width = self.page_width - 2 * mm # Учитываем поля
+
+ # Приблизительное количество элементов в штрихкоде Code 128 для средней длины
+ num_elements = 11 * len(barcode_data['barcode_value']) # Примерная оценка: 11 элементов на символ
+
+ # Рассчитываем ширину штриха
+ bar_width = available_width / num_elements
+ barcode = code128.Code128(
+ barcode_data['barcode_value'],
+ barWidth=bar_width,
+ barHeight=barcode_height,
+ humanReadable=True
+ )
+
+ # Добавление штрихкодов в список элементов документа
elements = []
- product_barcode_canvases = []
-
- for barcode_data in barcodes_data:
- # Создаем абзац с новым стилем
- paragraph = Paragraph(barcode_data.text, self.small_style)
-
- # Получаем ширину и высоту абзаца
- paragraph_width, paragraph_height = paragraph.wrap(self.page_width - 2 * mm, self.page_height)
-
- # Рассчитываем доступное пространство для штрихкода
- human_readable_height = 6 * mm # Высота human-readable текста
- space_between_text_and_barcode = 4 * mm # Отступ между текстом и штрихкодом
- barcode_height = self.page_height - paragraph_height - human_readable_height - space_between_text_and_barcode - 4 * mm # Учитываем поля и отступы
-
- # Создаем штрихкод
- available_width = self.page_width - 2 * mm # Учитываем поля
-
- # Приблизительное количество элементов в штрихкоде Code 128 для средней длины
- num_elements = 11 * len(barcode_data.barcode_value) # Примерная оценка: 11 элементов на символ
-
- # Рассчитываем ширину штриха
- bar_width = available_width / num_elements
- barcode = code128.Code128(
- barcode_data.barcode_value,
- barWidth=bar_width,
- barHeight=barcode_height,
- humanReadable=True
- )
- product_barcode_canvases.append(barcode)
-
- # Добавление штрихкодов в список элементов документа
- for _ in range(barcode_data.num_duplicates):
- elements.append(paragraph)
- elements.append(Spacer(1, space_between_text_and_barcode)) # Отступ между текстом и штрихкодом
- elements.append(PageBreak())
-
- # Добавление спейсеров
- for _ in range(self.number_of_spacing_pages):
- elements.append(PageBreak())
-
- # Удалить последние спейсеры
- elements = elements[:-2]
-
- product_counter, product_barcodes_counter = 0, 0
+ for _ in range(barcode_data['num_duplicates']):
+ elements.append(paragraph)
+ elements.append(Spacer(1, space_between_text_and_barcode)) # Отступ между текстом и штрихкодом
+ elements.append(PageBreak())
# Функция для отрисовки штрихкода на canvas
def add_barcode(canvas, doc):
- nonlocal product_barcodes_counter, product_counter
-
- barcode_canvas = product_barcode_canvases[product_counter]
-
- product_barcodes_counter += 1
-
- # Если данная страница это спейсер, то оставить пустой
- num_duplicates = barcodes_data[product_counter].num_duplicates
- if product_barcodes_counter > num_duplicates:
- if product_barcodes_counter >= num_duplicates + self.number_of_spacing_pages:
- product_barcodes_counter = 0
- product_counter += 1
- return
-
- # Отрисовка штрихкода
- barcode_width = barcode_canvas.width
+ barcode_width = barcode.width
barcode_x = (self.page_width - barcode_width) / 2 # Центрируем штрихкод
barcode_y = human_readable_height + 2 * mm # Размещаем штрихкод снизу с учетом отступа
- barcode_canvas.drawOn(canvas, barcode_x, barcode_y)
+ barcode.drawOn(canvas, barcode_x, barcode_y)
# Создаем документ
- doc.build(elements[:-1], onFirstPage=add_barcode, onLaterPages=add_barcode) # Убираем последний PageBreak
+ doc.build(elements, onFirstPage=add_barcode, onLaterPages=add_barcode) # Убираем последний PageBreak
buffer.seek(0)
return buffer
+ def _generate_for_one_product_using_img(self, barcode_data: PdfBarcodeImageGenData) -> BytesIO:
+ with open(barcode_data["barcode_image_url"], 'rb') as pdf_file:
+ pdf_bytes = pdf_file.read()
+
+ pdf_maker = PdfMaker((self.page_width, self.page_height))
+ for _ in range(barcode_data['num_duplicates']):
+ pdf_maker.add_pdfs(BytesIO(pdf_bytes))
+
+ return pdf_maker.get_bytes()
+
+ def _generate_spacers(self) -> BytesIO:
+ buffer = BytesIO()
+ doc = self._create_doc(buffer)
+ elements = []
+ for _ in range(self.number_of_spacing_pages):
+ elements.append(PageBreak())
+ doc.build(elements)
+ buffer.seek(0)
+ return buffer
+
+ def generate(self, barcodes_data: List[PdfBarcodeGenData | PdfBarcodeImageGenData]) -> BytesIO:
+ pdf_maker = PdfMaker((self.page_width, self.page_height))
+
+ pdf_files: list[BytesIO] = []
+
+ for barcode_data in barcodes_data:
+ if "barcode_value" in barcode_data:
+ pdf_files.append(self._generate_for_one_product(barcode_data))
+ else:
+ pdf_files.append(self._generate_for_one_product_using_img(barcode_data))
+ pdf_files.append(self._generate_spacers())
+
+ for file in pdf_files[:-1]:
+ pdf_maker.add_pdfs(file)
+
+ return pdf_maker.get_bytes()
+
def generate_barcode_image(self, barcode_image_url: str, path_to_save_pdf: str):
- print(type(path_to_save_pdf))
- print(path_to_save_pdf)
c = canvas.Canvas(path_to_save_pdf, pagesize=(self.page_width, self.page_height))
c.drawImage(barcode_image_url, 0, 0, width=self.page_width, height=self.page_height)
c.save()
diff --git a/barcodes/pdf/pdf_maker.py b/barcodes/pdf/pdf_maker.py
new file mode 100644
index 0000000..f261bb6
--- /dev/null
+++ b/barcodes/pdf/pdf_maker.py
@@ -0,0 +1,38 @@
+from io import BytesIO
+
+from fpdf import FPDF
+import pdfrw
+
+class PdfMaker:
+ def __init__(self, size: tuple):
+ self.size = size
+
+ self.writer = pdfrw.PdfWriter()
+
+ def clear(self):
+ del self.writer
+ self.writer = pdfrw.PdfWriter()
+
+ def add_image(self, image_data):
+ size = self.size
+
+ fpdf = FPDF(format=size, unit="pt")
+ width, height = self.size
+ fpdf.add_page()
+ fpdf.image(image_data, 0, 0, width, height)
+ fpdf_reader: pdfrw.PdfReader = pdfrw.PdfReader(fdata=bytes(fpdf.output()))
+ self.writer.addpage(fpdf_reader.getPage(0))
+
+ def add_pdf(self, pdf_data: BytesIO):
+ pdf_reader = pdfrw.PdfReader(fdata=bytes(pdf_data.read()))
+ self.writer.addpage(pdf_reader.getPage(0))
+
+ def add_pdfs(self, pdf_data: BytesIO):
+ pdf_reader = pdfrw.PdfReader(fdata=bytes(pdf_data.read()))
+ self.writer.addpages(pdf_reader.readpages(pdf_reader.Root))
+
+ def get_bytes(self):
+ result_io = BytesIO()
+ self.writer.write(result_io)
+ result_io.seek(0)
+ return result_io
diff --git a/barcodes/types.py b/barcodes/types.py
new file mode 100644
index 0000000..54d0b72
--- /dev/null
+++ b/barcodes/types.py
@@ -0,0 +1,21 @@
+from typing import TypedDict
+
+from models import BarcodeTemplate, Product
+
+
+class BarcodeData(TypedDict):
+ barcode: str
+ template: BarcodeTemplate
+ product: Product
+ num_duplicates: int
+
+
+class PdfBarcodeGenData(TypedDict):
+ barcode_value: str
+ text: str
+ num_duplicates: int
+
+
+class PdfBarcodeImageGenData(TypedDict):
+ num_duplicates: int
+ barcode_image_url: str
diff --git a/requirements.txt b/requirements.txt
index 253f479..4b7bb1e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -30,3 +30,5 @@ aioshutil
reportlab
weasyprint
number_to_string
+pdfrw
+fpdf
\ No newline at end of file
diff --git a/schemas/barcode.py b/schemas/barcode.py
index b886abb..d794ad6 100644
--- a/schemas/barcode.py
+++ b/schemas/barcode.py
@@ -47,12 +47,6 @@ class BarcodeSchema(BaseSchema):
additional_field: str | None = None
-class PdfBarcodeGenData(BaseSchema):
- barcode_value: str
- text: str
- num_duplicates: int = 1
-
-
# endregion
# region Requests
diff --git a/services/barcode.py b/services/barcode.py
index ec89bc7..e7253d5 100644
--- a/services/barcode.py
+++ b/services/barcode.py
@@ -6,6 +6,7 @@ from sqlalchemy.orm import selectinload, joinedload
from barcodes.attributes import AttributeWriterFactory
from barcodes.generator.default_generator import DefaultBarcodeGenerator
+from barcodes.images_uploader import BarcodeImagesUploader
from models import BarcodeTemplate, BarcodeTemplateAttribute, barcode_template_attribute_link, Product, \
BarcodeTemplateAdditionalField, BarcodeTemplateSize, Deal, DealProduct
from schemas.barcode import *
@@ -89,17 +90,27 @@ class BarcodeService(BaseService):
product: Product = query.scalar()
if not product:
raise ValueError('Товар не найден')
- barcode_template = await self._get_barcode_template(request, product)
- default_generator = DefaultBarcodeGenerator()
filename = f'{product.id}_barcode.pdf'
- pdf_buffer = default_generator.generate(
- [{
- "barcode": request.barcode,
- "product": product,
- "template": barcode_template,
- "quantity": request.quantity
- }]
- )
+ default_generator = DefaultBarcodeGenerator()
+
+ if product.barcode_image:
+ uploader = BarcodeImagesUploader()
+ pdf_buffer = default_generator.generate(
+ [{
+ "barcode_image_url": uploader.get_abs_path(product.barcode_image.filename),
+ "num_duplicates": request.quantity
+ }]
+ )
+ else:
+ barcode_template = await self._get_barcode_template(request, product)
+ pdf_buffer = default_generator.generate(
+ [{
+ "barcode": request.barcode,
+ "product": product,
+ "template": barcode_template,
+ "num_duplicates": request.quantity
+ }]
+ )
return filename, pdf_buffer
async def get_deal_barcodes_pdf(self, request: GetDealProductsBarcodesPdfRequest) -> Tuple[str, BytesIO]:
@@ -107,7 +118,7 @@ class BarcodeService(BaseService):
select(Deal)
.options(
selectinload(Deal.products).joinedload(DealProduct.product).selectinload(Product.client),
- selectinload(Deal.products).joinedload(DealProduct.product).joinedload(Product.barcodes)
+ selectinload(Deal.products).joinedload(DealProduct.product).joinedload(Product.barcodes),
)
.filter(Deal.id == request.deal_id)
)
@@ -116,20 +127,27 @@ class BarcodeService(BaseService):
if not deal:
raise ValueError('Сделка не найдена')
+ uploader = BarcodeImagesUploader()
barcodes_data: List[Dict[str, str | Product | BarcodeTemplate | int]] = []
for deal_product in deal.products:
- product_request = GetProductBarcodeRequest(
- product_id=deal_product.product_id,
- barcode="",
- barcode_template_id=deal_product.product.barcode_template_id,
- )
- barcode_template = await self._get_barcode_template(product_request, deal_product.product)
- barcodes_data.append({
- "barcode": deal_product.product.barcodes[0].barcode,
- "product": deal_product.product,
- "template": barcode_template,
- "quantity": deal_product.quantity
- })
+ if deal_product.product.barcode_image:
+ barcodes_data.append({
+ "barcode_image_url": uploader.get_abs_path(deal_product.product.barcode_image.filename),
+ "num_duplicates": deal_product.quantity
+ })
+ else:
+ product_request = GetProductBarcodeRequest(
+ product_id=deal_product.product_id,
+ barcode="",
+ barcode_template_id=deal_product.product.barcode_template_id,
+ )
+ barcode_template = await self._get_barcode_template(product_request, deal_product.product)
+ barcodes_data.append({
+ "barcode": deal_product.product.barcodes[0].barcode,
+ "product": deal_product.product,
+ "template": barcode_template,
+ "num_duplicates": deal_product.quantity
+ })
default_generator = DefaultBarcodeGenerator()
filename = f'{deal.id}_deal_barcodes.pdf'