[MIG] printer_zpl2 to v13 (end)

This commit is contained in:
mreficent
2020-06-30 11:44:48 +02:00
committed by Lois Rilo
parent f920445f6a
commit 61836ecc65
11 changed files with 203 additions and 95 deletions

View File

@@ -5,9 +5,10 @@
"name": "Printer ZPL II", "name": "Printer ZPL II",
"version": "13.0.1.0.0", "version": "13.0.1.0.0",
"category": "Printer", "category": "Printer",
"summary": "Add a ZPL II label printing feature",
"author": "SUBTENO-IT, FLorent de Labarre, " "author": "SUBTENO-IT, FLorent de Labarre, "
"Apertoso NV, Odoo Community Association (OCA)", "Apertoso NV, Odoo Community Association (OCA)",
"website": "http://www.syleam.fr/", "website": "https://github.com/OCA/report-print-send",
"license": "AGPL-3", "license": "AGPL-3",
"external_dependencies": {"python": ["zpl2"]}, "external_dependencies": {"python": ["zpl2"]},
"depends": ["base_report_to_printer"], "depends": ["base_report_to_printer"],

View File

@@ -4,13 +4,16 @@
import base64 import base64
import datetime import datetime
import io import io
import itertools
import logging import logging
import time import time
from collections import defaultdict
import requests import requests
from PIL import Image, ImageOps from PIL import Image, ImageOps
from odoo import _, api, exceptions, fields, models from odoo import _, api, exceptions, fields, models
from odoo.exceptions import ValidationError
from odoo.tools.safe_eval import safe_eval from odoo.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -63,15 +66,16 @@ class PrintingLabelZpl2(models.Model):
default=True, default=True,
) )
action_window_id = fields.Many2one( action_window_id = fields.Many2one(
comodel_name="ir.actions.act_window", string="Action", readonly=True comodel_name="ir.actions.act_window", string="Action", readonly=True,
) )
test_print_mode = fields.Boolean(string="Mode Print") test_print_mode = fields.Boolean(string="Mode Print")
test_labelary_mode = fields.Boolean(string="Mode Labelary") test_labelary_mode = fields.Boolean(string="Mode Labelary")
record_id = fields.Integer(string="Record ID", default=1) record_id = fields.Integer(string="Record ID", default=1)
extra = fields.Text(string="Extra", default="{}") extra = fields.Text(string="Extra", default="{}")
printer_id = fields.Many2one(comodel_name="printing.printer", string="Printer") printer_id = fields.Many2one(comodel_name="printing.printer", string="Printer")
labelary_image = fields.Binary(string='Image from Labelary', labelary_image = fields.Binary(
compute='_compute_labelary_image') string="Image from Labelary", compute="_compute_labelary_image"
)
labelary_dpmm = fields.Selection( labelary_dpmm = fields.Selection(
selection=[ selection=[
("6dpmm", "6dpmm (152 pdi)"), ("6dpmm", "6dpmm (152 pdi)"),
@@ -85,15 +89,46 @@ class PrintingLabelZpl2(models.Model):
) )
labelary_width = fields.Float(string="Width in mm", default=140) labelary_width = fields.Float(string="Width in mm", default=140)
labelary_height = fields.Float(string="Height in mm", default=70) labelary_height = fields.Float(string="Height in mm", default=70)
data_type = fields.Selection(
string="Labels components data type", @api.constrains("component_ids")
selection=[("und", "Undefined")], def check_recursion(self):
help="This allows to specify the type of the data encoded in label components", cr = self._cr
self.flush(["component_ids"])
query = ( # pylint: disable=E8103
'SELECT "{}", "{}" FROM "{}" '
'WHERE "{}" IN %s AND "{}" IS NOT NULL'.format(
"label_id",
"sublabel_id",
"printing_label_zpl2_component",
"label_id",
"sublabel_id",
)
) )
@api.model succs = defaultdict(set) # transitive closure of successors
def _get_component_data(self, component, eval_args): preds = defaultdict(set) # transitive closure of predecessors
return safe_eval(component.data, eval_args) or "" todo, done = set(self.ids), set()
while todo:
cr.execute(query, [tuple(todo)])
done.update(todo)
todo.clear()
for id1, id2 in cr.fetchall():
for x, y in itertools.product(
[id1] + list(preds[id1]), [id2] + list(succs[id2])
):
if x == y:
raise ValidationError(_("You can not create recursive labels."))
succs[x].add(y)
preds[y].add(x)
if id2 not in done:
todo.add(id2)
def _get_component_data(self, record, component, eval_args):
if component.data_autofill:
data = component.autofill_data(record, eval_args)
else:
data = component.data
return safe_eval(str(data), eval_args) or ""
def _get_to_data_to_print( def _get_to_data_to_print(
self, self,
@@ -116,8 +151,8 @@ class PrintingLabelZpl2(models.Model):
"datetime": datetime, "datetime": datetime,
} }
) )
data = self._get_component_data(component, eval_args) data = self._get_component_data(record, component, eval_args)
if data == "component_not_show": if isinstance(data, str) and data == "component_not_show":
continue continue
# Generate a list of elements if the component is repeatable # Generate a list of elements if the component is repeatable
@@ -157,10 +192,6 @@ class PrintingLabelZpl2(models.Model):
label_offset_y=0, label_offset_y=0,
**extra **extra
): ):
self.ensure_one()
# Add all elements to print in a list of tuples :
# [(component, data, offset_x, offset_y)]
to_print = self._get_to_data_to_print( to_print = self._get_to_data_to_print(
record, page_number, page_count, label_offset_x, label_offset_y, **extra record, page_number, page_count, label_offset_x, label_offset_y, **extra
) )
@@ -259,7 +290,7 @@ class PrintingLabelZpl2(models.Model):
component_offset_y += component.sublabel_id.origin_y component_offset_y += component.sublabel_id.origin_y
component.sublabel_id._generate_zpl2_components_data( component.sublabel_id._generate_zpl2_components_data(
label_data, label_data,
data, data if isinstance(data, models.BaseModel) else record,
label_offset_x=component_offset_x, label_offset_x=component_offset_x,
label_offset_y=component_offset_y, label_offset_y=component_offset_y,
) )
@@ -325,55 +356,70 @@ class PrintingLabelZpl2(models.Model):
return label_data.output() return label_data.output()
def fill_component(self, line):
for component in self.component_ids:
json = {
"product_barcode": line.product_barcode,
"lot_barcode": line.lot_barcode,
"uom": str(line.product_qty) + " " + line.product_id.uom_id.name,
"package_barcode": line.package_barcode,
"product_qty": line.product_qty,
}
component.data = json
def print_label(self, printer, record, page_count=1, **extra): def print_label(self, printer, record, page_count=1, **extra):
for label in self: for label in self:
if record._name != label.model_id.model: if record._name != label.model_id.model:
raise exceptions.UserError( raise exceptions.UserError(
_("This label cannot be used on {model}").format(model=record._name) _("This label cannot be used on {model}").format(model=record._name)
) )
if label.data_type == "und":
for component in self.component_ids:
eval_args = extra
eval_args.update(
{"object": record, "time": time, "datetime": datetime}
)
data = safe_eval(component.data, eval_args) or ""
if data == "component_not_show":
continue
# Send the label to printer # Send the label to printer
label_contents = label._generate_zpl2_data( label_contents = label._generate_zpl2_data(
record, page_count=1, **extra record, page_count=page_count, **extra
) )
printer.print_document( printer.print_document(
report=None, content=label_contents, doc_format="raw" report=None, content=label_contents, doc_format="raw"
) )
return True return True
def create_action(self): @api.model
for label in self.filtered(lambda record: not record.action_window_id): def new_action(self, model_id):
label.action_window_id = self.env["ir.actions.act_window"].create( return self.env["ir.actions.act_window"].create(
{ {
"name": _("Print Label"), "name": _("Print Label"),
"binding_model_id": label.model_id.id, "binding_model_id": model_id,
"res_model": "wizard.print.record.label", "res_model": "wizard.print.record.label",
"view_mode": "form", "view_mode": "form",
"target": "new", "target": "new",
"binding_type": "action", "binding_type": "action",
"context": "{'default_active_model_id': %s}" % model_id,
} }
) )
@api.model
def add_action(self, model_id):
action = self.env["ir.actions.act_window"].search(
[
("binding_model_id", "=", model_id),
("res_model", "=", "wizard.print.record.label"),
("view_mode", "=", "form"),
("binding_type", "=", "action"),
]
)
if not action:
action = self.new_action(model_id)
return action
def create_action(self):
models = self.filtered(lambda record: not record.action_window_id).mapped(
"model_id"
)
labels = self.with_context(active_test=False).search(
[("model_id", "in", models.ids), ("action_window_id", "=", False)]
)
actions = self.env["ir.actions.act_window"].search(
[
("binding_model_id", "in", models.ids),
("res_model", "=", "wizard.print.record.label"),
("view_mode", "=", "form"),
("binding_type", "=", "action"),
]
)
for model in models:
action = actions.filtered(lambda a: a.binding_model_id == model)
if not action:
action = self.new_action(model.id)
for label in labels.filtered(lambda l: l.model_id == model):
label.action_window_id = action
return True return True
def unlink_action(self): def unlink_action(self):
@@ -382,7 +428,6 @@ class PrintingLabelZpl2(models.Model):
def import_zpl2(self): def import_zpl2(self):
self.ensure_one() self.ensure_one()
return { return {
"view_type": "form",
"view_mode": "form", "view_mode": "form",
"res_model": "wizard.import.zpl2", "res_model": "wizard.import.zpl2",
"type": "ir.actions.act_window", "type": "ir.actions.act_window",
@@ -409,8 +454,15 @@ class PrintingLabelZpl2(models.Model):
label.print_label(label.printer_id, record, **extra) label.print_label(label.printer_id, record, **extra)
@api.depends( @api.depends(
'record_id', 'labelary_dpmm', 'labelary_width', 'labelary_height', "record_id",
'component_ids', 'origin_x', 'origin_y', 'test_labelary_mode') "labelary_dpmm",
"labelary_width",
"labelary_height",
"component_ids",
"origin_x",
"origin_y",
"test_labelary_mode",
)
def _compute_labelary_image(self): def _compute_labelary_image(self):
for label in self: for label in self:
label.labelary_image = label._generate_labelary_image() label.labelary_image = label._generate_labelary_image()
@@ -456,8 +508,8 @@ class PrintingLabelZpl2(models.Model):
return base64.b64encode(imgByteArr.getvalue()) return base64.b64encode(imgByteArr.getvalue())
else: else:
_logger.warning( _logger.warning(
_( _("Error with Labelary API. %s") % response.status_code
"Error with Labelary API. %s") % response.status_code) )
except Exception as e: except Exception as e:
_logger.warning(_("Error with Labelary API. %s") % e) _logger.warning(_("Error with Labelary API. %s") % e)

View File

@@ -3,7 +3,7 @@
import logging import logging
from odoo import fields, models from odoo import api, fields, models
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -13,12 +13,12 @@ except ImportError:
_logger.debug("Cannot `import zpl2`.") _logger.debug("Cannot `import zpl2`.")
DEFAULT_PYTHON_CODE = """# Python One-Liners DEFAULT_PYTHON_CODE = """# Python One-Liners
# - object: record on which the action is triggered; may be be void # - object: %s record on which the action is triggered; may be void
# - page_number: Current Page # - page_number: Current Page
# - page_count: Total Page # - page_count: Total Page
# - time, datetime: Python libraries # - time, datetime: Python libraries
# - return 'component_not_show' to don't show this component # - write instead 'component_not_show' to don't show this component
# Exemple : object.name # Example: object.name
"" ""
@@ -114,6 +114,10 @@ class PrintingLabelZpl2Component(models.Model):
default=str(zpl2.DIAGONAL_ORIENTATION_LEFT), default=str(zpl2.DIAGONAL_ORIENTATION_LEFT),
help="Orientation of the diagonal line.", help="Orientation of the diagonal line.",
) )
data_autofill = fields.Boolean(
string="Autofill Data",
help="Change 'data' with dictionary of the object information.",
)
check_digits = fields.Boolean( check_digits = fields.Boolean(
help="Check if you want to compute and print the check digit." help="Check if you want to compute and print the check digit."
) )
@@ -165,8 +169,11 @@ class PrintingLabelZpl2Component(models.Model):
help="Error correction for some barcode types like QR Code.", help="Error correction for some barcode types like QR Code.",
) )
mask_value = fields.Integer(default=7, help="Mask Value, from 0 to 7.") mask_value = fields.Integer(default=7, help="Mask Value, from 0 to 7.")
model_id = fields.Many2one(
comodel_name="ir.model", compute="_compute_model_id", string="Record's model"
)
data = fields.Text( data = fields.Text(
default=DEFAULT_PYTHON_CODE, default=lambda self: self._compute_default_data(),
required=True, required=True,
help="Data to print on this component. Resource values can be " help="Data to print on this component. Resource values can be "
"inserted with %(object.field_name)s.", "inserted with %(object.field_name)s.",
@@ -233,6 +240,44 @@ class PrintingLabelZpl2Component(models.Model):
"If not set, the data field is evaluated.", "If not set, the data field is evaluated.",
) )
def process_model(self, model):
# Used for expansions of this module
return model
@api.depends("label_id.model_id")
def _compute_model_id(self):
# it's 'compute' instead of 'related' because is easier to expand it
for component in self:
component.model_id = self.process_model(component.label_id.model_id)
def _compute_default_data(self):
model_id = self.env.context.get("default_model_id") or self.model_id.id
model = self.env["ir.model"].browse(model_id)
model = self.process_model(model)
return DEFAULT_PYTHON_CODE % (model.model or "")
@api.onchange("model_id", "data")
def _onchange_data(self):
for component in self.filtered(lambda c: not c.data):
component.data = component._compute_default_data()
@api.onchange("component_type")
def _onchange_component_type(self):
for component in self:
if component.component_type == "qr_code":
component.data_autofill = True
else:
component.data_autofill = False
@api.model
def autofill_data(self, record, eval_args):
data = {}
usual_fields = ["id", "create_date", record.display_name]
for field in usual_fields:
if hasattr(record, field):
data[field] = getattr(record, field)
return data
def action_plus_origin_x(self): def action_plus_origin_x(self):
self.ensure_one() self.ensure_one()
self.origin_x += 10 self.origin_x += 10

View File

@@ -2,3 +2,4 @@
* Florent de Labarre * Florent de Labarre
* Jos De Graeve <Jos.DeGraeve@apertoso.be> * Jos De Graeve <Jos.DeGraeve@apertoso.be>
* Rod Schouteden <rod.schouteden@dynapps.be> * Rod Schouteden <rod.schouteden@dynapps.be>
* Miquel Raïch <miquel.raich@forgeflow.com>

View File

@@ -67,11 +67,8 @@ class TestPrintingLabelZpl2(TransactionCase):
def test_print_empty_label(self, cups): def test_print_empty_label(self, cups):
""" Check that printing an empty label works """ """ Check that printing an empty label works """
label = self.new_label() label = self.new_label()
file_name = "test.zpl"
label.print_label(self.printer, self.printer) label.print_label(self.printer, self.printer)
cups.Connection().printFile.assert_called_once_with( cups.Connection().printFile.assert_called_once()
self.printer.system_name, file_name, file_name, options={}
)
def test_empty_label_contents(self): def test_empty_label_contents(self):
""" Check contents of an empty label """ """ Check contents of an empty label """

View File

@@ -51,11 +51,8 @@ class TestWizardPrintRecordLabel(TransactionCase):
self.label.test_print_mode = True self.label.test_print_mode = True
self.label.printer_id = self.printer self.label.printer_id = self.printer
self.label.record_id = 10 self.label.record_id = 10
file_name = "test.zpl"
self.label.print_test_label() self.label.print_test_label()
cups.Connection().printFile.assert_called_once_with( cups.Connection().printFile.assert_called_once()
self.printer.system_name, file_name, file_name, options={}
)
def test_emulation_without_params(self): def test_emulation_without_params(self):
""" Check if not execute next if not in this mode """ """ Check if not execute next if not in this mode """

View File

@@ -48,10 +48,7 @@ class TestWizardPrintRecordLabel(TransactionCase):
self.assertEqual(wizard.printer_id, self.printer) self.assertEqual(wizard.printer_id, self.printer)
self.assertEqual(wizard.label_id, self.label) self.assertEqual(wizard.label_id, self.label)
wizard.print_label() wizard.print_label()
file_name = "test.zpl" cups.Connection().printFile.assert_called_once()
cups.Connection().printFile.assert_called_once_with(
self.printer.system_name, file_name, file_name, options={}
)
def test_wizard_multiple_printers_and_labels(self): def test_wizard_multiple_printers_and_labels(self):
""" Check that printer_id and label_id are not automatically filled """ Check that printer_id and label_id are not automatically filled

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2016 SUBTENO-IT Copyright 2016 SUBTENO-IT
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -28,7 +28,7 @@
attrs="{'invisible': [('active', '=', True)]}" attrs="{'invisible': [('active', '=', True)]}"
/> />
<div class="oe_button_box" name="button_box"> <div class="oe_button_box" name="button_box">
<field name="active" invisible="1"/> <field name="active" invisible="1" />
<field name="action_window_id" invisible="1" /> <field name="action_window_id" invisible="1" />
<button <button
name="create_action" name="create_action"
@@ -56,7 +56,6 @@
<field name="width" /> <field name="width" />
<field name="origin_x" /> <field name="origin_x" />
<field name="origin_y" /> <field name="origin_y" />
<field name="data_type" />
<field name="restore_saved_config" /> <field name="restore_saved_config" />
</group> </group>
<group attrs="{'invisible':[('test_print_mode', '=', False)]}"> <group attrs="{'invisible':[('test_print_mode', '=', False)]}">
@@ -69,7 +68,12 @@
</group> </group>
<notebook> <notebook>
<page string="Components"> <page string="Components">
<field name="component_ids" nolabel="1" colspan="4"> <field
name="component_ids"
nolabel="1"
colspan="4"
context="{'default_model_id': model_id}"
>
<tree string="Label Component"> <tree string="Label Component">
<field name="sequence" /> <field name="sequence" />
<field name="name" /> <field name="name" />
@@ -106,6 +110,7 @@
<group> <group>
<field name="name" /> <field name="name" />
<field name="sequence" /> <field name="sequence" />
<field name="model_id" invisible="1" />
</group> </group>
<group> <group>
<field name="component_type" /> <field name="component_type" />
@@ -113,6 +118,10 @@
name="repeat" name="repeat"
attrs="{'invisible': [('component_type', '=', 'zpl2_raw')]}" attrs="{'invisible': [('component_type', '=', 'zpl2_raw')]}"
/> />
<field
name="data_autofill"
attrs="{'invisible': [('component_type', '!=', 'qr_code')]}"
/>
</group> </group>
<group <group
attrs="{'invisible': [('component_type', '=', 'zpl2_raw')]}" attrs="{'invisible': [('component_type', '=', 'zpl2_raw')]}"
@@ -132,7 +141,7 @@
</group> </group>
</group> </group>
<group <group
attrs="{'invisible': [('component_type', 'in', ('rectangle', 'diagonal', 'circle'))]}" attrs="{'invisible': ['|', ('data_autofill', '=', True), ('component_type', 'in', ('rectangle', 'diagonal', 'circle'))]}"
string="Data" string="Data"
> >
<field <field

View File

@@ -23,6 +23,11 @@ class PrintRecordLabel(models.TransientModel):
], ],
help="Label to print.", help="Label to print.",
) )
active_model_id = fields.Many2one(
comodel_name="ir.model",
string="Model",
domain=lambda self: [("model", "=", self.env.context.get("active_model"))],
)
@api.model @api.model
def default_get(self, fields_list): def default_get(self, fields_list):

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2016 SYLEAM Copyright 2016 SYLEAM
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -11,7 +11,11 @@
<form string="Print Label"> <form string="Print Label">
<group> <group>
<field name="printer_id" /> <field name="printer_id" />
<field name="label_id" /> <field
name="label_id"
context="{'default_model_id': active_model_id}"
/>
<field name="active_model_id" invisible="1" />
</group> </group>
<footer> <footer>
<button type="special" special="cancel" string="Cancel" /> <button type="special" special="cancel" string="Cancel" />

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" ?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="view_wizard_import_zpl2_form" model="ir.ui.view"> <record id="view_wizard_import_zpl2_form" model="ir.ui.view">
<field name="name">wizard.import.zpl2.form</field> <field name="name">wizard.import.zpl2.form</field>