[FIX] stock: fix text product label reports with special characters
Current behavior: When printing products ZPL Labels, special characters are not printed correctly. e.g. quotes become ' Steps to reproduce: - Modify a product name with special characters - Print a product label - Select ZPL Labels As the report is only rendered as text, the special characters are not dangerous and can be printed as is. opw-3684870 closes odoo/odoo#156624 X-original-commit: 7875c91be5dd467374b39bc1a7d05e9f983fbaef Signed-off-by: Quentin Wolfs (quwo) <quwo@odoo.com>
This commit is contained in:
@@ -6,6 +6,7 @@ from collections import defaultdict
|
||||
from odoo import _, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
import markupsafe
|
||||
|
||||
class ReportProductLabel(models.AbstractModel):
|
||||
_name = 'report.stock.label_product_product_view'
|
||||
@@ -22,13 +23,47 @@ class ReportProductLabel(models.AbstractModel):
|
||||
quantity_by_product = defaultdict(list)
|
||||
for p, q in data.get('quantity_by_product').items():
|
||||
product = Product.browse(int(p))
|
||||
quantity_by_product[product].append((product.barcode, q))
|
||||
default_code_markup = markupsafe.Markup(product.default_code) if product.default_code else ''
|
||||
product_info = {
|
||||
'barcode': markupsafe.Markup(product.barcode),
|
||||
'quantity': q,
|
||||
'display_name_markup': markupsafe.Markup(product.display_name),
|
||||
'default_code': (default_code_markup[:15], default_code_markup[15:30])
|
||||
}
|
||||
quantity_by_product[product].append(product_info)
|
||||
if data.get('custom_barcodes'):
|
||||
# we expect custom barcodes to be: {product: [(barcode, qty_of_barcode)]}
|
||||
for product, barcodes_qtys in data.get('custom_barcodes').items():
|
||||
quantity_by_product[Product.browse(int(product))] += (barcodes_qtys)
|
||||
product = Product.browse(int(product))
|
||||
default_code_markup = markupsafe.Markup(product.default_code) if product.default_code else ''
|
||||
for barcode_qty in barcodes_qtys:
|
||||
quantity_by_product[product].append({
|
||||
'barcode': markupsafe.Markup(barcode_qty[0]),
|
||||
'quantity': barcode_qty[1],
|
||||
'display_name_markup': markupsafe.Markup(product.display_name),
|
||||
'default_code': (default_code_markup[:15], default_code_markup[15:30])
|
||||
}
|
||||
)
|
||||
data['quantity'] = quantity_by_product
|
||||
layout_wizard = self.env['product.label.layout'].browse(data.get('layout_wizard'))
|
||||
data['pricelist'] = layout_wizard.pricelist_id
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class ReportLotLabel(models.AbstractModel):
|
||||
_name = 'report.stock.label_lot_template_view'
|
||||
_description = 'Lot Label Report'
|
||||
|
||||
def _get_report_values(self, docids, data):
|
||||
lots = self.env['stock.lot'].browse(docids)
|
||||
lot_list = []
|
||||
for lot in lots:
|
||||
lot_list.append({
|
||||
'display_name_markup': markupsafe.Markup(lot.product_id.display_name),
|
||||
'name': markupsafe.Markup(lot.name),
|
||||
'lot_record': lot
|
||||
})
|
||||
return {
|
||||
'docs': lot_list,
|
||||
}
|
||||
|
||||
@@ -4,23 +4,22 @@
|
||||
<template id="label_product_product_view">
|
||||
<t t-foreach="quantity.items()" t-as="barcode_and_qty_by_product">
|
||||
<t t-set="product" t-value="barcode_and_qty_by_product[0]"/>
|
||||
<t t-foreach="barcode_and_qty_by_product[1]" t-as="barcode_and_qty">
|
||||
<t t-set="barcode" t-value="barcode_and_qty[0]"/>
|
||||
<t t-foreach="barcode_and_qty_by_product[1]" t-as="product_info">
|
||||
<t t-set="barcode" t-value="product_info['barcode']"/>
|
||||
<t t-set="currency_id" t-value="pricelist.currency_id or product.currency_id"/>
|
||||
<t t-foreach="range(barcode_and_qty[1])" t-as="qty">
|
||||
<t t-foreach="range(product_info['quantity'])" t-as="qty">
|
||||
<t t-translation="off">
|
||||
^XA
|
||||
^FT100,80^A0N,40,30^FD<t t-esc="product.display_name"/>^FS
|
||||
^XA^CI28
|
||||
^FT100,80^A0N,40,30^FD<t t-out="product_info['display_name_markup']"/>^FS
|
||||
<t t-if="product.default_code and len(product.default_code) > 15">
|
||||
^FT100,115^A0N,30,24^FD<t t-esc="product.default_code[:15]"/>^FS
|
||||
^FT100,150^A0N,30,24^FD<t t-esc="product.default_code[15:30]"/>^FS
|
||||
^FT100,115^A0N,30,24^FD<t t-out="product_info['default_code'][0]"/>^FS
|
||||
^FT100,150^A0N,30,24^FD<t t-out="product_info['default_code'][1]"/>^FS
|
||||
</t>
|
||||
<t t-else="">
|
||||
^FT100,150^A0N,30,24^FD<t t-esc="product.default_code"/>^FS
|
||||
^FT100,150^A0N,30,24^FD<t t-out="product_info['default_code'][0]"/>^FS
|
||||
</t>
|
||||
<t t-if="price_included">
|
||||
^FO600,100,1
|
||||
^CI28
|
||||
<t t-if="currency_id.position == 'after'">
|
||||
^A0N,66,48^FH^FD<t t-esc="pricelist._get_product_price(product, 1, currency_id)" t-options='{"widget": "float", "precision": 2}'/><t t-esc="currency_id.symbol"/>^FS
|
||||
</t>
|
||||
@@ -31,7 +30,7 @@
|
||||
<t t-if="barcode">
|
||||
^FO100,160^BY3
|
||||
^BCN,100,Y,N,N
|
||||
^FD<t t-esc="barcode"/>^FS
|
||||
^FD<t t-out="barcode"/>^FS
|
||||
</t>
|
||||
^XZ
|
||||
</t>
|
||||
@@ -43,16 +42,16 @@
|
||||
<template id="label_lot_template_view">
|
||||
<t t-foreach="docs" t-as="lot">
|
||||
<t t-translation="off">
|
||||
^XA
|
||||
^XA^CI28
|
||||
^FO100,50
|
||||
^A0N,44,33^FD<t t-out="lot.product_id.display_name"/>^FS
|
||||
^A0N,44,33^FD<t t-out="lot['display_name_markup']"/>^FS
|
||||
^FO100,100
|
||||
^A0N,44,33^FDLN/SN: <t t-out="lot.name"/>^FS
|
||||
^A0N,44,33^FDLN/SN: <t t-out="lot['name']"/>^FS
|
||||
<t t-if="env.user.has_group('stock.group_stock_lot_print_gs1')">
|
||||
<t t-if="lot.product_id.valid_ean" t-set="final_barcode" t-value="'01' + '0' * (14 - len(lot.product_id.barcode)) + lot.product_id.barcode"/>
|
||||
<t t-if="lot['lot_record'].product_id.valid_ean" t-set="final_barcode" t-value="'01' + '0' * (14 - len(lot['lot_record'].product_id.barcode)) + lot['lot_record'].product_id.barcode"/>
|
||||
<!-- TODO: must keep lot/sn as last value in barcode because we cannot pad '0's without changing lot/sn name until we can scan in FNC1. -->
|
||||
<t t-if="lot.product_id.tracking == 'lot'" name="datamatrix_lot" t-set="final_barcode" t-value="(final_barcode or '') + '10' + lot.name"/>
|
||||
<t t-elif="lot.product_id.tracking == 'serial'" t-set="final_barcode" t-value="(final_barcode or '') + '21' + lot.name"/>
|
||||
<t t-if="lot['lot_record'].product_id.tracking == 'lot'" name="datamatrix_lot" t-set="final_barcode" t-value="(final_barcode or '') + '10' + lot['name']"/>
|
||||
<t t-elif="lot['lot_record'].product_id.tracking == 'serial'" t-set="final_barcode" t-value="(final_barcode or '') + '21' + lot['name']"/>
|
||||
^FO425,150^BY3
|
||||
^BXN,8,200
|
||||
^FD<t t-out="final_barcode"/>^FS
|
||||
@@ -60,7 +59,7 @@
|
||||
<t t-else="" name="code128_barcode">
|
||||
^FO100,150^BY3
|
||||
^BCN,100,Y,N,N
|
||||
^FD<t t-out="lot.name"/>^FS
|
||||
^FD<t t-out="lot['name']"/>^FS
|
||||
</t>
|
||||
^XZ
|
||||
</t>
|
||||
|
||||
@@ -17,6 +17,15 @@ class TestReportsCommon(TransactionCase):
|
||||
cls.supplier_location = cls.env['stock.location'].browse(cls.ModelDataObj._xmlid_to_res_id('stock.stock_location_suppliers'))
|
||||
cls.stock_location = cls.env['stock.location'].browse(cls.ModelDataObj._xmlid_to_res_id('stock.stock_location_stock'))
|
||||
|
||||
cls.product1 = cls.env['product.product'].create({
|
||||
'name': 'Mellohi"',
|
||||
'type': 'product',
|
||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||
'tracking': 'lot',
|
||||
'default_code': 'C4181234""154654654654',
|
||||
'barcode': 'scan""me'
|
||||
})
|
||||
|
||||
product_form = Form(cls.env['product.product'])
|
||||
product_form.detailed_type = 'product'
|
||||
product_form.name = 'Product'
|
||||
@@ -39,24 +48,47 @@ class TestReportsCommon(TransactionCase):
|
||||
|
||||
|
||||
class TestReports(TestReportsCommon):
|
||||
def test_reports(self):
|
||||
product1 = self.env['product.product'].create({
|
||||
'name': 'Mellohi',
|
||||
'default_code': 'C418',
|
||||
|
||||
def test_product_label_reports(self):
|
||||
""" Test that all the special characters are correctly rendered for the product name, the default code and the barcode.
|
||||
In this test we test that the double quote is rendered correctly.
|
||||
"""
|
||||
report = self.env.ref('stock.label_product_product')
|
||||
target = b'\n\n^XA^CI28\n^FT100,80^A0N,40,30^FD[C4181234""154654654654]Mellohi"^FS\n^FT100,115^A0N,30,24^FDC4181234""15465^FS\n^FT100,150^A0N,30,24^FD4654654^FS\n^FO100,160^BY3\n^BCN,100,Y,N,N\n^FDscan""me^FS\n^XZ\n\n\n^XA^CI28\n^FT100,80^A0N,40,30^FD[C4181234""154654654654]Mellohi"^FS\n^FT100,115^A0N,30,24^FDC4181234""15465^FS\n^FT100,150^A0N,30,24^FD4654654^FS\n^FO100,160^BY3\n^BCN,100,Y,N,N\n^FDscan""me^FS\n^XZ\n'
|
||||
rendering, qweb_type = report._render_qweb_text('stock.label_product_product', self.product1.product_tmpl_id.id, {'quantity_by_product': {self.product1.product_tmpl_id.id: 2}, 'active_model': 'product.template'})
|
||||
self.assertEqual(target, rendering.replace(b' ', b''), 'Product name, default code or barcode is not correctly rendered, make sure the quotes are escaped correctly')
|
||||
self.assertEqual(qweb_type, 'text', 'the report type is not good')
|
||||
|
||||
def test_product_label_custom_barcode_reports(self):
|
||||
""" Test that the custom barcodes are correctly rendered with special characters."""
|
||||
report = self.env.ref('stock.label_product_product')
|
||||
target = b'\n\n^XA^CI28\n^FT100,80^A0N,40,30^FD[C4181234""154654654654]Mellohi"^FS\n^FT100,115^A0N,30,24^FDC4181234""15465^FS\n^FT100,150^A0N,30,24^FD4654654^FS\n^FO100,160^BY3\n^BCN,100,Y,N,N\n^FD123"barcode^FS\n^XZ\n\n\n^XA^CI28\n^FT100,80^A0N,40,30^FD[C4181234""154654654654]Mellohi"^FS\n^FT100,115^A0N,30,24^FDC4181234""15465^FS\n^FT100,150^A0N,30,24^FD4654654^FS\n^FO100,160^BY3\n^BCN,100,Y,N,N\n^FD123"barcode^FS\n^XZ\n\n\n^XA^CI28\n^FT100,80^A0N,40,30^FD[C4181234""154654654654]Mellohi"^FS\n^FT100,115^A0N,30,24^FDC4181234""15465^FS\n^FT100,150^A0N,30,24^FD4654654^FS\n^FO100,160^BY3\n^BCN,100,Y,N,N\n^FDbarcode"456^FS\n^XZ\n\n\n^XA^CI28\n^FT100,80^A0N,40,30^FD[C4181234""154654654654]Mellohi"^FS\n^FT100,115^A0N,30,24^FDC4181234""15465^FS\n^FT100,150^A0N,30,24^FD4654654^FS\n^FO100,160^BY3\n^BCN,100,Y,N,N\n^FDbarcode"456^FS\n^XZ\n'
|
||||
rendering, qweb_type = report._render_qweb_text('stock.label_product_product', self.product1.product_tmpl_id.id, {'custom_barcodes': {self.product1.product_tmpl_id.id: [('123"barcode', 2), ('barcode"456', 2)]}, 'quantity_by_product': {}, 'active_model': 'product.template'})
|
||||
self.assertEqual(target, rendering.replace(b' ', b''), 'Custom barcodes are most likely not corretly rendered, make sure the quotes are escaped correctly')
|
||||
self.assertEqual(qweb_type, 'text', 'the report type is not good')
|
||||
|
||||
def test_reports_with_special_characters(self):
|
||||
product_test = self.env['product.product'].create({
|
||||
'name': 'Mellohi"',
|
||||
'type': 'product',
|
||||
'categ_id': self.env.ref('product.product_category_all').id,
|
||||
'tracking': 'lot',
|
||||
'barcode': 'scan_me'
|
||||
'default_code': 'C4181234""154654654654',
|
||||
'barcode': '9745213796142'
|
||||
})
|
||||
|
||||
lot1 = self.env['stock.lot'].create({
|
||||
'name': 'Volume-Beta',
|
||||
'product_id': product1.id,
|
||||
'name': 'Volume-Beta"',
|
||||
'product_id': product_test.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
target = b'\n\n^XA\n^FO100,50\n^A0N,44,33^FD[C418]Mellohi^FS\n^FO100,100\n^A0N,44,33^FDLN/SN:Volume-Beta^FS\n^FO100,150^BY3\n^BCN,100,Y,N,N\n^FDVolume-Beta^FS\n^XZ\n'
|
||||
#add group to the user
|
||||
self.env.user.groups_id += self.env.ref('stock.group_stock_lot_print_gs1')
|
||||
report = self.env.ref('stock.label_lot_template')
|
||||
target = b'\n\n^XA^CI28\n^FO100,50\n^A0N,44,33^FD[C4181234""154654654654]Mellohi"^FS\n^FO100,100\n^A0N,44,33^FDLN/SN:Volume-Beta"^FS\n\n^FO425,150^BY3\n^BXN,8,200\n^FD010974521379614210Volume-Beta"^FS\n^XZ\n'
|
||||
|
||||
rendering, qweb_type = self.env['ir.actions.report']._render_qweb_text('stock.label_lot_template', lot1.id)
|
||||
self.assertEqual(target, rendering.replace(b' ', b''), 'The rendering is not good')
|
||||
rendering, qweb_type = report._render_qweb_text('stock.label_lot_template', lot1.id)
|
||||
self.assertEqual(target, rendering.replace(b' ', b''), 'The rendering is not good, make sure quotes are correctly escaped')
|
||||
self.assertEqual(qweb_type, 'text', 'the report type is not good')
|
||||
|
||||
def test_report_quantity_1(self):
|
||||
|
||||
Reference in New Issue
Block a user