Odoo tiene muchas fortalezas pero imprimir reportes que sean bonitos no es una de ellas. Ni que hablar de reportes que sean increibles de lo bellos que son, y si a eso le sumas miles de páginas; olvidate del tema.
Eso se debe a que Odoo utiliza para la impresión de sus reportes q-web; lo que le permite a los usuarios utilizar templates y una sintaxis de HTML más Bootstrap que hacen que los reportes sean decentes; y que por lo general funciona. De vuelta; no son del todo bonitos (traten de mandar un reporte hecho con q-web a una editorial) y cuando tenes miles de registros; tenes problemas de generación del reporte ya que el procesamiento del mismo agota los recursos del sistema (convierte de html a pdf con wkhtmltopdf).
Y aca tenemos un problema. Muchos clientes que usan Odoo son distribuidoras que necesitan hacer circular (via e-mail o Whatsapp) PDFs con los catalogos de sus productos entre sus clientes; los cuales muchas veces se encuentran off-line. Y ahí Odoo no ofrece una buena solución por varios motivos. Por más flexible que sea q-web uno igual necesita programar el reporte (el cual no es del todo flexible, vieron como maneja los pies de página por ejemplo?). Por otra parte; no son bonitos. Y cualquier distribuidora tiene miles y miles de productos que publicar.
Al rescate llega Reportlab. Reportlab es una librería Python que permite generar reportes en PDF. Y es realmente poderosa; aca tienen un ejemplo de un catálogo (traten de replicarlo con Q-Web). Y al ser Python, se puede integrar con Odoo.
Integración con Odoo
Como es la integración con Odoo? No queda otra que desarrollar, pero bueno... tampoco es el fin del mundo. Para ello necesitamos un modelo que tenga un campo binario donde se almacene el resultado del catalogo que se va a crear

Y ya que estamos la lista de precios. La idea es tomar los precios calculados con la lista de precios seleccionada. Pero lo importante es el campo binario. La idea es generar el PDF en un archivo temporario y luego almacenarlo en el campo binario de la siguiente manera
# creamos el archivo temporario donde se creará el reporte
import os
import tempfile
fd, path = tempfile.mkstemp(suffix='.pdf'
os.close(fd)
...
# leemos el PDF del archivo, y lo guardamos en el campo binario
f = open(path, 'rb')
pdf_data = f.read()
encoded_data = base64.b64encode(pdf_data)
self.write({'catalog_file': encoded_data})
De esa manera guardamos el PDF generado en un campo binario y ya está listo para que lo descargue el usuario (o se envie vía e-mail). Ahora el paso siguiente es generar el reporte con Reportlab.
Al principio uno toma los datos de los productos que quiere imprimir; y luego los imprime. El primer paso es definir la página del reporte; podemos definir que sea una página A4 en blanco
doc = SimpleDocTemplate(
path,
pagesize=A4,
topMargin=30 * mm,
bottomMargin=15 * mm,
leftMargin=15 * mm,
rightMargin=15 * mm,
)
styles = getSampleStyleSheet()
elements = []
Y seguidamente debemos definir los elementos que se van van a agregar al reporte. Al fin y al cabo un reporte en Reportlab es un documento al que se le agregan los elementos a imprimir; y se construye. Supongamos que queremos tener una página por categoría (imprimiendo sus productos). Para ello definimos una tabla donde se va a imprimir la información de la categoría
for i, categ in enumerate(categs_with_products):
img = _image_flowable(categ.get("image"), img_size)
text_flow = [Paragraph(categ.get("name") or "(sin nombre)", styles["Heading1"])]
if categ.get("description"):
text_flow.append(Spacer(1, 2 * mm))
text_flow.append(simple_text_to_paragraph(categ.get("description"), styles["Normal"]))
header_row = [[img if img else "", text_flow]]
header_table = Table(header_row, colWidths=[img_col_width, text_col_width])
header_table.setStyle(TableStyle([
("VALIGN", (0, 0), (-1, -1), "TOP"),
("LEFTPADDING", (0, 0), (-1, -1), 0),
("RIGHTPADDING", (0, 0), (-1, -1), 6),
("TOPPADDING", (0, 0), (-1, -1), 0),
("BOTTOMPADDING", (0, 0), (-1, -1), 0),
]))
elements.append(header_table)
En este caso agregamos los elementos que van a ser las categorías de los productos (a los que les agregué dos atributos: un campo descriptión que es del tipo Text y un campo binario con una imagen de la categoría.
El paso siguiente es agregar una tabla con los atributos que necesitamos de los productos que de la categoría
# definimos el estilo de la tabla de los productos
product_table_style = TableStyle([
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#2c3e50")),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 9),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#bdc3c7")),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#f2f2f2")]),
("ALIGN", (1, 1), (1, -1), "RIGHT"), # precio alineado a la derecha
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
])
data = [["Código", "Precio"]]
for p in products:
if self.pricelist_id:
price = self.pricelist_id._get_product_price(
product=p,
quantity=1.0,
)
data.append([
p.default_code or "",
f"${price:,.2f}",
])
# se crea la tabla y se aplica el estilo; para luego agregar a los elementos que van a generar el reporte
table = Table(data, colWidths=[35 * mm, 25 * mm], repeatRows=1)
table.setStyle(product_table_style)
elements.append(table)
Como pueden ver; a nivel código uno debe definir toda la apariencia del reporte. Esto puede parecer molesto, pero es muchas veces lo que necesitamos para imprimir catalogos de alta calidad.
Y ahora el paso final; generar el PDF
doc.build(elements)
Como ven, no es el fin del mundo. Lo que lleva más tiempo es ajustar los detalles y hacer que el reporte quede bien. Pero Reportlab es una librería muy potente y flexible que se adapta bien a Odoo; que nos brinda una muy buena solución.