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

View File

@@ -4,13 +4,16 @@
import base64
import datetime
import io
import itertools
import logging
import time
from collections import defaultdict
import requests
from PIL import Image, ImageOps
from odoo import _, api, exceptions, fields, models
from odoo.exceptions import ValidationError
from odoo.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__)
@@ -63,15 +66,16 @@ class PrintingLabelZpl2(models.Model):
default=True,
)
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_labelary_mode = fields.Boolean(string="Mode Labelary")
record_id = fields.Integer(string="Record ID", default=1)
extra = fields.Text(string="Extra", default="{}")
printer_id = fields.Many2one(comodel_name="printing.printer", string="Printer")
labelary_image = fields.Binary(string='Image from Labelary',
compute='_compute_labelary_image')
labelary_image = fields.Binary(
string="Image from Labelary", compute="_compute_labelary_image"
)
labelary_dpmm = fields.Selection(
selection=[
("6dpmm", "6dpmm (152 pdi)"),
@@ -85,15 +89,46 @@ class PrintingLabelZpl2(models.Model):
)
labelary_width = fields.Float(string="Width in mm", default=140)
labelary_height = fields.Float(string="Height in mm", default=70)
data_type = fields.Selection(
string="Labels components data type",
selection=[("und", "Undefined")],
help="This allows to specify the type of the data encoded in label components",
@api.constrains("component_ids")
def check_recursion(self):
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
def _get_component_data(self, component, eval_args):
return safe_eval(component.data, eval_args) or ""
succs = defaultdict(set) # transitive closure of successors
preds = defaultdict(set) # transitive closure of predecessors
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(
self,
@@ -116,8 +151,8 @@ class PrintingLabelZpl2(models.Model):
"datetime": datetime,
}
)
data = self._get_component_data(component, eval_args)
if data == "component_not_show":
data = self._get_component_data(record, component, eval_args)
if isinstance(data, str) and data == "component_not_show":
continue
# Generate a list of elements if the component is repeatable
@@ -157,10 +192,6 @@ class PrintingLabelZpl2(models.Model):
label_offset_y=0,
**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(
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.sublabel_id._generate_zpl2_components_data(
label_data,
data,
data if isinstance(data, models.BaseModel) else record,
label_offset_x=component_offset_x,
label_offset_y=component_offset_y,
)
@@ -325,55 +356,70 @@ class PrintingLabelZpl2(models.Model):
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):
for label in self:
if record._name != label.model_id.model:
raise exceptions.UserError(
_("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
label_contents = label._generate_zpl2_data(
record, page_count=1, **extra
record, page_count=page_count, **extra
)
printer.print_document(
report=None, content=label_contents, doc_format="raw"
)
return True
def create_action(self):
for label in self.filtered(lambda record: not record.action_window_id):
label.action_window_id = self.env["ir.actions.act_window"].create(
@api.model
def new_action(self, model_id):
return self.env["ir.actions.act_window"].create(
{
"name": _("Print Label"),
"binding_model_id": label.model_id.id,
"binding_model_id": model_id,
"res_model": "wizard.print.record.label",
"view_mode": "form",
"target": "new",
"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
def unlink_action(self):
@@ -382,7 +428,6 @@ class PrintingLabelZpl2(models.Model):
def import_zpl2(self):
self.ensure_one()
return {
"view_type": "form",
"view_mode": "form",
"res_model": "wizard.import.zpl2",
"type": "ir.actions.act_window",
@@ -409,8 +454,15 @@ class PrintingLabelZpl2(models.Model):
label.print_label(label.printer_id, record, **extra)
@api.depends(
'record_id', 'labelary_dpmm', 'labelary_width', 'labelary_height',
'component_ids', 'origin_x', 'origin_y', 'test_labelary_mode')
"record_id",
"labelary_dpmm",
"labelary_width",
"labelary_height",
"component_ids",
"origin_x",
"origin_y",
"test_labelary_mode",
)
def _compute_labelary_image(self):
for label in self:
label.labelary_image = label._generate_labelary_image()
@@ -456,8 +508,8 @@ class PrintingLabelZpl2(models.Model):
return base64.b64encode(imgByteArr.getvalue())
else:
_logger.warning(
_(
"Error with Labelary API. %s") % response.status_code)
_("Error with Labelary API. %s") % response.status_code
)
except Exception as e:
_logger.warning(_("Error with Labelary API. %s") % e)

View File

@@ -3,7 +3,7 @@
import logging
from odoo import fields, models
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
@@ -13,12 +13,12 @@ except ImportError:
_logger.debug("Cannot `import zpl2`.")
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_count: Total Page
# - time, datetime: Python libraries
# - return 'component_not_show' to don't show this component
# Exemple : object.name
# - write instead 'component_not_show' to don't show this component
# Example: object.name
""
@@ -114,6 +114,10 @@ class PrintingLabelZpl2Component(models.Model):
default=str(zpl2.DIAGONAL_ORIENTATION_LEFT),
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(
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.",
)
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(
default=DEFAULT_PYTHON_CODE,
default=lambda self: self._compute_default_data(),
required=True,
help="Data to print on this component. Resource values can be "
"inserted with %(object.field_name)s.",
@@ -233,6 +240,44 @@ class PrintingLabelZpl2Component(models.Model):
"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):
self.ensure_one()
self.origin_x += 10

View File

@@ -2,3 +2,4 @@
* Florent de Labarre
* Jos De Graeve <Jos.DeGraeve@apertoso.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):
""" Check that printing an empty label works """
label = self.new_label()
file_name = "test.zpl"
label.print_label(self.printer, self.printer)
cups.Connection().printFile.assert_called_once_with(
self.printer.system_name, file_name, file_name, options={}
)
cups.Connection().printFile.assert_called_once()
def test_empty_label_contents(self):
""" Check contents of an empty label """

View File

@@ -51,11 +51,8 @@ class TestWizardPrintRecordLabel(TransactionCase):
self.label.test_print_mode = True
self.label.printer_id = self.printer
self.label.record_id = 10
file_name = "test.zpl"
self.label.print_test_label()
cups.Connection().printFile.assert_called_once_with(
self.printer.system_name, file_name, file_name, options={}
)
cups.Connection().printFile.assert_called_once()
def test_emulation_without_params(self):
""" 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.label_id, self.label)
wizard.print_label()
file_name = "test.zpl"
cups.Connection().printFile.assert_called_once_with(
self.printer.system_name, file_name, file_name, options={}
)
cups.Connection().printFile.assert_called_once()
def test_wizard_multiple_printers_and_labels(self):
""" 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
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -56,7 +56,6 @@
<field name="width" />
<field name="origin_x" />
<field name="origin_y" />
<field name="data_type" />
<field name="restore_saved_config" />
</group>
<group attrs="{'invisible':[('test_print_mode', '=', False)]}">
@@ -69,7 +68,12 @@
</group>
<notebook>
<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">
<field name="sequence" />
<field name="name" />
@@ -106,6 +110,7 @@
<group>
<field name="name" />
<field name="sequence" />
<field name="model_id" invisible="1" />
</group>
<group>
<field name="component_type" />
@@ -113,6 +118,10 @@
name="repeat"
attrs="{'invisible': [('component_type', '=', 'zpl2_raw')]}"
/>
<field
name="data_autofill"
attrs="{'invisible': [('component_type', '!=', 'qr_code')]}"
/>
</group>
<group
attrs="{'invisible': [('component_type', '=', 'zpl2_raw')]}"
@@ -132,7 +141,7 @@
</group>
</group>
<group
attrs="{'invisible': [('component_type', 'in', ('rectangle', 'diagonal', 'circle'))]}"
attrs="{'invisible': ['|', ('data_autofill', '=', True), ('component_type', 'in', ('rectangle', 'diagonal', 'circle'))]}"
string="Data"
>
<field

View File

@@ -23,6 +23,11 @@ class PrintRecordLabel(models.TransientModel):
],
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
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
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -11,7 +11,11 @@
<form string="Print Label">
<group>
<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>
<footer>
<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>
<record id="view_wizard_import_zpl2_form" model="ir.ui.view">
<field name="name">wizard.import.zpl2.form</field>